Compare commits

...

25 Commits

Author SHA1 Message Date
363a1ab68e nuget: bump Microsoft.NET.Test.Sdk from 17.9.0 to 17.11.1
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.9.0 to 17.11.1.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.9.0...v17.11.1)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-05 16:06:26 +00:00
ca59c3f499 Vulkan: Feedback loop detection and barriers (#7226)
* Vulkan: Feedback loop improvements

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

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

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

* Mark field readonly

* Add feedback loop dynamic state

* fix: add MoltenVK resolver workaround

fix: add MoltenVK resolver workaround

* Formatting

* Fix more complaints

* RADV dcc workaround

* Use dynamic state properly, cleanup.

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

* fix: add MoltenVK resolver workaround

fix: add MoltenVK resolver workaround

* Cleanup

* Readonly ref warnings

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

remove usage of reflection in device state

* remove rd.xml generation

* make applet manager reflection free

* fix typos

* fix encoding

* fix style report

* remove rogue generator reference

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

* fix avalonia inline keyboard input

* remove image sharp from gtk3 project

* add skiasharp linux assets

* fix whitespace

* fix format

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

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

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

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

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

* Add files via upload

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

---------

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

* Use a more engineered solution rather than string hacks.

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

* one more style fix

---------

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

* XML docs

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

* refactor: delete unused ByteMemoryPool and ByteMemoryPool.ByteMemoryPoolBuffer types

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

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

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

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

* Consolidate IsInvalid check

* InvalidAddress const

* Fix typo

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

---------

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

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

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

* Load applications when necessary

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

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

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

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

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

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

* eliminate temporary buffer allocations

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

* Catch FileNotFoundException in TryGetApplicationsFromFile()

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

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

I'm not sure what providing a different topology here would even do, as you'd think it would always be a patch list input.
2024-07-30 21:48:30 -03:00
117 changed files with 1953 additions and 860 deletions

View File

@ -13,15 +13,15 @@
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="2.2.0" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageVersion Include="DynamicData" Version="8.4.1" />
<PackageVersion Include="DynamicData" Version="9.0.4" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
<PackageVersion Include="LibHac" Version="0.19.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.6.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.0.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
<PackageVersion Include="NetCoreServer" Version="8.0.7" />
@ -39,11 +39,11 @@
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="shaderc.net" Version="0.1.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.8" />
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.21.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.21.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.21.0" />
<PackageVersion Include="SkiaSharp" Version="2.88.7" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.7" />
<PackageVersion Include="SPB" Version="0.0.4-build32" />
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
<PackageVersion Include="System.Management" Version="8.0.0" />

View File

@ -87,6 +87,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon", "src\Ryuj
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
@ -249,6 +251,10 @@ Global
{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

View File

@ -822,6 +822,10 @@ namespace ARMeilleure.Decoders
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);
@ -1007,6 +1011,8 @@ namespace ARMeilleure.Decoders
SetAsimd("111100100x10xxxxxxxx1011xxx0xxxx", InstName.Vqdmulh, InstEmit32.Vqdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100111x11<<10xxxx00101xx0xxx0", InstName.Vqmovn, InstEmit32.Vqmovn, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32);
SetAsimd("111100111x11<<10xxxx001001x0xxx0", InstName.Vqmovun, InstEmit32.Vqmovun, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32);
SetAsimd("111100110x01xxxxxxxx1011xxx0xxxx", InstName.Vqrdmulh, InstEmit32.Vqrdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100110x10xxxxxxxx1011xxx0xxxx", InstName.Vqrdmulh, InstEmit32.Vqrdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("1111001x1x>>>xxxxxxx100101x1xxx0", InstName.Vqrshrn, InstEmit32.Vqrshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
SetAsimd("111100111x>>>xxxxxxx100001x1xxx0", InstName.Vqrshrun, InstEmit32.Vqrshrun, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
SetAsimd("1111001x1x>>>xxxxxxx100100x1xxx0", InstName.Vqshrn, InstEmit32.Vqshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
@ -1030,6 +1036,7 @@ namespace ARMeilleure.Decoders
SetAsimd("1111001x1x>>>xxxxxxx101000x1xxxx", InstName.Vshll, InstEmit32.Vshll, OpCode32SimdShImmLong.Create, OpCode32SimdShImmLong.CreateT32); // A1 encoding.
SetAsimd("1111001x1x>>>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);
@ -1054,6 +1061,7 @@ namespace ARMeilleure.Decoders
SetAsimd("111100100x10xxxxxxxx1101xxx0xxxx", InstName.Vsub, InstEmit32.Vsub_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("1111001x1x<<xxxxxxx00010x0x0xxxx", InstName.Vsubl, InstEmit32.Vsubl_I, OpCode32SimdRegLong.Create, OpCode32SimdRegLong.CreateT32);
SetAsimd("1111001x1x<<xxxxxxx00011x0x0xxxx", InstName.Vsubw, InstEmit32.Vsubw_I, OpCode32SimdRegWide.Create, OpCode32SimdRegWide.CreateT32);
SetAsimd("111100111x110010xxxx00000xx0xxxx", InstName.Vswp, InstEmit32.Vswp, OpCode32Simd.Create, OpCode32Simd.CreateT32);
SetAsimd("111100111x11xxxxxxxx10xxxxx0xxxx", InstName.Vtbl, InstEmit32.Vtbl, OpCode32SimdTbl.Create, OpCode32SimdTbl.CreateT32);
SetAsimd("111100111x11<<10xxxx00001xx0xxxx", InstName.Vtrn, InstEmit32.Vtrn, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
SetAsimd("111100100x<<xxxxxxxx1000xxx1xxxx", InstName.Vtst, InstEmit32.Vtst, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);

View File

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

View File

@ -1246,6 +1246,33 @@ namespace ARMeilleure.Instructions
EmitVectorUnaryNarrowOp32(context, (op1) => EmitSatQ(context, op1, 8 << op.Size, signedSrc: true, signedDst: false), signed: true);
}
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;

View File

@ -191,6 +191,26 @@ namespace ARMeilleure.Instructions
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;

View File

@ -130,6 +130,36 @@ namespace ARMeilleure.Instructions
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;

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,33 @@
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)
{
Environment.SetEnvironmentVariable("mesa_glthread", enabled.ToString().ToLower());
Environment.SetEnvironmentVariable("__GL_THREADED_OPTIMIZATIONS", enabled ? "1" : "0");
OsUtils.SetEnvironmentVariableNoCaching("mesa_glthread", enabled.ToString().ToLower());
OsUtils.SetEnvironmentVariableNoCaching("__GL_THREADED_OPTIMIZATIONS", enabled ? "1" : "0");
try
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -340,7 +340,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <returns>True if any used entries of the pool might have been modified, false otherwise</returns>
public bool SamplerPoolModified()
{
return SamplerPool.WasModified(ref _samplerPoolSequence);
return SamplerPool != null && SamplerPool.WasModified(ref _samplerPoolSequence);
}
}
@ -516,12 +516,15 @@ namespace Ryujinx.Graphics.Gpu.Image
}
// Check if any of our cached samplers changed on the pool.
foreach ((int samplerId, (Sampler sampler, SamplerDescriptor descriptor)) in SamplerIds)
if (SamplerPool != null)
{
if (SamplerPool.GetCachedItem(samplerId) != sampler ||
(sampler == null && SamplerPool.IsValidId(samplerId) && !SamplerPool.GetDescriptorRef(samplerId).Equals(descriptor)))
foreach ((int samplerId, (Sampler sampler, SamplerDescriptor descriptor)) in SamplerIds)
{
return true;
if (SamplerPool.GetCachedItem(samplerId) != sampler ||
(sampler == null && SamplerPool.IsValidId(samplerId) && !SamplerPool.GetDescriptorRef(samplerId).Equals(descriptor)))
{
return true;
}
}
}
@ -899,13 +902,19 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
Sampler sampler = samplerPool?.Get(samplerId);
entry.TextureIds[textureId] = (texture, descriptor);
entry.SamplerIds[samplerId] = (sampler, samplerPool?.GetDescriptorRef(samplerId) ?? default);
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
ISampler hostSampler = sampler?.GetHostSampler(texture);
ISampler hostSampler = null;
if (!isImage && bindingInfo.Target != Target.TextureBuffer)
{
Sampler sampler = samplerPool?.Get(samplerId);
entry.SamplerIds[samplerId] = (sampler, samplerPool?.GetDescriptorRef(samplerId) ?? default);
hostSampler = sampler?.GetHostSampler(texture);
}
Format format = bindingInfo.Format;

View File

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

View File

@ -6,6 +6,7 @@ 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
@ -490,6 +491,8 @@ namespace Ryujinx.Graphics.Gpu.Image
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();
@ -540,6 +543,34 @@ namespace Ryujinx.Graphics.Gpu.Image
swizzleA);
}
/// <summary>
/// Clamps the amount of mipmap levels to the maximum allowed for the given texture dimensions.
/// </summary>
/// <param name="target">Number of texture dimensions (1D, 2D, 3D, Cube, etc)</param>
/// <param name="width">Width of the texture</param>
/// <param name="height">Height of the texture, ignored for 1D textures</param>
/// <param name="depthOrLayers">Depth of the texture for 3D textures, otherwise ignored</param>
/// <param name="levels">Original amount of mipmap levels</param>
/// <returns>Clamped mipmap levels</returns>
private static int ClampLevels(Target target, int width, int height, int depthOrLayers, int levels)
{
int maxSize = width;
if (target != Target.Texture1D &&
target != Target.Texture1DArray)
{
maxSize = Math.Max(maxSize, height);
}
if (target == Target.Texture3D)
{
maxSize = Math.Max(maxSize, depthOrLayers);
}
int maxLevels = BitOperations.Log2((uint)maxSize) + 1;
return Math.Min(levels, maxLevels);
}
/// <summary>
/// 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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -155,9 +155,14 @@ namespace Ryujinx.Graphics.Shader.Translation
localInputs[block.Index] |= GetMask(register) & ~localOutputs[block.Index];
}
if (operation.Dest != null && operation.Dest.Type == OperandType.Register)
for (int dstIndex = 0; dstIndex < operation.DestsCount; dstIndex++)
{
localOutputs[block.Index] |= GetMask(operation.Dest.GetRegister());
Operand dest = operation.GetDest(dstIndex);
if (dest != null && dest.Type == OperandType.Register)
{
localOutputs[block.Index] |= GetMask(dest.GetRegister());
}
}
}
}

View File

