Compare commits

..

14 Commits

Author SHA1 Message Date
6f28c4abad test: Make tests runnable on system without 4KiB page size (#5184)
* ARMeilleure: Do not hardcode 4KiB page size in JitCache

* test: Do not hardcode page size to 4KiB for Ryujinx.Tests.Memory.Tests

Fix running tests on Asahi Linux with 16KiB pages.

* test: Do not hardcode page size to 4KiB for Ryujinx.Tests.Cpu

Fix running tests on Asahi Linux.

Test runner still crash when trying to run all test suite.

* test: Do not hardcode page size to 4KiB for Ryujinx.Tests.Cpu

Fix somecrashes on Asahi Linux.

* test: Ignore Vshl test on ARM64 due to unicorn crashes

* test: Workaround hardcoded size on some tests

Change mapping of code and data in case of non 4KiB configuration.

* test: Make CpuTestT32Flow depends on code address

Fix failure with different page size.

* test: Disable CpuTestThumb.TestRandomTestCases when page size isn't 4KiB

The test data needs to be reevaluated to take different page size into account.

* Address gdkchan's comments
2023-06-14 18:02:41 +02:00
105c9712c1 Fix Arm32 double to int/uint conversion on Arm64 (#5292)
* Fix Arm32 double to int/uint conversion on Arm64

* PPTC version bump
2023-06-14 00:57:02 -03:00
4d804ed45e Mod Loader: Stop loading mods from folders that don't exactly match titleId (#5298)
* Stop loading mods from folders that don't exactly match titleId

* What the worst that can happen?
2023-06-13 20:47:33 +02:00
4a27d29412 infra: Sync paths-ignore with release job and attempt to fix review assign 2023-06-13 11:51:22 +02:00
5bd2c58ad6 UI: Correctly set 'shell/open/command; registry key for file associations (#5244)
* Correctly set 'shell/open/command; registry key for file associations

* File association fixes
* 'using' statements instead of blocks
* Idempotent unregistration
* Single "hey shell, we changed file associations" notification at the
  end instead of 1 for every operation, speeds things up greatly.

* Adapt and fix Linux specific function as well

---------

Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
2023-06-13 00:36:40 +00:00
cf4c78b9c8 Make LM skip instead of crashing for invalid messages (#5290) 2023-06-13 00:12:06 +00:00
52aa4b6c22 Fix action version (#5299) 2023-06-12 21:57:07 +02:00
5a02433080 infra: Fix PR triage workflow glob patterns (#5297)
* Use glob patterns to match file paths

* Update ignored paths for releases

* Adjust build.yml as well

* Add names to auto-assign steps

* Fix developer team name

* Allow build workflows to run if workflows changed
2023-06-12 18:42:27 +00:00
915a0f7173 hle: Stub IHidbusServer.GetBusHandle (#5284) 2023-06-12 17:33:13 +02:00
0cc266ff19 infra: Add PR triage action (#5293)
This is a bare minimal triage action that handle big categories.

In the future we could also label all services correctly but
I didn't felt this was required for a first iteration.
2023-06-12 12:29:41 +02:00
9a1b74799d Ava: Fix OpenGL on Linux again (#5216)
* ava: Fix OpenGL on Linux again

This shouldn't be working like that, but for some reason it does.

* Apply the correct fix

* gtk: Add warning messages for caught exceptions

* ava: Handle disposing the same way as GTK does

* Address review feedback
2023-06-11 18:31:22 +02:00
638f3761f3 Show/Hide UI Hotkey fix on Avalonia (#5133)
* fix show/hide ui for ava

* revert style

* unbound by default

* revert
2023-06-11 15:34:56 +02:00
193ca3c9a2 Implement fast path for AES crypto instructions on Arm64 (#5281)
* Implement fast path for AES crypto instructions on Arm64

* PPTC version bump

* Use AES HW feature check
2023-06-11 00:51:35 +00:00
eb0bb36bbf Implement transform feedback emulation for hardware without native support (#5080)
* Implement transform feedback emulation for hardware without native support

* Stop doing some useless buffer updates and account for non-zero base instance

* Reduce redundant updates even more

* Update descriptor init logic to account for ResourceLayout

* Fix transform feedback and storage buffers not being updated in some cases

* Shader cache version bump

* PR feedback

* SetInstancedDrawVertexCount must be always called after UpdateState

* Minor typo
2023-06-10 18:31:38 -03:00
62 changed files with 924 additions and 214 deletions

8
.github/assign/audio.yml vendored Normal file
View File

@ -0,0 +1,8 @@
addReviewers: true
reviewers:
- marysaka
filterLabels:
include:
- audio

11
.github/assign/cpu.yml vendored Normal file
View File

@ -0,0 +1,11 @@
addReviewers: true
reviewers:
- gdkchan
- riperiperi
- marysaka
- LDj3SNuD
filterLabels:
include:
- cpu

4
.github/assign/global.yml vendored Normal file
View File

@ -0,0 +1,4 @@
addReviewers: true
reviewers:
- Ryujinx/developers

10
.github/assign/gpu.yml vendored Normal file
View File

@ -0,0 +1,10 @@
addReviewers: true
reviewers:
- gdkchan
- riperiperi
- marysaka
filterLabels:
include:
- gpu

11
.github/assign/gui.yml vendored Normal file
View File

@ -0,0 +1,11 @@
addReviewers: true
reviewers:
- Ack77
- emmauss
- TSRBerry
- marysaka
filterLabels:
include:
- gui

11
.github/assign/horizon.yml vendored Normal file
View File

@ -0,0 +1,11 @@
addReviewers: true
reviewers:
- gdkchan
- Ack77
- marysaka
- TSRBerry
filterLabels:
include:
- horizon

9
.github/assign/infra.yml vendored Normal file
View File

@ -0,0 +1,9 @@
addReviewers: true
reviewers:
- marysaka
- TSRBerry
filterLabels:
include:
- infra

33
.github/labeler.yml vendored Normal file
View File

@ -0,0 +1,33 @@
audio: 'src/Ryujinx.Audio*/**'
cpu:
- 'src/ARMeilleure/**'
- 'src/Ryujinx.Cpu/**'
- 'src/Ryujinx.Memory/**'
gpu:
- 'src/Ryujinx.Graphics.*/**'
- 'src/Spv.Generator/**'
- 'src/Ryujinx.ShaderTools/**'
'graphics-backend:opengl': 'src/Ryujinx.Graphics.OpenGL/**'
'graphics-backend:vulkan':
- 'src/Ryujinx.Graphics.Vulkan/**'
- 'src/Spv.Generator/**'
gui:
- 'src/Ryujinx/**'
- 'src/Ryujinx.Ui.Common/**'
- 'src/Ryujinx.Ui.LocaleGenerator/**'
- 'src/Ryujinx.Ava/**'
horizon:
- 'src/Ryujinx.HLE/**'
- 'src/Ryujinx.Horizon*/**'
kernel: 'src/Ryujinx.HLE/HOS/Kernel/**'
infra:
- '.github/**'
- 'distribution/**'
- 'Directory.Packages.props'

View File

@ -3,19 +3,13 @@ name: Build job
on:
workflow_dispatch:
inputs: {}
#push:
# branches: [ master ]
# paths-ignore:
# - '.github/*'
# - '.github/ISSUE_TEMPLATE/**'
# - '*.yml'
# - 'README.md'
pull_request:
branches: [ master ]
paths-ignore:
- '.github/*'
- '.github/ISSUE_TEMPLATE/**'
- '.github/**'
- '*.yml'
- '*.json'
- '*.config'
- 'README.md'
concurrency:

54
.github/workflows/pr_triage.yml vendored Normal file
View File

@ -0,0 +1,54 @@
name: "Pull Request Triage"
on:
pull_request_target:
types: [opened, ready_for_review]
jobs:
triage:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- name: Update labels based on changes
uses: actions/labeler@v4
with:
sync-labels: true
dot: true
- name: Auto Assign [Audio]
uses: kentaro-m/auto-assign-action@v1.2.5
with:
configuration-path: '.github/assign/audio.yml'
- name: Auto Assign [CPU]
uses: kentaro-m/auto-assign-action@v1.2.5
with:
configuration-path: '.github/assign/cpu.yml'
- name: Auto Assign [GPU]
uses: kentaro-m/auto-assign-action@v1.2.5
with:
configuration-path: '.github/assign/gpu.yml'
- name: Auto Assign [GUI]
uses: kentaro-m/auto-assign-action@v1.2.5
with:
configuration-path: '.github/assign/gui.yml'
- name: Auto Assign [Horizon]
uses: kentaro-m/auto-assign-action@v1.2.5
with:
configuration-path: '.github/assign/horizon.yml'
- name: Auto Assign [Infra]
uses: kentaro-m/auto-assign-action@v1.2.5
with:
configuration-path: '.github/assign/infra.yml'
- name: Auto Assign [Global]
uses: kentaro-m/auto-assign-action@v1.2.5
with:
configuration-path: '.github/assign/global.yml'

View File

@ -6,9 +6,10 @@ on:
push:
branches: [ master ]
paths-ignore:
- '.github/*'
- '.github/ISSUE_TEMPLATE/**'
- '.github/**'
- '*.yml'
- '*.json'
- '*.config'
- 'README.md'
concurrency: release

View File

@ -168,8 +168,6 @@ namespace ARMeilleure.CodeGen.Arm64
Logger.StartPass(PassName.CodeGeneration);
//Console.Error.WriteLine(IRDumper.GetDump(cfg));
bool relocatable = (cctx.Options & CompilerOptions.Relocatable) != 0;
CodeGenContext context = new(allocResult, maxCallArgs, cfg.Blocks.Count, relocatable);

View File

@ -179,6 +179,35 @@ namespace ARMeilleure.CodeGen.Arm64
(uint)operation.GetSource(2).AsInt32());
break;
case IntrinsicType.Vector128Unary:
GenerateVectorUnary(
context,
1,
0,
info.Inst,
operation.Destination,
operation.GetSource(0));
break;
case IntrinsicType.Vector128Binary:
GenerateVectorBinary(
context,
1,
0,
info.Inst,
operation.Destination,
operation.GetSource(0),
operation.GetSource(1));
break;
case IntrinsicType.Vector128BinaryRd:
GenerateVectorUnary(
context,
1,
0,
info.Inst,
operation.Destination,
operation.GetSource(1));
break;
case IntrinsicType.VectorUnary:
GenerateVectorUnary(
context,

View File

@ -19,8 +19,8 @@ namespace ARMeilleure.CodeGen.Arm64
Add(Intrinsic.Arm64AddvV, new IntrinsicInfo(0x0e31b800u, IntrinsicType.VectorUnary));
Add(Intrinsic.Arm64AddS, new IntrinsicInfo(0x5e208400u, IntrinsicType.ScalarBinary));
Add(Intrinsic.Arm64AddV, new IntrinsicInfo(0x0e208400u, IntrinsicType.VectorBinary));
Add(Intrinsic.Arm64AesdV, new IntrinsicInfo(0x4e285800u, IntrinsicType.Vector128Unary));
Add(Intrinsic.Arm64AeseV, new IntrinsicInfo(0x4e284800u, IntrinsicType.Vector128Unary));
Add(Intrinsic.Arm64AesdV, new IntrinsicInfo(0x4e285800u, IntrinsicType.Vector128BinaryRd));
Add(Intrinsic.Arm64AeseV, new IntrinsicInfo(0x4e284800u, IntrinsicType.Vector128BinaryRd));
Add(Intrinsic.Arm64AesimcV, new IntrinsicInfo(0x4e287800u, IntrinsicType.Vector128Unary));
Add(Intrinsic.Arm64AesmcV, new IntrinsicInfo(0x4e286800u, IntrinsicType.Vector128Unary));
Add(Intrinsic.Arm64AndV, new IntrinsicInfo(0x0e201c00u, IntrinsicType.VectorBinaryBitwise));

View File

@ -23,6 +23,10 @@ namespace ARMeilleure.CodeGen.Arm64
ScalarTernaryShlRd,
ScalarTernaryShrRd,
Vector128Unary,
Vector128Binary,
Vector128BinaryRd,
VectorUnary,
VectorUnaryBitwise,
VectorUnaryByElem,
@ -50,9 +54,6 @@ namespace ARMeilleure.CodeGen.Arm64
VectorTernaryShlRd,
VectorTernaryShrRd,
Vector128Unary,
Vector128Binary,
GetRegister,
SetRegister
}

View File

@ -746,6 +746,7 @@ namespace ARMeilleure.CodeGen.Arm64
info.Type == IntrinsicType.ScalarTernaryFPRdByElem ||
info.Type == IntrinsicType.ScalarTernaryShlRd ||
info.Type == IntrinsicType.ScalarTernaryShrRd ||
info.Type == IntrinsicType.Vector128BinaryRd ||
info.Type == IntrinsicType.VectorBinaryRd ||
info.Type == IntrinsicType.VectorInsertByElem ||
info.Type == IntrinsicType.VectorTernaryRd ||

View File

@ -17,7 +17,11 @@ namespace ARMeilleure.Instructions
Operand res;
if (Optimizations.UseAesni)
if (Optimizations.UseArm64Aes)
{
res = context.AddIntrinsic(Intrinsic.Arm64AesdV, d, n);
}
else if (Optimizations.UseAesni)
{
res = context.AddIntrinsic(Intrinsic.X86Aesdeclast, context.AddIntrinsic(Intrinsic.X86Xorpd, d, n), context.VectorZero());
}
@ -38,7 +42,11 @@ namespace ARMeilleure.Instructions
Operand res;
if (Optimizations.UseAesni)
if (Optimizations.UseArm64Aes)
{
res = context.AddIntrinsic(Intrinsic.Arm64AeseV, d, n);
}
else if (Optimizations.UseAesni)
{
res = context.AddIntrinsic(Intrinsic.X86Aesenclast, context.AddIntrinsic(Intrinsic.X86Xorpd, d, n), context.VectorZero());
}
@ -58,7 +66,11 @@ namespace ARMeilleure.Instructions
Operand res;
if (Optimizations.UseAesni)
if (Optimizations.UseArm64Aes)
{
res = context.AddIntrinsic(Intrinsic.Arm64AesimcV, n);
}
else if (Optimizations.UseAesni)
{
res = context.AddIntrinsic(Intrinsic.X86Aesimc, n);
}
@ -78,7 +90,11 @@ namespace ARMeilleure.Instructions
Operand res;
if (Optimizations.UseAesni)
if (Optimizations.UseArm64Aes)
{
res = context.AddIntrinsic(Intrinsic.Arm64AesmcV, n);
}
else if (Optimizations.UseAesni)
{
Operand roundKey = context.VectorZero();

View File

@ -17,7 +17,11 @@ namespace ARMeilleure.Instructions
Operand res;
if (Optimizations.UseAesni)
if (Optimizations.UseArm64Aes)
{
res = context.AddIntrinsic(Intrinsic.Arm64AesdV, d, n);
}
else if (Optimizations.UseAesni)
{
res = context.AddIntrinsic(Intrinsic.X86Aesdeclast, context.AddIntrinsic(Intrinsic.X86Xorpd, d, n), context.VectorZero());
}
@ -38,7 +42,11 @@ namespace ARMeilleure.Instructions
Operand res;
if (Optimizations.UseAesni)
if (Optimizations.UseArm64Aes)
{
res = context.AddIntrinsic(Intrinsic.Arm64AeseV, d, n);
}
else if (Optimizations.UseAesni)
{
res = context.AddIntrinsic(Intrinsic.X86Aesenclast, context.AddIntrinsic(Intrinsic.X86Xorpd, d, n), context.VectorZero());
}
@ -58,7 +66,11 @@ namespace ARMeilleure.Instructions
Operand res;
if (Optimizations.UseAesni)
if (Optimizations.UseArm64Aes)
{
res = context.AddIntrinsic(Intrinsic.Arm64AesimcV, n);
}
else if (Optimizations.UseAesni)
{
res = context.AddIntrinsic(Intrinsic.X86Aesimc, n);
}
@ -78,7 +90,11 @@ namespace ARMeilleure.Instructions
Operand res;
if (Optimizations.UseAesni)
if (Optimizations.UseArm64Aes)
{
res = context.AddIntrinsic(Intrinsic.Arm64AesmcV, n);
}
else if (Optimizations.UseAesni)
{
Operand roundKey = context.VectorZero();

View File

@ -165,7 +165,7 @@ namespace ARMeilleure.Instructions
{
Operand m = GetVecA32(op.Vm >> 1);
Operand toConvert = InstEmitSimdHelper32Arm64.EmitExtractScalar(context, m, op.Vm, doubleSize);
Operand toConvert = InstEmitSimdHelper32Arm64.EmitExtractScalar(context, m, op.Vm, true);
Intrinsic inst = (unsigned ? Intrinsic.Arm64FcvtzuGp : Intrinsic.Arm64FcvtzsGp) | Intrinsic.Arm64VDouble;
@ -175,7 +175,7 @@ namespace ARMeilleure.Instructions
}
else
{
InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, unsigned ? Intrinsic.Arm64FcvtzuS : Intrinsic.Arm64FcvtzsS);
InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, unsigned ? Intrinsic.Arm64FcvtzuS : Intrinsic.Arm64FcvtzsS, false);
}
}
else if (!roundWithFpscr && Optimizations.UseSse41)
@ -260,28 +260,64 @@ namespace ARMeilleure.Instructions
if (Optimizations.UseAdvSimd)
{
if (unsigned)
bool doubleSize = floatSize == OperandType.FP64;
if (doubleSize)
{
inst = rm switch {
0b00 => Intrinsic.Arm64FcvtauS,
0b01 => Intrinsic.Arm64FcvtnuS,
0b10 => Intrinsic.Arm64FcvtpuS,
0b11 => Intrinsic.Arm64FcvtmuS,
_ => throw new ArgumentOutOfRangeException(nameof(rm))
};
Operand m = GetVecA32(op.Vm >> 1);
Operand toConvert = InstEmitSimdHelper32Arm64.EmitExtractScalar(context, m, op.Vm, true);
if (unsigned)
{
inst = rm switch {
0b00 => Intrinsic.Arm64FcvtauGp,
0b01 => Intrinsic.Arm64FcvtnuGp,
0b10 => Intrinsic.Arm64FcvtpuGp,
0b11 => Intrinsic.Arm64FcvtmuGp,
_ => throw new ArgumentOutOfRangeException(nameof(rm))
};
}
else
{
inst = rm switch {
0b00 => Intrinsic.Arm64FcvtasGp,
0b01 => Intrinsic.Arm64FcvtnsGp,
0b10 => Intrinsic.Arm64FcvtpsGp,
0b11 => Intrinsic.Arm64FcvtmsGp,
_ => throw new ArgumentOutOfRangeException(nameof(rm))
};
}
Operand asInteger = context.AddIntrinsicInt(inst | Intrinsic.Arm64VDouble, toConvert);
InsertScalar(context, op.Vd, asInteger);
}
else
{
inst = rm switch {
0b00 => Intrinsic.Arm64FcvtasS,
0b01 => Intrinsic.Arm64FcvtnsS,
0b10 => Intrinsic.Arm64FcvtpsS,
0b11 => Intrinsic.Arm64FcvtmsS,
_ => throw new ArgumentOutOfRangeException(nameof(rm))
};
}
if (unsigned)
{
inst = rm switch {
0b00 => Intrinsic.Arm64FcvtauS,
0b01 => Intrinsic.Arm64FcvtnuS,
0b10 => Intrinsic.Arm64FcvtpuS,
0b11 => Intrinsic.Arm64FcvtmuS,
_ => throw new ArgumentOutOfRangeException(nameof(rm))
};
}
else
{
inst = rm switch {
0b00 => Intrinsic.Arm64FcvtasS,
0b01 => Intrinsic.Arm64FcvtnsS,
0b10 => Intrinsic.Arm64FcvtpsS,
0b11 => Intrinsic.Arm64FcvtmsS,
_ => throw new ArgumentOutOfRangeException(nameof(rm))
};
}
InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, inst);
InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, inst);
}
}
else if (Optimizations.UseSse41)
{

View File

@ -192,11 +192,10 @@ namespace ARMeilleure.Instructions
EmitVectorTernaryOpSimd32(context, (d, n, m) => context.AddIntrinsic(inst, d, n, m));
}
public static void EmitScalarUnaryOpSimd32(ArmEmitterContext context, Func1I scalarFunc)
public static void EmitScalarUnaryOpSimd32(ArmEmitterContext context, Func1I scalarFunc, bool doubleSize)
{
OpCode32SimdS op = (OpCode32SimdS)context.CurrOp;
bool doubleSize = (op.Size & 1) != 0;
int shift = doubleSize ? 1 : 2;
Operand m = GetVecA32(op.Vm >> shift);
Operand d = GetVecA32(op.Vd >> shift);
@ -215,8 +214,13 @@ namespace ARMeilleure.Instructions
{
OpCode32SimdS op = (OpCode32SimdS)context.CurrOp;
inst |= ((op.Size & 1) != 0 ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128;
EmitScalarUnaryOpSimd32(context, (m) => (inst == 0) ? m : context.AddIntrinsic(inst, m));
EmitScalarUnaryOpF32(context, inst, (op.Size & 1) != 0);
}
public static void EmitScalarUnaryOpF32(ArmEmitterContext context, Intrinsic inst, bool doubleSize)
{
inst |= (doubleSize ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128;
EmitScalarUnaryOpSimd32(context, (m) => (inst == 0) ? m : context.AddIntrinsic(inst, m), doubleSize);
}
public static void EmitScalarBinaryOpSimd32(ArmEmitterContext context, Func2I scalarFunc)

View File

@ -13,6 +13,7 @@ namespace ARMeilleure
public static bool UseUnmanagedDispatchLoop { get; set; } = true;
public static bool UseAdvSimdIfAvailable { get; set; } = true;
public static bool UseArm64AesIfAvailable { get; set; } = true;
public static bool UseArm64PmullIfAvailable { get; set; } = true;
public static bool UseSseIfAvailable { get; set; } = true;
@ -41,6 +42,7 @@ namespace ARMeilleure
}
internal static bool UseAdvSimd => UseAdvSimdIfAvailable && Arm64HardwareCapabilities.SupportsAdvSimd;
internal static bool UseArm64Aes => UseArm64AesIfAvailable && Arm64HardwareCapabilities.SupportsAes;
internal static bool UseArm64Pmull => UseArm64PmullIfAvailable && Arm64HardwareCapabilities.SupportsPmull;
internal static bool UseSse => UseSseIfAvailable && X86HardwareCapabilities.SupportsSse;

View File

@ -2,6 +2,7 @@ using ARMeilleure.CodeGen;
using ARMeilleure.CodeGen.Unwinding;
using ARMeilleure.Memory;
using ARMeilleure.Native;
using Ryujinx.Memory;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@ -12,8 +13,8 @@ namespace ARMeilleure.Translation.Cache
{
static partial class JitCache
{
private const int PageSize = 4 * 1024;
private const int PageMask = PageSize - 1;
private static readonly int PageSize = (int)MemoryBlock.GetPageSize();
private static readonly int PageMask = PageSize - 1;
private const int CodeAlignment = 4; // Bytes.
private const int CacheSize = 2047 * 1024 * 1024;

View File

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

View File

@ -40,6 +40,7 @@ using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SPB.Graphics.Exceptions;
using SPB.Graphics.Vulkan;
using System;
using System.Collections.Generic;
@ -475,11 +476,20 @@ namespace Ryujinx.Ava
_windowsMultimediaTimerResolution = null;
}
(_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent();
if (_rendererHost.EmbeddedWindow is EmbeddedWindowOpenGL openGlWindow)
{
// Try to bind the OpenGL context before calling the shutdown event.
openGlWindow.MakeCurrent(false, false);
Device.DisposeGpu();
Device.DisposeGpu();
(_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(null);
// Unbind context and destroy everything.
openGlWindow.MakeCurrent(true, false);
}
else
{
Device.DisposeGpu();
}
}
private void HideCursorState_Changed(object sender, ReactiveEventArgs<HideCursorMode> state)
@ -930,7 +940,7 @@ namespace Ryujinx.Ava
_gpuDoneEvent.Set();
});
(_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(null);
(_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(true);
}
public void UpdateStatus()
@ -1044,7 +1054,7 @@ namespace Ryujinx.Ava
ScreenshotRequested = true;
break;
case KeyboardHotkeyState.ShowUi:
_viewModel.ShowMenuAndStatusBar = true;
_viewModel.ShowMenuAndStatusBar = !_viewModel.ShowMenuAndStatusBar;
break;
case KeyboardHotkeyState.Pause:
if (_viewModel.IsPaused)

View File

@ -123,7 +123,7 @@ namespace Ryujinx.Ava.UI.Renderer
}
else
{
X11Window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100) as GLXWindow;
X11Window = PlatformHelper.CreateOpenGLWindow(new FramebufferFormat(new ColorFormat(8, 8, 8, 0), 16, 0, ColorFormat.Zero, 0, 2, false), 0, 0, 100, 100) as GLXWindow;
}
WindowHandle = X11Window.WindowHandle.RawHandle;

View File

@ -1,9 +1,11 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.Ui.Common.Configuration;
using SPB.Graphics;
using SPB.Graphics.Exceptions;
using SPB.Graphics.OpenGL;
using SPB.Platform;
using SPB.Platform.WGL;
@ -18,8 +20,6 @@ namespace Ryujinx.Ava.UI.Renderer
public OpenGLContextBase Context { get; set; }
public EmbeddedWindowOpenGL() { }
protected override void OnWindowDestroying()
{
Context.Dispose();
@ -62,14 +62,21 @@ namespace Ryujinx.Ava.UI.Renderer
Context.MakeCurrent(null);
}
public void MakeCurrent()
public void MakeCurrent(bool unbind = false, bool shouldThrow = true)
{
Context?.MakeCurrent(_window);
}
try
{
Context?.MakeCurrent(!unbind ? _window : null);
}
catch (ContextException e)
{
if (shouldThrow)
{
throw;
}
public void MakeCurrent(NativeWindowBase window)
{
Context?.MakeCurrent(window);
Logger.Warning?.Print(LogClass.Ui, $"Failed to {(!unbind ? "bind" : "unbind")} OpenGL context: {e}");
}
}
public void SwapBuffers()

View File

@ -24,6 +24,24 @@ namespace Ryujinx.Common.Memory
return value;
}
public bool TryRead<T>(out T value) where T : unmanaged
{
int valueSize = Unsafe.SizeOf<T>();
if (valueSize > _input.Length)
{
value = default;
return false;
}
value = MemoryMarshal.Cast<byte, T>(_input)[0];
_input = _input.Slice(valueSize);
return true;
}
public ReadOnlySpan<byte> GetSpan(int size)
{
ReadOnlySpan<byte> data = _input.Slice(0, size);

View File

@ -28,6 +28,7 @@ namespace Ryujinx.Graphics.GAL
public readonly bool SupportsFragmentShaderOrderingIntel;
public readonly bool SupportsGeometryShader;
public readonly bool SupportsGeometryShaderPassthrough;
public readonly bool SupportsTransformFeedback;
public readonly bool SupportsImageLoadFormatted;
public readonly bool SupportsLayerVertexTessellation;
public readonly bool SupportsMismatchingViewFormat;
@ -77,6 +78,7 @@ namespace Ryujinx.Graphics.GAL
bool supportsFragmentShaderOrderingIntel,
bool supportsGeometryShader,
bool supportsGeometryShaderPassthrough,
bool supportsTransformFeedback,
bool supportsImageLoadFormatted,
bool supportsLayerVertexTessellation,
bool supportsMismatchingViewFormat,
@ -122,6 +124,7 @@ namespace Ryujinx.Graphics.GAL
SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel;
SupportsGeometryShader = supportsGeometryShader;
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
SupportsTransformFeedback = supportsTransformFeedback;
SupportsImageLoadFormatted = supportsImageLoadFormatted;
SupportsLayerVertexTessellation = supportsLayerVertexTessellation;
SupportsMismatchingViewFormat = supportsMismatchingViewFormat;

View File

@ -539,6 +539,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
engine.UpdateState();
if (instanceCount > 1)
{
// Must be called after UpdateState as it assumes the shader state
// has already been set, and that bindings have been updated already.
_channel.BufferManager.SetInstancedDrawVertexCount(count);
}
if (indexed)
{
_context.Renderer.Pipeline.DrawIndexed(count, instanceCount, firstIndex, firstVertex, firstInstance);
@ -676,6 +684,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_channel.BufferManager.SetIndexBuffer(br, IndexType.UInt);
}
_channel.BufferManager.SetInstancedDrawVertexCount(_instancedIndexCount);
_context.Renderer.Pipeline.DrawIndexed(
_instancedIndexCount,
_instanceIndex + 1,
@ -685,6 +695,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
}
else
{
_channel.BufferManager.SetInstancedDrawVertexCount(_instancedDrawStateCount);
_context.Renderer.Pipeline.Draw(
_instancedDrawStateCount,
_instanceIndex + 1,

View File

@ -269,7 +269,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_prevFirstVertex = _state.State.FirstVertex;
}
bool tfEnable = _state.State.TfEnable;
bool tfEnable = _state.State.TfEnable && _context.Capabilities.SupportsTransformFeedback;
if (!tfEnable && _prevTfEnable)
{
@ -1367,6 +1367,22 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_vsUsesDrawParameters = gs.Shaders[1]?.Info.UsesDrawParameters ?? false;
_vsClipDistancesWritten = gs.Shaders[1]?.Info.ClipDistancesWritten ?? 0;
bool hasTransformFeedback = gs.SpecializationState.TransformFeedbackDescriptors != null;
if (hasTransformFeedback != _channel.BufferManager.HasTransformFeedbackOutputs)
{
if (!_context.Capabilities.SupportsTransformFeedback)
{
// If host does not support transform feedback, and the shader changed,
// we might need to update bindings as transform feedback emulation
// uses storage buffer bindings that might have been used for something
// else in a previous draw.
_channel.BufferManager.ForceTransformFeedbackAndStorageBuffersDirty();
}
_channel.BufferManager.HasTransformFeedbackOutputs = hasTransformFeedback;
}
if (oldVsClipDistancesWritten != _vsClipDistancesWritten)
{
UpdateUserClipState();

View File

@ -6,6 +6,7 @@ using Ryujinx.Graphics.Shader;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Memory
{
@ -14,12 +15,17 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary>
class BufferManager
{
private const int TfInfoVertexCountOffset = Constants.TotalTransformFeedbackBuffers * sizeof(int);
private const int TfInfoBufferSize = TfInfoVertexCountOffset + sizeof(int);
private readonly GpuContext _context;
private readonly GpuChannel _channel;
private int _unalignedStorageBuffers;
public bool HasUnalignedStorageBuffers => _unalignedStorageBuffers > 0;
public bool HasTransformFeedbackOutputs { get; set; }
private IndexBuffer _indexBuffer;
private readonly VertexBuffer[] _vertexBuffers;
private readonly BufferBounds[] _transformFeedbackBuffers;
@ -98,6 +104,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
private readonly BuffersPerStage[] _gpStorageBuffers;
private readonly BuffersPerStage[] _gpUniformBuffers;
private BufferHandle _tfInfoBuffer;
private int[] _tfInfoData;
private bool _gpStorageBuffersDirty;
private bool _gpUniformBuffersDirty;
@ -137,6 +146,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
_bufferTextures = new List<BufferTextureBinding>();
_ranges = new BufferAssignment[Constants.TotalGpUniformBuffers * Constants.ShaderStages];
if (!context.Capabilities.SupportsTransformFeedback)
{
_tfInfoData = new int[Constants.TotalTransformFeedbackBuffers];
}
}
@ -319,6 +333,31 @@ namespace Ryujinx.Graphics.Gpu.Memory
_gpUniformBuffersDirty = true;
}
/// <summary>
/// Sets the number of vertices per instance on a instanced draw. Used for transform feedback emulation.
/// </summary>
/// <param name="vertexCount">Vertex count per instance</param>
public void SetInstancedDrawVertexCount(int vertexCount)
{
if (!_context.Capabilities.SupportsTransformFeedback &&
HasTransformFeedbackOutputs &&
_tfInfoBuffer != BufferHandle.Null)
{
Span<byte> data = stackalloc byte[sizeof(int)];
MemoryMarshal.Cast<byte, int>(data)[0] = vertexCount;
_context.Renderer.SetBufferData(_tfInfoBuffer, TfInfoVertexCountOffset, data);
}
}
/// <summary>
/// Forces transform feedback and storage buffers to be updated on the next draw.
/// </summary>
public void ForceTransformFeedbackAndStorageBuffersDirty()
{
_transformFeedbackBuffersDirty = true;
_gpStorageBuffersDirty = true;
}
/// <summary>
/// Sets the binding points for the storage buffers bound on the compute pipeline.
/// </summary>
@ -537,22 +576,75 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
_transformFeedbackBuffersDirty = false;
Span<BufferRange> tfbs = stackalloc BufferRange[Constants.TotalTransformFeedbackBuffers];
for (int index = 0; index < Constants.TotalTransformFeedbackBuffers; index++)
if (_context.Capabilities.SupportsTransformFeedback)
{
BufferBounds tfb = _transformFeedbackBuffers[index];
Span<BufferRange> tfbs = stackalloc BufferRange[Constants.TotalTransformFeedbackBuffers];
if (tfb.Address == 0)
for (int index = 0; index < Constants.TotalTransformFeedbackBuffers; index++)
{
tfbs[index] = BufferRange.Empty;
continue;
BufferBounds tfb = _transformFeedbackBuffers[index];
if (tfb.Address == 0)
{
tfbs[index] = BufferRange.Empty;
continue;
}
tfbs[index] = bufferCache.GetBufferRange(tfb.Address, tfb.Size, write: true);
}
tfbs[index] = bufferCache.GetBufferRange(tfb.Address, tfb.Size, write: true);
_context.Renderer.Pipeline.SetTransformFeedbackBuffers(tfbs);
}
else if (HasTransformFeedbackOutputs)
{
Span<int> info = _tfInfoData.AsSpan();
Span<BufferAssignment> buffers = stackalloc BufferAssignment[Constants.TotalTransformFeedbackBuffers + 1];
_context.Renderer.Pipeline.SetTransformFeedbackBuffers(tfbs);
bool needsDataUpdate = false;
if (_tfInfoBuffer == BufferHandle.Null)
{
_tfInfoBuffer = _context.Renderer.CreateBuffer(TfInfoBufferSize);
}
buffers[0] = new BufferAssignment(0, new BufferRange(_tfInfoBuffer, 0, TfInfoBufferSize));
int alignment = _context.Capabilities.StorageBufferOffsetAlignment;
for (int index = 0; index < Constants.TotalTransformFeedbackBuffers; index++)
{
BufferBounds tfb = _transformFeedbackBuffers[index];
if (tfb.Address == 0)
{
buffers[1 + index] = new BufferAssignment(1 + index, BufferRange.Empty);
}
else
{
ulong endAddress = tfb.Address + tfb.Size;
ulong address = BitUtils.AlignDown(tfb.Address, (ulong)alignment);
ulong size = endAddress - address;
int tfeOffset = ((int)tfb.Address & (alignment - 1)) / 4;
if (info[index] != tfeOffset)
{
info[index] = tfeOffset;
needsDataUpdate = true;
}
buffers[1 + index] = new BufferAssignment(1 + index, bufferCache.GetBufferRange(address, size, write: true));
}
}
if (needsDataUpdate)
{
Span<byte> infoData = MemoryMarshal.Cast<int, byte>(info);
_context.Renderer.SetBufferData(_tfInfoBuffer, 0, infoData);
}
_context.Renderer.Pipeline.SetStorageBuffers(buffers);
}
}
else
{

View File

@ -37,7 +37,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
ShaderSpecializationState oldSpecState,
ShaderSpecializationState newSpecState,
ResourceCounts counts,
int stageIndex) : base(context, counts, stageIndex)
int stageIndex) : base(context, counts, stageIndex, oldSpecState.TransformFeedbackDescriptors != null)
{
_data = data;
_cb1Data = cb1Data;

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 = 5044;
private const uint CodeGenVersion = 5080;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";
@ -368,7 +368,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
if (hostCode != null)
{
ShaderInfo shaderInfo = ShaderInfoBuilder.BuildForCache(context, shaders, specState.PipelineState);
ShaderInfo shaderInfo = ShaderInfoBuilder.BuildForCache(
context,
shaders,
specState.PipelineState,
specState.TransformFeedbackDescriptors != null);
IProgram hostProgram;

View File

@ -491,7 +491,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
{
ShaderSource[] shaderSources = new ShaderSource[compilation.TranslatedStages.Length];
ShaderInfoBuilder shaderInfoBuilder = new ShaderInfoBuilder(_context);
ShaderInfoBuilder shaderInfoBuilder = new ShaderInfoBuilder(_context, compilation.SpecializationState.TransformFeedbackDescriptors != null);
for (int index = 0; index < compilation.TranslatedStages.Length; index++)
{

View File

@ -30,7 +30,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
GpuContext context,
GpuChannel channel,
GpuAccessorState state,
int stageIndex) : base(context, state.ResourceCounts, stageIndex)
int stageIndex) : base(context, state.ResourceCounts, stageIndex, state.TransformFeedbackDescriptors != null)
{
_isVulkan = context.Capabilities.Api == TargetApi.Vulkan;
_channel = channel;
@ -44,7 +44,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="context">GPU context</param>
/// <param name="channel">GPU channel</param>
/// <param name="state">Current GPU state</param>
public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state) : base(context, state.ResourceCounts, 0)
public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state) : base(context, state.ResourceCounts, 0, false)
{
_channel = channel;
_state = state;

View File

@ -17,40 +17,56 @@ namespace Ryujinx.Graphics.Gpu.Shader
private readonly ResourceCounts _resourceCounts;
private readonly int _stageIndex;
private readonly int _reservedConstantBuffers;
private readonly int _reservedStorageBuffers;
/// <summary>
/// Creates a new GPU accessor.
/// </summary>
/// <param name="context">GPU context</param>
public GpuAccessorBase(GpuContext context, ResourceCounts resourceCounts, int stageIndex)
/// <param name="resourceCounts">Counter of GPU resources used by the shader</param>
/// <param name="stageIndex">Index of the shader stage, 0 for compute</param>
/// <param name="tfEnabled">Indicates if the current graphics shader is used with transform feedback enabled</param>
public GpuAccessorBase(GpuContext context, ResourceCounts resourceCounts, int stageIndex, bool tfEnabled)
{
_context = context;
_resourceCounts = resourceCounts;
_stageIndex = stageIndex;
_reservedConstantBuffers = 1; // For the support buffer.
_reservedStorageBuffers = !context.Capabilities.SupportsTransformFeedback && tfEnabled ? 5 : 0;
}
public int QueryBindingConstantBuffer(int index)
{
int binding;
if (_context.Capabilities.Api == TargetApi.Vulkan)
{
// We need to start counting from 1 since binding 0 is reserved for the support uniform buffer.
return GetBindingFromIndex(index, _context.Capabilities.MaximumUniformBuffersPerStage, "Uniform buffer") + 1;
binding = GetBindingFromIndex(index, _context.Capabilities.MaximumUniformBuffersPerStage, "Uniform buffer");
}
else
{
return _resourceCounts.UniformBuffersCount++;
binding = _resourceCounts.UniformBuffersCount++;
}
return binding + _reservedConstantBuffers;
}
public int QueryBindingStorageBuffer(int index)
{
int binding;
if (_context.Capabilities.Api == TargetApi.Vulkan)
{
return GetBindingFromIndex(index, _context.Capabilities.MaximumStorageBuffersPerStage, "Storage buffer");
binding = GetBindingFromIndex(index, _context.Capabilities.MaximumStorageBuffersPerStage, "Storage buffer");
}
else
{
return _resourceCounts.StorageBuffersCount++;
binding = _resourceCounts.StorageBuffersCount++;
}
return binding + _reservedStorageBuffers;
}
public int QueryBindingTexture(int index, bool isBuffer)
@ -149,6 +165,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod;
public bool QueryHostSupportsTransformFeedback() => _context.Capabilities.SupportsTransformFeedback;
public bool QueryHostSupportsViewportIndexVertexTessellation() => _context.Capabilities.SupportsViewportIndexVertexTessellation;
public bool QueryHostSupportsViewportMask() => _context.Capabilities.SupportsViewportMask;

View File

@ -24,13 +24,5 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// Total of images used by the shaders.
/// </summary>
public int ImagesCount;
/// <summary>
/// Creates a new instance of the shader resource counts class.
/// </summary>
public ResourceCounts()
{
UniformBuffersCount = 1; // The first binding is reserved for the support buffer.
}
}
}

View File

@ -362,7 +362,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
TranslatorContext previousStage = null;
ShaderInfoBuilder infoBuilder = new ShaderInfoBuilder(_context);
ShaderInfoBuilder infoBuilder = new ShaderInfoBuilder(_context, transformFeedbackDescriptors != null);
for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
{

View File

@ -16,15 +16,24 @@ namespace Ryujinx.Graphics.Gpu.Shader
private const int TextureSetIndex = 2;
private const int ImageSetIndex = 3;
private const ResourceStages SupportBufferStags =
private const ResourceStages SupportBufferStages =
ResourceStages.Compute |
ResourceStages.Vertex |
ResourceStages.Fragment;
private const ResourceStages VtgStages =
ResourceStages.Vertex |
ResourceStages.TessellationControl |
ResourceStages.TessellationEvaluation |
ResourceStages.Geometry;
private readonly GpuContext _context;
private int _fragmentOutputMap;
private readonly int _reservedConstantBuffers;
private readonly int _reservedStorageBuffers;
private readonly List<ResourceDescriptor>[] _resourceDescriptors;
private readonly List<ResourceUsage>[] _resourceUsages;
@ -32,7 +41,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// Creates a new shader info builder.
/// </summary>
/// <param name="context">GPU context that owns the shaders that will be added to the builder</param>
public ShaderInfoBuilder(GpuContext context)
/// <param name="tfEnabled">Indicates if the graphics shader is used with transform feedback enabled</param>
public ShaderInfoBuilder(GpuContext context, bool tfEnabled)
{
_context = context;
@ -47,7 +57,22 @@ namespace Ryujinx.Graphics.Gpu.Shader
_resourceUsages[index] = new();
}
AddDescriptor(SupportBufferStags, ResourceType.UniformBuffer, UniformSetIndex, 0, 1);
AddDescriptor(SupportBufferStages, ResourceType.UniformBuffer, UniformSetIndex, 0, 1);
_reservedConstantBuffers = 1; // For the support buffer.
if (!context.Capabilities.SupportsTransformFeedback && tfEnabled)
{
_reservedStorageBuffers = 5;
AddDescriptor(VtgStages, ResourceType.StorageBuffer, StorageSetIndex, 0, 5);
AddUsage(VtgStages, ResourceType.StorageBuffer, ResourceAccess.Read, StorageSetIndex, 0, 1);
AddUsage(VtgStages, ResourceType.StorageBuffer, ResourceAccess.Write, StorageSetIndex, 1, 4);
}
else
{
_reservedStorageBuffers = 0;
}
}
/// <summary>
@ -86,8 +111,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
int texturesPerStage = (int)_context.Capabilities.MaximumTexturesPerStage;
int imagesPerStage = (int)_context.Capabilities.MaximumImagesPerStage;
int uniformBinding = 1 + stageIndex * uniformsPerStage;
int storageBinding = stageIndex * storagesPerStage;
int uniformBinding = _reservedConstantBuffers + stageIndex * uniformsPerStage;
int storageBinding = _reservedStorageBuffers + stageIndex * storagesPerStage;
int textureBinding = stageIndex * texturesPerStage * 2;
int imageBinding = stageIndex * imagesPerStage * 2;
@ -133,6 +158,23 @@ namespace Ryujinx.Graphics.Gpu.Shader
AddDescriptor(stages, type2, setIndex, binding + count, count);
}
/// <summary>
/// Adds buffer usage information to the list of usages.
/// </summary>
/// <param name="stages">Shader stages where the resource is used</param>
/// <param name="type">Type of the resource</param>
/// <param name="access">How the resource is accessed by the shader stages where it is used</param>
/// <param name="setIndex">Descriptor set number where the resource will be bound</param>
/// <param name="binding">Binding number where the resource will be bound</param>
/// <param name="count">Number of resources bound at the binding location</param>
private void AddUsage(ResourceStages stages, ResourceType type, ResourceAccess access, int setIndex, int binding, int count)
{
for (int index = 0; index < count; index++)
{
_resourceUsages[setIndex].Add(new ResourceUsage(binding + index, type, stages, access));
}
}
/// <summary>
/// Adds buffer usage information to the list of usages.
/// </summary>
@ -212,10 +254,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="context">GPU context that owns the shaders</param>
/// <param name="programs">Shaders from the disk cache</param>
/// <param name="pipeline">Optional pipeline for background compilation</param>
/// <param name="tfEnabled">Indicates if the graphics shader is used with transform feedback enabled</param>
/// <returns>Shader information</returns>
public static ShaderInfo BuildForCache(GpuContext context, IEnumerable<CachedShaderStage> programs, ProgramPipelineState? pipeline)
public static ShaderInfo BuildForCache(
GpuContext context,
IEnumerable<CachedShaderStage> programs,
ProgramPipelineState? pipeline,
bool tfEnabled)
{
ShaderInfoBuilder builder = new ShaderInfoBuilder(context);
ShaderInfoBuilder builder = new ShaderInfoBuilder(context, tfEnabled);
foreach (CachedShaderStage program in programs)
{
@ -237,7 +284,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <returns>Shader information</returns>
public static ShaderInfo BuildForCompute(GpuContext context, ShaderProgramInfo info, bool fromCache = false)
{
ShaderInfoBuilder builder = new ShaderInfoBuilder(context);
ShaderInfoBuilder builder = new ShaderInfoBuilder(context, tfEnabled: false);
builder.AddStageInfo(info);

View File

@ -153,6 +153,7 @@ namespace Ryujinx.Graphics.OpenGL
supportsFragmentShaderOrderingIntel: HwCapabilities.SupportsFragmentShaderOrdering,
supportsGeometryShader: true,
supportsGeometryShaderPassthrough: HwCapabilities.SupportsGeometryShaderPassthrough,
supportsTransformFeedback: true,
supportsImageLoadFormatted: HwCapabilities.SupportsImageLoadFormatted,
supportsLayerVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray,
supportsMismatchingViewFormat: HwCapabilities.SupportsMismatchingViewFormat,

View File

@ -10,5 +10,11 @@ namespace Ryujinx.Graphics.Shader
public const int NvnBaseVertexByteOffset = 0x640;
public const int NvnBaseInstanceByteOffset = 0x644;
public const int NvnDrawIndexByteOffset = 0x648;
// Transform Feedback emulation.
public const int TfeInfoBinding = 0;
public const int TfeBufferBaseBinding = 1;
public const int TfeBuffersCount = 4;
}
}

View File

@ -367,6 +367,15 @@ namespace Ryujinx.Graphics.Shader
return true;
}
/// <summary>
/// Queries host GPU transform feedback support.
/// </summary>
/// <returns>True if the GPU and driver supports transform feedback, false otherwise</returns>
bool QueryHostSupportsTransformFeedback()
{
return true;
}
/// <summary>
/// Queries host support for writes to the viewport index from vertex or tessellation shader stages.
/// </summary>

View File

@ -234,6 +234,45 @@ namespace Ryujinx.Graphics.Shader.Translation
public void PrepareForVertexReturn()
{
if (!Config.GpuAccessor.QueryHostSupportsTransformFeedback() && Config.GpuAccessor.QueryTransformFeedbackEnabled())
{
Operand vertexCount = this.Load(StorageKind.StorageBuffer, Constants.TfeInfoBinding, Const(1));
for (int tfbIndex = 0; tfbIndex < Constants.TfeBuffersCount; tfbIndex++)
{
var locations = Config.GpuAccessor.QueryTransformFeedbackVaryingLocations(tfbIndex);
var stride = Config.GpuAccessor.QueryTransformFeedbackStride(tfbIndex);
Operand baseOffset = this.Load(StorageKind.StorageBuffer, Constants.TfeInfoBinding, Const(0), Const(tfbIndex));
Operand baseVertex = this.Load(StorageKind.Input, IoVariable.BaseVertex);
Operand baseInstance = this.Load(StorageKind.Input, IoVariable.BaseInstance);
Operand vertexIndex = this.Load(StorageKind.Input, IoVariable.VertexIndex);
Operand instanceIndex = this.Load(StorageKind.Input, IoVariable.InstanceIndex);
Operand outputVertexOffset = this.ISubtract(vertexIndex, baseVertex);
Operand outputInstanceOffset = this.ISubtract(instanceIndex, baseInstance);
Operand outputBaseVertex = this.IMultiply(outputInstanceOffset, vertexCount);
Operand vertexOffset = this.IMultiply(this.IAdd(outputBaseVertex, outputVertexOffset), Const(stride / 4));
baseOffset = this.IAdd(baseOffset, vertexOffset);
for (int j = 0; j < locations.Length; j++)
{
byte location = locations[j];
if (location == 0xff)
{
continue;
}
Operand offset = this.IAdd(baseOffset, Const(j));
Operand value = Instructions.AttributeMap.GenerateAttributeLoad(this, null, location * 4, isOutput: true, isPerPatch: false);
this.Store(StorageKind.StorageBuffer, Constants.TfeBufferBaseBinding + tfbIndex, Const(0), offset, value);
}
}
}
if (Config.GpuAccessor.QueryViewportTransformDisable())
{
Operand x = this.Load(StorageKind.Output, IoVariable.Position, null, Const(0));

View File

@ -132,6 +132,11 @@ namespace Ryujinx.Graphics.Shader.Translation
_transformFeedbackDefinitions = new Dictionary<TransformFeedbackVariable, TransformFeedbackOutput>();
TransformFeedbackEnabled =
stage != ShaderStage.Compute &&
gpuAccessor.QueryTransformFeedbackEnabled() &&
gpuAccessor.QueryHostSupportsTransformFeedback();
UsedInputAttributesPerPatch = new HashSet<int>();
UsedOutputAttributesPerPatch = new HashSet<int>();
@ -139,6 +144,31 @@ namespace Ryujinx.Graphics.Shader.Translation
_usedImages = new Dictionary<TextureInfo, TextureMeta>();
ResourceManager = new ResourceManager(stage, gpuAccessor, new ShaderProperties());
if (!gpuAccessor.QueryHostSupportsTransformFeedback() && gpuAccessor.QueryTransformFeedbackEnabled())
{
StructureType tfeInfoStruct = new StructureType(new StructureField[]
{
new StructureField(AggregateType.Array | AggregateType.U32, "base_offset", 4),
new StructureField(AggregateType.U32, "vertex_count")
});
BufferDefinition tfeInfoBuffer = new BufferDefinition(BufferLayout.Std430, 1, Constants.TfeInfoBinding, "tfe_info", tfeInfoStruct);
Properties.AddStorageBuffer(Constants.TfeInfoBinding, tfeInfoBuffer);
StructureType tfeDataStruct = new StructureType(new StructureField[]
{
new StructureField(AggregateType.Array | AggregateType.U32, "data", 0)
});
for (int i = 0; i < Constants.TfeBuffersCount; i++)
{
int binding = Constants.TfeBufferBaseBinding + i;
BufferDefinition tfeDataBuffer = new BufferDefinition(BufferLayout.Std430, 1, binding, $"tfe_data{i}", tfeDataStruct);
Properties.AddStorageBuffer(binding, tfeDataBuffer);
}
}
}
public ShaderConfig(
@ -151,7 +181,6 @@ namespace Ryujinx.Graphics.Shader.Translation
ThreadsPerInputPrimitive = 1;
OutputTopology = outputTopology;
MaxOutputVertices = maxOutputVertices;
TransformFeedbackEnabled = gpuAccessor.QueryTransformFeedbackEnabled();
}
public ShaderConfig(ShaderHeader header, IGpuAccessor gpuAccessor, TranslationOptions options) : this(header.Stage, gpuAccessor, options)
@ -165,7 +194,6 @@ namespace Ryujinx.Graphics.Shader.Translation
OmapTargets = header.OmapTargets;
OmapSampleMask = header.OmapSampleMask;
OmapDepth = header.OmapDepth;
TransformFeedbackEnabled = gpuAccessor.QueryTransformFeedbackEnabled();
LastInVertexPipeline = header.Stage < ShaderStage.Fragment;
}

View File

@ -16,9 +16,9 @@ namespace Ryujinx.Graphics.Vulkan
_descriptorSets = descriptorSets;
}
public void InitializeBuffers(int setIndex, int baseBinding, int countPerUnit, DescriptorType type, VkBuffer dummyBuffer)
public void InitializeBuffers(int setIndex, int baseBinding, int count, DescriptorType type, VkBuffer dummyBuffer)
{
Span<DescriptorBufferInfo> infos = stackalloc DescriptorBufferInfo[countPerUnit];
Span<DescriptorBufferInfo> infos = stackalloc DescriptorBufferInfo[count];
infos.Fill(new DescriptorBufferInfo()
{

View File

@ -596,36 +596,19 @@ namespace Ryujinx.Graphics.Vulkan
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Initialize(CommandBufferScoped cbs, int setIndex, DescriptorSetCollection dsc)
{
// We don't support clearing texture descriptors currently.
if (setIndex != PipelineBase.UniformSetIndex && setIndex != PipelineBase.StorageSetIndex)
{
return;
}
var dummyBuffer = _dummyBuffer?.GetBuffer().Get(cbs).Value ?? default;
uint stages = _program.Stages;
while (stages != 0)
foreach (ResourceBindingSegment segment in _program.ClearSegments[setIndex])
{
int stage = BitOperations.TrailingZeroCount(stages);
stages &= ~(1u << stage);
if (setIndex == PipelineBase.UniformSetIndex)
{
dsc.InitializeBuffers(
0,
1 + stage * Constants.MaxUniformBuffersPerStage,
Constants.MaxUniformBuffersPerStage,
DescriptorType.UniformBuffer,
dummyBuffer);
}
else if (setIndex == PipelineBase.StorageSetIndex)
{
dsc.InitializeBuffers(
0,
stage * Constants.MaxStorageBuffersPerStage,
Constants.MaxStorageBuffersPerStage,
DescriptorType.StorageBuffer,
dummyBuffer);
}
dsc.InitializeBuffers(0, segment.Binding, segment.Count, segment.Type.Convert(), dummyBuffer);
}
}

View File

@ -24,6 +24,7 @@ namespace Ryujinx.Graphics.Vulkan
public uint Stages { get; }
public ResourceBindingSegment[][] ClearSegments { get; }
public ResourceBindingSegment[][] BindingSegments { get; }
public ProgramLinkStatus LinkStatus { get; private set; }
@ -115,6 +116,7 @@ namespace Ryujinx.Graphics.Vulkan
Stages = stages;
ClearSegments = BuildClearSegments(resourceLayout.Sets);
BindingSegments = BuildBindingSegments(resourceLayout.SetUsages);
_compileTask = Task.CompletedTask;
@ -135,6 +137,60 @@ namespace Ryujinx.Graphics.Vulkan
_firstBackgroundUse = !fromCache;
}
private static ResourceBindingSegment[][] BuildClearSegments(ReadOnlyCollection<ResourceDescriptorCollection> sets)
{
ResourceBindingSegment[][] segments = new ResourceBindingSegment[sets.Count][];
for (int setIndex = 0; setIndex < sets.Count; setIndex++)
{
List<ResourceBindingSegment> currentSegments = new List<ResourceBindingSegment>();
ResourceDescriptor currentDescriptor = default;
int currentCount = 0;
for (int index = 0; index < sets[setIndex].Descriptors.Count; index++)
{
ResourceDescriptor descriptor = sets[setIndex].Descriptors[index];
if (currentDescriptor.Binding + currentCount != descriptor.Binding ||
currentDescriptor.Type != descriptor.Type ||
currentDescriptor.Stages != descriptor.Stages)
{
if (currentCount != 0)
{
currentSegments.Add(new ResourceBindingSegment(
currentDescriptor.Binding,
currentCount,
currentDescriptor.Type,
currentDescriptor.Stages,
ResourceAccess.ReadWrite));
}
currentDescriptor = descriptor;
currentCount = descriptor.Count;
}
else
{
currentCount += descriptor.Count;
}
}
if (currentCount != 0)
{
currentSegments.Add(new ResourceBindingSegment(
currentDescriptor.Binding,
currentCount,
currentDescriptor.Type,
currentDescriptor.Stages,
ResourceAccess.ReadWrite));
}
segments[setIndex] = currentSegments.ToArray();
}
return segments;
}
private static ResourceBindingSegment[][] BuildBindingSegments(ReadOnlyCollection<ResourceUsageCollection> setUsages)
{
ResourceBindingSegment[][] segments = new ResourceBindingSegment[setUsages.Count][];

View File

@ -589,6 +589,7 @@ namespace Ryujinx.Graphics.Vulkan
supportsFragmentShaderOrderingIntel: false,
supportsGeometryShader: Capabilities.SupportsGeometryShader,
supportsGeometryShaderPassthrough: Capabilities.SupportsGeometryShaderPassthrough,
supportsTransformFeedback: Capabilities.SupportsTransformFeedback,
supportsImageLoadFormatted: features2.Features.ShaderStorageImageReadWithoutFormat,
supportsLayerVertexTessellation: featuresVk12.ShaderOutputLayer,
supportsMismatchingViewFormat: true,

View File

@ -154,7 +154,7 @@ namespace Ryujinx.HLE.HOS
}
private static DirectoryInfo FindTitleDir(DirectoryInfo contentsDir, string titleId)
=> contentsDir.EnumerateDirectories($"{titleId}*", DirEnumOptions).FirstOrDefault();
=> contentsDir.EnumerateDirectories(titleId, DirEnumOptions).FirstOrDefault();
private static void AddModsFromDirectory(ModCache mods, DirectoryInfo dir, string titleId)
{

View File

@ -1,8 +1,29 @@
namespace Ryujinx.HLE.HOS.Services.Hid
using Ryujinx.Common;
using Ryujinx.Common.Logging;
namespace Ryujinx.HLE.HOS.Services.Hid
{
[Service("hidbus")]
class IHidbusServer : IpcService
{
public IHidbusServer(ServiceCtx context) { }
[CommandCmif(1)]
// GetBusHandle(nn::hid::NpadIdType, nn::hidbus::BusType, nn::applet::AppletResourceUserId) -> (bool HasHandle, nn::hidbus::BusHandle)
public ResultCode GetBusHandle(ServiceCtx context)
{
NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadInt32();
context.RequestData.BaseStream.Position += 4; // Padding
BusType busType = (BusType)context.RequestData.ReadInt64();
long appletResourceUserId = context.RequestData.ReadInt64();
context.ResponseData.Write(false);
context.ResponseData.BaseStream.Position += 7; // Padding
context.ResponseData.WriteStruct(new BusHandle());
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { npadIdType, busType, appletResourceUserId });
return ResultCode.Success;
}
}
}

View File

@ -0,0 +1,14 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Hid
{
[StructLayout(LayoutKind.Sequential)]
struct BusHandle
{
public int AbstractedPadId;
public byte InternalIndex;
public byte PlayerNumber;
public byte BusTypeId;
public byte IsValid;
}
}

View File

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public enum BusType : long
{
LeftJoyRail = 0,
RightJoyRail = 1,
InternalBus = 2
}
}

View File

@ -17,7 +17,7 @@ namespace Ryujinx.Horizon.LogManager.Ipc
private const int MessageLengthLimit = 5000;
private readonly LogService _log;
private readonly ulong _pid;
private readonly ulong _pid;
private LogPacket _logPacket;
@ -74,8 +74,12 @@ namespace Ryujinx.Horizon.LogManager.Ipc
private bool LogImpl(ReadOnlySpan<byte> message)
{
SpanReader reader = new(message);
LogPacketHeader header = reader.Read<LogPacketHeader>();
SpanReader reader = new(message);
if (!reader.TryRead(out LogPacketHeader header))
{
return true;
}
bool isHeadPacket = (header.Flags & LogPacketFlags.IsHead) != 0;
bool isTailPacket = (header.Flags & LogPacketFlags.IsTail) != 0;
@ -84,8 +88,10 @@ namespace Ryujinx.Horizon.LogManager.Ipc
while (reader.Length > 0)
{
int type = ReadUleb128(ref reader);
int size = ReadUleb128(ref reader);
if (!TryReadUleb128(ref reader, out int type) || !TryReadUleb128(ref reader, out int size))
{
return true;
}
LogDataChunkKey key = (LogDataChunkKey)type;
@ -101,15 +107,24 @@ namespace Ryujinx.Horizon.LogManager.Ipc
}
else if (key == LogDataChunkKey.Line)
{
_logPacket.Line = reader.Read<int>();
if (!reader.TryRead<int>(out _logPacket.Line))
{
return true;
}
}
else if (key == LogDataChunkKey.DropCount)
{
_logPacket.DropCount = reader.Read<long>();
if (!reader.TryRead<long>(out _logPacket.DropCount))
{
return true;
}
}
else if (key == LogDataChunkKey.Time)
{
_logPacket.Time = reader.Read<long>();
if (!reader.TryRead<long>(out _logPacket.Time))
{
return true;
}
}
else if (key == LogDataChunkKey.Message)
{
@ -154,23 +169,25 @@ namespace Ryujinx.Horizon.LogManager.Ipc
return isTailPacket;
}
private static int ReadUleb128(ref SpanReader reader)
private static bool TryReadUleb128(ref SpanReader reader, out int result)
{
int result = 0;
int count = 0;
result = 0;
int count = 0;
byte encoded;
do
{
encoded = reader.Read<byte>();
if (!reader.TryRead<byte>(out encoded))
{
return false;
}
result += (encoded & 0x7F) << (7 * count);
count++;
} while ((encoded & 0x80) != 0);
return result;
return true;
}
}
}

View File

@ -7,7 +7,7 @@ namespace Ryujinx.Tests.Memory
{
public class Tests
{
private const ulong MemorySize = 0x8000;
private static readonly ulong MemorySize = MemoryBlock.GetPageSize() * 8;
private MemoryBlock _memoryBlock;
@ -44,14 +44,17 @@ namespace Ryujinx.Tests.Memory
[Platform(Exclude = "MacOsX")]
public void Test_Alias()
{
using MemoryBlock backing = new MemoryBlock(0x10000, MemoryAllocationFlags.Mirrorable);
using MemoryBlock toAlias = new MemoryBlock(0x10000, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible);
ulong pageSize = MemoryBlock.GetPageSize();
ulong blockSize = MemoryBlock.GetPageSize() * 16;
toAlias.MapView(backing, 0x1000, 0, 0x4000);
toAlias.UnmapView(backing, 0x3000, 0x1000);
using MemoryBlock backing = new MemoryBlock(blockSize, MemoryAllocationFlags.Mirrorable);
using MemoryBlock toAlias = new MemoryBlock(blockSize, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible);
toAlias.MapView(backing, pageSize, 0, pageSize * 4);
toAlias.UnmapView(backing, pageSize * 3, pageSize);
toAlias.Write(0, 0xbadc0de);
Assert.AreEqual(Marshal.ReadInt32(backing.Pointer, 0x1000), 0xbadc0de);
Assert.AreEqual(Marshal.ReadInt32(backing.Pointer, (int)pageSize), 0xbadc0de);
}
[Test]
@ -59,8 +62,12 @@ namespace Ryujinx.Tests.Memory
[Platform(Exclude = "MacOsX")]
public void Test_AliasRandom()
{
using MemoryBlock backing = new MemoryBlock(0x80000, MemoryAllocationFlags.Mirrorable);
using MemoryBlock toAlias = new MemoryBlock(0x80000, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible);
ulong pageSize = MemoryBlock.GetPageSize();
int pageBits = (int)ulong.Log2(pageSize);
ulong blockSize = MemoryBlock.GetPageSize() * 128;
using MemoryBlock backing = new MemoryBlock(blockSize, MemoryAllocationFlags.Mirrorable);
using MemoryBlock toAlias = new MemoryBlock(blockSize, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible);
Random rng = new Random(123);
@ -72,16 +79,16 @@ namespace Ryujinx.Tests.Memory
if ((rng.Next() & 1) != 0)
{
toAlias.MapView(backing, (ulong)srcPage << 12, (ulong)dstPage << 12, (ulong)pages << 12);
toAlias.MapView(backing, (ulong)srcPage << pageBits, (ulong)dstPage << pageBits, (ulong)pages << pageBits);
int offset = rng.Next(0, 0x1000 - sizeof(int));
int offset = rng.Next(0, (int)pageSize - sizeof(int));
toAlias.Write((ulong)((dstPage << 12) + offset), 0xbadc0de);
Assert.AreEqual(Marshal.ReadInt32(backing.Pointer, (srcPage << 12) + offset), 0xbadc0de);
toAlias.Write((ulong)((dstPage << pageBits) + offset), 0xbadc0de);
Assert.AreEqual(Marshal.ReadInt32(backing.Pointer, (srcPage << pageBits) + offset), 0xbadc0de);
}
else
{
toAlias.UnmapView(backing, (ulong)dstPage << 12, (ulong)pages << 12);
toAlias.UnmapView(backing, (ulong)dstPage << pageBits, (ulong)pages << pageBits);
}
}
}
@ -91,7 +98,7 @@ namespace Ryujinx.Tests.Memory
[Platform(Exclude = "MacOsX")]
public void Test_AliasMapLeak()
{
ulong pageSize = 4096;
ulong pageSize = MemoryBlock.GetPageSize();
ulong size = 100000 * pageSize; // The mappings limit on Linux is usually around 65K, so let's make sure we are above that.
using MemoryBlock backing = new MemoryBlock(pageSize, MemoryAllocationFlags.Mirrorable);

View File

@ -13,9 +13,9 @@ namespace Ryujinx.Tests.Cpu
[TestFixture]
public class CpuTest
{
protected const ulong Size = 0x1000;
protected const ulong CodeBaseAddress = 0x1000;
protected const ulong DataBaseAddress = CodeBaseAddress + Size;
protected static readonly ulong Size = MemoryBlock.GetPageSize();
protected static ulong CodeBaseAddress = Size;
protected static ulong DataBaseAddress = CodeBaseAddress + Size;
private static bool Ignore_FpcrFz = false;
private static bool Ignore_FpcrDn = false;
@ -39,12 +39,24 @@ namespace Ryujinx.Tests.Cpu
[SetUp]
public void Setup()
{
_currAddress = CodeBaseAddress;
int pageBits = (int)ulong.Log2(Size);
_ram = new MemoryBlock(Size * 2);
_memory = new MemoryManager(_ram, 1ul << 16);
_memory = new MemoryManager(_ram, 1ul << (pageBits + 4));
_memory.IncrementReferenceCount();
_memory.Map(CodeBaseAddress, 0, Size * 2, MemoryMapFlags.Private);
// Some tests depends on hardcoded address that were computed for 4KiB.
// We change the layout on non 4KiB platforms to keep compat here.
if (Size > 0x1000)
{
DataBaseAddress = 0;
CodeBaseAddress = Size;
}
_currAddress = CodeBaseAddress;
_memory.Map(CodeBaseAddress, 0, Size, MemoryMapFlags.Private);
_memory.Map(DataBaseAddress, Size, Size, MemoryMapFlags.Private);
_context = CpuContext.CreateExecutionContext();
Translator.IsReadyForTranslation.Set();

View File

@ -13,9 +13,9 @@ namespace Ryujinx.Tests.Cpu
[TestFixture]
public class CpuTest32
{
protected const uint Size = 0x1000;
protected const uint CodeBaseAddress = 0x1000;
protected const uint DataBaseAddress = CodeBaseAddress + Size;
protected static readonly uint Size = (uint)MemoryBlock.GetPageSize();
protected static uint CodeBaseAddress = Size;
protected static uint DataBaseAddress = CodeBaseAddress + Size;
private uint _currAddress;
@ -33,12 +33,24 @@ namespace Ryujinx.Tests.Cpu
[SetUp]
public void Setup()
{
_currAddress = CodeBaseAddress;
int pageBits = (int)ulong.Log2(Size);
_ram = new MemoryBlock(Size * 2);
_memory = new MemoryManager(_ram, 1ul << 16);
_memory = new MemoryManager(_ram, 1ul << (pageBits + 4));
_memory.IncrementReferenceCount();
_memory.Map(CodeBaseAddress, 0, Size * 2, MemoryMapFlags.Private);
// Some tests depends on hardcoded address that were computed for 4KiB.
// We change the layout on non 4KiB platforms to keep compat here.
if (Size > 0x1000)
{
DataBaseAddress = 0;
CodeBaseAddress = Size;
}
_currAddress = CodeBaseAddress;
_memory.Map(CodeBaseAddress, 0, Size, MemoryMapFlags.Private);
_memory.Map(DataBaseAddress, Size, Size, MemoryMapFlags.Private);
_context = CpuContext.CreateExecutionContext();
_context.IsAarch32 = true;

View File

@ -1,6 +1,7 @@
#define SimdMemory32
using ARMeilleure.State;
using Ryujinx.Memory;
using NUnit.Framework;
using System;
@ -9,6 +10,7 @@ namespace Ryujinx.Tests.Cpu
[Category("SimdMemory32")]
public sealed class CpuTestSimdMemory32 : CpuTest32
{
private static readonly uint TestOffset = DataBaseAddress + 0x500;
#if SimdMemory32
private uint[] _ldStModes =
@ -42,7 +44,7 @@ namespace Ryujinx.Tests.Cpu
[Range(0u, 3u)] uint n,
[Values(0x0u)] uint offset)
{
var data = GenerateVectorSequence(0x1000);
var data = GenerateVectorSequence((int)MemoryBlock.GetPageSize());
SetWorkingMemory(0, data);
uint opcode = 0xf4a00000u; // VLD1.8 {D0[0]}, [R0], R0
@ -58,7 +60,7 @@ namespace Ryujinx.Tests.Cpu
opcode |= (n & 3) << 8; // LD1 is 0, LD2 is 1 etc.
SingleOpcode(opcode, r0: 0x2500, r1: offset, sp: 0x2500);
SingleOpcode(opcode, r0: TestOffset, r1: offset, sp: TestOffset);
CompareAgainstUnicorn();
}
@ -72,7 +74,7 @@ namespace Ryujinx.Tests.Cpu
[Values] bool t,
[Values(0x0u)] uint offset)
{
var data = GenerateVectorSequence(0x1000);
var data = GenerateVectorSequence((int)MemoryBlock.GetPageSize());
SetWorkingMemory(0, data);
uint opcode = 0xf4a00c00u; // VLD1.8 {D0[0]}, [R0], R0
@ -85,7 +87,7 @@ namespace Ryujinx.Tests.Cpu
opcode |= (n & 3) << 8; // LD1 is 0, LD2 is 1 etc.
if (t) opcode |= 1 << 5;
SingleOpcode(opcode, r0: 0x2500, r1: offset, sp: 0x2500);
SingleOpcode(opcode, r0: TestOffset, r1: offset, sp: TestOffset);
CompareAgainstUnicorn();
}
@ -98,7 +100,7 @@ namespace Ryujinx.Tests.Cpu
[Range(0u, 10u)] uint mode,
[Values(0x0u)] uint offset)
{
var data = GenerateVectorSequence(0x1000);
var data = GenerateVectorSequence((int)MemoryBlock.GetPageSize());
SetWorkingMemory(0, data);
uint opcode = 0xf4200000u; // VLD4.8 {D0, D1, D2, D3}, [R0], R0
@ -114,7 +116,7 @@ namespace Ryujinx.Tests.Cpu
opcode |= ((vd & 0x10) << 18);
opcode |= ((vd & 0xf) << 12);
SingleOpcode(opcode, r0: 0x2500, r1: offset, sp: 0x2500);
SingleOpcode(opcode, r0: TestOffset, r1: offset, sp: TestOffset);
CompareAgainstUnicorn();
}
@ -128,7 +130,7 @@ namespace Ryujinx.Tests.Cpu
[Range(0u, 3u)] uint n,
[Values(0x0u)] uint offset)
{
var data = GenerateVectorSequence(0x1000);
var data = GenerateVectorSequence((int)MemoryBlock.GetPageSize());
SetWorkingMemory(0, data);
(V128 vec1, V128 vec2, V128 vec3, V128 vec4) = GenerateTestVectors();
@ -146,7 +148,7 @@ namespace Ryujinx.Tests.Cpu
opcode |= (n & 3) << 8; // ST1 is 0, ST2 is 1 etc.
SingleOpcode(opcode, r0: 0x2500, r1: offset, v1: vec1, v2: vec2, v3: vec3, v4: vec4, sp: 0x2500);
SingleOpcode(opcode, r0: TestOffset, r1: offset, v1: vec1, v2: vec2, v3: vec3, v4: vec4, sp: TestOffset);
CompareAgainstUnicorn();
}
@ -159,7 +161,7 @@ namespace Ryujinx.Tests.Cpu
[Range(0u, 10u)] uint mode,
[Values(0x0u)] uint offset)
{
var data = GenerateVectorSequence(0x1000);
var data = GenerateVectorSequence((int)MemoryBlock.GetPageSize());
SetWorkingMemory(0, data);
(V128 vec1, V128 vec2, V128 vec3, V128 vec4) = GenerateTestVectors();
@ -177,7 +179,7 @@ namespace Ryujinx.Tests.Cpu
opcode |= ((vd & 0x10) << 18);
opcode |= ((vd & 0xf) << 12);
SingleOpcode(opcode, r0: 0x2500, r1: offset, v1: vec1, v2: vec2, v3: vec3, v4: vec4, sp: 0x2500);
SingleOpcode(opcode, r0: TestOffset, r1: offset, v1: vec1, v2: vec2, v3: vec3, v4: vec4, sp: TestOffset);
CompareAgainstUnicorn();
}
@ -189,7 +191,7 @@ namespace Ryujinx.Tests.Cpu
[Values(0x1u, 0x32u)] uint regs,
[Values] bool single)
{
var data = GenerateVectorSequence(0x1000);
var data = GenerateVectorSequence((int)MemoryBlock.GetPageSize());
SetWorkingMemory(0, data);
uint opcode = 0xec100a00u; // VST4.8 {D0, D1, D2, D3}, [R0], R0
@ -225,7 +227,7 @@ namespace Ryujinx.Tests.Cpu
opcode |= regs & 0xff;
SingleOpcode(opcode, r0: 0x2500, sp: 0x2500);
SingleOpcode(opcode, r0: TestOffset, sp: TestOffset);
CompareAgainstUnicorn();
}
@ -237,7 +239,7 @@ namespace Ryujinx.Tests.Cpu
[Values(0x0u)] uint imm,
[Values] bool sub)
{
var data = GenerateVectorSequence(0x1000);
var data = GenerateVectorSequence((int)MemoryBlock.GetPageSize());
SetWorkingMemory(0, data);
uint opcode = 0xed900a00u; // VLDR.32 S0, [R0, #0]
@ -260,7 +262,7 @@ namespace Ryujinx.Tests.Cpu
}
opcode |= imm & 0xff;
SingleOpcode(opcode, r0: 0x2500);
SingleOpcode(opcode, r0: TestOffset);
CompareAgainstUnicorn();
}
@ -272,7 +274,7 @@ namespace Ryujinx.Tests.Cpu
[Values(0x0u)] uint imm,
[Values] bool sub)
{
var data = GenerateVectorSequence(0x1000);
var data = GenerateVectorSequence((int)MemoryBlock.GetPageSize());
SetWorkingMemory(0, data);
uint opcode = 0xed800a00u; // VSTR.32 S0, [R0, #0]
@ -297,7 +299,7 @@ namespace Ryujinx.Tests.Cpu
(V128 vec1, V128 vec2, _, _) = GenerateTestVectors();
SingleOpcode(opcode, r0: 0x2500, v0: vec1, v1: vec2);
SingleOpcode(opcode, r0: TestOffset, v0: vec1, v1: vec2);
CompareAgainstUnicorn();
}

View File

@ -3,6 +3,7 @@
using ARMeilleure.State;
using NUnit.Framework;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Ryujinx.Tests.Cpu
{
@ -703,6 +704,11 @@ namespace Ryujinx.Tests.Cpu
[Values] bool q,
[Values] bool u)
{
if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{
Assert.Ignore("Unicorn on ARM64 crash while executing this test");
}
uint opcode = 0xf2000400u; // VSHL.S8 D0, D0, D0
if (q)
{

View File

@ -109,7 +109,7 @@ namespace Ryujinx.Tests.Cpu
ExecuteOpcodes(runUnicorn: false);
Assert.That(GetContext().GetX(0), Is.EqualTo(0x1005));
Assert.That(GetContext().GetX(0), Is.EqualTo(CodeBaseAddress + 0x5));
}
[Test]
@ -133,7 +133,7 @@ namespace Ryujinx.Tests.Cpu
ExecuteOpcodes(runUnicorn: false);
Assert.That(GetContext().GetX(0), Is.EqualTo(0x1005));
Assert.That(GetContext().GetX(0), Is.EqualTo(CodeBaseAddress + 0x5));
Assert.That(GetContext().GetPstateFlag(PState.TFlag), Is.EqualTo(false));
}
@ -160,7 +160,7 @@ namespace Ryujinx.Tests.Cpu
ExecuteOpcodes(runUnicorn: false);
Assert.That(GetContext().GetX(0), Is.EqualTo(0x1007));
Assert.That(GetContext().GetX(0), Is.EqualTo(CodeBaseAddress + 0x7));
Assert.That(GetContext().GetPstateFlag(PState.TFlag), Is.EqualTo(false));
}
}

View File

@ -268,6 +268,12 @@ namespace Ryujinx.Tests.Cpu
[Test]
public void TestRandomTestCases([ValueSource(nameof(RandomTestCases))] PrecomputedThumbTestCase test)
{
if (Size != 0x1000)
{
// TODO: Change it to depend on DataBaseAddress instead.
Assert.Ignore("This test currently only support 4KiB page size");
}
RunPrecomputedTestCase(test);
}

View File

@ -11,10 +11,10 @@ namespace Ryujinx.Ui.Common.Helper
{
public static partial class FileAssociationHelper
{
private static string[] _fileExtensions = new string[] { ".nca", ".nro", ".nso", ".nsp", ".xci" };
private static readonly string[] _fileExtensions = { ".nca", ".nro", ".nso", ".nsp", ".xci" };
[SupportedOSPlatform("linux")]
private static string _mimeDbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "mime");
private static readonly string _mimeDbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "mime");
private const int SHCNE_ASSOCCHANGED = 0x8000000;
private const int SHCNF_FLUSH = 0x1000;
@ -32,7 +32,7 @@ namespace Ryujinx.Ui.Common.Helper
{
string installKeyword = uninstall ? "uninstall" : "install";
if (!AreMimeTypesRegisteredLinux())
if ((uninstall && AreMimeTypesRegisteredLinux()) || (!uninstall && !AreMimeTypesRegisteredLinux()))
{
string mimeTypesFile = Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "mime", "Ryujinx.xml");
string additionalArgs = !uninstall ? "--novendor" : "";
@ -81,9 +81,9 @@ namespace Ryujinx.Ui.Common.Helper
return false;
}
key.OpenSubKey(@"shell\open\command");
var openCmd = key.OpenSubKey(@"shell\open\command");
string keyValue = (string)key.GetValue("");
string keyValue = (string)openCmd.GetValue("");
return keyValue is not null && (keyValue.Contains("Ryujinx") || keyValue.Contains(AppDomain.CurrentDomain.FriendlyName));
}
@ -107,30 +107,31 @@ namespace Ryujinx.Ui.Common.Helper
if (uninstall)
{
// If the types don't already exist, there's nothing to do and we can call this operation successful.
if (!AreMimeTypesRegisteredWindows())
{
return false;
return true;
}
Logger.Debug?.Print(LogClass.Application, $"Removing type association {ext}");
Registry.CurrentUser.DeleteSubKeyTree(keyString);
Logger.Debug?.Print(LogClass.Application, $"Removed type association {ext}");
}
else
{
RegistryKey key = Registry.CurrentUser.CreateSubKey(keyString);
using var key = Registry.CurrentUser.CreateSubKey(keyString);
if (key is null)
{
return false;
}
key.CreateSubKey(@"shell\open\command");
Logger.Debug?.Print(LogClass.Application, $"Adding type association {ext}");
using var openCmd = key.CreateSubKey(@"shell\open\command");
openCmd.SetValue("", $"\"{Environment.ProcessPath}\" \"%1\"");
Logger.Debug?.Print(LogClass.Application, $"Added type association {ext}");
key.SetValue("", $"\"{Environment.ProcessPath}\" \"%1\"");
key.Close();
}
// Notify Explorer the file association has been changed.
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSH, IntPtr.Zero, IntPtr.Zero);
return true;
}
@ -141,6 +142,9 @@ namespace Ryujinx.Ui.Common.Helper
registered |= RegisterExtension(ext, uninstall);
}
// Notify Explorer the file association has been changed.
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSH, IntPtr.Zero, IntPtr.Zero);
return registered;
}

View File

@ -1,8 +1,10 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.Input.HLE;
using SPB.Graphics;
using SPB.Graphics.Exceptions;
using SPB.Graphics.OpenGL;
using SPB.Platform;
using SPB.Platform.GLX;
@ -112,24 +114,30 @@ namespace Ryujinx.Ui
protected override void Dispose(bool disposing)
{
// Try to bind the OpenGL context before calling the shutdown event
// Try to bind the OpenGL context before calling the shutdown event.
try
{
_openGLContext?.MakeCurrent(_nativeWindow);
}
catch (Exception) { }
catch (ContextException e)
{
Logger.Warning?.Print(LogClass.Ui, $"Failed to bind OpenGL context: {e}");
}
Device?.DisposeGpu();
NpadManager.Dispose();
// Unbind context and destroy everything
// Unbind context and destroy everything.
try
{
_openGLContext?.MakeCurrent(null);
}
catch (Exception) { }
catch (ContextException e)
{
Logger.Warning?.Print(LogClass.Ui, $"Failed to unbind OpenGL context: {e}");
}
_openGLContext?.Dispose();
}
}
}
}