@ -1,7 +1,6 @@
using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities;
using System;
using System.Buffers;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
@ -293,9 +292,9 @@ namespace Ryujinx.Graphics.Texture.Astc
int depth,
int levels,
int layers,
out IMemoryOwner<byte> decoded)
out MemoryOwner<byte> decoded)
{
decoded = ByteMemoryPool.Rent(QueryDecompressedSize(width, height, depth, levels, layers));
decoded = MemoryOwner<byte>.Rent(QueryDecompressedSize(width, height, depth, levels, layers));
AstcDecoder decoder = new(data, decoded.Memory, blockWidth, blockHeight, width, height, depth, levels, layers);

View File

@ -1,7 +1,6 @@
using Ryujinx.Common;
using Ryujinx.Common.Memory;
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
@ -14,7 +13,7 @@ namespace Ryujinx.Graphics.Texture
private const int BlockWidth = 4;
private const int BlockHeight = 4;
public static IMemoryOwner<byte> DecodeBC1(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
public static MemoryOwner<byte> DecodeBC1(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
{
int size = 0;
@ -23,12 +22,12 @@ namespace Ryujinx.Graphics.Texture
size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4;
}
IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(size);
Span<byte> tile = stackalloc byte[BlockWidth * BlockHeight * 4];
Span<uint> tileAsUint = MemoryMarshal.Cast<byte, uint>(tile);
Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output.Span);
Span<Vector128<byte>> tileAsVector128 = MemoryMarshal.Cast<byte, Vector128<byte>>(tile);
@ -102,7 +101,7 @@ namespace Ryujinx.Graphics.Texture
return output;
}
public static IMemoryOwner<byte> DecodeBC2(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
public static MemoryOwner<byte> DecodeBC2(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
{
int size = 0;
@ -111,12 +110,12 @@ namespace Ryujinx.Graphics.Texture
size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4;
}
IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(size);
Span<byte> tile = stackalloc byte[BlockWidth * BlockHeight * 4];
Span<uint> tileAsUint = MemoryMarshal.Cast<byte, uint>(tile);
Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output.Span);
Span<Vector128<byte>> tileAsVector128 = MemoryMarshal.Cast<byte, Vector128<byte>>(tile);
@ -197,7 +196,7 @@ namespace Ryujinx.Graphics.Texture
return output;
}
public static IMemoryOwner<byte> DecodeBC3(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
public static MemoryOwner<byte> DecodeBC3(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
{
int size = 0;
@ -206,13 +205,13 @@ namespace Ryujinx.Graphics.Texture
size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4;
}
IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(size);
Span<byte> tile = stackalloc byte[BlockWidth * BlockHeight * 4];
Span<byte> rPal = stackalloc byte[8];
Span<uint> tileAsUint = MemoryMarshal.Cast<byte, uint>(tile);
Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output.Span);
Span<Vector128<byte>> tileAsVector128 = MemoryMarshal.Cast<byte, Vector128<byte>>(tile);
@ -294,7 +293,7 @@ namespace Ryujinx.Graphics.Texture
return output;
}
public static IMemoryOwner<byte> DecodeBC4(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed)
public static MemoryOwner<byte> DecodeBC4(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed)
{
int size = 0;
@ -306,8 +305,8 @@ namespace Ryujinx.Graphics.Texture
// Backends currently expect a stride alignment of 4 bytes, so output width must be aligned.
int alignedWidth = BitUtils.AlignUp(width, 4);
IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
Span<byte> outputSpan = output.Memory.Span;
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(size);
Span<byte> outputSpan = output.Span;
ReadOnlySpan<ulong> data64 = MemoryMarshal.Cast<byte, ulong>(data);
@ -402,7 +401,7 @@ namespace Ryujinx.Graphics.Texture
return output;
}
public static IMemoryOwner<byte> DecodeBC5(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed)
public static MemoryOwner<byte> DecodeBC5(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed)
{
int size = 0;
@ -414,7 +413,7 @@ namespace Ryujinx.Graphics.Texture
// Backends currently expect a stride alignment of 4 bytes, so output width must be aligned.
int alignedWidth = BitUtils.AlignUp(width, 2);
IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(size);
ReadOnlySpan<ulong> data64 = MemoryMarshal.Cast<byte, ulong>(data);
@ -423,7 +422,7 @@ namespace Ryujinx.Graphics.Texture
Span<byte> rPal = stackalloc byte[8];
Span<byte> gPal = stackalloc byte[8];
Span<ushort> outputAsUshort = MemoryMarshal.Cast<byte, ushort>(output.Memory.Span);
Span<ushort> outputAsUshort = MemoryMarshal.Cast<byte, ushort>(output.Span);
Span<uint> rTileAsUint = MemoryMarshal.Cast<byte, uint>(rTile);
Span<uint> gTileAsUint = MemoryMarshal.Cast<byte, uint>(gTile);
@ -527,7 +526,7 @@ namespace Ryujinx.Graphics.Texture
return output;
}
public static IMemoryOwner<byte> DecodeBC6(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed)
public static MemoryOwner<byte> DecodeBC6(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed)
{
int size = 0;
@ -536,8 +535,8 @@ namespace Ryujinx.Graphics.Texture
size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 8;
}
IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
Span<byte> outputSpan = output.Memory.Span;
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(size);
Span<byte> outputSpan = output.Span;
int inputOffset = 0;
int outputOffset = 0;
@ -566,7 +565,7 @@ namespace Ryujinx.Graphics.Texture
return output;
}
public static IMemoryOwner<byte> DecodeBC7(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
public static MemoryOwner<byte> DecodeBC7(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
{
int size = 0;
@ -575,8 +574,8 @@ namespace Ryujinx.Graphics.Texture
size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4;
}
IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
Span<byte> outputSpan = output.Memory.Span;
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(size);
Span<byte> outputSpan = output.Span;
int inputOffset = 0;
int outputOffset = 0;

View File

@ -2,7 +2,6 @@ using Ryujinx.Common;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.Texture.Encoders;
using System;
using System.Buffers;
namespace Ryujinx.Graphics.Texture
{
@ -11,7 +10,7 @@ namespace Ryujinx.Graphics.Texture
private const int BlockWidth = 4;
private const int BlockHeight = 4;
public static IMemoryOwner<byte> EncodeBC7(Memory<byte> data, int width, int height, int depth, int levels, int layers)
public static MemoryOwner<byte> EncodeBC7(Memory<byte> data, int width, int height, int depth, int levels, int layers)
{
int size = 0;
@ -23,7 +22,7 @@ namespace Ryujinx.Graphics.Texture
size += w * h * 16 * Math.Max(1, depth >> l) * layers;
}
IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(size);
Memory<byte> outputMemory = output.Memory;
int imageBaseIOffs = 0;

View File

@ -1,7 +1,6 @@
using Ryujinx.Common;
using Ryujinx.Common.Memory;
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Runtime.InteropServices;
@ -51,15 +50,15 @@ namespace Ryujinx.Graphics.Texture
new int[] { -3, -5, -7, -9, 2, 4, 6, 8 },
};
public static IMemoryOwner<byte> DecodeRgb(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
public static MemoryOwner<byte> DecodeRgb(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
{
ReadOnlySpan<ulong> dataUlong = MemoryMarshal.Cast<byte, ulong>(data);
int inputOffset = 0;
IMemoryOwner<byte> output = ByteMemoryPool.Rent(CalculateOutputSize(width, height, depth, levels, layers));
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(CalculateOutputSize(width, height, depth, levels, layers));
Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output.Span);
Span<uint> tile = stackalloc uint[BlockWidth * BlockHeight];
int imageBaseOOffs = 0;
@ -113,15 +112,15 @@ namespace Ryujinx.Graphics.Texture
return output;
}
public static IMemoryOwner<byte> DecodePta(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
public static MemoryOwner<byte> DecodePta(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
{
ReadOnlySpan<ulong> dataUlong = MemoryMarshal.Cast<byte, ulong>(data);
int inputOffset = 0;
IMemoryOwner<byte> output = ByteMemoryPool.Rent(CalculateOutputSize(width, height, depth, levels, layers));
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(CalculateOutputSize(width, height, depth, levels, layers));
Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output.Span);
Span<uint> tile = stackalloc uint[BlockWidth * BlockHeight];
int imageBaseOOffs = 0;
@ -170,15 +169,15 @@ namespace Ryujinx.Graphics.Texture
return output;
}
public static IMemoryOwner<byte> DecodeRgba(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
public static MemoryOwner<byte> DecodeRgba(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
{
ReadOnlySpan<ulong> dataUlong = MemoryMarshal.Cast<byte, ulong>(data);
int inputOffset = 0;
IMemoryOwner<byte> output = ByteMemoryPool.Rent(CalculateOutputSize(width, height, depth, levels, layers));
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(CalculateOutputSize(width, height, depth, levels, layers));
Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output.Span);
Span<uint> tile = stackalloc uint[BlockWidth * BlockHeight];
int imageBaseOOffs = 0;

View File

@ -1,7 +1,6 @@
using Ryujinx.Common;
using Ryujinx.Common.Memory;
using System;
using System.Buffers;
using System.Runtime.Intrinsics;
using static Ryujinx.Graphics.Texture.BlockLinearConstants;
@ -95,7 +94,7 @@ namespace Ryujinx.Graphics.Texture
};
}
public static IMemoryOwner<byte> ConvertBlockLinearToLinear(
public static MemoryOwner<byte> ConvertBlockLinearToLinear(
int width,
int height,
int depth,
@ -121,8 +120,8 @@ namespace Ryujinx.Graphics.Texture
blockHeight,
bytesPerPixel);
IMemoryOwner<byte> outputOwner = ByteMemoryPool.Rent(outSize);
Span<byte> output = outputOwner.Memory.Span;
MemoryOwner<byte> outputOwner = MemoryOwner<byte>.Rent(outSize);
Span<byte> output = outputOwner.Span;
int outOffs = 0;
@ -249,7 +248,7 @@ namespace Ryujinx.Graphics.Texture
return outputOwner;
}
public static IMemoryOwner<byte> ConvertLinearStridedToLinear(
public static MemoryOwner<byte> ConvertLinearStridedToLinear(
int width,
int height,
int blockWidth,
@ -265,8 +264,8 @@ namespace Ryujinx.Graphics.Texture
int outStride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment);
lineSize = Math.Min(lineSize, outStride);
IMemoryOwner<byte> output = ByteMemoryPool.Rent(h * outStride);
Span<byte> outSpan = output.Memory.Span;
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(h * outStride);
Span<byte> outSpan = output.Span;
int outOffs = 0;
int inOffs = 0;

View File

@ -1,7 +1,6 @@
using Ryujinx.Common;
using Ryujinx.Common.Memory;
using System;
using System.Buffers;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
@ -21,13 +20,14 @@ namespace Ryujinx.Graphics.Texture
return (remainder, outRemainder, length / stride);
}
public unsafe static IMemoryOwner<byte> ConvertR4G4ToR4G4B4A4(ReadOnlySpan<byte> data, int width)
public unsafe static MemoryOwner<byte> ConvertR4G4ToR4G4B4A4(ReadOnlySpan<byte> data, int width)
{
IMemoryOwner<byte> output = ByteMemoryPool.Rent(data.Length * 2);
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(data.Length * 2);
Span<byte> outputSpan = output.Span;
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 1, 2);
Span<ushort> outputSpan = MemoryMarshal.Cast<byte, ushort>(output.Memory.Span);
Span<ushort> outputSpanUInt16 = MemoryMarshal.Cast<byte, ushort>(outputSpan);
if (remainder == 0)
{
@ -38,7 +38,7 @@ namespace Ryujinx.Graphics.Texture
int sizeTrunc = data.Length & ~7;
start = sizeTrunc;
fixed (byte* inputPtr = data, outputPtr = output.Memory.Span)
fixed (byte* inputPtr = data, outputPtr = outputSpan)
{
for (ulong offset = 0; offset < (ulong)sizeTrunc; offset += 8)
{
@ -49,7 +49,7 @@ namespace Ryujinx.Graphics.Texture
for (int i = start; i < data.Length; i++)
{
outputSpan[i] = data[i];
outputSpanUInt16[i] = data[i];
}
}
else
@ -61,7 +61,7 @@ namespace Ryujinx.Graphics.Texture
{
for (int x = 0; x < width; x++)
{
outputSpan[outOffset++] = data[offset++];
outputSpanUInt16[outOffset++] = data[offset++];
}
offset += remainder;
@ -72,16 +72,16 @@ namespace Ryujinx.Graphics.Texture
return output;
}
public static IMemoryOwner<byte> ConvertR5G6B5ToR8G8B8A8(ReadOnlySpan<byte> data, int width)
public static MemoryOwner<byte> ConvertR5G6B5ToR8G8B8A8(ReadOnlySpan<byte> data, int width)
{
IMemoryOwner<byte> output = ByteMemoryPool.Rent(data.Length * 2);
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(data.Length * 2);
int offset = 0;
int outOffset = 0;
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4);
ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data);
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output.Span);
for (int y = 0; y < height; y++)
{
@ -109,16 +109,16 @@ namespace Ryujinx.Graphics.Texture
return output;
}
public static IMemoryOwner<byte> ConvertR5G5B5ToR8G8B8A8(ReadOnlySpan<byte> data, int width, bool forceAlpha)
public static MemoryOwner<byte> ConvertR5G5B5ToR8G8B8A8(ReadOnlySpan<byte> data, int width, bool forceAlpha)
{
IMemoryOwner<byte> output = ByteMemoryPool.Rent(data.Length * 2);
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(data.Length * 2);
int offset = 0;
int outOffset = 0;
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4);
ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data);
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output.Span);
for (int y = 0; y < height; y++)
{
@ -146,16 +146,16 @@ namespace Ryujinx.Graphics.Texture
return output;
}
public static IMemoryOwner<byte> ConvertA1B5G5R5ToR8G8B8A8(ReadOnlySpan<byte> data, int width)
public static MemoryOwner<byte> ConvertA1B5G5R5ToR8G8B8A8(ReadOnlySpan<byte> data, int width)
{
IMemoryOwner<byte> output = ByteMemoryPool.Rent(data.Length * 2);
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(data.Length * 2);
int offset = 0;
int outOffset = 0;
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4);
ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data);
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output.Span);
for (int y = 0; y < height; y++)
{
@ -183,16 +183,16 @@ namespace Ryujinx.Graphics.Texture
return output;
}
public static IMemoryOwner<byte> ConvertR4G4B4A4ToR8G8B8A8(ReadOnlySpan<byte> data, int width)
public static MemoryOwner<byte> ConvertR4G4B4A4ToR8G8B8A8(ReadOnlySpan<byte> data, int width)
{
IMemoryOwner<byte> output = ByteMemoryPool.Rent(data.Length * 2);
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(data.Length * 2);
int offset = 0;
int outOffset = 0;
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4);
ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data);
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output.Span);
for (int y = 0; y < height; y++)
{

View File

@ -32,10 +32,12 @@ namespace Ryujinx.Graphics.Vulkan
CommandBuffer
}
private bool _feedbackLoopActive;
private PipelineStageFlags _incoherentBufferWriteStages;
private PipelineStageFlags _incoherentTextureWriteStages;
private PipelineStageFlags _extraStages;
private IncoherentBarrierType _queuedIncoherentBarrier;
private bool _queuedFeedbackLoopBarrier;
public BarrierBatch(VulkanRenderer gd)
{
@ -53,17 +55,6 @@ namespace Ryujinx.Graphics.Vulkan
stages |= PipelineStageFlags.TransformFeedbackBitExt;
}
if (!gd.IsTBDR)
{
// Desktop GPUs can transform image barriers into memory barriers.
access |= AccessFlags.DepthStencilAttachmentWriteBit | AccessFlags.ColorAttachmentWriteBit;
access |= AccessFlags.DepthStencilAttachmentReadBit | AccessFlags.ColorAttachmentReadBit;
stages |= PipelineStageFlags.EarlyFragmentTestsBit | PipelineStageFlags.LateFragmentTestsBit;
stages |= PipelineStageFlags.ColorAttachmentOutputBit;
}
return (access, stages);
}
@ -178,16 +169,34 @@ namespace Ryujinx.Graphics.Vulkan
}
_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, inRenderPass, rpHolder, endRenderPass);
Flush(cbs, null, false, inRenderPass, rpHolder, endRenderPass);
}
public unsafe void Flush(CommandBufferScoped cbs, ShaderCollection program, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
public unsafe void Flush(CommandBufferScoped cbs, ShaderCollection program, bool feedbackLoopActive, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
{
if (program != null)
{
@ -195,6 +204,8 @@ namespace Ryujinx.Graphics.Vulkan
_incoherentTextureWriteStages |= program.IncoherentTextureWriteStages;
}
_feedbackLoopActive |= feedbackLoopActive;
FlushMemoryBarrier(program, inRenderPass);
if (!inRenderPass && rpHolder != null)
@ -406,6 +417,8 @@ namespace Ryujinx.Graphics.Vulkan
{
_queuedIncoherentBarrier = type;
}
_queuedFeedbackLoopBarrier = true;
}
public void QueueTextureBarrier()

View File

@ -122,7 +122,7 @@ namespace Ryujinx.Graphics.Vulkan
Range = (uint)size,
};
_gd.Api.CreateBufferView(_device, bufferViewCreateInfo, null, out var bufferView).ThrowOnError();
_gd.Api.CreateBufferView(_device, in bufferViewCreateInfo, null, out var bufferView).ThrowOnError();
return new Auto<DisposableBufferView>(new DisposableBufferView(_gd.Api, _device, bufferView), this, _waitable, _buffer);
}
@ -153,7 +153,7 @@ namespace Ryujinx.Graphics.Vulkan
PipelineStageFlags.AllCommandsBit,
DependencyFlags.DeviceGroupBit,
1,
memoryBarrier,
in memoryBarrier,
0,
null,
0,
@ -770,7 +770,7 @@ namespace Ryujinx.Graphics.Vulkan
0,
null,
1,
memoryBarrier,
in memoryBarrier,
0,
null);
}

View File

@ -221,7 +221,7 @@ namespace Ryujinx.Graphics.Vulkan
PBufferBinds = &bufferBind
};
gd.Api.QueueBindSparse(gd.Queue, 1, bindSparseInfo, default).ThrowOnError();
gd.Api.QueueBindSparse(gd.Queue, 1, in bindSparseInfo, default).ThrowOnError();
}
var holder = new BufferHolder(gd, _device, buffer, (int)size, storageAllocations);

View File

@ -25,7 +25,10 @@ namespace Ryujinx.Graphics.Vulkan
{
var buffer = _buffer.Get(cbs, _offset, _size, true).Value;
gd.TransformFeedbackApi.CmdBindTransformFeedbackBuffers(cbs.CommandBuffer, binding, 1, buffer, (ulong)_offset, (ulong)_size);
ulong offset = (ulong)_offset;
ulong size = (ulong)_size;
gd.TransformFeedbackApi.CmdBindTransformFeedbackBuffers(cbs.CommandBuffer, binding, 1, in buffer, in offset, in size);
}
}

View File

@ -45,7 +45,7 @@ namespace Ryujinx.Graphics.Vulkan
Level = CommandBufferLevel.Primary,
};
api.AllocateCommandBuffers(device, allocateInfo, out CommandBuffer);
api.AllocateCommandBuffers(device, in allocateInfo, out CommandBuffer);
Dependants = new List<IAuto>();
Waitables = new List<MultiFenceHolder>();
@ -83,7 +83,7 @@ namespace Ryujinx.Graphics.Vulkan
CommandPoolCreateFlags.ResetCommandBufferBit,
};
api.CreateCommandPool(device, commandPoolCreateInfo, null, out _pool).ThrowOnError();
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;
@ -253,7 +253,7 @@ namespace Ryujinx.Graphics.Vulkan
SType = StructureType.CommandBufferBeginInfo,
};
_api.BeginCommandBuffer(entry.CommandBuffer, commandBufferBeginInfo).ThrowOnError();
_api.BeginCommandBuffer(entry.CommandBuffer, in commandBufferBeginInfo).ThrowOnError();
return new CommandBufferScoped(this, entry.CommandBuffer, cursor);
}
@ -311,7 +311,7 @@ namespace Ryujinx.Graphics.Vulkan
lock (_queueLock)
{
_api.QueueSubmit(_queue, 1, sInfo, entry.Fence.GetUnsafe()).ThrowOnError();
_api.QueueSubmit(_queue, 1, in sInfo, entry.Fence.GetUnsafe()).ThrowOnError();
}
}
}

View File

@ -43,7 +43,7 @@ namespace Ryujinx.Graphics.Vulkan
PBufferInfo = &bufferInfo,
};
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null);
}
}
@ -66,7 +66,7 @@ namespace Ryujinx.Graphics.Vulkan
PBufferInfo = pBufferInfo,
};
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null);
}
}
@ -84,7 +84,7 @@ namespace Ryujinx.Graphics.Vulkan
PImageInfo = &imageInfo,
};
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null);
}
}
@ -107,7 +107,7 @@ namespace Ryujinx.Graphics.Vulkan
PImageInfo = pImageInfo,
};
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null);
}
}
@ -144,7 +144,7 @@ namespace Ryujinx.Graphics.Vulkan
PImageInfo = pImageInfo,
};
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null);
i += count - 1;
}
@ -166,7 +166,7 @@ namespace Ryujinx.Graphics.Vulkan
PTexelBufferView = &texelBufferView,
};
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null);
}
}
@ -200,7 +200,7 @@ namespace Ryujinx.Graphics.Vulkan
PTexelBufferView = pTexelBufferView + i,
};
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null);
}
i += count;

View File

@ -40,7 +40,7 @@ namespace Ryujinx.Graphics.Vulkan
PPoolSizes = pPoolsSize,
};
Api.CreateDescriptorPool(device, descriptorPoolCreateInfo, null, out _pool).ThrowOnError();
Api.CreateDescriptorPool(device, in descriptorPoolCreateInfo, null, out _pool).ThrowOnError();
}
}

View File

@ -4,6 +4,7 @@ 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;
@ -42,15 +43,15 @@ namespace Ryujinx.Graphics.Vulkan
private record struct TextureRef
{
public ShaderStage Stage;
public TextureStorage Storage;
public Auto<DisposableImageView> View;
public TextureView View;
public Auto<DisposableImageView> ImageView;
public Auto<DisposableSampler> Sampler;
public TextureRef(ShaderStage stage, TextureStorage storage, Auto<DisposableImageView> view, Auto<DisposableSampler> sampler)
public TextureRef(ShaderStage stage, TextureView view, Auto<DisposableImageView> imageView, Auto<DisposableSampler> sampler)
{
Stage = stage;
Storage = storage;
View = view;
ImageView = imageView;
Sampler = sampler;
}
}
@ -58,14 +59,14 @@ namespace Ryujinx.Graphics.Vulkan
private record struct ImageRef
{
public ShaderStage Stage;
public TextureStorage Storage;
public Auto<DisposableImageView> View;
public TextureView View;
public Auto<DisposableImageView> ImageView;
public ImageRef(ShaderStage stage, TextureStorage storage, Auto<DisposableImageView> view)
public ImageRef(ShaderStage stage, TextureView view, Auto<DisposableImageView> imageView)
{
Stage = stage;
Storage = storage;
View = view;
ImageView = imageView;
}
}
@ -124,6 +125,8 @@ namespace Ryujinx.Graphics.Vulkan
private readonly TextureView _dummyTexture;
private readonly SamplerHolder _dummySampler;
public List<TextureView> FeedbackLoopHazards { get; private set; }
public DescriptorSetUpdater(VulkanRenderer gd, Device device)
{
_gd = gd;
@ -209,10 +212,15 @@ namespace Ryujinx.Graphics.Vulkan
_templateUpdater = new();
}
public void Initialize()
public void Initialize(bool isMainPipeline)
{
IMemoryOwner<byte> dummyTextureData = ByteMemoryPool.RentCleared(4);
MemoryOwner<byte> dummyTextureData = MemoryOwner<byte>.RentCleared(4);
_dummyTexture.SetData(dummyTextureData);
if (isMainPipeline)
{
FeedbackLoopHazards = new();
}
}
private static bool BindingOverlaps(ref DescriptorBufferInfo info, int bindingOffset, int offset, int size)
@ -275,6 +283,18 @@ namespace Ryujinx.Graphics.Vulkan
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)
@ -284,7 +304,7 @@ namespace Ryujinx.Graphics.Vulkan
for (int i = 0; i < segment.Count; i++)
{
ref var texture = ref _textureRefs[segment.Binding + i];
texture.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, texture.Stage.ConvertToPipelineStageFlags());
texture.View?.PrepareForUsage(cbs, texture.Stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards);
}
}
else
@ -305,7 +325,7 @@ namespace Ryujinx.Graphics.Vulkan
for (int i = 0; i < segment.Count; i++)
{
ref var image = ref _imageRefs[segment.Binding + i];
image.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, image.Stage.ConvertToPipelineStageFlags());
image.View?.PrepareForUsage(cbs, image.Stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards);
}
}
else
@ -385,9 +405,12 @@ namespace Ryujinx.Graphics.Vulkan
}
else if (image is TextureView view)
{
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
ref ImageRef iRef = ref _imageRefs[binding];
_imageRefs[binding] = new(stage, view.Storage, view.GetView(imageFormat).GetIdentityImageView());
iRef.View?.ClearUsage(FeedbackLoopHazards);
view?.PrepareForUsage(cbs, stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards);
iRef = new(stage, view, view.GetView(imageFormat).GetIdentityImageView());
}
else
{
@ -486,9 +509,12 @@ namespace Ryujinx.Graphics.Vulkan
}
else if (texture is TextureView view)
{
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
ref TextureRef iRef = ref _textureRefs[binding];
_textureRefs[binding] = new(stage, view.Storage, view.GetImageView(), ((SamplerHolder)sampler)?.GetSampler());
iRef.View?.ClearUsage(FeedbackLoopHazards);
view?.PrepareForUsage(cbs, stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards);
iRef = new(stage, view, view.GetImageView(), ((SamplerHolder)sampler)?.GetSampler());
}
else
{
@ -510,7 +536,7 @@ namespace Ryujinx.Graphics.Vulkan
{
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
_textureRefs[binding] = new(stage, view.Storage, view.GetIdentityImageView(), ((SamplerHolder)sampler)?.GetSampler());
_textureRefs[binding] = new(stage, view, view.GetIdentityImageView(), ((SamplerHolder)sampler)?.GetSampler());
SignalDirty(DirtyFlags.Texture);
}
@ -836,7 +862,7 @@ namespace Ryujinx.Graphics.Vulkan
ref var texture = ref textures[i];
ref var refs = ref _textureRefs[binding + i];
texture.ImageView = refs.View?.Get(cbs).Value ?? default;
texture.ImageView = refs.ImageView?.Get(cbs).Value ?? default;
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
if (texture.ImageView.Handle == 0)
@ -886,7 +912,7 @@ namespace Ryujinx.Graphics.Vulkan
for (int i = 0; i < count; i++)
{
images[i].ImageView = _imageRefs[binding + i].View?.Get(cbs).Value ?? default;
images[i].ImageView = _imageRefs[binding + i].ImageView?.Get(cbs).Value ?? default;
}
tu.Push<DescriptorImageInfo>(images[..count]);
@ -957,7 +983,7 @@ namespace Ryujinx.Graphics.Vulkan
ref var texture = ref textures[i];
ref var refs = ref _textureRefs[binding + i];
texture.ImageView = refs.View?.Get(cbs).Value ?? default;
texture.ImageView = refs.ImageView?.Get(cbs).Value ?? default;
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
if (texture.ImageView.Handle == 0)

View File

@ -0,0 +1,12 @@
using System;
namespace Ryujinx.Graphics.Vulkan
{
[Flags]
internal enum FeedbackLoopAspects
{
None = 0,
Color = 1 << 0,
Depth = 1 << 1,
}
}

View File

@ -250,7 +250,7 @@ namespace Ryujinx.Graphics.Vulkan
Layers = Layers,
};
api.CreateFramebuffer(_device, framebufferCreateInfo, null, out var framebuffer).ThrowOnError();
api.CreateFramebuffer(_device, in framebufferCreateInfo, null, out var framebuffer).ThrowOnError();
return new Auto<DisposableFramebuffer>(new DisposableFramebuffer(api, _device, framebuffer), null, _attachments);
}
@ -302,6 +302,27 @@ namespace Ryujinx.Graphics.Vulkan
_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<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
VulkanRenderer gd,
Device device,

View File

@ -46,6 +46,8 @@ namespace Ryujinx.Graphics.Vulkan
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;
@ -84,6 +86,8 @@ namespace Ryujinx.Graphics.Vulkan
bool supportsViewportArray2,
bool supportsHostImportedMemory,
bool supportsDepthClipControl,
bool supportsAttachmentFeedbackLoop,
bool supportsDynamicAttachmentFeedbackLoop,
uint subgroupSize,
SampleCountFlags supportedSampleCounts,
PortabilitySubsetFlags portabilitySubset,
@ -121,6 +125,8 @@ namespace Ryujinx.Graphics.Vulkan
SupportsViewportArray2 = supportsViewportArray2;
SupportsHostImportedMemory = supportsHostImportedMemory;
SupportsDepthClipControl = supportsDepthClipControl;
SupportsAttachmentFeedbackLoop = supportsAttachmentFeedbackLoop;
SupportsDynamicAttachmentFeedbackLoop = supportsDynamicAttachmentFeedbackLoop;
SubgroupSize = subgroupSize;
SupportedSampleCounts = supportedSampleCounts;
PortabilitySubset = portabilitySubset;

View File

@ -115,7 +115,7 @@ namespace Ryujinx.Graphics.Vulkan
PNext = &importInfo,
};
Result result = _api.AllocateMemory(_device, memoryAllocateInfo, null, out var deviceMemory);
Result result = _api.AllocateMemory(_device, in memoryAllocateInfo, null, out var deviceMemory);
if (result < Result.Success)
{

View File

@ -95,7 +95,7 @@ namespace Ryujinx.Graphics.Vulkan
{
_cachedCommandBufferIndex = -1;
_storages = null;
SetDirty(_gd);
SetDirty(_gd, isImage: true);
}
public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags)

View File

@ -220,7 +220,7 @@ namespace Ryujinx.Graphics.Vulkan
MemoryTypeIndex = (uint)MemoryTypeIndex,
};
_api.AllocateMemory(_device, memoryAllocateInfo, null, out var deviceMemory).ThrowOnError();
_api.AllocateMemory(_device, in memoryAllocateInfo, null, out var deviceMemory).ThrowOnError();
IntPtr hostPointer = IntPtr.Zero;

View File

@ -1,3 +1,4 @@
using Silk.NET.Core.Loader;
using Silk.NET.Vulkan;
using System;
using System.Runtime.InteropServices;
@ -8,6 +9,8 @@ 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);
@ -29,5 +32,20 @@ namespace Ryujinx.Graphics.Vulkan.MoltenVK
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<string>();
}
public static void InitializeResolver()
{
((DefaultPathResolver)PathResolver.Default).Resolvers.Insert(0, Resolver);
}
}
}

View File

@ -2,6 +2,7 @@ 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;
@ -33,6 +34,7 @@ namespace Ryujinx.Graphics.Vulkan
public readonly Action EndRenderPassDelegate;
protected PipelineDynamicState DynamicState;
protected bool IsMainPipeline;
private PipelineState _newState;
private bool _graphicsStateDirty;
private bool _computeStateDirty;
@ -85,6 +87,9 @@ namespace Ryujinx.Graphics.Vulkan
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; }
@ -102,7 +107,7 @@ namespace Ryujinx.Graphics.Vulkan
SType = StructureType.PipelineCacheCreateInfo,
};
gd.Api.CreatePipelineCache(device, pipelineCacheCreateInfo, null, out PipelineCache).ThrowOnError();
gd.Api.CreatePipelineCache(device, in pipelineCacheCreateInfo, null, out PipelineCache).ThrowOnError();
_descriptorSetUpdater = new DescriptorSetUpdater(gd, device);
_vertexBufferUpdater = new VertexBufferUpdater(gd);
@ -126,7 +131,7 @@ namespace Ryujinx.Graphics.Vulkan
public void Initialize()
{
_descriptorSetUpdater.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);
@ -814,6 +819,8 @@ namespace Ryujinx.Graphics.Vulkan
_newState.DepthTestEnable = depthTest.TestEnable;
_newState.DepthWriteEnable = depthTest.WriteEnable;
_newState.DepthCompareOp = depthTest.Func.Convert();
UpdatePassDepthStencil();
SignalStateChange();
}
@ -1079,6 +1086,8 @@ namespace Ryujinx.Graphics.Vulkan
_newState.StencilFrontPassOp = stencilTest.FrontDpPass.Convert();
_newState.StencilFrontDepthFailOp = stencilTest.FrontDpFail.Convert();
_newState.StencilFrontCompareOp = stencilTest.FrontFunc.Convert();
UpdatePassDepthStencil();
SignalStateChange();
}
@ -1426,7 +1435,23 @@ namespace Ryujinx.Graphics.Vulkan
}
}
if (IsMainPipeline)
{
FramebufferParams?.ClearBindings();
}
FramebufferParams = new FramebufferParams(Device, colors, depthStencil);
if (IsMainPipeline)
{
FramebufferParams.AddBindings();
_newState.FeedbackLoopAspects = FeedbackLoopAspects.None;
_bindingBarriersDirty = true;
}
_passWritesDepthStencil = false;
UpdatePassDepthStencil();
UpdatePipelineAttachmentFormats();
}
@ -1493,11 +1518,82 @@ namespace Ryujinx.Graphics.Vulkan
}
}
Gd.Barriers.Flush(Cbs, _program, RenderPassActive, _rpHolder, EndRenderPassDelegate);
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<TextureView> 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))
@ -1505,7 +1601,7 @@ namespace Ryujinx.Graphics.Vulkan
Gd.FlushAllCommands();
}
DynamicState.ReplayIfDirty(Gd.Api, CommandBuffer);
DynamicState.ReplayIfDirty(Gd, CommandBuffer);
if (_needsIndexBufferRebind && _indexBufferPattern == null)
{
@ -1539,7 +1635,15 @@ namespace Ryujinx.Graphics.Vulkan
_vertexBufferUpdater.Commit(Cbs);
}
if (_graphicsStateDirty || Pbp != PipelineBindPoint.Graphics)
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))
{
@ -1548,17 +1652,9 @@ namespace Ryujinx.Graphics.Vulkan
_graphicsStateDirty = false;
Pbp = PipelineBindPoint.Graphics;
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, RenderPassActive, _rpHolder, EndRenderPassDelegate);
Gd.Barriers.Flush(Cbs, _program, _feedbackLoop != 0, RenderPassActive, _rpHolder, EndRenderPassDelegate);
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Graphics);
@ -1628,7 +1724,7 @@ namespace Ryujinx.Graphics.Vulkan
ClearValueCount = 1,
};
Gd.Api.CmdBeginRenderPass(CommandBuffer, renderPassBeginInfo, SubpassContents.Inline);
Gd.Api.CmdBeginRenderPass(CommandBuffer, in renderPassBeginInfo, SubpassContents.Inline);
RenderPassActive = true;
}
}

View File

@ -116,7 +116,7 @@ namespace Ryujinx.Graphics.Vulkan
DependencyCount = 1,
};
gd.Api.CreateRenderPass(device, renderPassCreateInfo, null, out var renderPass).ThrowOnError();
gd.Api.CreateRenderPass(device, in renderPassCreateInfo, null, out var renderPass).ThrowOnError();
return new DisposableRenderPass(gd.Api, device, renderPass);
}

View File

@ -1,5 +1,6 @@
using Ryujinx.Common.Memory;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.EXT;
namespace Ryujinx.Graphics.Vulkan
{
@ -21,6 +22,8 @@ namespace Ryujinx.Graphics.Vulkan
private Array4<float> _blendConstants;
private FeedbackLoopAspects _feedbackLoopAspects;
public uint ViewportsCount;
public Array16<Viewport> Viewports;
@ -32,7 +35,8 @@ namespace Ryujinx.Graphics.Vulkan
Scissor = 1 << 2,
Stencil = 1 << 3,
Viewport = 1 << 4,
All = Blend | DepthBias | Scissor | Stencil | Viewport,
FeedbackLoop = 1 << 5,
All = Blend | DepthBias | Scissor | Stencil | Viewport | FeedbackLoop,
}
private DirtyFlags _dirty;
@ -99,13 +103,22 @@ namespace Ryujinx.Graphics.Vulkan
}
}
public void SetFeedbackLoop(FeedbackLoopAspects aspects)
{
_feedbackLoopAspects = aspects;
_dirty |= DirtyFlags.FeedbackLoop;
}
public void ForceAllDirty()
{
_dirty = DirtyFlags.All;
}
public void ReplayIfDirty(Vk api, CommandBuffer commandBuffer)
public void ReplayIfDirty(VulkanRenderer gd, CommandBuffer commandBuffer)
{
Vk api = gd.Api;
if (_dirty.HasFlag(DirtyFlags.Blend))
{
RecordBlend(api, commandBuffer);
@ -131,6 +144,11 @@ namespace Ryujinx.Graphics.Vulkan
RecordViewport(api, commandBuffer);
}
if (_dirty.HasFlag(DirtyFlags.FeedbackLoop) && gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop)
{
RecordFeedbackLoop(gd.DynamicFeedbackLoopApi, commandBuffer);
}
_dirty = DirtyFlags.None;
}
@ -169,5 +187,17 @@ namespace Ryujinx.Graphics.Vulkan
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);
}
}
}

View File

@ -28,6 +28,8 @@ namespace Ryujinx.Graphics.Vulkan
_activeBufferMirrors = new();
CommandBuffer = (Cbs = gd.CommandBufferPool.Rent()).CommandBuffer;
IsMainPipeline = true;
}
private void CopyPendingQuery()
@ -235,7 +237,7 @@ namespace Ryujinx.Graphics.Vulkan
if (Pipeline != null && Pbp == PipelineBindPoint.Graphics)
{
DynamicState.ReplayIfDirty(Gd.Api, CommandBuffer);
DynamicState.ReplayIfDirty(Gd, CommandBuffer);
}
}

View File

@ -91,7 +91,7 @@ namespace Ryujinx.Graphics.Vulkan
Flags = flags,
};
gd.Api.CreateDescriptorSetLayout(device, descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError();
gd.Api.CreateDescriptorSetLayout(device, in descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError();
}
}

View File

@ -8,6 +8,7 @@ namespace Ryujinx.Graphics.Vulkan
struct PipelineState : IDisposable
{
private const int RequiredSubgroupSize = 32;
private const int MaxDynamicStatesCount = 9;
public PipelineUid Internal;
@ -299,6 +300,12 @@ namespace Ryujinx.Graphics.Vulkan
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<PipelineShaderStageCreateInfo> Stages;
public PipelineLayout PipelineLayout;
@ -439,7 +446,7 @@ namespace Ryujinx.Graphics.Vulkan
{
SType = StructureType.PipelineInputAssemblyStateCreateInfo,
PrimitiveRestartEnable = primitiveRestartEnable,
Topology = Topology,
Topology = HasTessellationControlShader ? PrimitiveTopology.PatchList : Topology,
};
var tessellationState = new PipelineTessellationStateCreateInfo
@ -564,9 +571,11 @@ namespace Ryujinx.Graphics.Vulkan
}
bool supportsExtDynamicState = gd.Capabilities.SupportsExtendedDynamicState;
int dynamicStatesCount = supportsExtDynamicState ? 8 : 7;
bool supportsFeedbackLoopDynamicState = gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop;
DynamicState* dynamicStates = stackalloc DynamicState[dynamicStatesCount];
DynamicState* dynamicStates = stackalloc DynamicState[MaxDynamicStatesCount];
int dynamicStatesCount = 7;
dynamicStates[0] = DynamicState.Viewport;
dynamicStates[1] = DynamicState.Scissor;
@ -578,7 +587,12 @@ namespace Ryujinx.Graphics.Vulkan
if (supportsExtDynamicState)
{
dynamicStates[7] = DynamicState.VertexInputBindingStrideExt;
dynamicStates[dynamicStatesCount++] = DynamicState.VertexInputBindingStrideExt;
}
if (supportsFeedbackLoopDynamicState)
{
dynamicStates[dynamicStatesCount++] = DynamicState.AttachmentFeedbackLoopEnableExt;
}
var pipelineDynamicStateCreateInfo = new PipelineDynamicStateCreateInfo
@ -588,9 +602,27 @@ namespace Ryujinx.Graphics.Vulkan
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,

View File

@ -52,7 +52,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
PipelineStatistics = flags,
};
gd.Api.CreateQueryPool(device, queryPoolCreateInfo, null, out _queryPool).ThrowOnError();
gd.Api.CreateQueryPool(device, in queryPoolCreateInfo, null, out _queryPool).ThrowOnError();
}
var buffer = gd.BufferManager.Create(gd, sizeof(long), forConditionalRendering: true);

View File

@ -125,7 +125,7 @@ namespace Ryujinx.Graphics.Vulkan
DependencyCount = 1,
};
gd.Api.CreateRenderPass(device, renderPassCreateInfo, null, out var renderPass).ThrowOnError();
gd.Api.CreateRenderPass(device, in renderPassCreateInfo, null, out var renderPass).ThrowOnError();
_renderPass = new Auto<DisposableRenderPass>(new DisposableRenderPass(gd.Api, device, renderPass));
}

View File

@ -14,13 +14,20 @@ namespace Ryujinx.Graphics.Vulkan
private int _bindCount;
protected void SetDirty(VulkanRenderer gd)
protected void SetDirty(VulkanRenderer gd, bool isImage)
{
ReleaseDescriptorSet();
if (_bindCount != 0)
{
gd.PipelineInternal.ForceTextureDirty();
if (isImage)
{
gd.PipelineInternal.ForceImageDirty();
}
else
{
gd.PipelineInternal.ForceTextureDirty();
}
}
}

View File

@ -68,7 +68,7 @@ namespace Ryujinx.Graphics.Vulkan
samplerCreateInfo.BorderColor = BorderColor.FloatCustomExt;
}
gd.Api.CreateSampler(device, samplerCreateInfo, null, out var sampler).ThrowOnError();
gd.Api.CreateSampler(device, in samplerCreateInfo, null, out var sampler).ThrowOnError();
_sampler = new Auto<DisposableSampler>(new DisposableSampler(gd.Api, device, sampler));
}

View File

@ -64,7 +64,7 @@ namespace Ryujinx.Graphics.Vulkan
PCode = (uint*)pCode,
};
api.CreateShaderModule(device, shaderModuleCreateInfo, null, out _module).ThrowOnError();
api.CreateShaderModule(device, in shaderModuleCreateInfo, null, out _module).ThrowOnError();
}
CompileStatus = ProgramLinkStatus.Success;

View File

@ -104,7 +104,7 @@ namespace Ryujinx.Graphics.Vulkan
{
_cachedCommandBufferIndex = -1;
_storages = null;
SetDirty(_gd);
SetDirty(_gd, isImage: false);
}
public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags)

View File

@ -88,7 +88,7 @@ namespace Ryujinx.Graphics.Vulkan
DstOffsets = dstOffsets,
};
api.CmdBlitImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, region, filter);
api.CmdBlitImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, in region, filter);
copySrcLevel++;
copyDstLevel++;
@ -320,13 +320,13 @@ namespace Ryujinx.Graphics.Vulkan
{
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, region);
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, region);
api.CmdCopyImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, in region);
}
width = Math.Max(1, width >> 1);
@ -422,7 +422,7 @@ namespace Ryujinx.Graphics.Vulkan
DependencyCount = 1,
};
gd.Api.CreateRenderPass2(device, renderPassCreateInfo, null, out var renderPass).ThrowOnError();
gd.Api.CreateRenderPass2(device, in renderPassCreateInfo, null, out var renderPass).ThrowOnError();
using var rp = new Auto<DisposableRenderPass>(new DisposableRenderPass(gd.Api, device, renderPass));
@ -445,7 +445,7 @@ namespace Ryujinx.Graphics.Vulkan
Layers = (uint)src.Layers,
};
gd.Api.CreateFramebuffer(device, framebufferCreateInfo, null, out var framebuffer).ThrowOnError();
gd.Api.CreateFramebuffer(device, in framebufferCreateInfo, null, out var framebuffer).ThrowOnError();
using var fb = new Auto<DisposableFramebuffer>(new DisposableFramebuffer(gd.Api, device, framebuffer), null, srcView, dstView);
var renderArea = new Rect2D(null, new Extent2D((uint)src.Info.Width, (uint)src.Info.Height));
@ -465,7 +465,7 @@ namespace Ryujinx.Graphics.Vulkan
// 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, renderPassBeginInfo, SubpassContents.Inline);
gd.Api.CmdBeginRenderPass(cbs.CommandBuffer, in renderPassBeginInfo, SubpassContents.Inline);
gd.Api.CmdEndRenderPass(cbs.CommandBuffer);
}
}

View File

@ -4,6 +4,7 @@ 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;
@ -12,6 +13,11 @@ namespace Ryujinx.Graphics.Vulkan
{
class TextureStorage : IDisposable
{
private struct TextureSliceInfo
{
public int BindCount;
}
private const MemoryPropertyFlags DefaultImageMemoryFlags =
MemoryPropertyFlags.DeviceLocalBit;
@ -43,6 +49,7 @@ namespace Ryujinx.Graphics.Vulkan
private readonly Image _image;
private readonly Auto<DisposableImage> _imageAuto;
private readonly Auto<MemoryAllocation> _allocationAuto;
private readonly int _depthOrLayers;
private Auto<MemoryAllocation> _foreignAllocationAuto;
private Dictionary<Format, TextureStorage> _aliasedStorages;
@ -55,6 +62,9 @@ namespace Ryujinx.Graphics.Vulkan
private int _viewsCount;
private readonly ulong _size;
private int _bindCount;
private readonly TextureSliceInfo[] _slices;
public VkFormat VkFormat { get; }
public unsafe TextureStorage(
@ -73,6 +83,7 @@ namespace Ryujinx.Graphics.Vulkan
var depth = (uint)(info.Target == Target.Texture3D ? info.Depth : 1);
VkFormat = format;
_depthOrLayers = info.GetDepthOrLayers();
var type = info.Target.Convert();
@ -80,7 +91,7 @@ namespace Ryujinx.Graphics.Vulkan
var sampleCountFlags = ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)info.Samples);
var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample);
var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities);
var flags = ImageCreateFlags.CreateMutableFormatBit | ImageCreateFlags.CreateExtendedUsageBit;
@ -114,7 +125,7 @@ namespace Ryujinx.Graphics.Vulkan
Flags = flags,
};
gd.Api.CreateImage(device, imageCreateInfo, null, out _image).ThrowOnError();
gd.Api.CreateImage(device, in imageCreateInfo, null, out _image).ThrowOnError();
if (foreignAllocation == null)
{
@ -148,6 +159,8 @@ namespace Ryujinx.Graphics.Vulkan
InitialTransition(ImageLayout.Preinitialized, ImageLayout.General);
}
_slices = new TextureSliceInfo[levels * _depthOrLayers];
}
public TextureStorage CreateAliasedColorForDepthStorageUnsafe(Format format)
@ -284,7 +297,7 @@ namespace Ryujinx.Graphics.Vulkan
0,
null,
1,
barrier);
in barrier);
if (useTempCbs)
{
@ -292,7 +305,7 @@ namespace Ryujinx.Graphics.Vulkan
}
}
public static ImageUsageFlags GetImageUsage(Format format, Target target, bool supportsMsStorage)
public static ImageUsageFlags GetImageUsage(Format format, Target target, in HardwareCapabilities capabilities)
{
var usage = DefaultUsageFlags;
@ -305,11 +318,19 @@ namespace Ryujinx.Graphics.Vulkan
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;
}
@ -401,11 +422,11 @@ namespace Ryujinx.Graphics.Vulkan
if (to)
{
_gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, region);
_gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, in region);
}
else
{
_gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, region);
_gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, in region);
}
offset += mipSize;
@ -510,6 +531,55 @@ namespace Ryujinx.Graphics.Vulkan
}
}
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++;

View File

@ -23,6 +23,8 @@ namespace Ryujinx.Graphics.Vulkan
private readonly Auto<DisposableImageView> _imageView2dArray;
private Dictionary<Format, TextureView> _selfManagedViews;
private int _hazardUses;
private readonly TextureCreateInfo _info;
private HashTableSlim<RenderPassCacheKey, RenderPassHolder> _renderPasses;
@ -60,7 +62,7 @@ namespace Ryujinx.Graphics.Vulkan
gd.Textures.Add(this);
var format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format);
var usage = TextureStorage.GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample);
var usage = TextureStorage.GetImageUsage(info.Format, info.Target, gd.Capabilities);
var levels = (uint)info.Levels;
var layers = (uint)info.GetLayers();
@ -117,7 +119,7 @@ namespace Ryujinx.Graphics.Vulkan
PNext = &imageViewUsage,
};
gd.Api.CreateImageView(device, imageCreateInfo, null, out var imageView).ThrowOnError();
gd.Api.CreateImageView(device, in imageCreateInfo, null, out var imageView).ThrowOnError();
return new Auto<DisposableImageView>(new DisposableImageView(gd.Api, device, imageView), null, storage.GetImage());
}
@ -492,7 +494,7 @@ namespace Ryujinx.Graphics.Vulkan
dstStageMask,
DependencyFlags.None,
1,
memoryBarrier,
in memoryBarrier,
0,
null,
0,
@ -557,7 +559,7 @@ namespace Ryujinx.Graphics.Vulkan
0,
null,
1,
memoryBarrier);
in memoryBarrier);
}
public TextureView GetView(Format format)
@ -949,11 +951,11 @@ namespace Ryujinx.Graphics.Vulkan
if (to)
{
_gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, region);
_gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, in region);
}
else
{
_gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, region);
_gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, in region);
}
offset += mipSize;
@ -1010,11 +1012,11 @@ namespace Ryujinx.Graphics.Vulkan
if (to)
{
_gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, region);
_gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, in region);
}
else
{
_gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, region);
_gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, in region);
}
}
@ -1034,6 +1036,34 @@ namespace Ryujinx.Graphics.Vulkan
throw new NotImplementedException();
}
public void PrepareForUsage(CommandBufferScoped cbs, PipelineStageFlags flags, List<TextureView> feedbackLoopHazards)
{
Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, flags);
if (feedbackLoopHazards != null && Storage.IsBound(this))
{
feedbackLoopHazards.Add(this);
_hazardUses++;
}
}
public void ClearUsage(List<TextureView> feedbackLoopHazards)
{
if (_hazardUses != 0 && feedbackLoopHazards != null)
{
feedbackLoopHazards.Remove(this);
_hazardUses--;
}
}
public void DecrementHazardUses()
{
if (_hazardUses != 0)
{
_hazardUses--;
}
}
public (RenderPassHolder rpHolder, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
VulkanRenderer gd,
Device device,

View File

@ -90,11 +90,9 @@ namespace Ryujinx.Graphics.Vulkan
DriverId.SamsungProprietary => "Samsung",
DriverId.MesaVenus => "Venus",
DriverId.MesaDozen => "Dozen",
// TODO: Use real enum when we have an up to date Silk.NET.
(DriverId)24 => "NVK",
(DriverId)25 => "Imagination (Open)",
(DriverId)26 => "Honeykrisp",
DriverId.MesaNvk => "NVK",
DriverId.ImaginationOpenSourceMesa => "Imagination (Open)",
DriverId.MesaAgxv => "Honeykrisp",
_ => id.ToString(),
};
}

View File

@ -44,6 +44,8 @@ namespace Ryujinx.Graphics.Vulkan
"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 = {
@ -357,6 +359,28 @@ namespace Ryujinx.Graphics.Vulkan
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,
@ -531,6 +555,36 @@ namespace Ryujinx.Graphics.Vulkan
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];

View File

@ -38,6 +38,7 @@ namespace Ryujinx.Graphics.Vulkan
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; }
@ -149,6 +150,11 @@ namespace Ryujinx.Graphics.Vulkan
DrawIndirectCountApi = drawIndirectCountApi;
}
if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtAttachmentFeedbackLoopDynamicState dynamicFeedbackLoopApi))
{
DynamicFeedbackLoopApi = dynamicFeedbackLoopApi;
}
if (maxQueueCount >= 2)
{
Api.GetDeviceQueue(_device, queueFamilyIndex, 1, out var backgroundQueue);
@ -243,6 +249,16 @@ namespace Ryujinx.Graphics.Vulkan
SType = StructureType.PhysicalDeviceDepthClipControlFeaturesExt,
};
PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT featuresAttachmentFeedbackLoop = new()
{
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt,
};
PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT featuresDynamicAttachmentFeedbackLoop = new()
{
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt,
};
PhysicalDevicePortabilitySubsetFeaturesKHR featuresPortabilitySubset = new()
{
SType = StructureType.PhysicalDevicePortabilitySubsetFeaturesKhr,
@ -279,6 +295,22 @@ namespace Ryujinx.Graphics.Vulkan
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)
@ -401,6 +433,8 @@ namespace Ryujinx.Graphics.Vulkan
_physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"),
_physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName),
supportsDepthClipControl && featuresDepthClipControl.DepthClipControl,
supportsAttachmentFeedbackLoop && featuresAttachmentFeedbackLoop.AttachmentFeedbackLoopLayout,
supportsDynamicAttachmentFeedbackLoop && featuresDynamicAttachmentFeedbackLoop.AttachmentFeedbackLoopDynamicState,
propertiesSubgroup.SubgroupSize,
supportedSampleCounts,
portabilityFlags,

View File

@ -160,7 +160,7 @@ namespace Ryujinx.Graphics.Vulkan
SwizzleComponent.Blue,
SwizzleComponent.Alpha);
_gd.SwapchainApi.CreateSwapchain(_device, swapchainCreateInfo, null, out _swapchain).ThrowOnError();
_gd.SwapchainApi.CreateSwapchain(_device, in swapchainCreateInfo, null, out _swapchain).ThrowOnError();
_gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, null);
@ -187,14 +187,14 @@ namespace Ryujinx.Graphics.Vulkan
for (int i = 0; i < _imageAvailableSemaphores.Length; i++)
{
_gd.Api.CreateSemaphore(_device, semaphoreCreateInfo, null, out _imageAvailableSemaphores[i]).ThrowOnError();
_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, semaphoreCreateInfo, null, out _renderFinishedSemaphores[i]).ThrowOnError();
_gd.Api.CreateSemaphore(_device, in semaphoreCreateInfo, null, out _renderFinishedSemaphores[i]).ThrowOnError();
}
}
@ -220,7 +220,7 @@ namespace Ryujinx.Graphics.Vulkan
SubresourceRange = subresourceRange,
};
_gd.Api.CreateImageView(_device, imageCreateInfo, null, out var imageView).ThrowOnError();
_gd.Api.CreateImageView(_device, in imageCreateInfo, null, out var imageView).ThrowOnError();
return new TextureView(_gd, _device, new DisposableImageView(_gd.Api, _device, imageView), info, format);
}
@ -479,7 +479,7 @@ namespace Ryujinx.Graphics.Vulkan
lock (_gd.QueueLock)
{
_gd.SwapchainApi.QueuePresent(_gd.Queue, presentInfo);
_gd.SwapchainApi.QueuePresent(_gd.Queue, in presentInfo);
}
}
@ -611,7 +611,7 @@ namespace Ryujinx.Graphics.Vulkan
0,
null,
1,
barrier);
in barrier);
}
private void CaptureFrame(TextureView texture, int x, int y, int width, int height, bool isBgra, bool flipX, bool flipY)

View File

@ -4,6 +4,8 @@ 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;
@ -13,7 +15,6 @@ using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper;
using Ryujinx.UI.Common.SystemInfo;
using Ryujinx.UI.Widgets;
using SixLabors.ImageSharp.Formats.Jpeg;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@ -41,9 +42,6 @@ namespace Ryujinx
[LibraryImport("user32.dll", SetLastError = true)]
public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type);
[LibraryImport("libc", SetLastError = true)]
private static partial int setenv([MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string value, int overwrite);
private const uint MbIconWarning = 0x30;
static Program()
@ -105,12 +103,13 @@ namespace Ryujinx
throw new NotSupportedException("Failed to initialize multi-threading support.");
}
Environment.SetEnvironmentVariable("GDK_BACKEND", "x11");
setenv("GDK_BACKEND", "x11", 1);
OsUtils.SetEnvironmentVariableNoCaching("GDK_BACKEND", "x11");
}
if (OperatingSystem.IsMacOS())
{
MVKInitialization.InitializeResolver();
string baseDirectory = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
string resourcesDataDir;
@ -123,19 +122,13 @@ namespace Ryujinx
resourcesDataDir = baseDirectory;
}
static void SetEnvironmentVariableNoCaching(string key, string value)
{
int res = setenv(key, value, 1);
Debug.Assert(res != -1);
}
// On macOS, GTK3 needs XDG_DATA_DIRS to be set, otherwise it will try searching for "gschemas.compiled" in system directories.
SetEnvironmentVariableNoCaching("XDG_DATA_DIRS", Path.Combine(resourcesDataDir, "share"));
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.
SetEnvironmentVariableNoCaching("GDK_PIXBUF_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gdk-pixbuf-2.0", "2.10.0", "loaders.cache"));
OsUtils.SetEnvironmentVariableNoCaching("GDK_PIXBUF_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gdk-pixbuf-2.0", "2.10.0", "loaders.cache"));
SetEnvironmentVariableNoCaching("GTK_IM_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gtk-3.0", "3.0.0", "immodules.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);
@ -162,12 +155,6 @@ namespace Ryujinx
});
};
// Sets ImageSharp Jpeg Encoder Quality.
SixLabors.ImageSharp.Configuration.Default.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder()
{
Quality = 100,
});
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
@ -237,9 +224,9 @@ namespace Ryujinx
// Logging system information.
PrintSystemInfo();
// Enable OGL multithreading on the driver, when available.
// Enable OGL multithreading on the driver, and some other flags.
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
DriverUtilities.ToggleOGLThreading(threadingMode == BackendThreading.Off);
DriverUtilities.InitDriverConfig(threadingMode == BackendThreading.Off);
// Initialize Gtk.
Application.Init();
@ -256,6 +243,12 @@ namespace Ryujinx
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;

View File

@ -30,7 +30,6 @@
<PackageReference Include="OpenTK.Graphics" />
<PackageReference Include="SPB" />
<PackageReference Include="SharpZipLib" />
<PackageReference Include="SixLabors.ImageSharp" />
</ItemGroup>
<ItemGroup>

View File

@ -187,7 +187,10 @@ namespace Ryujinx.UI
: IntegrityCheckLevel.None;
// Instantiate GUI objects.
ApplicationLibrary = new ApplicationLibrary(_virtualFileSystem, checkLevel);
ApplicationLibrary = new ApplicationLibrary(_virtualFileSystem, checkLevel)
{
DesiredLanguage = ConfigurationState.Instance.System.Language,
};
_uiHandler = new GtkHostUIHandler(this);
_deviceExitStatus = new AutoResetEvent(false);
@ -325,7 +328,6 @@ namespace Ryujinx.UI
_hideUI.Label = _hideUI.Label.Replace("SHOWUIKEY", ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI.ToString());
UpdateColumns();
UpdateGameTable();
ConfigurationState.Instance.UI.GameDirs.Event += (sender, args) =>
{
@ -738,7 +740,8 @@ namespace Ryujinx.UI
Thread applicationLibraryThread = new(() =>
{
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language);
ApplicationLibrary.DesiredLanguage = ConfigurationState.Instance.System.Language;
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs);
_updatingGameTable = false;
})

View File

@ -13,16 +13,13 @@ using Ryujinx.Input.HLE;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper;
using Ryujinx.UI.Widgets;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SkiaSharp;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Image = SixLabors.ImageSharp.Image;
using Key = Ryujinx.Input.Key;
using ScalingFilter = Ryujinx.Graphics.GAL.ScalingFilter;
using Switch = Ryujinx.HLE.Switch;
@ -404,23 +401,31 @@ namespace Ryujinx.UI
return;
}
Image image = e.IsBgra ? Image.LoadPixelData<Bgra32>(e.Data, e.Width, e.Height)
: Image.LoadPixelData<Rgba32>(e.Data, e.Width, e.Height);
var colorType = e.IsBgra ? SKColorType.Bgra8888 : SKColorType.Rgba8888;
using var image = new SKBitmap(new SKImageInfo(e.Width, e.Height, colorType, SKAlphaType.Premul));
if (e.FlipX)
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)
{
image.Mutate(x => x.Flip(FlipMode.Horizontal));
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());
if (e.FlipY)
{
image.Mutate(x => x.Flip(FlipMode.Vertical));
}
image.SaveAsPng(path, new PngEncoder()
{
ColorType = PngColorType.Rgb,
});
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();

View File

@ -9,16 +9,13 @@ using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Memory;
using Ryujinx.HLE.FileSystem;
using Ryujinx.UI.Common.Configuration;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SkiaSharp;
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Image = SixLabors.ImageSharp.Image;
using System.Runtime.InteropServices;
namespace Ryujinx.UI.Windows
{
@ -144,9 +141,11 @@ namespace Ryujinx.UI.Windows
stream.Position = 0;
Image avatarImage = Image.LoadPixelData<Rgba32>(DecompressYaz0(stream), 256, 256);
using var avatarImage = new SKBitmap(new SKImageInfo(256, 256, SKColorType.Rgba8888));
var data = DecompressYaz0(stream);
Marshal.Copy(data, 0, avatarImage.GetPixels(), data.Length);
avatarImage.SaveAsPng(streamPng);
avatarImage.Encode(streamPng, SKEncodedImageFormat.Png, 80);
_avatarDict.Add(item.FullPath, streamPng.ToArray());
}
@ -170,15 +169,23 @@ namespace Ryujinx.UI.Windows
{
using MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream();
Image avatarImage = Image.Load(data, new PngDecoder());
using var avatarImage = SKBitmap.Decode(data);
using var surface = SKSurface.Create(avatarImage.Info);
avatarImage.Mutate(x => x.BackgroundColor(new Rgba32(
var background = new SKColor(
(byte)(_backgroundColor.Red * 255),
(byte)(_backgroundColor.Green * 255),
(byte)(_backgroundColor.Blue * 255),
(byte)(_backgroundColor.Alpha * 255)
)));
avatarImage.SaveAsJpeg(streamJpg);
);
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();
}

View File

@ -4,15 +4,13 @@ using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Widgets;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Image = SixLabors.ImageSharp.Image;
namespace Ryujinx.UI.Windows
{
@ -177,13 +175,13 @@ namespace Ryujinx.UI.Windows
private void ProcessProfileImage(byte[] buffer)
{
using Image image = Image.Load(buffer);
using var image = SKBitmap.Decode(buffer);
image.Mutate(x => x.Resize(256, 256));
image.Resize(new SKImageInfo(256, 256), SKFilterQuality.High);
using MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream();
image.SaveAsJpeg(streamJpg);
image.Encode(streamJpg, SKEncodedImageFormat.Jpeg, 80);
_bufferImageProfile = streamJpg.ToArray();
}

View File

@ -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();
}
}
}

View File

@ -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());
}
}
}

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
<IsRoslynComponent>true</IsRoslynComponent>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
</ItemGroup>
</Project>

View File

@ -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<ClassDeclarationSyntax> Types = new HashSet<ClassDeclarationSyntax>();
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is ClassDeclarationSyntax classDeclaration)
{
if (classDeclaration.BaseList == null)
{
return;
}
Types.Add(classDeclaration);
}
}
}
}

View File

@ -8,27 +8,24 @@ namespace Ryujinx.HLE.HOS.Applets
{
static class AppletManager
{
private static readonly Dictionary<AppletId, Type> _appletMapping;
static AppletManager()
{
_appletMapping = new Dictionary<AppletId, Type>
{
{ AppletId.Error, typeof(ErrorApplet) },
{ AppletId.PlayerSelect, typeof(PlayerSelectApplet) },
{ AppletId.Controller, typeof(ControllerApplet) },
{ AppletId.SoftwareKeyboard, typeof(SoftwareKeyboardApplet) },
{ AppletId.LibAppletWeb, typeof(BrowserApplet) },
{ AppletId.LibAppletShop, typeof(BrowserApplet) },
{ AppletId.LibAppletOff, typeof(BrowserApplet) },
};
}
public static IApplet Create(AppletId applet, Horizon system)
{
if (_appletMapping.TryGetValue(applet, out Type appletClass))
switch (applet)
{
return (IApplet)Activator.CreateInstance(appletClass, system);
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.");

View File

@ -112,11 +112,16 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{
// Update the parameters that were provided.
_state.InputText = inputText ?? _state.InputText;
_state.CursorBegin = cursorBegin.GetValueOrDefault(_state.CursorBegin);
_state.CursorEnd = cursorEnd.GetValueOrDefault(_state.CursorEnd);
_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;

View File

@ -1,14 +1,9 @@
using Ryujinx.HLE.UI;
using Ryujinx.Memory;
using SixLabors.Fonts;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SkiaSharp;
using System;
using System.Diagnostics;
using System.IO;
using System.Numerics;
using System.Reflection;
using System.Runtime.InteropServices;
@ -29,38 +24,39 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
private readonly object _bufferLock = new();
private RenderingSurfaceInfo _surfaceInfo = null;
private Image<Argb32> _surface = null;
private SKImageInfo _imageInfo;
private SKSurface _surface = null;
private byte[] _bufferData = null;
private readonly Image _ryujinxLogo = null;
private readonly Image _padAcceptIcon = null;
private readonly Image _padCancelIcon = null;
private readonly Image _keyModeIcon = 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 Color _textNormalColor;
private readonly Color _textSelectedColor;
private readonly Color _textOverCursorColor;
private readonly SKColor _textNormalColor;
private readonly SKColor _textSelectedColor;
private readonly SKColor _textOverCursorColor;
private readonly Brush _panelBrush;
private readonly Brush _disabledBrush;
private readonly Brush _cursorBrush;
private readonly Brush _selectionBoxBrush;
private readonly SKPaint _panelBrush;
private readonly SKPaint _disabledBrush;
private readonly SKPaint _cursorBrush;
private readonly SKPaint _selectionBoxBrush;
private readonly Pen _textBoxOutlinePen;
private readonly Pen _cursorPen;
private readonly Pen _selectionBoxPen;
private readonly Pen _padPressedPen;
private readonly SKPaint _textBoxOutlinePen;
private readonly SKPaint _cursorPen;
private readonly SKPaint _selectionBoxPen;
private readonly SKPaint _padPressedPen;
private readonly int _inputTextFontSize;
private Font _messageFont;
private Font _inputTextFont;
private Font _labelsTextFont;
private SKFont _messageFont;
private SKFont _inputTextFont;
private SKFont _labelsTextFont;
private RectangleF _panelRectangle;
private Point _logoPosition;
private SKRect _panelRectangle;
private SKPoint _logoPosition;
private float _messagePositionY;
public SoftwareKeyboardRendererBase(IHostUITheme uiTheme)
@ -78,10 +74,10 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
_padCancelIcon = LoadResource(typeof(SoftwareKeyboardRendererBase).Assembly, padCancelIconPath, 0, 0);
_keyModeIcon = LoadResource(typeof(SoftwareKeyboardRendererBase).Assembly, keyModeIconPath, 0, 0);
Color panelColor = ToColor(uiTheme.DefaultBackgroundColor, 255);
Color panelTransparentColor = ToColor(uiTheme.DefaultBackgroundColor, 150);
Color borderColor = ToColor(uiTheme.DefaultBorderColor);
Color selectionBackgroundColor = ToColor(uiTheme.SelectionBackgroundColor);
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);
@ -92,15 +88,29 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
_textBoxOutlineWidth = 2;
_padPressedPenWidth = 2;
_panelBrush = new SolidBrush(panelColor);
_disabledBrush = new SolidBrush(panelTransparentColor);
_cursorBrush = new SolidBrush(_textNormalColor);
_selectionBoxBrush = new SolidBrush(selectionBackgroundColor);
_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 = Pens.Solid(borderColor, _textBoxOutlineWidth);
_cursorPen = Pens.Solid(_textNormalColor, cursorWidth);
_selectionBoxPen = Pens.Solid(selectionBackgroundColor, cursorWidth);
_padPressedPen = Pens.Solid(borderColor, _padPressedPenWidth);
_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;
@ -123,9 +133,10 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{
try
{
_messageFont = SystemFonts.CreateFont(fontFamily, 26, FontStyle.Regular);
_inputTextFont = SystemFonts.CreateFont(fontFamily, _inputTextFontSize, FontStyle.Regular);
_labelsTextFont = SystemFonts.CreateFont(fontFamily, 24, FontStyle.Regular);
using var typeface = SKTypeface.FromFamilyName(fontFamily, SKFontStyle.Normal);
_messageFont = new SKFont(typeface, 26);
_inputTextFont = new SKFont(typeface, _inputTextFontSize);
_labelsTextFont = new SKFont(typeface, 24);
return;
}
@ -137,7 +148,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
throw new Exception($"None of these fonts were found in the system: {String.Join(", ", availableFonts)}!");
}
private static Color ToColor(ThemeColor color, byte? overrideAlpha = null, bool flipRgb = false)
private static SKColor ToColor(ThemeColor color, byte? overrideAlpha = null, bool flipRgb = false)
{
var a = (byte)(color.A * 255);
var r = (byte)(color.R * 255);
@ -151,34 +162,33 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
b = (byte)(255 - b);
}
return Color.FromRgba(r, g, b, overrideAlpha.GetValueOrDefault(a));
return new SKColor(r, g, b, overrideAlpha.GetValueOrDefault(a));
}
private static Image LoadResource(Assembly assembly, string resourcePath, int newWidth, int newHeight)
private static SKBitmap LoadResource(Assembly assembly, string resourcePath, int newWidth, int newHeight)
{
Stream resourceStream = assembly.GetManifestResourceStream(resourcePath);
return LoadResource(resourceStream, newWidth, newHeight);
}
private static Image LoadResource(Stream resourceStream, int newWidth, int newHeight)
private static SKBitmap LoadResource(Stream resourceStream, int newWidth, int newHeight)
{
Debug.Assert(resourceStream != null);
var image = Image.Load(resourceStream);
var bitmap = SKBitmap.Decode(resourceStream);
if (newHeight != 0 && newWidth != 0)
{
image.Mutate(x => x.Resize(newWidth, newHeight, KnownResamplers.Lanczos3));
var resized = bitmap.Resize(new SKImageInfo(newWidth, newHeight), SKFilterQuality.High);
if (resized != null)
{
bitmap.Dispose();
bitmap = resized;
}
}
return image;
}
private static void SetGraphicsOptions(IImageProcessingContext context)
{
context.GetGraphicsOptions().Antialias = true;
context.GetDrawingOptions().GraphicsOptions.Antialias = true;
return bitmap;
}
private void DrawImmutableElements()
@ -187,22 +197,18 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{
return;
}
var canvas = _surface.Canvas;
_surface.Mutate(context =>
{
SetGraphicsOptions(context);
canvas.Clear(SKColors.Transparent);
canvas.DrawRect(_panelRectangle, _panelBrush);
canvas.DrawBitmap(_ryujinxLogo, _logoPosition);
context.Clear(Color.Transparent);
context.Fill(_panelBrush, _panelRectangle);
context.DrawImage(_ryujinxLogo, _logoPosition, 1);
float halfWidth = _panelRectangle.Width / 2;
float buttonsY = _panelRectangle.Top + 185;
float halfWidth = _panelRectangle.Width / 2;
float buttonsY = _panelRectangle.Y + 185;
SKPoint disableButtonPosition = new(halfWidth + 180, buttonsY);
PointF disableButtonPosition = new(halfWidth + 180, buttonsY);
DrawControllerToggle(context, disableButtonPosition);
});
DrawControllerToggle(canvas, disableButtonPosition);
}
public void DrawMutableElements(SoftwareKeyboardUIState state)
@ -212,40 +218,43 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
return;
}
_surface.Mutate(context =>
using var paint = new SKPaint(_messageFont)
{
var messageRectangle = MeasureString(MessageText, _messageFont);
float messagePositionX = (_panelRectangle.Width - messageRectangle.Width) / 2 - messageRectangle.X;
float messagePositionY = _messagePositionY - messageRectangle.Y;
var messagePosition = new PointF(messagePositionX, messagePositionY);
var messageBoundRectangle = new RectangleF(messagePositionX, messagePositionY, messageRectangle.Width, messageRectangle.Height);
Color = _textNormalColor,
IsAntialias = true
};
SetGraphicsOptions(context);
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);
context.Fill(_panelBrush, messageBoundRectangle);
canvas.DrawRect(messageBoundRectangle, _panelBrush);
context.DrawText(MessageText, _messageFont, _textNormalColor, messagePosition);
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.
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.
context.Fill(_disabledBrush, messageBoundRectangle);
}
canvas.DrawRect(messageBoundRectangle, _disabledBrush);
}
DrawTextBox(context, state);
DrawTextBox(canvas, state);
float halfWidth = _panelRectangle.Width / 2;
float buttonsY = _panelRectangle.Y + 185;
float halfWidth = _panelRectangle.Width / 2;
float buttonsY = _panelRectangle.Top + 185;
PointF acceptButtonPosition = new(halfWidth - 180, buttonsY);
PointF cancelButtonPosition = new(halfWidth, buttonsY);
PointF disableButtonPosition = new(halfWidth + 180, buttonsY);
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);
DrawPadButton(context, acceptButtonPosition, _padAcceptIcon, AcceptText, state.AcceptPressed, state.ControllerEnabled);
DrawPadButton(context, cancelButtonPosition, _padCancelIcon, CancelText, state.CancelPressed, state.ControllerEnabled);
});
}
public void CreateSurface(RenderingSurfaceInfo surfaceInfo)
@ -268,7 +277,8 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
Debug.Assert(_surfaceInfo.Height <= totalHeight);
Debug.Assert(_surfaceInfo.Pitch * _surfaceInfo.Height <= _surfaceInfo.Size);
_surface = new Image<Argb32>((int)totalWidth, (int)totalHeight);
_imageInfo = new SKImageInfo((int)totalWidth, (int)totalHeight, SKColorType.Rgba8888);
_surface = SKSurface.Create(_imageInfo);
ComputeConstants();
DrawImmutableElements();
@ -282,76 +292,81 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
int panelHeight = 240;
int panelPositionY = totalHeight - panelHeight;
_panelRectangle = new RectangleF(0, panelPositionY, totalWidth, panelHeight);
_panelRectangle = SKRect.Create(0, panelPositionY, totalWidth, panelHeight);
_messagePositionY = panelPositionY + 60;
int logoPositionX = (totalWidth - _ryujinxLogo.Width) / 2;
int logoPositionY = panelPositionY + 18;
_logoPosition = new Point(logoPositionX, logoPositionY);
_logoPosition = new SKPoint(logoPositionX, logoPositionY);
}
private static RectangleF MeasureString(string text, Font font)
private static SKRect MeasureString(string text, SKPaint paint)
{
TextOptions options = new(font);
SKRect bounds = SKRect.Empty;
if (text == "")
{
FontRectangle emptyRectangle = TextMeasurer.MeasureSize(" ", options);
return new RectangleF(0, emptyRectangle.Y, 0, emptyRectangle.Height);
paint.MeasureText(" ", ref bounds);
}
else
{
paint.MeasureText(text, ref bounds);
}
FontRectangle rectangle = TextMeasurer.MeasureSize(text, options);
return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
return bounds;
}
private static RectangleF MeasureString(ReadOnlySpan<char> text, Font font)
private static SKRect MeasureString(ReadOnlySpan<char> text, SKPaint paint)
{
TextOptions options = new(font);
SKRect bounds = SKRect.Empty;
if (text == "")
{
FontRectangle emptyRectangle = TextMeasurer.MeasureSize(" ", options);
return new RectangleF(0, emptyRectangle.Y, 0, emptyRectangle.Height);
paint.MeasureText(" ", ref bounds);
}
else
{
paint.MeasureText(text, ref bounds);
}
FontRectangle rectangle = TextMeasurer.MeasureSize(text, options);
return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
return bounds;
}
private void DrawTextBox(IImageProcessingContext context, SoftwareKeyboardUIState state)
private void DrawTextBox(SKCanvas canvas, SoftwareKeyboardUIState state)
{
var inputTextRectangle = MeasureString(state.InputText, _inputTextFont);
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.X + 8));
float boxWidth = (int)(Math.Max(300, inputTextRectangle.Width + inputTextRectangle.Left + 8));
float boxHeight = 32;
float boxY = _panelRectangle.Y + 110;
float boxY = _panelRectangle.Top + 110;
float boxX = (int)((_panelRectangle.Width - boxWidth) / 2);
RectangleF boxRectangle = new(boxX, boxY, boxWidth, boxHeight);
SKRect boxRectangle = SKRect.Create(boxX, boxY, boxWidth, boxHeight);
RectangleF boundRectangle = new(_panelRectangle.X, boxY - _textBoxOutlineWidth,
SKRect boundRectangle = SKRect.Create(_panelRectangle.Left, boxY - _textBoxOutlineWidth,
_panelRectangle.Width, boxHeight + 2 * _textBoxOutlineWidth);
context.Fill(_panelBrush, boundRectangle);
canvas.DrawRect(boundRectangle, _panelBrush);
context.Draw(_textBoxOutlinePen, boxRectangle);
canvas.DrawRect(boxRectangle, _textBoxOutlinePen);
float inputTextX = (_panelRectangle.Width - inputTextRectangle.Width) / 2 - inputTextRectangle.X;
float inputTextX = (_panelRectangle.Width - inputTextRectangle.Width) / 2 - inputTextRectangle.Left;
float inputTextY = boxY + 5;
var inputTextPosition = new PointF(inputTextX, inputTextY);
context.DrawText(state.InputText, _inputTextFont, _textNormalColor, inputTextPosition);
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.
Color cursorTextColor;
Brush cursorBrush;
Pen cursorPen;
SKColor cursorTextColor;
SKPaint cursorBrush;
SKPaint cursorPen;
float cursorPositionYTop = inputTextY + 1;
float cursorPositionYBottom = cursorPositionYTop + _inputTextFontSize + 1;
@ -371,12 +386,12 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
ReadOnlySpan<char> textUntilBegin = state.InputText.AsSpan(0, state.CursorBegin);
ReadOnlySpan<char> textUntilEnd = state.InputText.AsSpan(0, state.CursorEnd);
var selectionBeginRectangle = MeasureString(textUntilBegin, _inputTextFont);
var selectionEndRectangle = MeasureString(textUntilEnd, _inputTextFont);
var selectionBeginRectangle = MeasureString(textUntilBegin, textPaint);
var selectionEndRectangle = MeasureString(textUntilEnd, textPaint);
cursorVisible = true;
cursorPositionXLeft = inputTextX + selectionBeginRectangle.Width + selectionBeginRectangle.X;
cursorPositionXRight = inputTextX + selectionEndRectangle.Width + selectionEndRectangle.X;
cursorPositionXLeft = inputTextX + selectionBeginRectangle.Width + selectionBeginRectangle.Left;
cursorPositionXRight = inputTextX + selectionEndRectangle.Width + selectionEndRectangle.Left;
}
else
{
@ -390,10 +405,10 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
int cursorBegin = Math.Min(state.InputText.Length, state.CursorBegin);
ReadOnlySpan<char> textUntilCursor = state.InputText.AsSpan(0, cursorBegin);
var cursorTextRectangle = MeasureString(textUntilCursor, _inputTextFont);
var cursorTextRectangle = MeasureString(textUntilCursor, textPaint);
cursorVisible = true;
cursorPositionXLeft = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
cursorPositionXLeft = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.Left;
if (state.OverwriteMode)
{
@ -402,8 +417,8 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
if (state.CursorBegin < state.InputText.Length)
{
textUntilCursor = state.InputText.AsSpan(0, cursorBegin + 1);
cursorTextRectangle = MeasureString(textUntilCursor, _inputTextFont);
cursorPositionXRight = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
cursorTextRectangle = MeasureString(textUntilCursor, textPaint);
cursorPositionXRight = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.Left;
}
else
{
@ -430,29 +445,32 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
if (cursorWidth == 0)
{
PointF[] points = {
new PointF(cursorPositionXLeft, cursorPositionYTop),
new PointF(cursorPositionXLeft, cursorPositionYBottom),
};
context.DrawLine(cursorPen, points);
canvas.DrawLine(new SKPoint(cursorPositionXLeft, cursorPositionYTop),
new SKPoint(cursorPositionXLeft, cursorPositionYBottom),
cursorPen);
}
else
{
var cursorRectangle = new RectangleF(cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight);
var cursorRectangle = SKRect.Create(cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight);
context.Draw(cursorPen, cursorRectangle);
context.Fill(cursorBrush, cursorRectangle);
canvas.DrawRect(cursorRectangle, cursorPen);
canvas.DrawRect(cursorRectangle, cursorBrush);
Image<Argb32> textOverCursor = new((int)cursorRectangle.Width, (int)cursorRectangle.Height);
textOverCursor.Mutate(context =>
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)
{
var textRelativePosition = new PointF(inputTextPosition.X - cursorRectangle.X, inputTextPosition.Y - cursorRectangle.Y);
context.DrawText(state.InputText, _inputTextFont, cursorTextColor, textRelativePosition);
});
Color = cursorTextColor,
IsAntialias = true
};
var cursorPosition = new Point((int)cursorRectangle.X, (int)cursorRectangle.Y);
context.DrawImage(textOverCursor, cursorPosition, 1);
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)
@ -460,11 +478,11 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
// 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.
context.Fill(_disabledBrush, boundRectangle);
canvas.DrawRect(boundRectangle, _disabledBrush);
}
}
private void DrawPadButton(IImageProcessingContext context, PointF point, Image icon, string label, bool pressed, bool enabled)
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.
@ -473,12 +491,18 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
float iconWidth = icon.Width;
float iconHeight = icon.Height;
var labelRectangle = MeasureString(label, _labelsTextFont);
using var paint = new SKPaint(_labelsTextFont)
{
Color = _textNormalColor,
IsAntialias = true
};
float labelPositionX = iconWidth + 8 - labelRectangle.X;
var labelRectangle = MeasureString(label, paint);
float labelPositionX = iconWidth + 8 - labelRectangle.Left;
float labelPositionY = 3;
float fullWidth = labelPositionX + labelRectangle.Width + labelRectangle.X;
float fullWidth = labelPositionX + labelRectangle.Width + labelRectangle.Left;
float fullHeight = iconHeight;
// Convert all relative positions into absolute.
@ -489,24 +513,24 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
iconX += originX;
iconY += originY;
var iconPosition = new Point((int)iconX, (int)iconY);
var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
var iconPosition = new SKPoint((int)iconX, (int)iconY);
var labelPosition = new SKPoint(labelPositionX + originX, labelPositionY + originY);
var selectedRectangle = new RectangleF(originX - 2 * _padPressedPenWidth, originY - 2 * _padPressedPenWidth,
var selectedRectangle = SKRect.Create(originX - 2 * _padPressedPenWidth, originY - 2 * _padPressedPenWidth,
fullWidth + 4 * _padPressedPenWidth, fullHeight + 4 * _padPressedPenWidth);
var boundRectangle = new RectangleF(originX, originY, fullWidth, fullHeight);
var boundRectangle = SKRect.Create(originX, originY, fullWidth, fullHeight);
boundRectangle.Inflate(4 * _padPressedPenWidth, 4 * _padPressedPenWidth);
context.Fill(_panelBrush, boundRectangle);
context.DrawImage(icon, iconPosition, 1);
context.DrawText(label, _labelsTextFont, _textNormalColor, labelPosition);
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)
{
context.Draw(_padPressedPen, selectedRectangle);
canvas.DrawRect(selectedRectangle, _padPressedPen);
}
}
else
@ -514,21 +538,26 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
// 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.
context.Fill(_disabledBrush, boundRectangle);
canvas.DrawRect(boundRectangle, _disabledBrush);
}
}
private void DrawControllerToggle(IImageProcessingContext context, PointF point)
private void DrawControllerToggle(SKCanvas canvas, SKPoint point)
{
var labelRectangle = MeasureString(ControllerToggleText, _labelsTextFont);
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.X;
float labelPositionY = -labelRectangle.Y - 1;
float labelPositionX = keyWidth + 8 - labelRectangle.Left;
float labelPositionY = -labelRectangle.Top - 1;
float keyX = 0;
float keyY = (int)((labelPositionY + labelRectangle.Height - keyHeight) / 2);
@ -544,14 +573,14 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
keyX += originX;
keyY += originY;
var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
var overlayPosition = new Point((int)keyX, (int)keyY);
var labelPosition = new SKPoint(labelPositionX + originX, labelPositionY + originY);
var overlayPosition = new SKPoint((int)keyX, (int)keyY);
context.DrawImage(_keyModeIcon, overlayPosition, 1);
context.DrawText(ControllerToggleText, _labelsTextFont, _textNormalColor, labelPosition);
canvas.DrawBitmap(_keyModeIcon, overlayPosition);
canvas.DrawText(ControllerToggleText, labelPosition.X, labelPosition.Y + _labelsTextFont.Metrics.XHeight, paint);
}
public void CopyImageToBuffer()
public unsafe void CopyImageToBuffer()
{
lock (_bufferLock)
{
@ -561,21 +590,20 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
}
// Convert the pixel format used in the image to the one used in the Switch surface.
_surface.Flush();
if (!_surface.DangerousTryGetSinglePixelMemory(out Memory<Argb32> pixels))
var buffer = new byte[_imageInfo.BytesSize];
fixed (byte* bufferPtr = buffer)
{
return;
if (!_surface.ReadPixels(_imageInfo, (nint)bufferPtr, _imageInfo.RowBytes, 0, 0))
{
return;
}
}
_bufferData = MemoryMarshal.AsBytes(pixels.Span).ToArray();
Span<uint> dataConvert = MemoryMarshal.Cast<byte, uint>(_bufferData);
_bufferData = buffer;
Debug.Assert(_bufferData.Length == _surfaceInfo.Size);
for (int i = 0; i < dataConvert.Length; i++)
{
dataConvert[i] = BitOperations.RotateRight(dataConvert[i], 8);
}
Debug.Assert(buffer.Length == _surfaceInfo.Size);
}
}

View File

@ -1,10 +1,10 @@
using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Caps.Types;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
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
@ -118,7 +118,11 @@ namespace Ryujinx.HLE.HOS.Services.Caps
}
// NOTE: The saved JPEG file doesn't have the limitation in the extra EXIF data.
Image.LoadPixelData<Rgba32>(screenshotData, 1280, 720).SaveAsJpegAsync(filePath);
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;
}

View File

@ -40,5 +40,12 @@ namespace Ryujinx.HLE.HOS.Services.Nim
return ResultCode.Success;
}
[CommandCmif(5)] // 17.0.0+
// CreateServerInterface2(pid, handle<unknown>, u64) -> object<nn::ec::IshopServiceAccessServer>
public ResultCode CreateServerInterface2(ServiceCtx context)
{
return CreateServerInterface(context);
}
}
}

View File

@ -9,5 +9,6 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types
public byte Sm0TpcIndex;
public byte Sm1GpcIndex;
public byte Sm1TpcIndex;
public uint Reserved;
}
}

View File

@ -2,6 +2,7 @@ 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;
@ -12,7 +13,7 @@ using System.Text;
namespace Ryujinx.HLE.HOS.Services.Sm
{
class IUserInterface : IpcService
partial class IUserInterface : IpcService
{
private static readonly Dictionary<string, Type> _services;
@ -95,9 +96,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm
{
ServiceAttribute serviceAttribute = (ServiceAttribute)type.GetCustomAttributes(typeof(ServiceAttribute)).First(service => ((ServiceAttribute)service).Name == name);
IpcService service = serviceAttribute.Parameter != null
? (IpcService)Activator.CreateInstance(type, context, serviceAttribute.Parameter)
: (IpcService)Activator.CreateInstance(type, context);
IpcService service = GetServiceInstance(type, context, serviceAttribute.Parameter);
service.TrySetServer(_commonServer);
service.Server.AddSessionObj(session.ServerSession, service);

View File

@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
@ -11,6 +12,7 @@
<ProjectReference Include="..\Ryujinx.Graphics.Host1x\Ryujinx.Graphics.Host1x.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Nvdec\Ryujinx.Graphics.Nvdec.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Vic\Ryujinx.Graphics.Vic.csproj" />
<ProjectReference Include="..\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
<ProjectReference Include="..\Ryujinx.Horizon.Common\Ryujinx.Horizon.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\Ryujinx.Horizon\Ryujinx.Horizon.csproj" />
@ -24,8 +26,8 @@
<PackageReference Include="LibHac" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
<PackageReference Include="MsgPack.Cli" />
<PackageReference Include="SixLabors.ImageSharp" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" />
<PackageReference Include="SkiaSharp" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux" />
<PackageReference Include="NetCoreServer" />
</ItemGroup>

View File

@ -7,6 +7,7 @@ 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;
@ -18,6 +19,7 @@ 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;
@ -88,6 +90,11 @@ namespace Ryujinx.Headless.SDL2
};
}
if (OperatingSystem.IsMacOS())
{
MVKInitialization.InitializeResolver();
}
Parser.Default.ParseArguments<Options>(args)
.WithParsed(Load)
.WithNotParsed(errors => errors.Output());
@ -457,6 +464,8 @@ namespace Ryujinx.Headless.SDL2
GraphicsConfig.ShadersDumpPath = option.GraphicsShadersDumpPath;
GraphicsConfig.EnableMacroHLE = !option.DisableMacroHLE;
DriverUtilities.InitDriverConfig(option.BackendThreading == BackendThreading.Off);
while (true)
{
LoadApplication(option);

View File

@ -4,6 +4,22 @@ namespace Ryujinx.Memory.Range
{
MultiRange Range { get; }
ulong BaseAddress => Range.GetSubRange(0).Address;
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;
}
}
}
}

View File

@ -5,6 +5,11 @@ namespace Ryujinx.Memory.Range
/// </summary>
public readonly record struct MemoryRange
{
/// <summary>
/// Special address value used to indicate than an address is invalid.
/// </summary>
internal const ulong InvalidAddress = ulong.MaxValue;
/// <summary>
/// An empty memory range, with a null address and zero size.
/// </summary>
@ -58,13 +63,24 @@ namespace Ryujinx.Memory.Range
return thisAddress < otherEndAddress && otherAddress < thisEndAddress;
}
/// <summary>
/// Checks if a given sub-range of memory is invalid.
/// Those are used to represent unmapped memory regions (holes in the region mapping).
/// </summary>
/// <param name="subRange">Memory range to check</param>
/// <returns>True if the memory range is considered invalid, false otherwise</returns>
internal static bool IsInvalid(ref MemoryRange subRange)
{
return subRange.Address == InvalidAddress;
}
/// <summary>
/// Returns a string summary of the memory range.
/// </summary>
/// <returns>A string summary of the memory range</returns>
public override string ToString()
{
if (Address == ulong.MaxValue)
if (Address == InvalidAddress)
{
return $"[Unmapped 0x{Size:X}]";
}

View File

@ -30,7 +30,7 @@ namespace Ryujinx.Memory.Range
{
var subrange = range.GetSubRange(i);
if (IsInvalid(ref subrange))
if (MemoryRange.IsInvalid(ref subrange))
{
continue;
}
@ -56,7 +56,7 @@ namespace Ryujinx.Memory.Range
{
var subrange = range.GetSubRange(i);
if (IsInvalid(ref subrange))
if (MemoryRange.IsInvalid(ref subrange))
{
continue;
}
@ -99,7 +99,7 @@ namespace Ryujinx.Memory.Range
{
var subrange = range.GetSubRange(i);
if (IsInvalid(ref subrange))
if (MemoryRange.IsInvalid(ref subrange))
{
continue;
}
@ -142,17 +142,6 @@ namespace Ryujinx.Memory.Range
return overlapCount;
}
/// <summary>
/// Checks if a given sub-range of memory is invalid.
/// Those are used to represent unmapped memory regions (holes in the region mapping).
/// </summary>
/// <param name="subRange">Memory range to checl</param>
/// <returns>True if the memory range is considered invalid, false otherwise</returns>
private static bool IsInvalid(ref MemoryRange subRange)
{
return subRange.Address == ulong.MaxValue;
}
/// <summary>
/// Gets all items on the list starting at the specified memory address.
/// </summary>

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