Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
4c2d9ff3ff | ||
|
8198b99935 | ||
|
460f96967d | ||
|
7ca779a26d | ||
|
b5032b3c91 | ||
|
f0a3dff136 | ||
|
f659dcb9d8 | ||
|
a34fb0e939 | ||
|
21ce8a9b80 | ||
|
9ecbee8032 | ||
|
80519af67d | ||
|
26e30faff3 | ||
|
0992310b76 | ||
|
009c1101d2 | ||
|
ba95ee54ab | ||
|
4ce4299ca2 | ||
|
17620d18db | ||
|
9f1cf6458c | ||
|
67b4e63cff | ||
|
c05c688ee8 | ||
|
b2623dc27d | ||
|
5131b71437 |
11
.github/workflows/release.yml
vendored
11
.github/workflows/release.yml
vendored
@@ -112,6 +112,17 @@ jobs:
|
||||
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
|
||||
token: ${{ secrets.RELEASE_TOKEN }}
|
||||
|
||||
- name: Create tag
|
||||
uses: actions/github-script@v5
|
||||
with:
|
||||
script: |
|
||||
github.rest.git.createRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: 'refs/tags/${{ steps.version_info.outputs.build_version }}',
|
||||
sha: context.sha
|
||||
})
|
||||
|
||||
flatpak_release:
|
||||
uses: ./.github/workflows/flatpak.yml
|
||||
needs: release
|
||||
|
@@ -7,6 +7,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -1,6 +1,7 @@
|
||||
using ARMeilleure.CodeGen.Linking;
|
||||
using ARMeilleure.CodeGen.RegisterAllocators;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -59,7 +60,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
|
||||
public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable)
|
||||
{
|
||||
_stream = new MemoryStream();
|
||||
_stream = MemoryStreamManager.Shared.GetStream();
|
||||
|
||||
AllocResult = allocResult;
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using ARMeilleure.CodeGen.Linking;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@@ -1033,7 +1034,13 @@ namespace ARMeilleure.CodeGen.X86
|
||||
|
||||
Debug.Assert(opCode != BadOp, "Invalid opcode value.");
|
||||
|
||||
if ((flags & InstructionFlags.Vex) != 0 && HardwareCapabilities.SupportsVexEncoding)
|
||||
if ((flags & InstructionFlags.Evex) != 0 && HardwareCapabilities.SupportsEvexEncoding)
|
||||
{
|
||||
WriteEvexInst(dest, src1, src2, type, flags, opCode);
|
||||
|
||||
opCode &= 0xff;
|
||||
}
|
||||
else if ((flags & InstructionFlags.Vex) != 0 && HardwareCapabilities.SupportsVexEncoding)
|
||||
{
|
||||
// In a vex encoding, only one prefix can be active at a time. The active prefix is encoded in the second byte using two bits.
|
||||
|
||||
@@ -1152,6 +1159,103 @@ namespace ARMeilleure.CodeGen.X86
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteEvexInst(
|
||||
Operand dest,
|
||||
Operand src1,
|
||||
Operand src2,
|
||||
OperandType type,
|
||||
InstructionFlags flags,
|
||||
int opCode,
|
||||
bool broadcast = false,
|
||||
int registerWidth = 128,
|
||||
int maskRegisterIdx = 0,
|
||||
bool zeroElements = false)
|
||||
{
|
||||
int op1Idx = dest.GetRegister().Index;
|
||||
int op2Idx = src1.GetRegister().Index;
|
||||
int op3Idx = src2.GetRegister().Index;
|
||||
|
||||
WriteByte(0x62);
|
||||
|
||||
// P0
|
||||
// Extend operand 1 register
|
||||
bool r = (op1Idx & 8) == 0;
|
||||
// Extend operand 3 register
|
||||
bool x = (op3Idx & 16) == 0;
|
||||
// Extend operand 3 register
|
||||
bool b = (op3Idx & 8) == 0;
|
||||
// Extend operand 1 register
|
||||
bool rp = (op1Idx & 16) == 0;
|
||||
// Escape code index
|
||||
byte mm = 0b00;
|
||||
|
||||
switch ((ushort)(opCode >> 8))
|
||||
{
|
||||
case 0xf00: mm = 0b01; break;
|
||||
case 0xf38: mm = 0b10; break;
|
||||
case 0xf3a: mm = 0b11; break;
|
||||
|
||||
default: Debug.Fail($"Failed to EVEX encode opcode 0x{opCode:X}."); break;
|
||||
}
|
||||
|
||||
WriteByte(
|
||||
(byte)(
|
||||
(r ? 0x80 : 0) |
|
||||
(x ? 0x40 : 0) |
|
||||
(b ? 0x20 : 0) |
|
||||
(rp ? 0x10 : 0) |
|
||||
mm));
|
||||
|
||||
// P1
|
||||
// Specify 64-bit lane mode
|
||||
bool w = Is64Bits(type);
|
||||
// Operand 2 register index
|
||||
byte vvvv = (byte)(~op2Idx & 0b1111);
|
||||
// Opcode prefix
|
||||
byte pp = (flags & InstructionFlags.PrefixMask) switch
|
||||
{
|
||||
InstructionFlags.Prefix66 => 0b01,
|
||||
InstructionFlags.PrefixF3 => 0b10,
|
||||
InstructionFlags.PrefixF2 => 0b11,
|
||||
_ => 0
|
||||
};
|
||||
WriteByte(
|
||||
(byte)(
|
||||
(w ? 0x80 : 0) |
|
||||
(vvvv << 3) |
|
||||
0b100 |
|
||||
pp));
|
||||
|
||||
// P2
|
||||
// Mask register determines what elements to zero, rather than what elements to merge
|
||||
bool z = zeroElements;
|
||||
// Specifies register-width
|
||||
byte ll = 0b00;
|
||||
switch (registerWidth)
|
||||
{
|
||||
case 128: ll = 0b00; break;
|
||||
case 256: ll = 0b01; break;
|
||||
case 512: ll = 0b10; break;
|
||||
|
||||
default: Debug.Fail($"Invalid EVEX vector register width {registerWidth}."); break;
|
||||
}
|
||||
// Embedded broadcast in the case of a memory operand
|
||||
bool bcast = broadcast;
|
||||
// Extend operand 2 register
|
||||
bool vp = (op2Idx & 16) == 0;
|
||||
// Mask register index
|
||||
Debug.Assert(maskRegisterIdx < 8, $"Invalid mask register index {maskRegisterIdx}.");
|
||||
byte aaa = (byte)(maskRegisterIdx & 0b111);
|
||||
|
||||
WriteByte(
|
||||
(byte)(
|
||||
(z ? 0x80 : 0) |
|
||||
(ll << 5) |
|
||||
(bcast ? 0x10 : 0) |
|
||||
(vp ? 8 : 0) |
|
||||
aaa));
|
||||
}
|
||||
|
||||
private void WriteCompactInst(Operand operand, int opCode)
|
||||
{
|
||||
int regIndex = operand.GetRegister().Index;
|
||||
@@ -1285,7 +1389,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
// Write the code, ignoring the dummy bytes after jumps, into a new stream.
|
||||
_stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
using var codeStream = new MemoryStream();
|
||||
using var codeStream = MemoryStreamManager.Shared.GetStream();
|
||||
var assembler = new Assembler(codeStream, HasRelocs);
|
||||
|
||||
bool hasRelocs = HasRelocs;
|
||||
|
@@ -20,6 +20,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Reg8Dest = 1 << 2,
|
||||
RexW = 1 << 3,
|
||||
Vex = 1 << 4,
|
||||
Evex = 1 << 5,
|
||||
|
||||
PrefixBit = 16,
|
||||
PrefixMask = 7 << PrefixBit,
|
||||
@@ -278,6 +279,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Add(X86Instruction.Vfnmsub231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
|
||||
Add(X86Instruction.Vfnmsub231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vpblendvb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4c, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vpternlogd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a25, InstructionFlags.Evex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Xor, new InstructionInfo(0x00000031, 0x06000083, 0x06000081, BadOp, 0x00000033, InstructionFlags.None));
|
||||
Add(X86Instruction.Xorpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Xorps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex));
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using ARMeilleure.CodeGen.RegisterAllocators;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
|
||||
@@ -22,7 +23,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
|
||||
public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable)
|
||||
{
|
||||
_stream = new MemoryStream();
|
||||
_stream = MemoryStreamManager.Shared.GetStream();
|
||||
_blockLabels = new Operand[blocksCount];
|
||||
|
||||
AllocResult = allocResult;
|
||||
|
@@ -1,10 +1,14 @@
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
|
||||
namespace ARMeilleure.CodeGen.X86
|
||||
{
|
||||
static class HardwareCapabilities
|
||||
{
|
||||
private delegate uint GetXcr0();
|
||||
|
||||
static HardwareCapabilities()
|
||||
{
|
||||
if (!X86Base.IsSupported)
|
||||
@@ -24,6 +28,34 @@ namespace ARMeilleure.CodeGen.X86
|
||||
FeatureInfo7Ebx = (FeatureFlags7Ebx)ebx7;
|
||||
FeatureInfo7Ecx = (FeatureFlags7Ecx)ecx7;
|
||||
}
|
||||
|
||||
Xcr0InfoEax = (Xcr0FlagsEax)GetXcr0Eax();
|
||||
}
|
||||
|
||||
private static uint GetXcr0Eax()
|
||||
{
|
||||
if (!FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Xsave))
|
||||
{
|
||||
// XSAVE feature required for xgetbv
|
||||
return 0;
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> asmGetXcr0 = new byte[]
|
||||
{
|
||||
0x31, 0xc9, // xor ecx, ecx
|
||||
0xf, 0x01, 0xd0, // xgetbv
|
||||
0xc3, // ret
|
||||
};
|
||||
|
||||
using MemoryBlock memGetXcr0 = new MemoryBlock((ulong)asmGetXcr0.Length);
|
||||
|
||||
memGetXcr0.Write(0, asmGetXcr0);
|
||||
|
||||
memGetXcr0.Reprotect(0, (ulong)asmGetXcr0.Length, MemoryPermission.ReadAndExecute);
|
||||
|
||||
var fGetXcr0 = Marshal.GetDelegateForFunctionPointer<GetXcr0>(memGetXcr0.Pointer);
|
||||
|
||||
return fGetXcr0();
|
||||
}
|
||||
|
||||
[Flags]
|
||||
@@ -44,6 +76,8 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Sse42 = 1 << 20,
|
||||
Popcnt = 1 << 23,
|
||||
Aes = 1 << 25,
|
||||
Xsave = 1 << 26,
|
||||
Osxsave = 1 << 27,
|
||||
Avx = 1 << 28,
|
||||
F16c = 1 << 29
|
||||
}
|
||||
@@ -52,7 +86,11 @@ namespace ARMeilleure.CodeGen.X86
|
||||
public enum FeatureFlags7Ebx
|
||||
{
|
||||
Avx2 = 1 << 5,
|
||||
Sha = 1 << 29
|
||||
Avx512f = 1 << 16,
|
||||
Avx512dq = 1 << 17,
|
||||
Sha = 1 << 29,
|
||||
Avx512bw = 1 << 30,
|
||||
Avx512vl = 1 << 31
|
||||
}
|
||||
|
||||
[Flags]
|
||||
@@ -61,10 +99,21 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Gfni = 1 << 8,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum Xcr0FlagsEax
|
||||
{
|
||||
Sse = 1 << 1,
|
||||
YmmHi128 = 1 << 2,
|
||||
Opmask = 1 << 5,
|
||||
ZmmHi256 = 1 << 6,
|
||||
Hi16Zmm = 1 << 7
|
||||
}
|
||||
|
||||
public static FeatureFlags1Edx FeatureInfo1Edx { get; }
|
||||
public static FeatureFlags1Ecx FeatureInfo1Ecx { get; }
|
||||
public static FeatureFlags7Ebx FeatureInfo7Ebx { get; } = 0;
|
||||
public static FeatureFlags7Ecx FeatureInfo7Ecx { get; } = 0;
|
||||
public static Xcr0FlagsEax Xcr0InfoEax { get; } = 0;
|
||||
|
||||
public static bool SupportsSse => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse);
|
||||
public static bool SupportsSse2 => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse2);
|
||||
@@ -76,8 +125,13 @@ namespace ARMeilleure.CodeGen.X86
|
||||
public static bool SupportsSse42 => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Sse42);
|
||||
public static bool SupportsPopcnt => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Popcnt);
|
||||
public static bool SupportsAesni => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Aes);
|
||||
public static bool SupportsAvx => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Avx);
|
||||
public static bool SupportsAvx => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Avx | FeatureFlags1Ecx.Xsave | FeatureFlags1Ecx.Osxsave) && Xcr0InfoEax.HasFlag(Xcr0FlagsEax.Sse | Xcr0FlagsEax.YmmHi128);
|
||||
public static bool SupportsAvx2 => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx2) && SupportsAvx;
|
||||
public static bool SupportsAvx512F => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512f) && FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Xsave | FeatureFlags1Ecx.Osxsave)
|
||||
&& Xcr0InfoEax.HasFlag(Xcr0FlagsEax.Sse | Xcr0FlagsEax.YmmHi128 | Xcr0FlagsEax.Opmask | Xcr0FlagsEax.ZmmHi256 | Xcr0FlagsEax.Hi16Zmm);
|
||||
public static bool SupportsAvx512Vl => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512vl) && SupportsAvx512F;
|
||||
public static bool SupportsAvx512Bw => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512bw) && SupportsAvx512F;
|
||||
public static bool SupportsAvx512Dq => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512dq) && SupportsAvx512F;
|
||||
public static bool SupportsF16c => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.F16c);
|
||||
public static bool SupportsSha => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Sha);
|
||||
public static bool SupportsGfni => FeatureInfo7Ecx.HasFlag(FeatureFlags7Ecx.Gfni);
|
||||
@@ -85,5 +139,6 @@ namespace ARMeilleure.CodeGen.X86
|
||||
public static bool ForceLegacySse { get; set; }
|
||||
|
||||
public static bool SupportsVexEncoding => SupportsAvx && !ForceLegacySse;
|
||||
public static bool SupportsEvexEncoding => SupportsAvx512F && !ForceLegacySse;
|
||||
}
|
||||
}
|
@@ -180,6 +180,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Add(Intrinsic.X86Vfnmadd231ss, new IntrinsicInfo(X86Instruction.Vfnmadd231ss, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfnmsub231sd, new IntrinsicInfo(X86Instruction.Vfnmsub231sd, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfnmsub231ss, new IntrinsicInfo(X86Instruction.Vfnmsub231ss, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vpternlogd, new IntrinsicInfo(X86Instruction.Vpternlogd, IntrinsicType.TernaryImm));
|
||||
Add(Intrinsic.X86Xorpd, new IntrinsicInfo(X86Instruction.Xorpd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Xorps, new IntrinsicInfo(X86Instruction.Xorps, IntrinsicType.Binary));
|
||||
}
|
||||
|
@@ -219,6 +219,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Vfnmsub231sd,
|
||||
Vfnmsub231ss,
|
||||
Vpblendvb,
|
||||
Vpternlogd,
|
||||
Xor,
|
||||
Xorpd,
|
||||
Xorps,
|
||||
|
@@ -254,7 +254,22 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
public static void Not_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
if (Optimizations.UseAvx512Ortho)
|
||||
{
|
||||
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
||||
|
||||
Operand n = GetVec(op.Rn);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, n, Const(~0b10101010));
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else if (Optimizations.UseSse2)
|
||||
{
|
||||
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
||||
|
||||
@@ -283,6 +298,22 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64OrnV);
|
||||
}
|
||||
else if (Optimizations.UseAvx512Ortho)
|
||||
{
|
||||
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
||||
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, m, Const(0b11001100 | ~0b10101010));
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else if (Optimizations.UseSse2)
|
||||
{
|
||||
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
||||
|
@@ -151,6 +151,13 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
InstEmitSimdHelper32Arm64.EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.Arm64OrnV | Intrinsic.Arm64V128, n, m));
|
||||
}
|
||||
else if (Optimizations.UseAvx512Ortho)
|
||||
{
|
||||
EmitVectorBinaryOpSimd32(context, (n, m) =>
|
||||
{
|
||||
return context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, m, Const(0b11001100 | ~0b10101010));
|
||||
});
|
||||
}
|
||||
else if (Optimizations.UseSse2)
|
||||
{
|
||||
Operand mask = context.VectorOne();
|
||||
|
@@ -34,7 +34,14 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
public static void Vmvn_I(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
if (Optimizations.UseAvx512Ortho)
|
||||
{
|
||||
EmitVectorUnaryOpSimd32(context, (op1) =>
|
||||
{
|
||||
return context.AddIntrinsic(Intrinsic.X86Vpternlogd, op1, op1, Const(0b01010101));
|
||||
});
|
||||
}
|
||||
else if (Optimizations.UseSse2)
|
||||
{
|
||||
EmitVectorUnaryOpSimd32(context, (op1) =>
|
||||
{
|
||||
|
@@ -173,6 +173,7 @@ namespace ARMeilleure.IntermediateRepresentation
|
||||
X86Vfnmadd231ss,
|
||||
X86Vfnmsub231sd,
|
||||
X86Vfnmsub231ss,
|
||||
X86Vpternlogd,
|
||||
X86Xorpd,
|
||||
X86Xorps,
|
||||
|
||||
|
@@ -23,6 +23,10 @@ namespace ARMeilleure
|
||||
public static bool UseSse42IfAvailable { get; set; } = true;
|
||||
public static bool UsePopCntIfAvailable { get; set; } = true;
|
||||
public static bool UseAvxIfAvailable { get; set; } = true;
|
||||
public static bool UseAvx512FIfAvailable { get; set; } = true;
|
||||
public static bool UseAvx512VlIfAvailable { get; set; } = true;
|
||||
public static bool UseAvx512BwIfAvailable { get; set; } = true;
|
||||
public static bool UseAvx512DqIfAvailable { get; set; } = true;
|
||||
public static bool UseF16cIfAvailable { get; set; } = true;
|
||||
public static bool UseFmaIfAvailable { get; set; } = true;
|
||||
public static bool UseAesniIfAvailable { get; set; } = true;
|
||||
@@ -47,11 +51,18 @@ namespace ARMeilleure
|
||||
internal static bool UseSse42 => UseSse42IfAvailable && X86HardwareCapabilities.SupportsSse42;
|
||||
internal static bool UsePopCnt => UsePopCntIfAvailable && X86HardwareCapabilities.SupportsPopcnt;
|
||||
internal static bool UseAvx => UseAvxIfAvailable && X86HardwareCapabilities.SupportsAvx && !ForceLegacySse;
|
||||
internal static bool UseAvx512F => UseAvx512FIfAvailable && X86HardwareCapabilities.SupportsAvx512F && !ForceLegacySse;
|
||||
internal static bool UseAvx512Vl => UseAvx512VlIfAvailable && X86HardwareCapabilities.SupportsAvx512Vl && !ForceLegacySse;
|
||||
internal static bool UseAvx512Bw => UseAvx512BwIfAvailable && X86HardwareCapabilities.SupportsAvx512Bw && !ForceLegacySse;
|
||||
internal static bool UseAvx512Dq => UseAvx512DqIfAvailable && X86HardwareCapabilities.SupportsAvx512Dq && !ForceLegacySse;
|
||||
internal static bool UseF16c => UseF16cIfAvailable && X86HardwareCapabilities.SupportsF16c;
|
||||
internal static bool UseFma => UseFmaIfAvailable && X86HardwareCapabilities.SupportsFma;
|
||||
internal static bool UseAesni => UseAesniIfAvailable && X86HardwareCapabilities.SupportsAesni;
|
||||
internal static bool UsePclmulqdq => UsePclmulqdqIfAvailable && X86HardwareCapabilities.SupportsPclmulqdq;
|
||||
internal static bool UseSha => UseShaIfAvailable && X86HardwareCapabilities.SupportsSha;
|
||||
internal static bool UseGfni => UseGfniIfAvailable && X86HardwareCapabilities.SupportsGfni;
|
||||
|
||||
internal static bool UseAvx512Ortho => UseAvx512F && UseAvx512Vl;
|
||||
internal static bool UseAvx512OrthoFloat => UseAvx512Ortho && UseAvx512Dq;
|
||||
}
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ using ARMeilleure.Memory;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
@@ -29,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 = 4484; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
private const uint InternalVersion = 4485; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
|
||||
private const string ActualDir = "0";
|
||||
private const string BackupDir = "1";
|
||||
@@ -150,10 +151,10 @@ namespace ARMeilleure.Translation.PTC
|
||||
|
||||
private void InitializeCarriers()
|
||||
{
|
||||
_infosStream = new MemoryStream();
|
||||
_infosStream = MemoryStreamManager.Shared.GetStream();
|
||||
_codesList = new List<byte[]>();
|
||||
_relocsStream = new MemoryStream();
|
||||
_unwindInfosStream = new MemoryStream();
|
||||
_relocsStream = MemoryStreamManager.Shared.GetStream();
|
||||
_unwindInfosStream = MemoryStreamManager.Shared.GetStream();
|
||||
}
|
||||
|
||||
private void DisposeCarriers()
|
||||
@@ -968,6 +969,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
(ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap,
|
||||
(ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap2,
|
||||
(ulong)Arm64HardwareCapabilities.MacOsFeatureInfo,
|
||||
0,
|
||||
0);
|
||||
}
|
||||
else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
|
||||
@@ -976,11 +978,12 @@ namespace ARMeilleure.Translation.PTC
|
||||
(ulong)X86HardwareCapabilities.FeatureInfo1Ecx,
|
||||
(ulong)X86HardwareCapabilities.FeatureInfo1Edx,
|
||||
(ulong)X86HardwareCapabilities.FeatureInfo7Ebx,
|
||||
(ulong)X86HardwareCapabilities.FeatureInfo7Ecx);
|
||||
(ulong)X86HardwareCapabilities.FeatureInfo7Ecx,
|
||||
(ulong)X86HardwareCapabilities.Xcr0InfoEax);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new FeatureInfo(0, 0, 0, 0);
|
||||
return new FeatureInfo(0, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1001,7 +1004,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
return osPlatform;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 78*/)]
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 86*/)]
|
||||
private struct OuterHeader
|
||||
{
|
||||
public ulong Magic;
|
||||
@@ -1033,8 +1036,8 @@ namespace ARMeilleure.Translation.PTC
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 32*/)]
|
||||
private record struct FeatureInfo(ulong FeatureInfo0, ulong FeatureInfo1, ulong FeatureInfo2, ulong FeatureInfo3);
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 40*/)]
|
||||
private record struct FeatureInfo(ulong FeatureInfo0, ulong FeatureInfo1, ulong FeatureInfo2, ulong FeatureInfo3, ulong FeatureInfo4);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 128*/)]
|
||||
private struct InnerHeader
|
||||
|
@@ -1,6 +1,7 @@
|
||||
using ARMeilleure.State;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Concurrent;
|
||||
@@ -182,7 +183,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
return false;
|
||||
}
|
||||
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||
{
|
||||
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
|
||||
|
||||
@@ -274,7 +275,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
|
||||
outerHeader.SetHeaderHash();
|
||||
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||
{
|
||||
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
|
||||
|
||||
|
@@ -22,6 +22,7 @@
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||
@@ -33,7 +34,7 @@
|
||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
|
||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.26.1-build23" />
|
||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.26.3-build25" />
|
||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
|
||||
|
@@ -18,6 +18,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
private ulong _playedSampleCount;
|
||||
private ManualResetEvent _updateRequiredEvent;
|
||||
private uint _outputStream;
|
||||
private bool _hasSetupError;
|
||||
private SDL_AudioCallback _callbackDelegate;
|
||||
private int _bytesPerFrame;
|
||||
private uint _sampleCount;
|
||||
@@ -42,7 +43,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
private void EnsureAudioStreamSetup(AudioBuffer buffer)
|
||||
{
|
||||
uint bufferSampleCount = (uint)GetSampleCount(buffer);
|
||||
bool needAudioSetup = _outputStream == 0 ||
|
||||
bool needAudioSetup = (_outputStream == 0 && !_hasSetupError) ||
|
||||
(bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount);
|
||||
|
||||
if (needAudioSetup)
|
||||
@@ -51,12 +52,9 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
|
||||
uint newOutputStream = SDL2HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate);
|
||||
|
||||
if (newOutputStream == 0)
|
||||
{
|
||||
// No stream in place, this is unexpected.
|
||||
throw new InvalidOperationException($"OpenStream failed with error: \"{SDL_GetError()}\"");
|
||||
}
|
||||
else
|
||||
_hasSetupError = newOutputStream == 0;
|
||||
|
||||
if (!_hasSetupError)
|
||||
{
|
||||
if (_outputStream != 0)
|
||||
{
|
||||
@@ -151,11 +149,20 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
{
|
||||
EnsureAudioStreamSetup(buffer);
|
||||
|
||||
SDL2AudioBuffer driverBuffer = new SDL2AudioBuffer(buffer.DataPointer, GetSampleCount(buffer));
|
||||
if (_outputStream != 0)
|
||||
{
|
||||
SDL2AudioBuffer driverBuffer = new SDL2AudioBuffer(buffer.DataPointer, GetSampleCount(buffer));
|
||||
|
||||
_ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
|
||||
_ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
|
||||
|
||||
_queuedBuffers.Enqueue(driverBuffer);
|
||||
_queuedBuffers.Enqueue(driverBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
Interlocked.Add(ref _playedSampleCount, GetSampleCount(buffer));
|
||||
|
||||
_updateRequiredEvent.Set();
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetVolume(float volume)
|
||||
|
@@ -320,10 +320,14 @@ namespace Ryujinx.Ava
|
||||
|
||||
_viewModel.IsGameRunning = true;
|
||||
|
||||
string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) ? string.Empty : $" - {Device.Application.TitleName}";
|
||||
string titleVersionSection = string.IsNullOrWhiteSpace(Device.Application.DisplayVersion) ? string.Empty : $" v{Device.Application.DisplayVersion}";
|
||||
string titleIdSection = string.IsNullOrWhiteSpace(Device.Application.TitleIdText) ? string.Empty : $" ({Device.Application.TitleIdText.ToUpper()})";
|
||||
string titleArchSection = Device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)";
|
||||
var activeProcess = Device.Processes.ActiveApplication;
|
||||
var nacp = activeProcess.ApplicationControlProperties;
|
||||
int desiredLanguage = (int)Device.System.State.DesiredTitleLanguage;
|
||||
|
||||
string titleNameSection = string.IsNullOrWhiteSpace(nacp.Title[desiredLanguage].NameString.ToString()) ? string.Empty : $" - {nacp.Title[desiredLanguage].NameString.ToString()}";
|
||||
string titleVersionSection = string.IsNullOrWhiteSpace(nacp.DisplayVersionString.ToString()) ? string.Empty : $" v{nacp.DisplayVersionString.ToString()}";
|
||||
string titleIdSection = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText) ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})";
|
||||
string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
@@ -423,9 +427,9 @@ namespace Ryujinx.Ava
|
||||
|
||||
private void Dispose()
|
||||
{
|
||||
if (Device.Application != null)
|
||||
if (Device.Processes != null)
|
||||
{
|
||||
_viewModel.UpdateGameMetadata(Device.Application.TitleIdText);
|
||||
_viewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText);
|
||||
}
|
||||
|
||||
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
|
||||
@@ -539,7 +543,12 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA).");
|
||||
|
||||
Device.LoadNca(ApplicationPath);
|
||||
if (!Device.LoadNca(ApplicationPath))
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (Directory.Exists(ApplicationPath))
|
||||
{
|
||||
@@ -554,13 +563,23 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
|
||||
|
||||
Device.LoadCart(ApplicationPath, romFsFiles[0]);
|
||||
if (!Device.LoadCart(ApplicationPath, romFsFiles[0]))
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
|
||||
|
||||
Device.LoadCart(ApplicationPath);
|
||||
if (!Device.LoadCart(ApplicationPath))
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (File.Exists(ApplicationPath))
|
||||
@@ -571,7 +590,12 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
|
||||
|
||||
Device.LoadXci(ApplicationPath);
|
||||
if (!Device.LoadXci(ApplicationPath))
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -579,7 +603,12 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
|
||||
|
||||
Device.LoadNca(ApplicationPath);
|
||||
if (!Device.LoadNca(ApplicationPath))
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -588,7 +617,12 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
|
||||
|
||||
Device.LoadNsp(ApplicationPath);
|
||||
if (!Device.LoadNsp(ApplicationPath))
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -598,13 +632,18 @@ namespace Ryujinx.Ava
|
||||
|
||||
try
|
||||
{
|
||||
Device.LoadProgram(ApplicationPath);
|
||||
if (!Device.LoadProgram(ApplicationPath))
|
||||
{
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
|
||||
|
||||
Dispose();
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -617,14 +656,14 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
|
||||
|
||||
Dispose();
|
||||
Device.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
DiscordIntegrationModule.SwitchToPlayingState(Device.Application.TitleIdText, Device.Application.TitleName);
|
||||
DiscordIntegrationModule.SwitchToPlayingState(Device.Processes.ActiveApplication.ProgramIdText, Device.Processes.ActiveApplication.Name);
|
||||
|
||||
_viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Application.TitleIdText, appMetadata =>
|
||||
_viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata =>
|
||||
{
|
||||
appMetadata.LastPlayed = DateTime.UtcNow.ToString();
|
||||
});
|
||||
@@ -950,7 +989,7 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen)
|
||||
{
|
||||
Device.Application.DiskCacheLoadState?.Cancel();
|
||||
Device.Processes.ActiveApplication.DiskCacheLoadState?.Cancel();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1088,4 +1127,4 @@ namespace Ryujinx.Ava
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
@@ -227,7 +228,7 @@ namespace Ryujinx.Ava.Common
|
||||
return;
|
||||
}
|
||||
|
||||
(Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
|
||||
(Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
|
||||
if (updatePatchNca != null)
|
||||
{
|
||||
patchNca = updatePatchNca;
|
||||
|
@@ -1208,10 +1208,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public void SetUIProgressHandlers(Switch emulationContext)
|
||||
{
|
||||
if (emulationContext.Application.DiskCacheLoadState != null)
|
||||
if (emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null)
|
||||
{
|
||||
emulationContext.Application.DiskCacheLoadState.StateChanged -= ProgressHandler;
|
||||
emulationContext.Application.DiskCacheLoadState.StateChanged += ProgressHandler;
|
||||
emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler;
|
||||
emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler;
|
||||
}
|
||||
|
||||
emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
|
||||
@@ -1705,8 +1705,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleName))
|
||||
{
|
||||
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Application.TitleName);
|
||||
TitleName = AppHost.Device.Application.TitleName;
|
||||
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
|
||||
TitleName = AppHost.Device.Processes.ActiveApplication.Name;
|
||||
}
|
||||
|
||||
SwitchToRenderer(startFullscreen);
|
||||
|
@@ -17,6 +17,7 @@ using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -162,7 +163,7 @@ public class TitleUpdateViewModel : BaseModel
|
||||
|
||||
try
|
||||
{
|
||||
(Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
|
||||
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
|
||||
|
||||
if (controlNca != null && patchNca != null)
|
||||
{
|
||||
|
@@ -126,7 +126,7 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
|
||||
if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
|
||||
{
|
||||
string titleId = ViewModel.AppHost.Device.Application.TitleIdText.ToUpper();
|
||||
string titleId = ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper();
|
||||
AmiiboWindow window = new(ViewModel.ShowAll, ViewModel.LastScannedAmiiboId, titleId);
|
||||
|
||||
await window.ShowDialog(Window);
|
||||
@@ -148,13 +148,11 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
return;
|
||||
}
|
||||
|
||||
ApplicationLoader application = ViewModel.AppHost.Device.Application;
|
||||
if (application != null)
|
||||
{
|
||||
await new CheatWindow(Window.VirtualFileSystem, application.TitleIdText, application.TitleName).ShowDialog(Window);
|
||||
string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString();
|
||||
|
||||
ViewModel.AppHost.Device.EnableCheats();
|
||||
}
|
||||
await new CheatWindow(Window.VirtualFileSystem, ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText, name).ShowDialog(Window);
|
||||
|
||||
ViewModel.AppHost.Device.EnableCheats();
|
||||
}
|
||||
|
||||
private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
|
||||
|
@@ -12,19 +12,5 @@ namespace Ryujinx.Common
|
||||
{
|
||||
return MemoryMarshal.Cast<byte, T>(reader.ReadBytes(Unsafe.SizeOf<T>()))[0];
|
||||
}
|
||||
|
||||
public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
ReadOnlySpan<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1));
|
||||
|
||||
writer.Write(data);
|
||||
}
|
||||
|
||||
public static void Write(this BinaryWriter writer, UInt128 value)
|
||||
{
|
||||
writer.Write((ulong)value);
|
||||
writer.Write((ulong)(value >> 64));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
28
Ryujinx.Common/Extensions/BinaryWriterExtensions.cs
Normal file
28
Ryujinx.Common/Extensions/BinaryWriterExtensions.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
public static class BinaryWriterExtensions
|
||||
{
|
||||
public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
ReadOnlySpan<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1));
|
||||
|
||||
writer.Write(data);
|
||||
}
|
||||
|
||||
public static void Write(this BinaryWriter writer, UInt128 value)
|
||||
{
|
||||
writer.Write((ulong)value);
|
||||
writer.Write((ulong)(value >> 64));
|
||||
}
|
||||
|
||||
public static void Write(this BinaryWriter writer, MemoryStream stream)
|
||||
{
|
||||
stream.CopyTo(writer.BaseStream);
|
||||
}
|
||||
}
|
||||
}
|
138
Ryujinx.Common/Extensions/StreamExtensions.cs
Normal file
138
Ryujinx.Common/Extensions/StreamExtensions.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
public static class StreamExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes a <cref="ReadOnlySpan<int>" /> to this stream.
|
||||
///
|
||||
/// This default implementation converts each buffer value to a stack-allocated
|
||||
/// byte array, then writes it to the Stream using <cref="System.Stream.Write(byte[])" />.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to be written to</param>
|
||||
/// <param name="buffer">The buffer of values to be written</param>
|
||||
public static void Write(this Stream stream, ReadOnlySpan<int> buffer)
|
||||
{
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
ReadOnlySpan<byte> byteBuffer = MemoryMarshal.Cast<int, byte>(buffer);
|
||||
stream.Write(byteBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
Span<byte> byteBuffer = stackalloc byte[sizeof(int)];
|
||||
|
||||
foreach (int value in buffer)
|
||||
{
|
||||
BinaryPrimitives.WriteInt32LittleEndian(byteBuffer, value);
|
||||
stream.Write(byteBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a four-byte signed integer to this stream. The current position
|
||||
/// of the stream is advanced by four.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to be written to</param>
|
||||
/// <param name="value">The value to be written</param>
|
||||
public static void Write(this Stream stream, int value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(int)];
|
||||
BinaryPrimitives.WriteInt32LittleEndian(buffer, value);
|
||||
stream.Write(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an eight-byte signed integer to this stream. The current position
|
||||
/// of the stream is advanced by eight.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to be written to</param>
|
||||
/// <param name="value">The value to be written</param>
|
||||
public static void Write(this Stream stream, long value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(long)];
|
||||
BinaryPrimitives.WriteInt64LittleEndian(buffer, value);
|
||||
stream.Write(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
// Writes a four-byte unsigned integer to this stream. The current position
|
||||
// of the stream is advanced by four.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to be written to</param>
|
||||
/// <param name="value">The value to be written</param>
|
||||
public static void Write(this Stream stream, uint value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(uint)];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
|
||||
stream.Write(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an eight-byte unsigned integer to this stream. The current
|
||||
/// position of the stream is advanced by eight.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to be written to</param>
|
||||
/// <param name="value">The value to be written</param>
|
||||
public static void Write(this Stream stream, ulong value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(buffer, value);
|
||||
stream.Write(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the contents of source to stream by calling source.CopyTo(stream).
|
||||
/// Provides consistency with other Stream.Write methods.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to be written to</param>
|
||||
/// <param name="source">The stream to be read from</param>
|
||||
public static void Write(this Stream stream, Stream source)
|
||||
{
|
||||
source.CopyTo(stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a sequence of bytes to the Stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to be written to.</param>
|
||||
/// <param name="value">The byte to be written</param>
|
||||
/// <param name="count">The number of times the value should be written</param>
|
||||
public static void WriteByte(this Stream stream, byte value, int count)
|
||||
{
|
||||
if (count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int BlockSize = 16;
|
||||
|
||||
int blockCount = count / BlockSize;
|
||||
if (blockCount > 0)
|
||||
{
|
||||
Span<byte> span = stackalloc byte[BlockSize];
|
||||
span.Fill(value);
|
||||
for (int x = 0; x < blockCount; x++)
|
||||
{
|
||||
stream.Write(span);
|
||||
}
|
||||
}
|
||||
|
||||
int nonBlockBytes = count % BlockSize;
|
||||
for (int x = 0; x < nonBlockBytes; x++)
|
||||
{
|
||||
stream.WriteByte(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
99
Ryujinx.Common/Memory/MemoryStreamManager.cs
Normal file
99
Ryujinx.Common/Memory/MemoryStreamManager.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using Microsoft.IO;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Common.Memory
|
||||
{
|
||||
public static class MemoryStreamManager
|
||||
{
|
||||
private static readonly RecyclableMemoryStreamManager _shared = new RecyclableMemoryStreamManager();
|
||||
|
||||
/// <summary>
|
||||
/// We don't expose the <c>RecyclableMemoryStreamManager</c> directly because version 2.x
|
||||
/// returns them as <c>MemoryStream</c>. This Shared class is here to a) offer only the GetStream() versions we use
|
||||
/// and b) return them as <c>RecyclableMemoryStream</c> so we don't have to cast.
|
||||
/// </summary>
|
||||
public static class Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieve a new <c>MemoryStream</c> object with no tag and a default initial capacity.
|
||||
/// </summary>
|
||||
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||
public static RecyclableMemoryStream GetStream()
|
||||
=> new RecyclableMemoryStream(_shared);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a new <c>MemoryStream</c> object with the contents copied from the provided
|
||||
/// buffer. The provided buffer is not wrapped or used after construction.
|
||||
/// </summary>
|
||||
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
|
||||
/// <param name="buffer">The byte buffer to copy data from</param>
|
||||
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||
public static RecyclableMemoryStream GetStream(byte[] buffer)
|
||||
=> GetStream(Guid.NewGuid(), null, buffer, 0, buffer.Length);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a new <c>MemoryStream</c> object with the given tag and with contents copied from the provided
|
||||
/// buffer. The provided buffer is not wrapped or used after construction.
|
||||
/// </summary>
|
||||
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
|
||||
/// <param name="buffer">The byte buffer to copy data from</param>
|
||||
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||
public static RecyclableMemoryStream GetStream(ReadOnlySpan<byte> buffer)
|
||||
=> GetStream(Guid.NewGuid(), null, buffer);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a new <c>RecyclableMemoryStream</c> object with the given tag and with contents copied from the provided
|
||||
/// buffer. The provided buffer is not wrapped or used after construction.
|
||||
/// </summary>
|
||||
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
|
||||
/// <param name="id">A unique identifier which can be used to trace usages of the stream</param>
|
||||
/// <param name="tag">A tag which can be used to track the source of the stream</param>
|
||||
/// <param name="buffer">The byte buffer to copy data from</param>
|
||||
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||
public static RecyclableMemoryStream GetStream(Guid id, string tag, ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
RecyclableMemoryStream stream = null;
|
||||
try
|
||||
{
|
||||
stream = new RecyclableMemoryStream(_shared, id, tag, buffer.Length);
|
||||
stream.Write(buffer);
|
||||
stream.Position = 0;
|
||||
return stream;
|
||||
}
|
||||
catch
|
||||
{
|
||||
stream?.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a new <c>RecyclableMemoryStream</c> object with the given tag and with contents copied from the provided
|
||||
/// buffer. The provided buffer is not wrapped or used after construction.
|
||||
/// </summary>
|
||||
/// <remarks>The new stream's position is set to the beginning of the stream when returned</remarks>
|
||||
/// <param name="id">A unique identifier which can be used to trace usages of the stream</param>
|
||||
/// <param name="tag">A tag which can be used to track the source of the stream</param>
|
||||
/// <param name="buffer">The byte buffer to copy data from</param>
|
||||
/// <param name="offset">The offset from the start of the buffer to copy from</param>
|
||||
/// <param name="count">The number of bytes to copy from the buffer</param>
|
||||
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||
public static RecyclableMemoryStream GetStream(Guid id, string tag, byte[] buffer, int offset, int count)
|
||||
{
|
||||
RecyclableMemoryStream stream = null;
|
||||
try
|
||||
{
|
||||
stream = new RecyclableMemoryStream(_shared, id, tag, count);
|
||||
stream.Write(buffer, offset, count);
|
||||
stream.Position = 0;
|
||||
return stream;
|
||||
}
|
||||
catch
|
||||
{
|
||||
stream?.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,6 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" />
|
||||
<PackageReference Include="MsgPack.Cli" />
|
||||
<PackageReference Include="System.Management" />
|
||||
</ItemGroup>
|
||||
|
@@ -1,18 +1,21 @@
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Common.SystemInterop
|
||||
{
|
||||
public partial class StdErrAdapter : IDisposable
|
||||
{
|
||||
private bool _disposable = false;
|
||||
private UnixStream _pipeReader;
|
||||
private UnixStream _pipeWriter;
|
||||
private Thread _worker;
|
||||
private Stream _pipeReader;
|
||||
private Stream _pipeWriter;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private Task _worker;
|
||||
|
||||
public StdErrAdapter()
|
||||
{
|
||||
@@ -31,37 +34,39 @@ namespace Ryujinx.Common.SystemInterop
|
||||
(int readFd, int writeFd) = MakePipe();
|
||||
dup2(writeFd, stdErrFileno);
|
||||
|
||||
_pipeReader = new UnixStream(readFd);
|
||||
_pipeWriter = new UnixStream(writeFd);
|
||||
_pipeReader = CreateFileDescriptorStream(readFd);
|
||||
_pipeWriter = CreateFileDescriptorStream(writeFd);
|
||||
|
||||
_worker = new Thread(EventWorker);
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_worker = Task.Run(async () => await EventWorkerAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
|
||||
_disposable = true;
|
||||
_worker.Start();
|
||||
}
|
||||
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
[SupportedOSPlatform("macos")]
|
||||
private void EventWorker()
|
||||
private async Task EventWorkerAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
TextReader reader = new StreamReader(_pipeReader);
|
||||
using TextReader reader = new StreamReader(_pipeReader, leaveOpen: true);
|
||||
string line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
while (cancellationToken.IsCancellationRequested == false && (line = await reader.ReadLineAsync(cancellationToken)) != null)
|
||||
{
|
||||
Logger.Error?.PrintRawMsg(line);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposable)
|
||||
{
|
||||
_disposable = false;
|
||||
|
||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
_worker.Wait(0);
|
||||
_pipeReader?.Close();
|
||||
_pipeWriter?.Close();
|
||||
}
|
||||
|
||||
_disposable = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,11 +79,11 @@ namespace Ryujinx.Common.SystemInterop
|
||||
private static partial int dup2(int fd, int fd2);
|
||||
|
||||
[LibraryImport("libc", SetLastError = true)]
|
||||
private static unsafe partial int pipe(int* pipefd);
|
||||
private static partial int pipe(Span<int> pipefd);
|
||||
|
||||
private static unsafe (int, int) MakePipe()
|
||||
private static (int, int) MakePipe()
|
||||
{
|
||||
int *pipefd = stackalloc int[2];
|
||||
Span<int> pipefd = stackalloc int[2];
|
||||
|
||||
if (pipe(pipefd) == 0)
|
||||
{
|
||||
@@ -89,5 +94,16 @@ namespace Ryujinx.Common.SystemInterop
|
||||
throw new();
|
||||
}
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
[SupportedOSPlatform("macos")]
|
||||
private static Stream CreateFileDescriptorStream(int fd)
|
||||
{
|
||||
return new FileStream(
|
||||
new SafeFileHandle((IntPtr)fd, ownsHandle: true),
|
||||
FileAccess.ReadWrite
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -1,155 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Common.SystemInterop
|
||||
{
|
||||
[SupportedOSPlatform("linux")]
|
||||
[SupportedOSPlatform("macos")]
|
||||
public partial class UnixStream : Stream, IDisposable
|
||||
{
|
||||
private const int InvalidFd = -1;
|
||||
|
||||
private int _fd;
|
||||
|
||||
[LibraryImport("libc", SetLastError = true)]
|
||||
private static partial long read(int fd, IntPtr buf, ulong count);
|
||||
|
||||
[LibraryImport("libc", SetLastError = true)]
|
||||
private static partial long write(int fd, IntPtr buf, ulong count);
|
||||
|
||||
[LibraryImport("libc", SetLastError = true)]
|
||||
private static partial int close(int fd);
|
||||
|
||||
public UnixStream(int fd)
|
||||
{
|
||||
if (InvalidFd == fd)
|
||||
{
|
||||
throw new ArgumentException("Invalid file descriptor");
|
||||
}
|
||||
|
||||
_fd = fd;
|
||||
|
||||
CanRead = read(fd, IntPtr.Zero, 0) != -1;
|
||||
CanWrite = write(fd, IntPtr.Zero, 0) != -1;
|
||||
}
|
||||
|
||||
~UnixStream()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
public override bool CanRead { get; }
|
||||
public override bool CanWrite { get; }
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => throw new NotSupportedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override unsafe int Read([In, Out] byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (offset < 0 || offset > (buffer.Length - count) || count < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
long r = 0;
|
||||
fixed (byte* buf = &buffer[offset])
|
||||
{
|
||||
do
|
||||
{
|
||||
r = read(_fd, (IntPtr)buf, (ulong)count);
|
||||
} while (ShouldRetry(r));
|
||||
}
|
||||
|
||||
return (int)r;
|
||||
}
|
||||
|
||||
public override unsafe void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (offset < 0 || offset > (buffer.Length - count) || count < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
fixed (byte* buf = &buffer[offset])
|
||||
{
|
||||
long r = 0;
|
||||
do {
|
||||
r = write(_fd, (IntPtr)buf, (ulong)count);
|
||||
} while (ShouldRetry(r));
|
||||
}
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
if (_fd == InvalidFd)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Flush();
|
||||
|
||||
int r;
|
||||
do {
|
||||
r = close(_fd);
|
||||
} while (ShouldRetry(r));
|
||||
|
||||
_fd = InvalidFd;
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
private bool ShouldRetry(long r)
|
||||
{
|
||||
if (r == -1)
|
||||
{
|
||||
const int eintr = 4;
|
||||
|
||||
int errno = Marshal.GetLastPInvokeError();
|
||||
|
||||
if (errno == eintr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new SystemException($"Operation failed with error 0x{errno:X}");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +1,5 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -38,12 +40,7 @@ namespace Ryujinx.Common
|
||||
return null;
|
||||
}
|
||||
|
||||
using (var mem = new MemoryStream())
|
||||
{
|
||||
stream.CopyTo(mem);
|
||||
|
||||
return mem.ToArray();
|
||||
}
|
||||
return StreamUtils.StreamToBytes(stream);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,12 +53,7 @@ namespace Ryujinx.Common
|
||||
return null;
|
||||
}
|
||||
|
||||
using (var mem = new MemoryStream())
|
||||
{
|
||||
await stream.CopyToAsync(mem);
|
||||
|
||||
return mem.ToArray();
|
||||
}
|
||||
return await StreamUtils.StreamToBytesAsync(stream);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,8 @@
|
||||
using System.IO;
|
||||
using Microsoft.IO;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Common.Utilities
|
||||
{
|
||||
@@ -6,12 +10,22 @@ namespace Ryujinx.Common.Utilities
|
||||
{
|
||||
public static byte[] StreamToBytes(Stream input)
|
||||
{
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||
{
|
||||
input.CopyTo(stream);
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<byte[]> StreamToBytesAsync(Stream input, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||
{
|
||||
await input.CopyToAsync(stream, cancellationToken);
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,6 @@
|
||||
using Ryujinx.Memory;
|
||||
using Microsoft.IO;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -40,7 +42,7 @@ namespace Ryujinx.Cpu
|
||||
|
||||
public static string ReadAsciiString(IVirtualMemoryManager memory, ulong position, long maxSize = -1)
|
||||
{
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
using (RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream())
|
||||
{
|
||||
for (long offs = 0; offs < maxSize || maxSize == -1; offs++)
|
||||
{
|
||||
@@ -54,7 +56,7 @@ namespace Ryujinx.Cpu
|
||||
ms.WriteByte(value);
|
||||
}
|
||||
|
||||
return Encoding.ASCII.GetString(ms.ToArray());
|
||||
return Encoding.ASCII.GetString(ms.GetReadOnlySequence());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -15,7 +15,12 @@ namespace Ryujinx.Graphics.GAL
|
||||
|
||||
void BackgroundContextAction(Action action, bool alwaysBackground = false);
|
||||
|
||||
BufferHandle CreateBuffer(int size);
|
||||
BufferHandle CreateBuffer(int size, BufferHandle storageHint);
|
||||
|
||||
BufferHandle CreateBuffer(int size)
|
||||
{
|
||||
return CreateBuffer(size, BufferHandle.Null);
|
||||
}
|
||||
|
||||
IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info);
|
||||
|
||||
@@ -26,7 +31,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
|
||||
void DeleteBuffer(BufferHandle buffer);
|
||||
|
||||
ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size);
|
||||
PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size);
|
||||
|
||||
Capabilities GetCapabilities();
|
||||
ulong GetCurrentSync();
|
||||
|
@@ -15,8 +15,8 @@ namespace Ryujinx.Graphics.GAL
|
||||
|
||||
ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel);
|
||||
|
||||
ReadOnlySpan<byte> GetData();
|
||||
ReadOnlySpan<byte> GetData(int layer, int level);
|
||||
PinnedSpan<byte> GetData();
|
||||
PinnedSpan<byte> GetData(int layer, int level);
|
||||
|
||||
void SetData(SpanOrArray<byte> data);
|
||||
void SetData(SpanOrArray<byte> data, int layer, int level);
|
||||
|
@@ -21,9 +21,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer
|
||||
|
||||
public static void Run(ref BufferGetDataCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
ReadOnlySpan<byte> result = renderer.GetBufferData(threaded.Buffers.MapBuffer(command._buffer), command._offset, command._size);
|
||||
PinnedSpan<byte> result = renderer.GetBufferData(threaded.Buffers.MapBuffer(command._buffer), command._offset, command._size);
|
||||
|
||||
command._result.Get(threaded).Result = new PinnedSpan<byte>(result);
|
||||
command._result.Get(threaded).Result = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,16 +5,25 @@
|
||||
public CommandType CommandType => CommandType.CreateBuffer;
|
||||
private BufferHandle _threadedHandle;
|
||||
private int _size;
|
||||
private BufferHandle _storageHint;
|
||||
|
||||
public void Set(BufferHandle threadedHandle, int size)
|
||||
public void Set(BufferHandle threadedHandle, int size, BufferHandle storageHint)
|
||||
{
|
||||
_threadedHandle = threadedHandle;
|
||||
_size = size;
|
||||
_storageHint = storageHint;
|
||||
}
|
||||
|
||||
public static void Run(ref CreateBufferCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._size));
|
||||
BufferHandle hint = BufferHandle.Null;
|
||||
|
||||
if (command._storageHint != BufferHandle.Null)
|
||||
{
|
||||
hint = threaded.Buffers.MapBuffer(command._storageHint);
|
||||
}
|
||||
|
||||
threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._size, hint));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -18,9 +18,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
||||
|
||||
public static void Run(ref TextureGetDataCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
ReadOnlySpan<byte> result = command._texture.Get(threaded).Base.GetData();
|
||||
PinnedSpan<byte> result = command._texture.Get(threaded).Base.GetData();
|
||||
|
||||
command._result.Get(threaded).Result = new PinnedSpan<byte>(result);
|
||||
command._result.Get(threaded).Result = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -22,9 +22,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
||||
|
||||
public static void Run(ref TextureGetDataSliceCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
ReadOnlySpan<byte> result = command._texture.Get(threaded).Base.GetData(command._layer, command._level);
|
||||
PinnedSpan<byte> result = command._texture.Get(threaded).Base.GetData(command._layer, command._level);
|
||||
|
||||
command._result.Get(threaded).Result = new PinnedSpan<byte>(result);
|
||||
command._result.Get(threaded).Result = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Model
|
||||
{
|
||||
unsafe struct PinnedSpan<T> where T : unmanaged
|
||||
{
|
||||
private void* _ptr;
|
||||
private int _size;
|
||||
|
||||
public PinnedSpan(ReadOnlySpan<T> span)
|
||||
{
|
||||
_ptr = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span));
|
||||
_size = span.Length;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<T> Get()
|
||||
{
|
||||
return new ReadOnlySpan<T>(_ptr, _size * Unsafe.SizeOf<T>());
|
||||
}
|
||||
}
|
||||
}
|
@@ -72,7 +72,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
||||
return newTex;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetData()
|
||||
public PinnedSpan<byte> GetData()
|
||||
{
|
||||
if (_renderer.IsGpuThread())
|
||||
{
|
||||
@@ -80,7 +80,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
||||
_renderer.New<TextureGetDataCommand>().Set(Ref(this), Ref(box));
|
||||
_renderer.InvokeCommand();
|
||||
|
||||
return box.Result.Get();
|
||||
return box.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -90,7 +90,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
||||
}
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetData(int layer, int level)
|
||||
public PinnedSpan<byte> GetData(int layer, int level)
|
||||
{
|
||||
if (_renderer.IsGpuThread())
|
||||
{
|
||||
@@ -98,7 +98,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
||||
_renderer.New<TextureGetDataSliceCommand>().Set(Ref(this), Ref(box), layer, level);
|
||||
_renderer.InvokeCommand();
|
||||
|
||||
return box.Result.Get();
|
||||
return box.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@@ -265,10 +265,10 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
}
|
||||
}
|
||||
|
||||
public BufferHandle CreateBuffer(int size)
|
||||
public BufferHandle CreateBuffer(int size, BufferHandle storageHint)
|
||||
{
|
||||
BufferHandle handle = Buffers.CreateBufferHandle();
|
||||
New<CreateBufferCommand>().Set(handle, size);
|
||||
New<CreateBufferCommand>().Set(handle, size, storageHint);
|
||||
QueueCommand();
|
||||
|
||||
return handle;
|
||||
@@ -329,7 +329,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
QueueCommand();
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
|
||||
public PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
|
||||
{
|
||||
if (IsGpuThread())
|
||||
{
|
||||
@@ -337,7 +337,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
New<BufferGetDataCommand>().Set(buffer, offset, size, Ref(box));
|
||||
InvokeCommand();
|
||||
|
||||
return box.Result.Get();
|
||||
return box.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
53
Ryujinx.Graphics.GAL/PinnedSpan.cs
Normal file
53
Ryujinx.Graphics.GAL/PinnedSpan.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public unsafe struct PinnedSpan<T> : IDisposable where T : unmanaged
|
||||
{
|
||||
private void* _ptr;
|
||||
private int _size;
|
||||
private Action _disposeAction;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new PinnedSpan from an existing ReadOnlySpan. The span *must* be pinned in memory.
|
||||
/// The data must be guaranteed to live until disposeAction is called.
|
||||
/// </summary>
|
||||
/// <param name="span">Existing span</param>
|
||||
/// <param name="disposeAction">Action to call on dispose</param>
|
||||
/// <remarks>
|
||||
/// If a dispose action is not provided, it is safe to assume the resource will be available until the next call.
|
||||
/// </remarks>
|
||||
public static PinnedSpan<T> UnsafeFromSpan(ReadOnlySpan<T> span, Action disposeAction = null)
|
||||
{
|
||||
return new PinnedSpan<T>(Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)), span.Length, disposeAction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new PinnedSpan from an existing unsafe region. The data must be guaranteed to live until disposeAction is called.
|
||||
/// </summary>
|
||||
/// <param name="ptr">Pointer to the region</param>
|
||||
/// <param name="size">The total items of T the region contains</param>
|
||||
/// <param name="disposeAction">Action to call on dispose</param>
|
||||
/// <remarks>
|
||||
/// If a dispose action is not provided, it is safe to assume the resource will be available until the next call.
|
||||
/// </remarks>
|
||||
public PinnedSpan(void* ptr, int size, Action disposeAction = null)
|
||||
{
|
||||
_ptr = ptr;
|
||||
_size = size;
|
||||
_disposeAction = disposeAction;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<T> Get()
|
||||
{
|
||||
return new ReadOnlySpan<T>(_ptr, _size * Unsafe.SizeOf<T>());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_disposeAction?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
@@ -180,7 +180,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
int firstInstance = (int)_state.State.FirstInstance;
|
||||
|
||||
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount();
|
||||
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount(_context.Renderer);
|
||||
|
||||
if (inlineIndexCount != 0)
|
||||
{
|
||||
@@ -670,7 +670,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
if (indexedInline)
|
||||
{
|
||||
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount();
|
||||
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount(_context.Renderer);
|
||||
BufferRange br = new BufferRange(_drawState.IbStreamer.GetInlineIndexBuffer(), 0, inlineIndexCount * 4);
|
||||
|
||||
_channel.BufferManager.SetIndexBuffer(br, IndexType.UInt);
|
||||
|
@@ -11,9 +11,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
struct IbStreamer
|
||||
{
|
||||
private const int BufferCapacity = 256; // Must be a power of 2.
|
||||
|
||||
private BufferHandle _inlineIndexBuffer;
|
||||
private int _inlineIndexBufferSize;
|
||||
private int _inlineIndexCount;
|
||||
private uint[] _buffer;
|
||||
private int _bufferOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if any index buffer data has been pushed.
|
||||
@@ -38,9 +42,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// Gets the number of elements on the current inline index buffer,
|
||||
/// while also reseting it to zero for the next draw.
|
||||
/// </summary>
|
||||
/// <param name="renderer">Host renderer</param>
|
||||
/// <returns>Inline index bufffer count</returns>
|
||||
public int GetAndResetInlineIndexCount()
|
||||
public int GetAndResetInlineIndexCount(IRenderer renderer)
|
||||
{
|
||||
UpdateRemaining(renderer);
|
||||
int temp = _inlineIndexCount;
|
||||
_inlineIndexCount = 0;
|
||||
return temp;
|
||||
@@ -58,16 +64,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
byte i2 = (byte)(argument >> 16);
|
||||
byte i3 = (byte)(argument >> 24);
|
||||
|
||||
Span<uint> data = stackalloc uint[4];
|
||||
int offset = _inlineIndexCount;
|
||||
|
||||
data[0] = i0;
|
||||
data[1] = i1;
|
||||
data[2] = i2;
|
||||
data[3] = i3;
|
||||
|
||||
int offset = _inlineIndexCount * 4;
|
||||
|
||||
renderer.SetBufferData(GetInlineIndexBuffer(renderer, offset), offset, MemoryMarshal.Cast<uint, byte>(data));
|
||||
PushData(renderer, offset, i0);
|
||||
PushData(renderer, offset + 1, i1);
|
||||
PushData(renderer, offset + 2, i2);
|
||||
PushData(renderer, offset + 3, i3);
|
||||
|
||||
_inlineIndexCount += 4;
|
||||
}
|
||||
@@ -82,14 +84,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
ushort i0 = (ushort)argument;
|
||||
ushort i1 = (ushort)(argument >> 16);
|
||||
|
||||
Span<uint> data = stackalloc uint[2];
|
||||
int offset = _inlineIndexCount;
|
||||
|
||||
data[0] = i0;
|
||||
data[1] = i1;
|
||||
|
||||
int offset = _inlineIndexCount * 4;
|
||||
|
||||
renderer.SetBufferData(GetInlineIndexBuffer(renderer, offset), offset, MemoryMarshal.Cast<uint, byte>(data));
|
||||
PushData(renderer, offset, i0);
|
||||
PushData(renderer, offset + 1, i1);
|
||||
|
||||
_inlineIndexCount += 2;
|
||||
}
|
||||
@@ -103,13 +101,61 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
uint i0 = (uint)argument;
|
||||
|
||||
Span<uint> data = stackalloc uint[1];
|
||||
int offset = _inlineIndexCount++;
|
||||
|
||||
data[0] = i0;
|
||||
PushData(renderer, offset, i0);
|
||||
}
|
||||
|
||||
int offset = _inlineIndexCount++ * 4;
|
||||
/// <summary>
|
||||
/// Pushes a 32-bit value to the index buffer.
|
||||
/// </summary>
|
||||
/// <param name="renderer">Host renderer</param>
|
||||
/// <param name="offset">Offset where the data should be written, in 32-bit words</param>
|
||||
/// <param name="value">Index value to be written</param>
|
||||
private void PushData(IRenderer renderer, int offset, uint value)
|
||||
{
|
||||
if (_buffer == null)
|
||||
{
|
||||
_buffer = new uint[BufferCapacity];
|
||||
}
|
||||
|
||||
renderer.SetBufferData(GetInlineIndexBuffer(renderer, offset), offset, MemoryMarshal.Cast<uint, byte>(data));
|
||||
// We upload data in chunks.
|
||||
// If we are at the start of a chunk, then the buffer might be full,
|
||||
// in that case we need to submit any existing data before overwriting the buffer.
|
||||
int subOffset = offset & (BufferCapacity - 1);
|
||||
|
||||
if (subOffset == 0 && offset != 0)
|
||||
{
|
||||
int baseOffset = (offset - BufferCapacity) * sizeof(uint);
|
||||
BufferHandle buffer = GetInlineIndexBuffer(renderer, baseOffset, BufferCapacity * sizeof(uint));
|
||||
renderer.SetBufferData(buffer, baseOffset, MemoryMarshal.Cast<uint, byte>(_buffer));
|
||||
}
|
||||
|
||||
_buffer[subOffset] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes sure that any pending data is submitted to the GPU before the index buffer is used.
|
||||
/// </summary>
|
||||
/// <param name="renderer">Host renderer</param>
|
||||
private void UpdateRemaining(IRenderer renderer)
|
||||
{
|
||||
int offset = _inlineIndexCount;
|
||||
if (offset == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int count = offset & (BufferCapacity - 1);
|
||||
if (count == 0)
|
||||
{
|
||||
count = BufferCapacity;
|
||||
}
|
||||
|
||||
int baseOffset = (offset - count) * sizeof(uint);
|
||||
int length = count * sizeof(uint);
|
||||
BufferHandle buffer = GetInlineIndexBuffer(renderer, baseOffset, length);
|
||||
renderer.SetBufferData(buffer, baseOffset, MemoryMarshal.Cast<uint, byte>(_buffer).Slice(0, length));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -117,12 +163,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
/// <param name="renderer">Host renderer</param>
|
||||
/// <param name="offset">Offset where the data will be written</param>
|
||||
/// <param name="length">Number of bytes that will be written</param>
|
||||
/// <returns>Buffer handle</returns>
|
||||
private BufferHandle GetInlineIndexBuffer(IRenderer renderer, int offset)
|
||||
private BufferHandle GetInlineIndexBuffer(IRenderer renderer, int offset, int length)
|
||||
{
|
||||
// Calculate a reasonable size for the buffer that can fit all the data,
|
||||
// and that also won't require frequent resizes if we need to push more data.
|
||||
int size = BitUtils.AlignUp(offset + 0x10, 0x200);
|
||||
int size = BitUtils.AlignUp(offset + length + 0x10, 0x200);
|
||||
|
||||
if (_inlineIndexBuffer == BufferHandle.Null)
|
||||
{
|
||||
|
@@ -1022,13 +1022,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// This method should be used to retrieve data that was modified by the host GPU.
|
||||
/// This is not cheap, avoid doing that unless strictly needed.
|
||||
/// </remarks>
|
||||
/// <param name="output">An output span to place the texture data into. If empty, one is generated</param>
|
||||
/// <param name="output">An output span to place the texture data into</param>
|
||||
/// <param name="blacklist">True if the texture should be blacklisted, false otherwise</param>
|
||||
/// <param name="texture">The specific host texture to flush. Defaults to this texture</param>
|
||||
/// <returns>The span containing the texture data</returns>
|
||||
private ReadOnlySpan<byte> GetTextureDataFromGpu(Span<byte> output, bool blacklist, ITexture texture = null)
|
||||
private void GetTextureDataFromGpu(Span<byte> output, bool blacklist, ITexture texture = null)
|
||||
{
|
||||
ReadOnlySpan<byte> data;
|
||||
PinnedSpan<byte> data;
|
||||
|
||||
if (texture != null)
|
||||
{
|
||||
@@ -1054,9 +1053,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
}
|
||||
|
||||
data = ConvertFromHostCompatibleFormat(output, data);
|
||||
ConvertFromHostCompatibleFormat(output, data.Get());
|
||||
|
||||
return data;
|
||||
data.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1071,10 +1070,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="level">The level of the texture to flush</param>
|
||||
/// <param name="blacklist">True if the texture should be blacklisted, false otherwise</param>
|
||||
/// <param name="texture">The specific host texture to flush. Defaults to this texture</param>
|
||||
/// <returns>The span containing the texture data</returns>
|
||||
public ReadOnlySpan<byte> GetTextureDataSliceFromGpu(Span<byte> output, int layer, int level, bool blacklist, ITexture texture = null)
|
||||
public void GetTextureDataSliceFromGpu(Span<byte> output, int layer, int level, bool blacklist, ITexture texture = null)
|
||||
{
|
||||
ReadOnlySpan<byte> data;
|
||||
PinnedSpan<byte> data;
|
||||
|
||||
if (texture != null)
|
||||
{
|
||||
@@ -1100,9 +1098,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
}
|
||||
|
||||
data = ConvertFromHostCompatibleFormat(output, data, level, true);
|
||||
ConvertFromHostCompatibleFormat(output, data.Get(), level, true);
|
||||
|
||||
return data;
|
||||
data.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1475,8 +1473,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
MultiRange otherRange = texture.Range;
|
||||
|
||||
IEnumerable<MultiRange> regions = _sizeInfo.AllRegions().Select((region) => Range.GetSlice((ulong)region.Offset, (ulong)region.Size));
|
||||
IEnumerable<MultiRange> otherRegions = texture._sizeInfo.AllRegions().Select((region) => otherRange.GetSlice((ulong)region.Offset, (ulong)region.Size));
|
||||
IEnumerable<MultiRange> regions = _sizeInfo.AllRegions().Select((region) => Range.Slice((ulong)region.Offset, (ulong)region.Size));
|
||||
IEnumerable<MultiRange> otherRegions = texture._sizeInfo.AllRegions().Select((region) => otherRange.Slice((ulong)region.Offset, (ulong)region.Size));
|
||||
|
||||
foreach (MultiRange region in regions)
|
||||
{
|
||||
|
@@ -397,7 +397,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
int endOffset = Math.Min(spanLast + _sliceSizes[endInfo.BaseLevel + endInfo.Levels - 1], (int)Storage.Size);
|
||||
int size = endOffset - spanBase;
|
||||
|
||||
dataSpan = _physicalMemory.GetSpan(Storage.Range.GetSlice((ulong)spanBase, (ulong)size));
|
||||
dataSpan = _physicalMemory.GetSpan(Storage.Range.Slice((ulong)spanBase, (ulong)size));
|
||||
}
|
||||
|
||||
// Only one of these will be greater than 1, as partial sync is only called when there are sub-image views.
|
||||
@@ -473,7 +473,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
int endOffset = Math.Min(offset + _sliceSizes[level], (int)Storage.Size);
|
||||
int size = endOffset - offset;
|
||||
|
||||
using WritableRegion region = _physicalMemory.GetWritableRegion(Storage.Range.GetSlice((ulong)offset, (ulong)size), tracked);
|
||||
using WritableRegion region = _physicalMemory.GetWritableRegion(Storage.Range.Slice((ulong)offset, (ulong)size), tracked);
|
||||
|
||||
Storage.GetTextureDataSliceFromGpu(region.Memory.Span, layer, level, tracked, texture);
|
||||
}
|
||||
@@ -1419,13 +1419,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
for (int i = 0; i < _allOffsets.Length; i++)
|
||||
{
|
||||
(int layer, int level) = GetLayerLevelForView(i);
|
||||
MultiRange handleRange = Storage.Range.GetSlice((ulong)_allOffsets[i], 1);
|
||||
MultiRange handleRange = Storage.Range.Slice((ulong)_allOffsets[i], 1);
|
||||
ulong handleBase = handleRange.GetSubRange(0).Address;
|
||||
|
||||
for (int j = 0; j < other._handles.Length; j++)
|
||||
{
|
||||
(int otherLayer, int otherLevel) = other.GetLayerLevelForView(j);
|
||||
MultiRange otherHandleRange = other.Storage.Range.GetSlice((ulong)other._allOffsets[j], 1);
|
||||
MultiRange otherHandleRange = other.Storage.Range.Slice((ulong)other._allOffsets[j], 1);
|
||||
ulong otherHandleBase = otherHandleRange.GetSubRange(0).Address;
|
||||
|
||||
if (handleBase == otherHandleBase)
|
||||
@@ -1502,7 +1502,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
// Handles list is not modified by another thread, only replaced, so this is thread safe.
|
||||
// Remove modified flags from all overlapping handles, so that the textures don't flush to unmapped/remapped GPU memory.
|
||||
|
||||
MultiRange subRange = Storage.Range.GetSlice((ulong)handle.Offset, (ulong)handle.Size);
|
||||
MultiRange subRange = Storage.Range.Slice((ulong)handle.Offset, (ulong)handle.Size);
|
||||
|
||||
if (range.OverlapsWith(subRange))
|
||||
{
|
||||
|
@@ -130,6 +130,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return ref descriptor;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
texture.SynchronizeMemory();
|
||||
}
|
||||
|
||||
Items[id] = texture;
|
||||
|
||||
@@ -233,7 +237,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queues a request to update a texture's mapping.
|
||||
/// Queues a request to update a texture's mapping.
|
||||
/// Mapping is updated later to avoid deleting the texture if it is still sparsely mapped.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture with potential mapping change</param>
|
||||
|
@@ -82,7 +82,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
Address = address;
|
||||
Size = size;
|
||||
|
||||
Handle = context.Renderer.CreateBuffer((int)size);
|
||||
Handle = context.Renderer.CreateBuffer((int)size, baseBuffers?.MaxBy(x => x.Size).Handle ?? BufferHandle.Null);
|
||||
|
||||
_useGranular = size > GranularBufferThreshold;
|
||||
|
||||
@@ -415,10 +415,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
int offset = (int)(address - Address);
|
||||
|
||||
ReadOnlySpan<byte> data = _context.Renderer.GetBufferData(Handle, offset, (int)size);
|
||||
using PinnedSpan<byte> data = _context.Renderer.GetBufferData(Handle, offset, (int)size);
|
||||
|
||||
// TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers.
|
||||
_physicalMemory.WriteUntracked(address, data);
|
||||
_physicalMemory.WriteUntracked(address, data.Get());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -1,3 +1,5 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
@@ -11,16 +13,15 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
{
|
||||
public static byte[] Pack(ShaderSource[] sources)
|
||||
{
|
||||
using MemoryStream output = new MemoryStream();
|
||||
using BinaryWriter writer = new BinaryWriter(output);
|
||||
using MemoryStream output = MemoryStreamManager.Shared.GetStream();
|
||||
|
||||
writer.Write(sources.Length);
|
||||
output.Write(sources.Length);
|
||||
|
||||
for (int i = 0; i < sources.Length; i++)
|
||||
foreach (ShaderSource source in sources)
|
||||
{
|
||||
writer.Write((int)sources[i].Stage);
|
||||
writer.Write(sources[i].BinaryCode.Length);
|
||||
writer.Write(sources[i].BinaryCode);
|
||||
output.Write((int)source.Stage);
|
||||
output.Write(source.BinaryCode.Length);
|
||||
output.Write(source.BinaryCode);
|
||||
}
|
||||
|
||||
return output.ToArray();
|
||||
|
@@ -55,11 +55,14 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
(IntPtr)size);
|
||||
}
|
||||
|
||||
public static unsafe ReadOnlySpan<byte> GetData(OpenGLRenderer renderer, BufferHandle buffer, int offset, int size)
|
||||
public static unsafe PinnedSpan<byte> GetData(OpenGLRenderer renderer, BufferHandle buffer, int offset, int size)
|
||||
{
|
||||
// Data in the persistent buffer and host array is guaranteed to be available
|
||||
// until the next time the host thread requests data.
|
||||
|
||||
if (HwCapabilities.UsePersistentBufferForFlush)
|
||||
{
|
||||
return renderer.PersistentBuffers.Default.GetBufferData(buffer, offset, size);
|
||||
return PinnedSpan<byte>.UnsafeFromSpan(renderer.PersistentBuffers.Default.GetBufferData(buffer, offset, size));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -69,7 +72,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
|
||||
GL.GetBufferSubData(BufferTarget.CopyReadBuffer, (IntPtr)offset, size, target);
|
||||
|
||||
return new ReadOnlySpan<byte>(target.ToPointer(), size);
|
||||
return new PinnedSpan<byte>(target.ToPointer(), size);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -39,12 +39,12 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetData()
|
||||
public PinnedSpan<byte> GetData()
|
||||
{
|
||||
return Buffer.GetData(_renderer, _buffer, _bufferOffset, _bufferSize);
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetData(int layer, int level)
|
||||
public PinnedSpan<byte> GetData(int layer, int level)
|
||||
{
|
||||
return GetData();
|
||||
}
|
||||
|
@@ -166,7 +166,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
_renderer.TextureCopy.Copy(this, (TextureView)destination, srcRegion, dstRegion, linearFilter);
|
||||
}
|
||||
|
||||
public unsafe ReadOnlySpan<byte> GetData()
|
||||
public unsafe PinnedSpan<byte> GetData()
|
||||
{
|
||||
int size = 0;
|
||||
int levels = Info.GetLevelsClamped();
|
||||
@@ -196,16 +196,16 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
data = FormatConverter.ConvertD24S8ToS8D24(data);
|
||||
}
|
||||
|
||||
return data;
|
||||
return PinnedSpan<byte>.UnsafeFromSpan(data);
|
||||
}
|
||||
|
||||
public unsafe ReadOnlySpan<byte> GetData(int layer, int level)
|
||||
public unsafe PinnedSpan<byte> GetData(int layer, int level)
|
||||
{
|
||||
int size = Info.GetMipSize(level);
|
||||
|
||||
if (HwCapabilities.UsePersistentBufferForFlush)
|
||||
{
|
||||
return _renderer.PersistentBuffers.Default.GetTextureData(this, size, layer, level);
|
||||
return PinnedSpan<byte>.UnsafeFromSpan(_renderer.PersistentBuffers.Default.GetTextureData(this, size, layer, level));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -213,7 +213,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
|
||||
int offset = WriteTo2D(target, layer, level);
|
||||
|
||||
return new ReadOnlySpan<byte>(target.ToPointer(), size).Slice(offset);
|
||||
return new PinnedSpan<byte>((byte*)target.ToPointer() + offset, size);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -57,7 +57,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
ResourcePool = new ResourcePool();
|
||||
}
|
||||
|
||||
public BufferHandle CreateBuffer(int size)
|
||||
public BufferHandle CreateBuffer(int size, BufferHandle storageHint)
|
||||
{
|
||||
BufferCount++;
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
return new HardwareInfo(GpuVendor, GpuRenderer);
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
|
||||
public PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
|
||||
{
|
||||
return Buffer.GetData(this, buffer, offset, size);
|
||||
}
|
||||
|
@@ -74,7 +74,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
||||
{
|
||||
result = Marshal.ReadInt64(_bufferMap);
|
||||
|
||||
return WaitingForValue(result);
|
||||
return !WaitingForValue(result);
|
||||
}
|
||||
|
||||
public long AwaitResult(AutoResetEvent wakeSignal = null)
|
||||
|
12
Ryujinx.Graphics.Vulkan/BufferAllocationType.cs
Normal file
12
Ryujinx.Graphics.Vulkan/BufferAllocationType.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
internal enum BufferAllocationType
|
||||
{
|
||||
Auto = 0,
|
||||
|
||||
HostMappedNoCache,
|
||||
HostMapped,
|
||||
DeviceLocal,
|
||||
DeviceLocalMapped
|
||||
}
|
||||
}
|
@@ -1,7 +1,10 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using VkBuffer = Silk.NET.Vulkan.Buffer;
|
||||
using VkFormat = Silk.NET.Vulkan.Format;
|
||||
|
||||
@@ -11,6 +14,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
private const int MaxUpdateBufferSize = 0x10000;
|
||||
|
||||
private const int SetCountThreshold = 100;
|
||||
private const int WriteCountThreshold = 50;
|
||||
private const int FlushCountThreshold = 5;
|
||||
|
||||
public const int DeviceLocalSizeThreshold = 256 * 1024; // 256kb
|
||||
|
||||
public const AccessFlags DefaultAccessFlags =
|
||||
AccessFlags.IndirectCommandReadBit |
|
||||
AccessFlags.ShaderReadBit |
|
||||
@@ -21,10 +30,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private readonly VulkanRenderer _gd;
|
||||
private readonly Device _device;
|
||||
private readonly MemoryAllocation _allocation;
|
||||
private readonly Auto<DisposableBuffer> _buffer;
|
||||
private readonly Auto<MemoryAllocation> _allocationAuto;
|
||||
private readonly ulong _bufferHandle;
|
||||
private MemoryAllocation _allocation;
|
||||
private Auto<DisposableBuffer> _buffer;
|
||||
private Auto<MemoryAllocation> _allocationAuto;
|
||||
private ulong _bufferHandle;
|
||||
|
||||
private CacheByRange<BufferHolder> _cachedConvertedBuffers;
|
||||
|
||||
@@ -32,11 +41,28 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private IntPtr _map;
|
||||
|
||||
private readonly MultiFenceHolder _waitable;
|
||||
private MultiFenceHolder _waitable;
|
||||
|
||||
private bool _lastAccessIsWrite;
|
||||
|
||||
public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, MemoryAllocation allocation, int size)
|
||||
private BufferAllocationType _baseType;
|
||||
private BufferAllocationType _currentType;
|
||||
private bool _swapQueued;
|
||||
|
||||
public BufferAllocationType DesiredType { get; private set; }
|
||||
|
||||
private int _setCount;
|
||||
private int _writeCount;
|
||||
private int _flushCount;
|
||||
private int _flushTemp;
|
||||
|
||||
private ReaderWriterLock _flushLock;
|
||||
private FenceHolder _flushFence;
|
||||
private int _flushWaiting;
|
||||
|
||||
private List<Action> _swapActions;
|
||||
|
||||
public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, MemoryAllocation allocation, int size, BufferAllocationType type, BufferAllocationType currentType)
|
||||
{
|
||||
_gd = gd;
|
||||
_device = device;
|
||||
@@ -47,9 +73,153 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_bufferHandle = buffer.Handle;
|
||||
Size = size;
|
||||
_map = allocation.HostPointer;
|
||||
|
||||
_baseType = type;
|
||||
_currentType = currentType;
|
||||
DesiredType = currentType;
|
||||
|
||||
_flushLock = new ReaderWriterLock();
|
||||
}
|
||||
|
||||
public unsafe Auto<DisposableBufferView> CreateView(VkFormat format, int offset, int size)
|
||||
public bool TryBackingSwap(ref CommandBufferScoped? cbs)
|
||||
{
|
||||
if (_swapQueued && DesiredType != _currentType)
|
||||
{
|
||||
// Only swap if the buffer is not used in any queued command buffer.
|
||||
bool isRented = _buffer.HasRentedCommandBufferDependency(_gd.CommandBufferPool);
|
||||
|
||||
if (!isRented && _gd.CommandBufferPool.OwnedByCurrentThread && !_flushLock.IsReaderLockHeld)
|
||||
{
|
||||
var currentAllocation = _allocationAuto;
|
||||
var currentBuffer = _buffer;
|
||||
IntPtr currentMap = _map;
|
||||
|
||||
(VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) = _gd.BufferManager.CreateBacking(_gd, Size, DesiredType, false, _currentType);
|
||||
|
||||
if (buffer.Handle != 0)
|
||||
{
|
||||
_flushLock.AcquireWriterLock(Timeout.Infinite);
|
||||
|
||||
ClearFlushFence();
|
||||
|
||||
_waitable = new MultiFenceHolder(Size);
|
||||
|
||||
_allocation = allocation;
|
||||
_allocationAuto = new Auto<MemoryAllocation>(allocation);
|
||||
_buffer = new Auto<DisposableBuffer>(new DisposableBuffer(_gd.Api, _device, buffer), _waitable, _allocationAuto);
|
||||
_bufferHandle = buffer.Handle;
|
||||
_map = allocation.HostPointer;
|
||||
|
||||
if (_map != IntPtr.Zero && currentMap != IntPtr.Zero)
|
||||
{
|
||||
// Copy data directly. Readbacks don't have to wait if this is done.
|
||||
|
||||
unsafe
|
||||
{
|
||||
new Span<byte>((void*)currentMap, Size).CopyTo(new Span<byte>((void*)_map, Size));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cbs == null)
|
||||
{
|
||||
cbs = _gd.CommandBufferPool.Rent();
|
||||
}
|
||||
|
||||
CommandBufferScoped cbsV = cbs.Value;
|
||||
|
||||
Copy(_gd, cbsV, currentBuffer, _buffer, 0, 0, Size);
|
||||
|
||||
// Need to wait for the data to reach the new buffer before data can be flushed.
|
||||
|
||||
_flushFence = _gd.CommandBufferPool.GetFence(cbsV.CommandBufferIndex);
|
||||
_flushFence.Get();
|
||||
}
|
||||
|
||||
Logger.Debug?.PrintMsg(LogClass.Gpu, $"Converted {Size} buffer {_currentType} to {resultType}");
|
||||
|
||||
_currentType = resultType;
|
||||
|
||||
if (_swapActions != null)
|
||||
{
|
||||
foreach (var action in _swapActions)
|
||||
{
|
||||
action();
|
||||
}
|
||||
|
||||
_swapActions.Clear();
|
||||
}
|
||||
|
||||
currentBuffer.Dispose();
|
||||
currentAllocation.Dispose();
|
||||
|
||||
_gd.PipelineInternal.SwapBuffer(currentBuffer, _buffer);
|
||||
|
||||
_flushLock.ReleaseWriterLock();
|
||||
}
|
||||
|
||||
_swapQueued = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_swapQueued = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void ConsiderBackingSwap()
|
||||
{
|
||||
if (_baseType == BufferAllocationType.Auto)
|
||||
{
|
||||
if (_writeCount >= WriteCountThreshold || _setCount >= SetCountThreshold || _flushCount >= FlushCountThreshold)
|
||||
{
|
||||
if (_flushCount > 0 || _flushTemp-- > 0)
|
||||
{
|
||||
// Buffers that flush should ideally be mapped in host address space for easy copies.
|
||||
// If the buffer is large it will do better on GPU memory, as there will be more writes than data flushes (typically individual pages).
|
||||
// If it is small, then it's likely most of the buffer will be flushed so we want it on host memory, as access is cached.
|
||||
DesiredType = Size > DeviceLocalSizeThreshold ? BufferAllocationType.DeviceLocalMapped : BufferAllocationType.HostMapped;
|
||||
|
||||
// It's harder for a buffer that is flushed to revert to another type of mapping.
|
||||
if (_flushCount > 0)
|
||||
{
|
||||
_flushTemp = 1000;
|
||||
}
|
||||
}
|
||||
else if (_writeCount >= WriteCountThreshold)
|
||||
{
|
||||
// Buffers that are written often should ideally be in the device local heap. (Storage buffers)
|
||||
DesiredType = BufferAllocationType.DeviceLocal;
|
||||
}
|
||||
else if (_setCount > SetCountThreshold)
|
||||
{
|
||||
// Buffers that have their data set often should ideally be host mapped. (Constant buffers)
|
||||
DesiredType = BufferAllocationType.HostMapped;
|
||||
}
|
||||
|
||||
_flushCount = 0;
|
||||
_writeCount = 0;
|
||||
_setCount = 0;
|
||||
}
|
||||
|
||||
if (!_swapQueued && DesiredType != _currentType)
|
||||
{
|
||||
_swapQueued = true;
|
||||
|
||||
_gd.PipelineInternal.AddBackingSwap(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe Auto<DisposableBufferView> CreateView(VkFormat format, int offset, int size, Action invalidateView)
|
||||
{
|
||||
var bufferViewCreateInfo = new BufferViewCreateInfo()
|
||||
{
|
||||
@@ -62,9 +232,19 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_gd.Api.CreateBufferView(_device, bufferViewCreateInfo, null, out var bufferView).ThrowOnError();
|
||||
|
||||
(_swapActions ??= new List<Action>()).Add(invalidateView);
|
||||
|
||||
return new Auto<DisposableBufferView>(new DisposableBufferView(_gd.Api, _device, bufferView), _waitable, _buffer);
|
||||
}
|
||||
|
||||
public void InheritMetrics(BufferHolder other)
|
||||
{
|
||||
_setCount = other._setCount;
|
||||
_writeCount = other._writeCount;
|
||||
_flushCount = other._flushCount;
|
||||
_flushTemp = other._flushTemp;
|
||||
}
|
||||
|
||||
public unsafe void InsertBarrier(CommandBuffer commandBuffer, bool isWrite)
|
||||
{
|
||||
// If the last access is write, we always need a barrier to be sure we will read or modify
|
||||
@@ -104,12 +284,22 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, bool isWrite = false)
|
||||
public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, bool isWrite = false, bool isSSBO = false)
|
||||
{
|
||||
if (isWrite)
|
||||
{
|
||||
_writeCount++;
|
||||
|
||||
SignalWrite(0, Size);
|
||||
}
|
||||
else if (isSSBO)
|
||||
{
|
||||
// Always consider SSBO access for swapping to device local memory.
|
||||
|
||||
_writeCount++;
|
||||
|
||||
ConsiderBackingSwap();
|
||||
}
|
||||
|
||||
return _buffer;
|
||||
}
|
||||
@@ -118,6 +308,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
if (isWrite)
|
||||
{
|
||||
_writeCount++;
|
||||
|
||||
SignalWrite(offset, size);
|
||||
}
|
||||
|
||||
@@ -126,6 +318,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void SignalWrite(int offset, int size)
|
||||
{
|
||||
ConsiderBackingSwap();
|
||||
|
||||
if (offset == 0 && size == Size)
|
||||
{
|
||||
_cachedConvertedBuffers.Clear();
|
||||
@@ -147,11 +341,76 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return _map;
|
||||
}
|
||||
|
||||
public unsafe ReadOnlySpan<byte> GetData(int offset, int size)
|
||||
private void ClearFlushFence()
|
||||
{
|
||||
// Asusmes _flushLock is held as writer.
|
||||
|
||||
if (_flushFence != null)
|
||||
{
|
||||
if (_flushWaiting == 0)
|
||||
{
|
||||
_flushFence.Put();
|
||||
}
|
||||
|
||||
_flushFence = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void WaitForFlushFence()
|
||||
{
|
||||
// Assumes the _flushLock is held as reader, returns in same state.
|
||||
|
||||
if (_flushFence != null)
|
||||
{
|
||||
// If storage has changed, make sure the fence has been reached so that the data is in place.
|
||||
|
||||
var cookie = _flushLock.UpgradeToWriterLock(Timeout.Infinite);
|
||||
|
||||
if (_flushFence != null)
|
||||
{
|
||||
var fence = _flushFence;
|
||||
Interlocked.Increment(ref _flushWaiting);
|
||||
|
||||
// Don't wait in the lock.
|
||||
|
||||
var restoreCookie = _flushLock.ReleaseLock();
|
||||
|
||||
fence.Wait();
|
||||
|
||||
_flushLock.RestoreLock(ref restoreCookie);
|
||||
|
||||
if (Interlocked.Decrement(ref _flushWaiting) == 0)
|
||||
{
|
||||
fence.Put();
|
||||
}
|
||||
|
||||
_flushFence = null;
|
||||
}
|
||||
|
||||
_flushLock.DowngradeFromWriterLock(ref cookie);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe PinnedSpan<byte> GetData(int offset, int size)
|
||||
{
|
||||
_flushLock.AcquireReaderLock(Timeout.Infinite);
|
||||
|
||||
WaitForFlushFence();
|
||||
|
||||
_flushCount++;
|
||||
|
||||
Span<byte> result;
|
||||
|
||||
if (_map != IntPtr.Zero)
|
||||
{
|
||||
return GetDataStorage(offset, size);
|
||||
result = GetDataStorage(offset, size);
|
||||
|
||||
// Need to be careful here, the buffer can't be unmapped while the data is being used.
|
||||
_buffer.IncrementReferenceCount();
|
||||
|
||||
_flushLock.ReleaseReaderLock();
|
||||
|
||||
return PinnedSpan<byte>.UnsafeFromSpan(result, _buffer.DecrementReferenceCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -161,12 +420,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
_gd.FlushAllCommands();
|
||||
|
||||
return resource.GetFlushBuffer().GetBufferData(_gd.CommandBufferPool, this, offset, size);
|
||||
result = resource.GetFlushBuffer().GetBufferData(_gd.CommandBufferPool, this, offset, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
return resource.GetFlushBuffer().GetBufferData(resource.GetPool(), this, offset, size);
|
||||
result = resource.GetFlushBuffer().GetBufferData(resource.GetPool(), this, offset, size);
|
||||
}
|
||||
|
||||
_flushLock.ReleaseReaderLock();
|
||||
|
||||
// Flush buffer is pinned until the next GetBufferData on the thread, which is fine for current uses.
|
||||
return PinnedSpan<byte>.UnsafeFromSpan(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +454,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return;
|
||||
}
|
||||
|
||||
_setCount++;
|
||||
|
||||
if (_map != IntPtr.Zero)
|
||||
{
|
||||
// If persistently mapped, set the data directly if the buffer is not currently in use.
|
||||
@@ -268,6 +534,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
var dstBuffer = GetBuffer(cbs.CommandBuffer, dstOffset, data.Length, true).Get(cbs, dstOffset, data.Length).Value;
|
||||
|
||||
_writeCount--;
|
||||
|
||||
InsertBufferBarrier(
|
||||
_gd,
|
||||
cbs.CommandBuffer,
|
||||
@@ -502,11 +770,19 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_swapQueued = false;
|
||||
|
||||
_gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size);
|
||||
|
||||
_buffer.Dispose();
|
||||
_allocationAuto.Dispose();
|
||||
_cachedConvertedBuffers.Dispose();
|
||||
|
||||
_flushLock.AcquireWriterLock(Timeout.Infinite);
|
||||
|
||||
ClearFlushFence();
|
||||
|
||||
_flushLock.ReleaseWriterLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using VkFormat = Silk.NET.Vulkan.Format;
|
||||
using VkBuffer = Silk.NET.Vulkan.Buffer;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
@@ -16,17 +17,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
// Some drivers don't expose a "HostCached" memory type,
|
||||
// so we need those alternative flags for the allocation to succeed there.
|
||||
private const MemoryPropertyFlags DefaultBufferMemoryAltFlags =
|
||||
private const MemoryPropertyFlags DefaultBufferMemoryNoCacheFlags =
|
||||
MemoryPropertyFlags.HostVisibleBit |
|
||||
MemoryPropertyFlags.HostCoherentBit;
|
||||
|
||||
private const MemoryPropertyFlags DeviceLocalBufferMemoryFlags =
|
||||
MemoryPropertyFlags.DeviceLocalBit;
|
||||
|
||||
private const MemoryPropertyFlags FlushableDeviceLocalBufferMemoryFlags =
|
||||
private const MemoryPropertyFlags DeviceLocalMappedBufferMemoryFlags =
|
||||
MemoryPropertyFlags.DeviceLocalBit |
|
||||
MemoryPropertyFlags.HostVisibleBit |
|
||||
MemoryPropertyFlags.HostCoherentBit |
|
||||
MemoryPropertyFlags.DeviceLocalBit;
|
||||
MemoryPropertyFlags.HostCoherentBit;
|
||||
|
||||
private const BufferUsageFlags DefaultBufferUsageFlags =
|
||||
BufferUsageFlags.TransferSrcBit |
|
||||
@@ -54,14 +55,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
StagingBuffer = new StagingBuffer(gd, this);
|
||||
}
|
||||
|
||||
public BufferHandle CreateWithHandle(VulkanRenderer gd, int size, bool deviceLocal)
|
||||
public BufferHandle CreateWithHandle(VulkanRenderer gd, int size, BufferAllocationType baseType = BufferAllocationType.HostMapped, BufferHandle storageHint = default)
|
||||
{
|
||||
return CreateWithHandle(gd, size, deviceLocal, out _);
|
||||
return CreateWithHandle(gd, size, out _, baseType, storageHint);
|
||||
}
|
||||
|
||||
public BufferHandle CreateWithHandle(VulkanRenderer gd, int size, bool deviceLocal, out BufferHolder holder)
|
||||
public BufferHandle CreateWithHandle(VulkanRenderer gd, int size, out BufferHolder holder, BufferAllocationType baseType = BufferAllocationType.HostMapped, BufferHandle storageHint = default)
|
||||
{
|
||||
holder = Create(gd, size, deviceLocal: deviceLocal);
|
||||
holder = Create(gd, size, baseType: baseType, storageHint: storageHint);
|
||||
if (holder == null)
|
||||
{
|
||||
return BufferHandle.Null;
|
||||
@@ -74,7 +75,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return Unsafe.As<ulong, BufferHandle>(ref handle64);
|
||||
}
|
||||
|
||||
public unsafe BufferHolder Create(VulkanRenderer gd, int size, bool forConditionalRendering = false, bool deviceLocal = false)
|
||||
public unsafe (VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) CreateBacking(
|
||||
VulkanRenderer gd,
|
||||
int size,
|
||||
BufferAllocationType type,
|
||||
bool forConditionalRendering = false,
|
||||
BufferAllocationType fallbackType = BufferAllocationType.Auto)
|
||||
{
|
||||
var usage = DefaultBufferUsageFlags;
|
||||
|
||||
@@ -98,48 +104,106 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError();
|
||||
gd.Api.GetBufferMemoryRequirements(_device, buffer, out var requirements);
|
||||
|
||||
MemoryPropertyFlags allocateFlags;
|
||||
MemoryPropertyFlags allocateFlagsAlt;
|
||||
MemoryAllocation allocation;
|
||||
|
||||
if (deviceLocal)
|
||||
do
|
||||
{
|
||||
allocateFlags = DeviceLocalBufferMemoryFlags;
|
||||
allocateFlagsAlt = DeviceLocalBufferMemoryFlags;
|
||||
}
|
||||
else
|
||||
{
|
||||
allocateFlags = DefaultBufferMemoryFlags;
|
||||
allocateFlagsAlt = DefaultBufferMemoryAltFlags;
|
||||
}
|
||||
var allocateFlags = type switch
|
||||
{
|
||||
BufferAllocationType.HostMappedNoCache => DefaultBufferMemoryNoCacheFlags,
|
||||
BufferAllocationType.HostMapped => DefaultBufferMemoryFlags,
|
||||
BufferAllocationType.DeviceLocal => DeviceLocalBufferMemoryFlags,
|
||||
BufferAllocationType.DeviceLocalMapped => DeviceLocalMappedBufferMemoryFlags,
|
||||
_ => DefaultBufferMemoryFlags
|
||||
};
|
||||
|
||||
var allocation = gd.MemoryAllocator.AllocateDeviceMemory(requirements, allocateFlags, allocateFlagsAlt);
|
||||
// If an allocation with this memory type fails, fall back to the previous one.
|
||||
try
|
||||
{
|
||||
allocation = gd.MemoryAllocator.AllocateDeviceMemory(requirements, allocateFlags, true);
|
||||
}
|
||||
catch (VulkanException)
|
||||
{
|
||||
allocation = default;
|
||||
}
|
||||
}
|
||||
while (allocation.Memory.Handle == 0 && (--type != fallbackType));
|
||||
|
||||
if (allocation.Memory.Handle == 0UL)
|
||||
{
|
||||
gd.Api.DestroyBuffer(_device, buffer, null);
|
||||
return null;
|
||||
return default;
|
||||
}
|
||||
|
||||
gd.Api.BindBufferMemory(_device, buffer, allocation.Memory, allocation.Offset);
|
||||
|
||||
return new BufferHolder(gd, _device, buffer, allocation, size);
|
||||
return (buffer, allocation, type);
|
||||
}
|
||||
|
||||
public Auto<DisposableBufferView> CreateView(BufferHandle handle, VkFormat format, int offset, int size)
|
||||
public unsafe BufferHolder Create(
|
||||
VulkanRenderer gd,
|
||||
int size,
|
||||
bool forConditionalRendering = false,
|
||||
BufferAllocationType baseType = BufferAllocationType.HostMapped,
|
||||
BufferHandle storageHint = default)
|
||||
{
|
||||
if (TryGetBuffer(handle, out var holder))
|
||||
BufferAllocationType type = baseType;
|
||||
BufferHolder storageHintHolder = null;
|
||||
|
||||
if (baseType == BufferAllocationType.Auto)
|
||||
{
|
||||
return holder.CreateView(format, offset, size);
|
||||
if (gd.IsSharedMemory)
|
||||
{
|
||||
baseType = BufferAllocationType.HostMapped;
|
||||
type = baseType;
|
||||
}
|
||||
else
|
||||
{
|
||||
type = size >= BufferHolder.DeviceLocalSizeThreshold ? BufferAllocationType.DeviceLocal : BufferAllocationType.HostMapped;
|
||||
}
|
||||
|
||||
if (storageHint != BufferHandle.Null)
|
||||
{
|
||||
if (TryGetBuffer(storageHint, out storageHintHolder))
|
||||
{
|
||||
type = storageHintHolder.DesiredType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) =
|
||||
CreateBacking(gd, size, type, forConditionalRendering);
|
||||
|
||||
if (buffer.Handle != 0)
|
||||
{
|
||||
var holder = new BufferHolder(gd, _device, buffer, allocation, size, baseType, resultType);
|
||||
|
||||
if (storageHintHolder != null)
|
||||
{
|
||||
holder.InheritMetrics(storageHintHolder);
|
||||
}
|
||||
|
||||
return holder;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, BufferHandle handle, bool isWrite)
|
||||
public Auto<DisposableBufferView> CreateView(BufferHandle handle, VkFormat format, int offset, int size, Action invalidateView)
|
||||
{
|
||||
if (TryGetBuffer(handle, out var holder))
|
||||
{
|
||||
return holder.GetBuffer(commandBuffer, isWrite);
|
||||
return holder.CreateView(format, offset, size, invalidateView);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, BufferHandle handle, bool isWrite, bool isSSBO = false)
|
||||
{
|
||||
if (TryGetBuffer(handle, out var holder))
|
||||
{
|
||||
return holder.GetBuffer(commandBuffer, isWrite, isSSBO);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -332,14 +396,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return null;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetData(BufferHandle handle, int offset, int size)
|
||||
public PinnedSpan<byte> GetData(BufferHandle handle, int offset, int size)
|
||||
{
|
||||
if (TryGetBuffer(handle, out var holder))
|
||||
{
|
||||
return holder.GetData(offset, size);
|
||||
}
|
||||
|
||||
return ReadOnlySpan<byte>.Empty;
|
||||
return new PinnedSpan<byte>();
|
||||
}
|
||||
|
||||
public void SetData<T>(BufferHandle handle, int offset, ReadOnlySpan<T> data) where T : unmanaged
|
||||
|
@@ -2,14 +2,14 @@
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
readonly struct BufferState : IDisposable
|
||||
struct BufferState : IDisposable
|
||||
{
|
||||
public static BufferState Null => new BufferState(null, 0, 0);
|
||||
|
||||
private readonly int _offset;
|
||||
private readonly int _size;
|
||||
|
||||
private readonly Auto<DisposableBuffer> _buffer;
|
||||
private Auto<DisposableBuffer> _buffer;
|
||||
|
||||
public BufferState(Auto<DisposableBuffer> buffer, int offset, int size)
|
||||
{
|
||||
@@ -29,6 +29,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
public void Swap(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||
{
|
||||
if (_buffer == from)
|
||||
{
|
||||
_buffer.DecrementReferenceCount();
|
||||
to.IncrementReferenceCount();
|
||||
|
||||
_buffer = to;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_buffer?.DecrementReferenceCount();
|
||||
|
@@ -94,7 +94,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
else
|
||||
{
|
||||
// If null descriptors are not supported, we need to pass the handle of a dummy buffer on unused bindings.
|
||||
_dummyBuffer = gd.BufferManager.Create(gd, 0x10000, forConditionalRendering: false, deviceLocal: true);
|
||||
_dummyBuffer = gd.BufferManager.Create(gd, 0x10000, forConditionalRendering: false, baseType: BufferAllocationType.DeviceLocal);
|
||||
}
|
||||
|
||||
_dummyTexture = gd.CreateTextureView(new TextureCreateInfo(
|
||||
@@ -178,7 +178,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var buffer = assignment.Range;
|
||||
int index = assignment.Binding;
|
||||
|
||||
Auto<DisposableBuffer> vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false);
|
||||
Auto<DisposableBuffer> vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false, isSSBO: true);
|
||||
ref Auto<DisposableBuffer> currentVkBuffer = ref _storageBufferRefs[index];
|
||||
|
||||
DescriptorBufferInfo info = new DescriptorBufferInfo()
|
||||
@@ -236,7 +236,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
else if (texture is TextureView view)
|
||||
{
|
||||
view.Storage.InsertBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
|
||||
view.Storage.InsertWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
|
||||
|
||||
_textureRefs[binding] = view.GetImageView();
|
||||
_samplerRefs[binding] = ((SamplerHolder)sampler)?.GetSampler();
|
||||
@@ -640,6 +640,23 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Array.Clear(_storageSet);
|
||||
}
|
||||
|
||||
private void SwapBuffer(Auto<DisposableBuffer>[] list, Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||
{
|
||||
for (int i = 0; i < list.Length; i++)
|
||||
{
|
||||
if (list[i] == from)
|
||||
{
|
||||
list[i] = to;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SwapBuffer(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||
{
|
||||
SwapBuffer(_uniformBufferRefs, from, to);
|
||||
SwapBuffer(_storageBufferRefs, from, to);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
|
@@ -156,11 +156,11 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
};
|
||||
|
||||
int rangeSize = dimensionsBuffer.Length * sizeof(float);
|
||||
var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize, false);
|
||||
var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize);
|
||||
_renderer.BufferManager.SetData(bufferHandle, 0, dimensionsBuffer);
|
||||
|
||||
ReadOnlySpan<float> sharpeningBuffer = stackalloc float[] { 1.5f - (Level * 0.01f * 1.5f)};
|
||||
var sharpeningBufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, sizeof(float), false);
|
||||
var sharpeningBufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, sizeof(float));
|
||||
_renderer.BufferManager.SetData(sharpeningBufferHandle, 0, sharpeningBuffer);
|
||||
|
||||
int threadGroupWorkRegionDim = 16;
|
||||
|
@@ -87,7 +87,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
|
||||
ReadOnlySpan<float> resolutionBuffer = stackalloc float[] { view.Width, view.Height };
|
||||
int rangeSize = resolutionBuffer.Length * sizeof(float);
|
||||
var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize, false);
|
||||
var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize);
|
||||
|
||||
_renderer.BufferManager.SetData(bufferHandle, 0, resolutionBuffer);
|
||||
|
||||
|
@@ -266,7 +266,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
|
||||
ReadOnlySpan<float> resolutionBuffer = stackalloc float[] { view.Width, view.Height };
|
||||
int rangeSize = resolutionBuffer.Length * sizeof(float);
|
||||
var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize, false);
|
||||
var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize);
|
||||
|
||||
_renderer.BufferManager.SetData(bufferHandle, 0, resolutionBuffer);
|
||||
var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize);
|
||||
|
@@ -322,7 +322,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
GAL.Format.S8Uint => ImageAspectFlags.StencilBit,
|
||||
GAL.Format.D24UnormS8Uint or
|
||||
GAL.Format.D32FloatS8Uint or
|
||||
GAL.Format.S8UintD24Unorm => ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit,
|
||||
GAL.Format.S8UintD24Unorm => ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit,
|
||||
_ => ImageAspectFlags.ColorBit
|
||||
};
|
||||
}
|
||||
|
@@ -218,5 +218,23 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
AccessFlags.DepthStencilAttachmentWriteBit,
|
||||
PipelineStageFlags.ColorAttachmentOutputBit);
|
||||
}
|
||||
|
||||
public void InsertClearBarrier(CommandBufferScoped cbs, int index)
|
||||
{
|
||||
if (_colors != null)
|
||||
{
|
||||
int realIndex = Array.IndexOf(AttachmentIndices, index);
|
||||
|
||||
if (realIndex != -1)
|
||||
{
|
||||
_colors[realIndex].Storage?.InsertReadToWriteBarrier(cbs, AccessFlags.ColorAttachmentWriteBit, PipelineStageFlags.ColorAttachmentOutputBit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void InsertClearBarrierDS(CommandBufferScoped cbs)
|
||||
{
|
||||
_depthStencil?.Storage?.InsertReadToWriteBarrier(cbs, AccessFlags.DepthStencilAttachmentWriteBit, PipelineStageFlags.EarlyFragmentTestsBit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -394,7 +394,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
(region[2], region[3]) = (region[3], region[2]);
|
||||
}
|
||||
|
||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize, false);
|
||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize);
|
||||
|
||||
gd.BufferManager.SetData<float>(bufferHandle, 0, region);
|
||||
|
||||
@@ -495,7 +495,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
(region[2], region[3]) = (region[3], region[2]);
|
||||
}
|
||||
|
||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize, false);
|
||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize);
|
||||
|
||||
gd.BufferManager.SetData<float>(bufferHandle, 0, region);
|
||||
|
||||
@@ -649,7 +649,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_pipeline.SetCommandBuffer(cbs);
|
||||
|
||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ClearColorBufferSize, false);
|
||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ClearColorBufferSize);
|
||||
|
||||
gd.BufferManager.SetData<float>(bufferHandle, 0, clearColor);
|
||||
|
||||
@@ -726,7 +726,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
(region[2], region[3]) = (region[3], region[2]);
|
||||
}
|
||||
|
||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize, false);
|
||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize);
|
||||
|
||||
gd.BufferManager.SetData<float>(bufferHandle, 0, region);
|
||||
|
||||
@@ -802,7 +802,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
shaderParams[2] = size;
|
||||
shaderParams[3] = srcOffset;
|
||||
|
||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize, false);
|
||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize);
|
||||
|
||||
gd.BufferManager.SetData<int>(bufferHandle, 0, shaderParams);
|
||||
|
||||
@@ -958,7 +958,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
shaderParams[0] = BitOperations.Log2((uint)ratio);
|
||||
|
||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize, false);
|
||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize);
|
||||
|
||||
gd.BufferManager.SetData<int>(bufferHandle, 0, shaderParams);
|
||||
|
||||
@@ -1050,7 +1050,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
(shaderParams[0], shaderParams[1]) = GetSampleCountXYLog2(samples);
|
||||
(shaderParams[2], shaderParams[3]) = GetSampleCountXYLog2((int)TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)samples));
|
||||
|
||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize, false);
|
||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize);
|
||||
|
||||
gd.BufferManager.SetData<int>(bufferHandle, 0, shaderParams);
|
||||
|
||||
@@ -1133,7 +1133,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
(shaderParams[0], shaderParams[1]) = GetSampleCountXYLog2(samples);
|
||||
(shaderParams[2], shaderParams[3]) = GetSampleCountXYLog2((int)TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)samples));
|
||||
|
||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize, false);
|
||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize);
|
||||
|
||||
gd.BufferManager.SetData<int>(bufferHandle, 0, shaderParams);
|
||||
|
||||
@@ -1407,7 +1407,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
pattern.OffsetIndex.CopyTo(shaderParams.Slice(0, pattern.OffsetIndex.Length));
|
||||
|
||||
var patternBufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize, false, out var patternBuffer);
|
||||
var patternBufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize, out var patternBuffer);
|
||||
var patternBufferAuto = patternBuffer.GetBuffer();
|
||||
|
||||
gd.BufferManager.SetData<int>(patternBufferHandle, 0, shaderParams);
|
||||
|
@@ -82,7 +82,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
|
||||
// Expand the repeating pattern to the number of requested primitives.
|
||||
BufferHandle newBuffer = _gd.CreateBuffer(expectedSize * sizeof(int));
|
||||
BufferHandle newBuffer = _gd.BufferManager.CreateWithHandle(_gd, expectedSize * sizeof(int));
|
||||
|
||||
// Copy the old data to the new one.
|
||||
if (_repeatingBuffer != BufferHandle.Null)
|
||||
|
@@ -146,5 +146,16 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
return _buffer == buffer;
|
||||
}
|
||||
|
||||
public void Swap(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||
{
|
||||
if (_buffer == from)
|
||||
{
|
||||
_buffer.DecrementReferenceCount();
|
||||
to.IncrementReferenceCount();
|
||||
|
||||
_buffer = to;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -28,32 +28,25 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public MemoryAllocation AllocateDeviceMemory(
|
||||
MemoryRequirements requirements,
|
||||
MemoryPropertyFlags flags = 0)
|
||||
MemoryPropertyFlags flags = 0,
|
||||
bool isBuffer = false)
|
||||
{
|
||||
return AllocateDeviceMemory(requirements, flags, flags);
|
||||
}
|
||||
|
||||
public MemoryAllocation AllocateDeviceMemory(
|
||||
MemoryRequirements requirements,
|
||||
MemoryPropertyFlags flags,
|
||||
MemoryPropertyFlags alternativeFlags)
|
||||
{
|
||||
int memoryTypeIndex = FindSuitableMemoryTypeIndex(requirements.MemoryTypeBits, flags, alternativeFlags);
|
||||
int memoryTypeIndex = FindSuitableMemoryTypeIndex(requirements.MemoryTypeBits, flags);
|
||||
if (memoryTypeIndex < 0)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
bool map = flags.HasFlag(MemoryPropertyFlags.HostVisibleBit);
|
||||
return Allocate(memoryTypeIndex, requirements.Size, requirements.Alignment, map);
|
||||
return Allocate(memoryTypeIndex, requirements.Size, requirements.Alignment, map, isBuffer);
|
||||
}
|
||||
|
||||
private MemoryAllocation Allocate(int memoryTypeIndex, ulong size, ulong alignment, bool map)
|
||||
private MemoryAllocation Allocate(int memoryTypeIndex, ulong size, ulong alignment, bool map, bool isBuffer)
|
||||
{
|
||||
for (int i = 0; i < _blockLists.Count; i++)
|
||||
{
|
||||
var bl = _blockLists[i];
|
||||
if (bl.MemoryTypeIndex == memoryTypeIndex)
|
||||
if (bl.MemoryTypeIndex == memoryTypeIndex && bl.ForBuffer == isBuffer)
|
||||
{
|
||||
lock (bl)
|
||||
{
|
||||
@@ -62,18 +55,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
var newBl = new MemoryAllocatorBlockList(_api, _device, memoryTypeIndex, _blockAlignment);
|
||||
var newBl = new MemoryAllocatorBlockList(_api, _device, memoryTypeIndex, _blockAlignment, isBuffer);
|
||||
_blockLists.Add(newBl);
|
||||
return newBl.Allocate(size, alignment, map);
|
||||
}
|
||||
|
||||
private int FindSuitableMemoryTypeIndex(
|
||||
uint memoryTypeBits,
|
||||
MemoryPropertyFlags flags,
|
||||
MemoryPropertyFlags alternativeFlags)
|
||||
MemoryPropertyFlags flags)
|
||||
{
|
||||
int bestCandidateIndex = -1;
|
||||
|
||||
for (int i = 0; i < _physicalDeviceMemoryProperties.MemoryTypeCount; i++)
|
||||
{
|
||||
var type = _physicalDeviceMemoryProperties.MemoryTypes[i];
|
||||
@@ -84,14 +74,27 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
return i;
|
||||
}
|
||||
else if (type.PropertyFlags.HasFlag(alternativeFlags))
|
||||
{
|
||||
bestCandidateIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestCandidateIndex;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static bool IsDeviceMemoryShared(Vk api, PhysicalDevice physicalDevice)
|
||||
{
|
||||
// The device is regarded as having shared memory if all heaps have the device local bit.
|
||||
|
||||
api.GetPhysicalDeviceMemoryProperties(physicalDevice, out var properties);
|
||||
|
||||
for (int i = 0; i < properties.MemoryHeapCount; i++)
|
||||
{
|
||||
if (!properties.MemoryHeaps[i].Flags.HasFlag(MemoryHeapFlags.DeviceLocalBit))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
@@ -162,15 +162,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private readonly Device _device;
|
||||
|
||||
public int MemoryTypeIndex { get; }
|
||||
public bool ForBuffer { get; }
|
||||
|
||||
private readonly int _blockAlignment;
|
||||
|
||||
public MemoryAllocatorBlockList(Vk api, Device device, int memoryTypeIndex, int blockAlignment)
|
||||
public MemoryAllocatorBlockList(Vk api, Device device, int memoryTypeIndex, int blockAlignment, bool forBuffer)
|
||||
{
|
||||
_blocks = new List<Block>();
|
||||
_api = api;
|
||||
_device = device;
|
||||
MemoryTypeIndex = memoryTypeIndex;
|
||||
ForBuffer = forBuffer;
|
||||
_blockAlignment = blockAlignment;
|
||||
}
|
||||
|
||||
|
@@ -226,6 +226,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var attachment = new ClearAttachment(ImageAspectFlags.ColorBit, (uint)index, clearValue);
|
||||
var clearRect = FramebufferParams.GetClearRect(ClearScissor, layer, layerCount);
|
||||
|
||||
FramebufferParams.InsertClearBarrier(Cbs, index);
|
||||
|
||||
Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
|
||||
}
|
||||
|
||||
@@ -256,6 +258,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var attachment = new ClearAttachment(flags, 0, clearValue);
|
||||
var clearRect = FramebufferParams.GetClearRect(ClearScissor, layer, layerCount);
|
||||
|
||||
FramebufferParams.InsertClearBarrierDS(Cbs);
|
||||
|
||||
Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
|
||||
}
|
||||
|
||||
@@ -1297,6 +1301,25 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SignalStateChange();
|
||||
}
|
||||
|
||||
public void SwapBuffer(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||
{
|
||||
_indexBuffer.Swap(from, to);
|
||||
|
||||
for (int i = 0; i < _vertexBuffers.Length; i++)
|
||||
{
|
||||
_vertexBuffers[i].Swap(from, to);
|
||||
}
|
||||
|
||||
for (int i = 0; i < _transformFeedbackBuffers.Length; i++)
|
||||
{
|
||||
_transformFeedbackBuffers[i].Swap(from, to);
|
||||
}
|
||||
|
||||
_descriptorSetUpdater.SwapBuffer(from, to);
|
||||
|
||||
SignalCommandBufferChange();
|
||||
}
|
||||
|
||||
public unsafe void TextureBarrier()
|
||||
{
|
||||
MemoryBarrier memoryBarrier = new MemoryBarrier()
|
||||
|
@@ -17,10 +17,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private ulong _byteWeight;
|
||||
|
||||
private List<BufferHolder> _backingSwaps;
|
||||
|
||||
public PipelineFull(VulkanRenderer gd, Device device) : base(gd, device)
|
||||
{
|
||||
_activeQueries = new List<(QueryPool, bool)>();
|
||||
_pendingQueryCopies = new();
|
||||
_backingSwaps = new();
|
||||
|
||||
CommandBuffer = (Cbs = gd.CommandBufferPool.Rent()).CommandBuffer;
|
||||
}
|
||||
@@ -185,6 +188,20 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
private void TryBackingSwaps()
|
||||
{
|
||||
CommandBufferScoped? cbs = null;
|
||||
|
||||
_backingSwaps.RemoveAll((holder) => holder.TryBackingSwap(ref cbs));
|
||||
|
||||
cbs?.Dispose();
|
||||
}
|
||||
|
||||
public void AddBackingSwap(BufferHolder holder)
|
||||
{
|
||||
_backingSwaps.Add(holder);
|
||||
}
|
||||
|
||||
public void Restore()
|
||||
{
|
||||
if (Pipeline != null)
|
||||
@@ -230,6 +247,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
Gd.ResetCounterPool();
|
||||
|
||||
TryBackingSwaps();
|
||||
|
||||
Restore();
|
||||
}
|
||||
|
||||
|
@@ -57,12 +57,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetData()
|
||||
public PinnedSpan<byte> GetData()
|
||||
{
|
||||
return _gd.GetBufferData(_bufferHandle, _offset, _size);
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetData(int layer, int level)
|
||||
public PinnedSpan<byte> GetData(int layer, int level)
|
||||
{
|
||||
return GetData();
|
||||
}
|
||||
@@ -128,7 +128,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
if (_bufferView == null)
|
||||
{
|
||||
_bufferView = _gd.BufferManager.CreateView(_bufferHandle, VkFormat, _offset, _size);
|
||||
_bufferView = _gd.BufferManager.CreateView(_bufferHandle, VkFormat, _offset, _size, ReleaseImpl);
|
||||
}
|
||||
|
||||
return _bufferView?.Get(cbs, _offset, _size).Value ?? default;
|
||||
@@ -147,7 +147,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return bufferView.Get(cbs, _offset, _size).Value;
|
||||
}
|
||||
|
||||
bufferView = _gd.BufferManager.CreateView(_bufferHandle, vkFormat, _offset, _size);
|
||||
bufferView = _gd.BufferManager.CreateView(_bufferHandle, vkFormat, _offset, _size, ReleaseImpl);
|
||||
|
||||
if (bufferView != null)
|
||||
{
|
||||
|
@@ -46,6 +46,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private AccessFlags _lastModificationAccess;
|
||||
private PipelineStageFlags _lastModificationStage;
|
||||
private AccessFlags _lastReadAccess;
|
||||
private PipelineStageFlags _lastReadStage;
|
||||
|
||||
private int _viewsCount;
|
||||
private ulong _size;
|
||||
@@ -440,31 +442,39 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_lastModificationStage = stage;
|
||||
}
|
||||
|
||||
public void InsertBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags)
|
||||
public void InsertReadToWriteBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags)
|
||||
{
|
||||
if (_lastReadAccess != AccessFlags.NoneKhr)
|
||||
{
|
||||
ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
|
||||
|
||||
TextureView.InsertImageBarrier(
|
||||
_gd.Api,
|
||||
cbs.CommandBuffer,
|
||||
_imageAuto.Get(cbs).Value,
|
||||
_lastReadAccess,
|
||||
dstAccessFlags,
|
||||
_lastReadStage,
|
||||
dstStageFlags,
|
||||
aspectFlags,
|
||||
0,
|
||||
0,
|
||||
_info.GetLayers(),
|
||||
_info.Levels);
|
||||
|
||||
_lastReadAccess = AccessFlags.NoneKhr;
|
||||
_lastReadStage = PipelineStageFlags.None;
|
||||
}
|
||||
}
|
||||
|
||||
public void InsertWriteToReadBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags)
|
||||
{
|
||||
_lastReadAccess |= dstAccessFlags;
|
||||
_lastReadStage |= dstStageFlags;
|
||||
|
||||
if (_lastModificationAccess != AccessFlags.NoneKhr)
|
||||
{
|
||||
ImageAspectFlags aspectFlags;
|
||||
|
||||
if (_info.Format.IsDepthOrStencil())
|
||||
{
|
||||
if (_info.Format == GAL.Format.S8Uint)
|
||||
{
|
||||
aspectFlags = ImageAspectFlags.StencilBit;
|
||||
}
|
||||
else if (_info.Format == GAL.Format.D16Unorm || _info.Format == GAL.Format.D32Float)
|
||||
{
|
||||
aspectFlags = ImageAspectFlags.DepthBit;
|
||||
}
|
||||
else
|
||||
{
|
||||
aspectFlags = ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
aspectFlags = ImageAspectFlags.ColorBit;
|
||||
}
|
||||
ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
|
||||
|
||||
TextureView.InsertImageBarrier(
|
||||
_gd.Api,
|
||||
|
@@ -531,7 +531,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetData()
|
||||
public PinnedSpan<byte> GetData()
|
||||
{
|
||||
BackgroundResource resources = _gd.BackgroundResources.Get();
|
||||
|
||||
@@ -539,15 +539,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
_gd.FlushAllCommands();
|
||||
|
||||
return GetData(_gd.CommandBufferPool, resources.GetFlushBuffer());
|
||||
return PinnedSpan<byte>.UnsafeFromSpan(GetData(_gd.CommandBufferPool, resources.GetFlushBuffer()));
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetData(resources.GetPool(), resources.GetFlushBuffer());
|
||||
return PinnedSpan<byte>.UnsafeFromSpan(GetData(resources.GetPool(), resources.GetFlushBuffer()));
|
||||
}
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetData(int layer, int level)
|
||||
public PinnedSpan<byte> GetData(int layer, int level)
|
||||
{
|
||||
BackgroundResource resources = _gd.BackgroundResources.Get();
|
||||
|
||||
@@ -555,11 +555,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
_gd.FlushAllCommands();
|
||||
|
||||
return GetData(_gd.CommandBufferPool, resources.GetFlushBuffer(), layer, level);
|
||||
return PinnedSpan<byte>.UnsafeFromSpan(GetData(_gd.CommandBufferPool, resources.GetFlushBuffer(), layer, level));
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetData(resources.GetPool(), resources.GetFlushBuffer(), layer, level);
|
||||
return PinnedSpan<byte>.UnsafeFromSpan(GetData(resources.GetPool(), resources.GetFlushBuffer(), layer, level));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -129,6 +129,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return _buffer == buffer;
|
||||
}
|
||||
|
||||
public void Swap(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||
{
|
||||
if (_buffer == from)
|
||||
{
|
||||
_buffer.DecrementReferenceCount();
|
||||
to.IncrementReferenceCount();
|
||||
|
||||
_buffer = to;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Only dispose if this buffer is not refetched on each bind.
|
||||
|
@@ -20,7 +20,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private const string AppName = "Ryujinx.Graphics.Vulkan";
|
||||
private const int QueuesCount = 2;
|
||||
|
||||
public static string[] DesirableExtensions { get; } = new string[]
|
||||
private static readonly string[] _desirableExtensions = new string[]
|
||||
{
|
||||
ExtConditionalRendering.ExtensionName,
|
||||
ExtExtendedDynamicState.ExtensionName,
|
||||
@@ -42,7 +42,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
"VK_KHR_portability_subset", // By spec, we should enable this if present.
|
||||
};
|
||||
|
||||
public static string[] RequiredExtensions { get; } = new string[]
|
||||
private static readonly string[] _requiredExtensions = new string[]
|
||||
{
|
||||
KhrSwapchain.ExtensionName
|
||||
};
|
||||
@@ -337,14 +337,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
string extensionName = Marshal.PtrToStringAnsi((IntPtr)pExtensionProperties[i].ExtensionName);
|
||||
|
||||
if (RequiredExtensions.Contains(extensionName))
|
||||
if (_requiredExtensions.Contains(extensionName))
|
||||
{
|
||||
extensionMatches++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return extensionMatches == RequiredExtensions.Length && FindSuitableQueueFamily(api, physicalDevice, surface, out _) != InvalidIndex;
|
||||
return extensionMatches == _requiredExtensions.Length && FindSuitableQueueFamily(api, physicalDevice, surface, out _) != InvalidIndex;
|
||||
}
|
||||
|
||||
internal static uint FindSuitableQueueFamily(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface, out uint queueCount)
|
||||
@@ -626,7 +626,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
pExtendedFeatures = &featuresCustomBorderColor;
|
||||
}
|
||||
|
||||
var enabledExtensions = RequiredExtensions.Union(DesirableExtensions.Intersect(supportedExtensions)).ToArray();
|
||||
var enabledExtensions = _requiredExtensions.Union(_desirableExtensions.Intersect(supportedExtensions)).ToArray();
|
||||
|
||||
IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length];
|
||||
|
||||
@@ -672,11 +672,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return extensionProperties.Select(x => Marshal.PtrToStringAnsi((IntPtr)x.ExtensionName)).ToArray();
|
||||
}
|
||||
|
||||
internal static CommandBufferPool CreateCommandBufferPool(Vk api, Device device, Queue queue, object queueLock, uint queueFamilyIndex)
|
||||
{
|
||||
return new CommandBufferPool(api, device, queue, queueLock, queueFamilyIndex);
|
||||
}
|
||||
|
||||
internal unsafe static void CreateDebugMessenger(
|
||||
Vk api,
|
||||
GraphicsDebugLevel logLevel,
|
||||
|
@@ -80,6 +80,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
internal bool IsAmdGcn { get; private set; }
|
||||
internal bool IsMoltenVk { get; private set; }
|
||||
internal bool IsTBDR { get; private set; }
|
||||
internal bool IsSharedMemory { get; private set; }
|
||||
public string GpuVendor { get; private set; }
|
||||
public string GpuRenderer { get; private set; }
|
||||
public string GpuVersion { get; private set; }
|
||||
@@ -167,7 +168,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SType = StructureType.PhysicalDeviceSubgroupSizeControlPropertiesExt
|
||||
};
|
||||
|
||||
if (Capabilities.SupportsSubgroupSizeControl)
|
||||
bool supportsSubgroupSizeControl = supportedExtensions.Contains("VK_EXT_subgroup_size_control");
|
||||
|
||||
if (supportsSubgroupSizeControl)
|
||||
{
|
||||
properties2.PNext = &propertiesSubgroupSizeControl;
|
||||
}
|
||||
@@ -291,7 +294,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
supportedExtensions.Contains(KhrDrawIndirectCount.ExtensionName),
|
||||
supportedExtensions.Contains("VK_EXT_fragment_shader_interlock"),
|
||||
supportedExtensions.Contains("VK_NV_geometry_shader_passthrough"),
|
||||
supportedExtensions.Contains("VK_EXT_subgroup_size_control"),
|
||||
supportsSubgroupSizeControl,
|
||||
featuresShaderInt8.ShaderInt8,
|
||||
supportedExtensions.Contains("VK_EXT_shader_stencil_export"),
|
||||
supportedExtensions.Contains(ExtConditionalRendering.ExtensionName),
|
||||
@@ -313,9 +316,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
portabilityFlags,
|
||||
vertexBufferAlignment);
|
||||
|
||||
IsSharedMemory = MemoryAllocator.IsDeviceMemoryShared(Api, _physicalDevice);
|
||||
|
||||
MemoryAllocator = new MemoryAllocator(Api, _physicalDevice, _device, properties.Limits.MaxMemoryAllocationCount);
|
||||
|
||||
CommandBufferPool = VulkanInitialization.CreateCommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex);
|
||||
CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex);
|
||||
|
||||
DescriptorSetManager = new DescriptorSetManager(_device);
|
||||
|
||||
@@ -373,9 +378,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
public BufferHandle CreateBuffer(int size)
|
||||
public BufferHandle CreateBuffer(int size, BufferHandle storageHint)
|
||||
{
|
||||
return BufferManager.CreateWithHandle(this, size, false);
|
||||
return BufferManager.CreateWithHandle(this, size, BufferAllocationType.Auto, storageHint);
|
||||
}
|
||||
|
||||
public IProgram CreateProgram(ShaderSource[] sources, ShaderInfo info)
|
||||
@@ -439,7 +444,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_syncManager.RegisterFlush();
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
|
||||
public PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
|
||||
{
|
||||
return BufferManager.GetData(buffer, offset, size);
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using LibHac.Tools.Ncm;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.Exceptions;
|
||||
using Ryujinx.HLE.HOS.Services.Ssl;
|
||||
@@ -637,12 +638,12 @@ namespace Ryujinx.HLE.FileSystem
|
||||
|
||||
private Stream GetZipStream(ZipArchiveEntry entry)
|
||||
{
|
||||
MemoryStream dest = new MemoryStream();
|
||||
MemoryStream dest = MemoryStreamManager.Shared.GetStream();
|
||||
|
||||
Stream src = entry.Open();
|
||||
|
||||
src.CopyTo(dest);
|
||||
src.Dispose();
|
||||
using (Stream src = entry.Open())
|
||||
{
|
||||
src.CopyTo(dest);
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
@@ -20,7 +20,6 @@ using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Path = System.IO.Path;
|
||||
using RightsId = LibHac.Fs.RightsId;
|
||||
|
||||
@@ -146,6 +145,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||
|
||||
return $"{basePath}:/{fileName}";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||
fsServerClient = horizon.CreatePrivilegedHorizonClient();
|
||||
var fsServer = new FileSystemServer(fsServerClient);
|
||||
|
||||
RandomDataGenerator randomGenerator = buffer => Random.Shared.NextBytes(buffer);
|
||||
RandomDataGenerator randomGenerator = Random.Shared.NextBytes;
|
||||
|
||||
DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet, fsServer, randomGenerator);
|
||||
|
||||
@@ -264,7 +264,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||
|
||||
if (result.IsSuccess())
|
||||
{
|
||||
Ticket ticket = new Ticket(ticketFile.Get.AsStream());
|
||||
Ticket ticket = new(ticketFile.Get.AsStream());
|
||||
var titleKey = ticket.GetTitleKey(KeySet);
|
||||
|
||||
if (titleKey != null)
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -70,7 +71,7 @@ namespace Ryujinx.HLE.HOS.Applets.Browser
|
||||
|
||||
private byte[] BuildResponseOld(WebCommonReturnValue result)
|
||||
{
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||
using (BinaryWriter writer = new BinaryWriter(stream))
|
||||
{
|
||||
writer.WriteStruct(result);
|
||||
@@ -80,7 +81,7 @@ namespace Ryujinx.HLE.HOS.Applets.Browser
|
||||
}
|
||||
private byte[] BuildResponseNew(List<BrowserOutput> outputArguments)
|
||||
{
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||
using (BinaryWriter writer = new BinaryWriter(stream))
|
||||
{
|
||||
writer.WriteStruct(new WebArgHeader
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
|
||||
using Ryujinx.HLE.HOS.Services.Hid;
|
||||
using Ryujinx.HLE.HOS.Services.Hid.Types;
|
||||
@@ -123,7 +124,7 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
|
||||
private byte[] BuildResponse(ControllerSupportResultInfo result)
|
||||
{
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||
using (BinaryWriter writer = new BinaryWriter(stream))
|
||||
{
|
||||
writer.Write(MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref result, Unsafe.SizeOf<ControllerSupportResultInfo>())));
|
||||
@@ -134,7 +135,7 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
|
||||
private byte[] BuildResponse()
|
||||
{
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||
using (BinaryWriter writer = new BinaryWriter(stream))
|
||||
{
|
||||
writer.Write((ulong)ResultCode.Success);
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
|
||||
using System;
|
||||
using System.IO;
|
||||
@@ -43,7 +44,7 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
{
|
||||
UserProfile currentUser = _system.AccountManager.LastOpenedUser;
|
||||
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||
using (BinaryWriter writer = new BinaryWriter(stream))
|
||||
{
|
||||
writer.Write((ulong)PlayerSelectResult.Success);
|
||||
|
@@ -1,908 +0,0 @@
|
||||
using LibHac;
|
||||
using LibHac.Account;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Fs.Shim;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Loader;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Ns;
|
||||
using LibHac.Tools.Fs;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.Loaders.Executables;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using static Ryujinx.HLE.HOS.ModLoader;
|
||||
using ApplicationId = LibHac.Ncm.ApplicationId;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
namespace Ryujinx.HLE.HOS
|
||||
{
|
||||
using JsonHelper = Common.Utilities.JsonHelper;
|
||||
|
||||
public class ApplicationLoader
|
||||
{
|
||||
// Binaries from exefs are loaded into mem in this order. Do not change.
|
||||
internal static readonly string[] ExeFsPrefixes =
|
||||
{
|
||||
"rtld",
|
||||
"main",
|
||||
"subsdk0",
|
||||
"subsdk1",
|
||||
"subsdk2",
|
||||
"subsdk3",
|
||||
"subsdk4",
|
||||
"subsdk5",
|
||||
"subsdk6",
|
||||
"subsdk7",
|
||||
"subsdk8",
|
||||
"subsdk9",
|
||||
"sdk"
|
||||
};
|
||||
|
||||
private readonly Switch _device;
|
||||
private string _titleName;
|
||||
private string _displayVersion;
|
||||
private BlitStruct<ApplicationControlProperty> _controlData;
|
||||
|
||||
public BlitStruct<ApplicationControlProperty> ControlData => _controlData;
|
||||
public string TitleName => _titleName;
|
||||
public string DisplayVersion => _displayVersion;
|
||||
|
||||
public ulong TitleId { get; private set; }
|
||||
public bool TitleIs64Bit { get; private set; }
|
||||
|
||||
public string TitleIdText => TitleId.ToString("x16");
|
||||
|
||||
public IDiskCacheLoadState DiskCacheLoadState { get; private set; }
|
||||
|
||||
public ApplicationLoader(Switch device)
|
||||
{
|
||||
_device = device;
|
||||
_controlData = new BlitStruct<ApplicationControlProperty>(1);
|
||||
}
|
||||
|
||||
public void LoadCart(string exeFsDir, string romFsFile = null)
|
||||
{
|
||||
LocalFileSystem codeFs = new LocalFileSystem(exeFsDir);
|
||||
|
||||
MetaLoader metaData = ReadNpdm(codeFs);
|
||||
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
|
||||
new[] { TitleId },
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
|
||||
|
||||
if (TitleId != 0)
|
||||
{
|
||||
EnsureSaveData(new ApplicationId(TitleId));
|
||||
}
|
||||
|
||||
ulong pid = LoadExeFs(codeFs, string.Empty, metaData);
|
||||
|
||||
if (romFsFile != null)
|
||||
{
|
||||
_device.Configuration.VirtualFileSystem.LoadRomFs(pid, romFsFile);
|
||||
}
|
||||
}
|
||||
|
||||
public static (Nca main, Nca patch, Nca control) GetGameData(VirtualFileSystem fileSystem, PartitionFileSystem pfs, int programIndex)
|
||||
{
|
||||
Nca mainNca = null;
|
||||
Nca patchNca = null;
|
||||
Nca controlNca = null;
|
||||
|
||||
fileSystem.ImportTickets(pfs);
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
||||
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage());
|
||||
|
||||
int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
|
||||
|
||||
if (ncaProgramIndex != programIndex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nca.Header.ContentType == NcaContentType.Program)
|
||||
{
|
||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||
|
||||
if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||
{
|
||||
patchNca = nca;
|
||||
}
|
||||
else
|
||||
{
|
||||
mainNca = nca;
|
||||
}
|
||||
}
|
||||
else if (nca.Header.ContentType == NcaContentType.Control)
|
||||
{
|
||||
controlNca = nca;
|
||||
}
|
||||
}
|
||||
|
||||
return (mainNca, patchNca, controlNca);
|
||||
}
|
||||
|
||||
public static (Nca patch, Nca control) GetGameUpdateDataFromPartition(VirtualFileSystem fileSystem, PartitionFileSystem pfs, string titleId, int programIndex)
|
||||
{
|
||||
Nca patchNca = null;
|
||||
Nca controlNca = null;
|
||||
|
||||
fileSystem.ImportTickets(pfs);
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
||||
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage());
|
||||
|
||||
int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
|
||||
|
||||
if (ncaProgramIndex != programIndex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleId)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (nca.Header.ContentType == NcaContentType.Program)
|
||||
{
|
||||
patchNca = nca;
|
||||
}
|
||||
else if (nca.Header.ContentType == NcaContentType.Control)
|
||||
{
|
||||
controlNca = nca;
|
||||
}
|
||||
}
|
||||
|
||||
return (patchNca, controlNca);
|
||||
}
|
||||
|
||||
public static (Nca patch, Nca control) GetGameUpdateData(VirtualFileSystem fileSystem, string titleId, int programIndex, out string updatePath)
|
||||
{
|
||||
updatePath = null;
|
||||
|
||||
if (ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase))
|
||||
{
|
||||
// Clear the program index part.
|
||||
titleIdBase &= 0xFFFFFFFFFFFFFFF0;
|
||||
|
||||
// Load update informations if existing.
|
||||
string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json");
|
||||
|
||||
if (File.Exists(titleUpdateMetadataPath))
|
||||
{
|
||||
updatePath = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(titleUpdateMetadataPath).Selected;
|
||||
|
||||
if (File.Exists(updatePath))
|
||||
{
|
||||
FileStream file = new FileStream(updatePath, FileMode.Open, FileAccess.Read);
|
||||
PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
|
||||
|
||||
return GetGameUpdateDataFromPartition(fileSystem, nsp, titleIdBase.ToString("x16"), programIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
public void LoadXci(string xciFile)
|
||||
{
|
||||
FileStream file = new FileStream(xciFile, FileMode.Open, FileAccess.Read);
|
||||
Xci xci = new Xci(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage());
|
||||
|
||||
if (!xci.HasPartition(XciPartitionType.Secure))
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find XCI secure partition");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
PartitionFileSystem securePartition = xci.OpenPartition(XciPartitionType.Secure);
|
||||
|
||||
Nca mainNca;
|
||||
Nca patchNca;
|
||||
Nca controlNca;
|
||||
|
||||
try
|
||||
{
|
||||
(mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, securePartition, _device.Configuration.UserChannelPersistence.Index);
|
||||
|
||||
RegisterProgramMapInfo(securePartition).ThrowIfFailure();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, $"Unable to load XCI: {e.Message}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainNca == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find Main NCA");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_device.Configuration.ContentManager.LoadEntries(_device);
|
||||
_device.Configuration.ContentManager.ClearAocData();
|
||||
_device.Configuration.ContentManager.AddAocData(securePartition, xciFile, mainNca.Header.TitleId, _device.Configuration.FsIntegrityCheckLevel);
|
||||
|
||||
LoadNca(mainNca, patchNca, controlNca);
|
||||
}
|
||||
|
||||
public void LoadNsp(string nspFile)
|
||||
{
|
||||
FileStream file = new FileStream(nspFile, FileMode.Open, FileAccess.Read);
|
||||
PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
|
||||
|
||||
Nca mainNca;
|
||||
Nca patchNca;
|
||||
Nca controlNca;
|
||||
|
||||
try
|
||||
{
|
||||
(mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, nsp, _device.Configuration.UserChannelPersistence.Index);
|
||||
|
||||
RegisterProgramMapInfo(nsp).ThrowIfFailure();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, $"Unable to load NSP: {e.Message}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainNca != null)
|
||||
{
|
||||
_device.Configuration.ContentManager.ClearAocData();
|
||||
_device.Configuration.ContentManager.AddAocData(nsp, nspFile, mainNca.Header.TitleId, _device.Configuration.FsIntegrityCheckLevel);
|
||||
|
||||
LoadNca(mainNca, patchNca, controlNca);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// This is not a normal NSP, it's actually a ExeFS as a NSP
|
||||
LoadExeFs(nsp, null, isHomebrew: true);
|
||||
}
|
||||
|
||||
public void LoadNca(string ncaFile)
|
||||
{
|
||||
FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read);
|
||||
Nca nca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false));
|
||||
|
||||
LoadNca(nca, null, null);
|
||||
}
|
||||
|
||||
public void LoadServiceNca(string ncaFile)
|
||||
{
|
||||
FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read);
|
||||
Nca mainNca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false));
|
||||
|
||||
if (mainNca.Header.ContentType != NcaContentType.Program)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, "Selected NCA is not a \"Program\" NCA");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
IFileSystem codeFs = null;
|
||||
|
||||
if (mainNca.CanOpenSection(NcaSectionType.Code))
|
||||
{
|
||||
codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
|
||||
}
|
||||
|
||||
if (codeFs == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
using var npdmFile = new UniqueRef<IFile>();
|
||||
|
||||
Result result = codeFs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read);
|
||||
|
||||
MetaLoader metaData;
|
||||
|
||||
npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure();
|
||||
|
||||
var npdmBuffer = new byte[fileSize];
|
||||
npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure();
|
||||
|
||||
metaData = new MetaLoader();
|
||||
metaData.Load(npdmBuffer).ThrowIfFailure();
|
||||
|
||||
NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length];
|
||||
|
||||
for (int i = 0; i < nsos.Length; i++)
|
||||
{
|
||||
string name = ExeFsPrefixes[i];
|
||||
|
||||
if (!codeFs.FileExists($"/{name}"))
|
||||
{
|
||||
continue; // File doesn't exist, skip.
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
|
||||
|
||||
using var nsoFile = new UniqueRef<IFile>();
|
||||
|
||||
codeFs.OpenFile(ref nsoFile.Ref, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
|
||||
}
|
||||
|
||||
// Collect the nsos, ignoring ones that aren't used.
|
||||
NsoExecutable[] programs = nsos.Where(x => x != null).ToArray();
|
||||
|
||||
string displayVersion = _device.System.ContentManager.GetCurrentFirmwareVersion().VersionString;
|
||||
bool usePtc = _device.System.EnablePtc;
|
||||
|
||||
metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
|
||||
ProgramInfo programInfo = new ProgramInfo(in npdm, displayVersion, usePtc, allowCodeMemoryForJit: false);
|
||||
ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: programs);
|
||||
|
||||
string titleIdText = npdm.Aci.ProgramId.Value.ToString("x16");
|
||||
bool titleIs64Bit = (npdm.Meta.Flags & 1) != 0;
|
||||
|
||||
string programName = Encoding.ASCII.GetString(npdm.Meta.ProgramName).TrimEnd('\0');
|
||||
|
||||
Logger.Info?.Print(LogClass.Loader, $"Service Loaded: {programName} [{titleIdText}] [{(titleIs64Bit ? "64-bit" : "32-bit")}]");
|
||||
}
|
||||
|
||||
private void LoadNca(Nca mainNca, Nca patchNca, Nca controlNca)
|
||||
{
|
||||
if (mainNca.Header.ContentType != NcaContentType.Program)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, "Selected NCA is not a \"Program\" NCA");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
IStorage dataStorage = null;
|
||||
IFileSystem codeFs = null;
|
||||
|
||||
(Nca updatePatchNca, Nca updateControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), _device.Configuration.UserChannelPersistence.Index, out _);
|
||||
|
||||
if (updatePatchNca != null)
|
||||
{
|
||||
patchNca = updatePatchNca;
|
||||
}
|
||||
|
||||
if (updateControlNca != null)
|
||||
{
|
||||
controlNca = updateControlNca;
|
||||
}
|
||||
|
||||
// Load program 0 control NCA as we are going to need it for display version.
|
||||
(_, Nca updateProgram0ControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _);
|
||||
|
||||
// Load Aoc
|
||||
string titleAocMetadataPath = Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "dlc.json");
|
||||
|
||||
if (File.Exists(titleAocMetadataPath))
|
||||
{
|
||||
List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(titleAocMetadataPath);
|
||||
|
||||
foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList)
|
||||
{
|
||||
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
|
||||
{
|
||||
if (File.Exists(downloadableContentContainer.ContainerPath) && downloadableContentNca.Enabled)
|
||||
{
|
||||
_device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Cannot find AddOnContent file {downloadableContentContainer.ContainerPath}. It may have been moved or renamed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (patchNca == null)
|
||||
{
|
||||
if (mainNca.CanOpenSection(NcaSectionType.Data))
|
||||
{
|
||||
dataStorage = mainNca.OpenStorage(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
|
||||
}
|
||||
|
||||
if (mainNca.CanOpenSection(NcaSectionType.Code))
|
||||
{
|
||||
codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (patchNca.CanOpenSection(NcaSectionType.Data))
|
||||
{
|
||||
dataStorage = mainNca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
|
||||
}
|
||||
|
||||
if (patchNca.CanOpenSection(NcaSectionType.Code))
|
||||
{
|
||||
codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
|
||||
}
|
||||
}
|
||||
|
||||
if (codeFs == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
MetaLoader metaData = ReadNpdm(codeFs);
|
||||
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
|
||||
_device.Configuration.ContentManager.GetAocTitleIds().Prepend(TitleId),
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
|
||||
|
||||
string displayVersion = string.Empty;
|
||||
|
||||
if (controlNca != null)
|
||||
{
|
||||
ReadControlData(_device, controlNca, ref _controlData, ref _titleName, ref displayVersion);
|
||||
}
|
||||
else
|
||||
{
|
||||
ControlData.ByteSpan.Clear();
|
||||
}
|
||||
|
||||
// NOTE: Nintendo doesn't guarantee that the display version will be updated on sub programs when updating a multi program application.
|
||||
// BODY: As such, to avoid PTC cache confusion, we only trust the the program 0 display version when launching a sub program.
|
||||
if (updateProgram0ControlNca != null && _device.Configuration.UserChannelPersistence.Index != 0)
|
||||
{
|
||||
string dummyTitleName = "";
|
||||
BlitStruct<ApplicationControlProperty> dummyControl = new BlitStruct<ApplicationControlProperty>(1);
|
||||
|
||||
ReadControlData(_device, updateProgram0ControlNca, ref dummyControl, ref dummyTitleName, ref displayVersion);
|
||||
}
|
||||
|
||||
_displayVersion = displayVersion;
|
||||
|
||||
ulong pid = LoadExeFs(codeFs, displayVersion, metaData);
|
||||
|
||||
if (dataStorage == null)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Loader, "No RomFS found in NCA");
|
||||
}
|
||||
else
|
||||
{
|
||||
IStorage newStorage = _device.Configuration.VirtualFileSystem.ModLoader.ApplyRomFsMods(TitleId, dataStorage);
|
||||
|
||||
_device.Configuration.VirtualFileSystem.SetRomFs(pid, newStorage.AsStream(FileAccess.Read));
|
||||
}
|
||||
|
||||
// Don't create save data for system programs.
|
||||
if (TitleId != 0 && (TitleId < SystemProgramId.Start.Value || TitleId > SystemAppletId.End.Value))
|
||||
{
|
||||
// Multi-program applications can technically use any program ID for the main program, but in practice they always use 0 in the low nibble.
|
||||
// We'll know if this changes in the future because stuff will get errors when trying to mount the correct save.
|
||||
EnsureSaveData(new ApplicationId(TitleId & ~0xFul));
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {TitleName} v{DisplayVersion} [{TitleIdText}] [{(TitleIs64Bit ? "64-bit" : "32-bit")}]");
|
||||
}
|
||||
|
||||
// Sets TitleId, so be sure to call before using it
|
||||
private MetaLoader ReadNpdm(IFileSystem fs)
|
||||
{
|
||||
using var npdmFile = new UniqueRef<IFile>();
|
||||
|
||||
Result result = fs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read);
|
||||
|
||||
MetaLoader metaData;
|
||||
|
||||
if (ResultFs.PathNotFound.Includes(result))
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Loader, "NPDM file not found, using default values!");
|
||||
|
||||
metaData = GetDefaultNpdm();
|
||||
}
|
||||
else
|
||||
{
|
||||
npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure();
|
||||
|
||||
var npdmBuffer = new byte[fileSize];
|
||||
npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure();
|
||||
|
||||
metaData = new MetaLoader();
|
||||
metaData.Load(npdmBuffer).ThrowIfFailure();
|
||||
}
|
||||
|
||||
metaData.GetNpdm(out var npdm).ThrowIfFailure();
|
||||
|
||||
TitleId = npdm.Aci.ProgramId.Value;
|
||||
TitleIs64Bit = (npdm.Meta.Flags & 1) != 0;
|
||||
_device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId);
|
||||
|
||||
return metaData;
|
||||
}
|
||||
|
||||
private static void ReadControlData(Switch device, Nca controlNca, ref BlitStruct<ApplicationControlProperty> controlData, ref string titleName, ref string displayVersion)
|
||||
{
|
||||
using var controlFile = new UniqueRef<IFile>();
|
||||
|
||||
IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel);
|
||||
Result result = controlFs.OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read);
|
||||
|
||||
if (result.IsSuccess())
|
||||
{
|
||||
result = controlFile.Get.Read(out long bytesRead, 0, controlData.ByteSpan, ReadOption.None);
|
||||
|
||||
if (result.IsSuccess() && bytesRead == controlData.ByteSpan.Length)
|
||||
{
|
||||
titleName = controlData.Value.Title[(int)device.System.State.DesiredTitleLanguage].NameString.ToString();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleName))
|
||||
{
|
||||
titleName = controlData.Value.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString();
|
||||
}
|
||||
|
||||
displayVersion = controlData.Value.DisplayVersionString.ToString();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
controlData.ByteSpan.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private ulong LoadExeFs(IFileSystem codeFs, string displayVersion, MetaLoader metaData = null, bool isHomebrew = false)
|
||||
{
|
||||
if (_device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs))
|
||||
{
|
||||
metaData = null; // TODO: Check if we should retain old npdm.
|
||||
}
|
||||
|
||||
metaData ??= ReadNpdm(codeFs);
|
||||
|
||||
NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length];
|
||||
|
||||
for (int i = 0; i < nsos.Length; i++)
|
||||
{
|
||||
string name = ExeFsPrefixes[i];
|
||||
|
||||
if (!codeFs.FileExists($"/{name}"))
|
||||
{
|
||||
continue; // File doesn't exist, skip.
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
|
||||
|
||||
using var nsoFile = new UniqueRef<IFile>();
|
||||
|
||||
codeFs.OpenFile(ref nsoFile.Ref, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
|
||||
}
|
||||
|
||||
// ExeFs file replacements.
|
||||
ModLoadResult modLoadResult = _device.Configuration.VirtualFileSystem.ModLoader.ApplyExefsMods(TitleId, nsos);
|
||||
|
||||
// Collect the nsos, ignoring ones that aren't used.
|
||||
NsoExecutable[] programs = nsos.Where(x => x != null).ToArray();
|
||||
|
||||
// Take the npdm from mods if present.
|
||||
if (modLoadResult.Npdm != null)
|
||||
{
|
||||
metaData = modLoadResult.Npdm;
|
||||
}
|
||||
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.ApplyNsoPatches(TitleId, programs);
|
||||
|
||||
_device.Configuration.ContentManager.LoadEntries(_device);
|
||||
|
||||
bool usePtc = _device.System.EnablePtc;
|
||||
|
||||
// Don't use PPTC if ExeFs files have been replaced.
|
||||
usePtc &= !modLoadResult.Modified;
|
||||
|
||||
if (_device.System.EnablePtc && !usePtc)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Ptc, $"Detected unsupported ExeFs modifications. PPTC disabled.");
|
||||
}
|
||||
|
||||
Graphics.Gpu.GraphicsConfig.TitleId = TitleIdText;
|
||||
_device.Gpu.HostInitalized.Set();
|
||||
|
||||
MemoryManagerMode memoryManagerMode = _device.Configuration.MemoryManagerMode;
|
||||
|
||||
if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible))
|
||||
{
|
||||
memoryManagerMode = MemoryManagerMode.SoftwarePageTable;
|
||||
}
|
||||
|
||||
// We allow it for nx-hbloader because it can be used to launch homebrew.
|
||||
bool allowCodeMemoryForJit = TitleId == 0x010000000000100DUL || isHomebrew;
|
||||
|
||||
metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
|
||||
ProgramInfo programInfo = new ProgramInfo(in npdm, displayVersion, usePtc, allowCodeMemoryForJit);
|
||||
ProgramLoadResult result = ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: programs);
|
||||
|
||||
DiskCacheLoadState = result.DiskCacheLoadState;
|
||||
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, result.TamperInfo, _device.TamperMachine);
|
||||
|
||||
return result.ProcessId;
|
||||
}
|
||||
|
||||
public void LoadProgram(string filePath)
|
||||
{
|
||||
MetaLoader metaData = GetDefaultNpdm();
|
||||
metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
|
||||
ProgramInfo programInfo = new ProgramInfo(in npdm, string.Empty, diskCacheEnabled: false, allowCodeMemoryForJit: true);
|
||||
|
||||
bool isNro = Path.GetExtension(filePath).ToLower() == ".nro";
|
||||
|
||||
IExecutable executable;
|
||||
Stream romfsStream = null;
|
||||
|
||||
if (isNro)
|
||||
{
|
||||
FileStream input = new FileStream(filePath, FileMode.Open);
|
||||
NroExecutable obj = new NroExecutable(input.AsStorage());
|
||||
|
||||
executable = obj;
|
||||
|
||||
// Homebrew NRO can actually have some data after the actual NRO.
|
||||
if (input.Length > obj.FileSize)
|
||||
{
|
||||
input.Position = obj.FileSize;
|
||||
|
||||
BinaryReader reader = new BinaryReader(input);
|
||||
|
||||
uint asetMagic = reader.ReadUInt32();
|
||||
if (asetMagic == 0x54455341)
|
||||
{
|
||||
uint asetVersion = reader.ReadUInt32();
|
||||
if (asetVersion == 0)
|
||||
{
|
||||
ulong iconOffset = reader.ReadUInt64();
|
||||
ulong iconSize = reader.ReadUInt64();
|
||||
|
||||
ulong nacpOffset = reader.ReadUInt64();
|
||||
ulong nacpSize = reader.ReadUInt64();
|
||||
|
||||
ulong romfsOffset = reader.ReadUInt64();
|
||||
ulong romfsSize = reader.ReadUInt64();
|
||||
|
||||
if (romfsSize != 0)
|
||||
{
|
||||
romfsStream = new HomebrewRomFsStream(input, obj.FileSize + (long)romfsOffset);
|
||||
}
|
||||
|
||||
if (nacpSize != 0)
|
||||
{
|
||||
input.Seek(obj.FileSize + (long)nacpOffset, SeekOrigin.Begin);
|
||||
|
||||
reader.Read(ControlData.ByteSpan);
|
||||
|
||||
ref ApplicationControlProperty nacp = ref ControlData.Value;
|
||||
|
||||
programInfo.Name = nacp.Title[(int)_device.System.State.DesiredTitleLanguage].NameString.ToString();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(programInfo.Name))
|
||||
{
|
||||
programInfo.Name = nacp.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString();
|
||||
}
|
||||
|
||||
if (nacp.PresenceGroupId != 0)
|
||||
{
|
||||
programInfo.ProgramId = nacp.PresenceGroupId;
|
||||
}
|
||||
else if (nacp.SaveDataOwnerId != 0)
|
||||
{
|
||||
programInfo.ProgramId = nacp.SaveDataOwnerId;
|
||||
}
|
||||
else if (nacp.AddOnContentBaseId != 0)
|
||||
{
|
||||
programInfo.ProgramId = nacp.AddOnContentBaseId - 0x1000;
|
||||
}
|
||||
else
|
||||
{
|
||||
programInfo.ProgramId = 0000000000000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Loader, $"Unsupported ASET header version found \"{asetVersion}\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
executable = new NsoExecutable(new LocalStorage(filePath, FileAccess.Read), Path.GetFileNameWithoutExtension(filePath));
|
||||
}
|
||||
|
||||
_device.Configuration.ContentManager.LoadEntries(_device);
|
||||
|
||||
_titleName = programInfo.Name;
|
||||
TitleId = programInfo.ProgramId;
|
||||
TitleIs64Bit = (npdm.Meta.Flags & 1) != 0;
|
||||
_device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId);
|
||||
|
||||
// Explicitly null titleid to disable the shader cache.
|
||||
Graphics.Gpu.GraphicsConfig.TitleId = null;
|
||||
_device.Gpu.HostInitalized.Set();
|
||||
|
||||
ProgramLoadResult result = ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: executable);
|
||||
|
||||
if (romfsStream != null)
|
||||
{
|
||||
_device.Configuration.VirtualFileSystem.SetRomFs(result.ProcessId, romfsStream);
|
||||
}
|
||||
|
||||
DiskCacheLoadState = result.DiskCacheLoadState;
|
||||
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, result.TamperInfo, _device.TamperMachine);
|
||||
}
|
||||
|
||||
private MetaLoader GetDefaultNpdm()
|
||||
{
|
||||
Assembly asm = Assembly.GetCallingAssembly();
|
||||
|
||||
using (Stream npdmStream = asm.GetManifestResourceStream("Ryujinx.HLE.Homebrew.npdm"))
|
||||
{
|
||||
var npdmBuffer = new byte[npdmStream.Length];
|
||||
npdmStream.Read(npdmBuffer);
|
||||
|
||||
var metaLoader = new MetaLoader();
|
||||
metaLoader.Load(npdmBuffer).ThrowIfFailure();
|
||||
|
||||
return metaLoader;
|
||||
}
|
||||
}
|
||||
|
||||
private static (ulong applicationId, int programCount) GetMultiProgramInfo(VirtualFileSystem fileSystem, PartitionFileSystem pfs)
|
||||
{
|
||||
ulong mainProgramId = 0;
|
||||
Span<bool> hasIndex = stackalloc bool[0x10];
|
||||
|
||||
fileSystem.ImportTickets(pfs);
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
||||
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage());
|
||||
|
||||
if (nca.Header.ContentType != NcaContentType.Program)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||
|
||||
if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong currentProgramId = nca.Header.TitleId;
|
||||
ulong currentMainProgramId = currentProgramId & ~0xFFFul;
|
||||
|
||||
if (mainProgramId == 0 && currentMainProgramId != 0)
|
||||
{
|
||||
mainProgramId = currentMainProgramId;
|
||||
}
|
||||
|
||||
if (mainProgramId != currentMainProgramId)
|
||||
{
|
||||
// As far as I know there aren't any multi-application game cards containing multi-program applications,
|
||||
// so because multi-application game cards are the only way we should run into multiple applications
|
||||
// we'll just return that there's a single program.
|
||||
return (mainProgramId, 1);
|
||||
}
|
||||
|
||||
hasIndex[(int)(currentProgramId & 0xF)] = true;
|
||||
}
|
||||
|
||||
int programCount = 0;
|
||||
|
||||
for (int i = 0; i < hasIndex.Length && hasIndex[i]; i++)
|
||||
{
|
||||
programCount++;
|
||||
}
|
||||
|
||||
return (mainProgramId, programCount);
|
||||
}
|
||||
|
||||
private Result RegisterProgramMapInfo(PartitionFileSystem pfs)
|
||||
{
|
||||
(ulong applicationId, int programCount) = GetMultiProgramInfo(_device.Configuration.VirtualFileSystem, pfs);
|
||||
|
||||
if (programCount <= 0)
|
||||
return Result.Success;
|
||||
|
||||
Span<ProgramIndexMapInfo> mapInfo = stackalloc ProgramIndexMapInfo[0x10];
|
||||
|
||||
for (int i = 0; i < programCount; i++)
|
||||
{
|
||||
mapInfo[i].ProgramId = new ProgramId(applicationId + (uint)i);
|
||||
mapInfo[i].MainProgramId = new ApplicationId(applicationId);
|
||||
mapInfo[i].ProgramIndex = (byte)i;
|
||||
}
|
||||
|
||||
return _device.System.LibHacHorizonManager.NsClient.Fs.RegisterProgramIndexMapInfo(mapInfo.Slice(0, programCount));
|
||||
}
|
||||
|
||||
private Result EnsureSaveData(ApplicationId applicationId)
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, "Ensuring required savedata exists.");
|
||||
|
||||
Uid user = _device.System.AccountManager.LastOpenedUser.UserId.ToLibHacUid();
|
||||
|
||||
ref ApplicationControlProperty control = ref ControlData.Value;
|
||||
|
||||
if (LibHac.Common.Utilities.IsZeros(ControlData.ByteSpan))
|
||||
{
|
||||
// If the current application doesn't have a loaded control property, create a dummy one
|
||||
// and set the savedata sizes so a user savedata will be created.
|
||||
control = ref new BlitStruct<ApplicationControlProperty>(1).Value;
|
||||
|
||||
// The set sizes don't actually matter as long as they're non-zero because we use directory savedata.
|
||||
control.UserAccountSaveDataSize = 0x4000;
|
||||
control.UserAccountSaveDataJournalSize = 0x4000;
|
||||
control.SaveDataOwnerId = applicationId.Value;
|
||||
|
||||
Logger.Warning?.Print(LogClass.Application,
|
||||
"No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
|
||||
}
|
||||
|
||||
HorizonClient hos = _device.System.LibHacHorizonManager.RyujinxClient;
|
||||
Result resultCode = hos.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, in control);
|
||||
|
||||
if (resultCode.IsFailure())
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {resultCode.ToStringWithName()}");
|
||||
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
resultCode = hos.Fs.EnsureApplicationSaveData(out _, applicationId, in control, in user);
|
||||
|
||||
if (resultCode.IsFailure())
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {resultCode.ToStringWithName()}");
|
||||
}
|
||||
|
||||
return resultCode;
|
||||
}
|
||||
}
|
||||
}
|
@@ -35,6 +35,7 @@ using Ryujinx.HLE.HOS.Services.SurfaceFlinger;
|
||||
using Ryujinx.HLE.HOS.Services.Time.Clock;
|
||||
using Ryujinx.HLE.HOS.SystemState;
|
||||
using Ryujinx.HLE.Loaders.Executables;
|
||||
using Ryujinx.HLE.Loaders.Processes;
|
||||
using Ryujinx.Horizon;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -358,11 +359,11 @@ namespace Ryujinx.HLE.HOS
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadKip(string kipPath)
|
||||
public bool LoadKip(string kipPath)
|
||||
{
|
||||
using var kipFile = new SharedRef<IStorage>(new LocalStorage(kipPath, FileAccess.Read));
|
||||
|
||||
ProgramLoader.LoadKip(KernelContext, new KipExecutable(in kipFile));
|
||||
return ProcessLoaderHelper.LoadKip(KernelContext, new KipExecutable(in kipFile));
|
||||
}
|
||||
|
||||
public void ChangeDockedModeState(bool newState)
|
||||
|
@@ -1,3 +1,6 @@
|
||||
using Microsoft.IO;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
@@ -18,20 +21,27 @@ namespace Ryujinx.HLE.HOS.Ipc
|
||||
|
||||
HasPId = (word & 1) != 0;
|
||||
|
||||
ToCopy = new int[(word >> 1) & 0xf];
|
||||
ToMove = new int[(word >> 5) & 0xf];
|
||||
|
||||
PId = HasPId ? reader.ReadUInt64() : 0;
|
||||
|
||||
for (int index = 0; index < ToCopy.Length; index++)
|
||||
int toCopySize = (word >> 1) & 0xf;
|
||||
int[] toCopy = toCopySize == 0 ? Array.Empty<int>() : new int[toCopySize];
|
||||
|
||||
for (int index = 0; index < toCopy.Length; index++)
|
||||
{
|
||||
ToCopy[index] = reader.ReadInt32();
|
||||
toCopy[index] = reader.ReadInt32();
|
||||
}
|
||||
|
||||
for (int index = 0; index < ToMove.Length; index++)
|
||||
ToCopy = toCopy;
|
||||
|
||||
int toMoveSize = (word >> 5) & 0xf;
|
||||
int[] toMove = toMoveSize == 0 ? Array.Empty<int>() : new int[toMoveSize];
|
||||
|
||||
for (int index = 0; index < toMove.Length; index++)
|
||||
{
|
||||
ToMove[index] = reader.ReadInt32();
|
||||
toMove[index] = reader.ReadInt32();
|
||||
}
|
||||
|
||||
ToMove = toMove;
|
||||
}
|
||||
|
||||
public IpcHandleDesc(int[] copy, int[] move)
|
||||
@@ -57,36 +67,27 @@ namespace Ryujinx.HLE.HOS.Ipc
|
||||
return new IpcHandleDesc(Array.Empty<int>(), handles);
|
||||
}
|
||||
|
||||
public byte[] GetBytes()
|
||||
public RecyclableMemoryStream GetStream()
|
||||
{
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream();
|
||||
|
||||
int word = HasPId ? 1 : 0;
|
||||
|
||||
word |= (ToCopy.Length & 0xf) << 1;
|
||||
word |= (ToMove.Length & 0xf) << 5;
|
||||
|
||||
ms.Write(word);
|
||||
|
||||
if (HasPId)
|
||||
{
|
||||
BinaryWriter writer = new BinaryWriter(ms);
|
||||
|
||||
int word = HasPId ? 1 : 0;
|
||||
|
||||
word |= (ToCopy.Length & 0xf) << 1;
|
||||
word |= (ToMove.Length & 0xf) << 5;
|
||||
|
||||
writer.Write(word);
|
||||
|
||||
if (HasPId)
|
||||
{
|
||||
writer.Write(PId);
|
||||
}
|
||||
|
||||
foreach (int handle in ToCopy)
|
||||
{
|
||||
writer.Write(handle);
|
||||
}
|
||||
|
||||
foreach (int handle in ToMove)
|
||||
{
|
||||
writer.Write(handle);
|
||||
}
|
||||
|
||||
return ms.ToArray();
|
||||
ms.Write(PId);
|
||||
}
|
||||
|
||||
ms.Write(ToCopy);
|
||||
ms.Write(ToMove);
|
||||
|
||||
ms.Position = 0;
|
||||
return ms;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,8 @@
|
||||
using Microsoft.IO;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
@@ -32,9 +36,9 @@ namespace Ryujinx.HLE.HOS.Ipc
|
||||
ObjectIds = new List<int>();
|
||||
}
|
||||
|
||||
public IpcMessage(byte[] data, long cmdPtr) : this()
|
||||
public IpcMessage(ReadOnlySpan<byte> data, long cmdPtr) : this()
|
||||
{
|
||||
using (MemoryStream ms = new MemoryStream(data))
|
||||
using (RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data))
|
||||
{
|
||||
BinaryReader reader = new BinaryReader(ms);
|
||||
|
||||
@@ -114,124 +118,119 @@ namespace Ryujinx.HLE.HOS.Ipc
|
||||
|
||||
for (int index = 0; index < recvListCount; index++)
|
||||
{
|
||||
RecvListBuff.Add(new IpcRecvListBuffDesc(reader));
|
||||
RecvListBuff.Add(new IpcRecvListBuffDesc(reader.ReadUInt64()));
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] GetBytes(long cmdPtr, ulong recvListAddr)
|
||||
public RecyclableMemoryStream GetStream(long cmdPtr, ulong recvListAddr)
|
||||
{
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream();
|
||||
|
||||
int word0;
|
||||
int word1;
|
||||
|
||||
word0 = (int)Type;
|
||||
word0 |= (PtrBuff.Count & 0xf) << 16;
|
||||
word0 |= (SendBuff.Count & 0xf) << 20;
|
||||
word0 |= (ReceiveBuff.Count & 0xf) << 24;
|
||||
word0 |= (ExchangeBuff.Count & 0xf) << 28;
|
||||
|
||||
using RecyclableMemoryStream handleDataStream = HandleDesc?.GetStream();
|
||||
|
||||
int dataLength = RawData?.Length ?? 0;
|
||||
|
||||
dataLength = (dataLength + 3) & ~3;
|
||||
|
||||
int rawLength = dataLength;
|
||||
|
||||
int pad0 = (int)GetPadSize16(cmdPtr + 8 + (handleDataStream?.Length ?? 0) + PtrBuff.Count * 8);
|
||||
|
||||
// Apparently, padding after Raw Data is 16 bytes, however when there is
|
||||
// padding before Raw Data too, we need to subtract the size of this padding.
|
||||
// This is the weirdest padding I've seen so far...
|
||||
int pad1 = 0x10 - pad0;
|
||||
|
||||
dataLength = (dataLength + pad0 + pad1) / 4;
|
||||
|
||||
word1 = (dataLength & 0x3ff) | (2 << 10);
|
||||
|
||||
if (HandleDesc != null)
|
||||
{
|
||||
BinaryWriter writer = new BinaryWriter(ms);
|
||||
|
||||
int word0;
|
||||
int word1;
|
||||
|
||||
word0 = (int)Type;
|
||||
word0 |= (PtrBuff.Count & 0xf) << 16;
|
||||
word0 |= (SendBuff.Count & 0xf) << 20;
|
||||
word0 |= (ReceiveBuff.Count & 0xf) << 24;
|
||||
word0 |= (ExchangeBuff.Count & 0xf) << 28;
|
||||
|
||||
byte[] handleData = Array.Empty<byte>();
|
||||
|
||||
if (HandleDesc != null)
|
||||
{
|
||||
handleData = HandleDesc.GetBytes();
|
||||
}
|
||||
|
||||
int dataLength = RawData?.Length ?? 0;
|
||||
|
||||
dataLength = (dataLength + 3) & ~3;
|
||||
|
||||
int rawLength = dataLength;
|
||||
|
||||
int pad0 = (int)GetPadSize16(cmdPtr + 8 + handleData.Length + PtrBuff.Count * 8);
|
||||
|
||||
// Apparently, padding after Raw Data is 16 bytes, however when there is
|
||||
// padding before Raw Data too, we need to subtract the size of this padding.
|
||||
// This is the weirdest padding I've seen so far...
|
||||
int pad1 = 0x10 - pad0;
|
||||
|
||||
dataLength = (dataLength + pad0 + pad1) / 4;
|
||||
|
||||
word1 = (dataLength & 0x3ff) | (2 << 10);
|
||||
|
||||
if (HandleDesc != null)
|
||||
{
|
||||
word1 |= 1 << 31;
|
||||
}
|
||||
|
||||
writer.Write(word0);
|
||||
writer.Write(word1);
|
||||
writer.Write(handleData);
|
||||
|
||||
for (int index = 0; index < PtrBuff.Count; index++)
|
||||
{
|
||||
writer.Write(PtrBuff[index].GetWord0());
|
||||
writer.Write(PtrBuff[index].GetWord1());
|
||||
}
|
||||
|
||||
ms.Seek(pad0, SeekOrigin.Current);
|
||||
|
||||
if (RawData != null)
|
||||
{
|
||||
writer.Write(RawData);
|
||||
ms.Seek(rawLength - RawData.Length, SeekOrigin.Current);
|
||||
}
|
||||
|
||||
writer.Write(new byte[pad1]);
|
||||
writer.Write(recvListAddr);
|
||||
|
||||
return ms.ToArray();
|
||||
word1 |= 1 << 31;
|
||||
}
|
||||
|
||||
ms.Write(word0);
|
||||
ms.Write(word1);
|
||||
|
||||
if (handleDataStream != null)
|
||||
{
|
||||
ms.Write(handleDataStream);
|
||||
}
|
||||
|
||||
foreach (IpcPtrBuffDesc ptrBuffDesc in PtrBuff)
|
||||
{
|
||||
ms.Write(ptrBuffDesc.GetWord0());
|
||||
ms.Write(ptrBuffDesc.GetWord1());
|
||||
}
|
||||
|
||||
ms.WriteByte(0, pad0);
|
||||
|
||||
if (RawData != null)
|
||||
{
|
||||
ms.Write(RawData);
|
||||
ms.WriteByte(0, rawLength - RawData.Length);
|
||||
}
|
||||
|
||||
ms.WriteByte(0, pad1);
|
||||
|
||||
ms.Write(recvListAddr);
|
||||
|
||||
ms.Position = 0;
|
||||
|
||||
return ms;
|
||||
}
|
||||
|
||||
public byte[] GetBytesTipc()
|
||||
public RecyclableMemoryStream GetStreamTipc()
|
||||
{
|
||||
Debug.Assert(PtrBuff.Count == 0);
|
||||
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream();
|
||||
|
||||
int word0;
|
||||
int word1;
|
||||
|
||||
word0 = (int)Type;
|
||||
word0 |= (SendBuff.Count & 0xf) << 20;
|
||||
word0 |= (ReceiveBuff.Count & 0xf) << 24;
|
||||
word0 |= (ExchangeBuff.Count & 0xf) << 28;
|
||||
|
||||
using RecyclableMemoryStream handleDataStream = HandleDesc?.GetStream();
|
||||
|
||||
int dataLength = RawData?.Length ?? 0;
|
||||
|
||||
dataLength = ((dataLength + 3) & ~3) / 4;
|
||||
|
||||
word1 = (dataLength & 0x3ff);
|
||||
|
||||
if (HandleDesc != null)
|
||||
{
|
||||
BinaryWriter writer = new BinaryWriter(ms);
|
||||
|
||||
int word0;
|
||||
int word1;
|
||||
|
||||
word0 = (int)Type;
|
||||
word0 |= (SendBuff.Count & 0xf) << 20;
|
||||
word0 |= (ReceiveBuff.Count & 0xf) << 24;
|
||||
word0 |= (ExchangeBuff.Count & 0xf) << 28;
|
||||
|
||||
byte[] handleData = Array.Empty<byte>();
|
||||
|
||||
if (HandleDesc != null)
|
||||
{
|
||||
handleData = HandleDesc.GetBytes();
|
||||
}
|
||||
|
||||
int dataLength = RawData?.Length ?? 0;
|
||||
|
||||
dataLength = ((dataLength + 3) & ~3) / 4;
|
||||
|
||||
word1 = (dataLength & 0x3ff);
|
||||
|
||||
if (HandleDesc != null)
|
||||
{
|
||||
word1 |= 1 << 31;
|
||||
}
|
||||
|
||||
writer.Write(word0);
|
||||
writer.Write(word1);
|
||||
writer.Write(handleData);
|
||||
|
||||
if (RawData != null)
|
||||
{
|
||||
writer.Write(RawData);
|
||||
}
|
||||
|
||||
return ms.ToArray();
|
||||
word1 |= 1 << 31;
|
||||
}
|
||||
|
||||
ms.Write(word0);
|
||||
ms.Write(word1);
|
||||
|
||||
if (handleDataStream != null)
|
||||
{
|
||||
ms.Write(handleDataStream);
|
||||
}
|
||||
|
||||
if (RawData != null)
|
||||
{
|
||||
ms.Write(RawData);
|
||||
}
|
||||
|
||||
return ms;
|
||||
}
|
||||
|
||||
private long GetPadSize16(long position)
|
||||
|
@@ -13,13 +13,11 @@ namespace Ryujinx.HLE.HOS.Ipc
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public IpcRecvListBuffDesc(BinaryReader reader)
|
||||
public IpcRecvListBuffDesc(ulong packedValue)
|
||||
{
|
||||
ulong value = reader.ReadUInt64();
|
||||
Position = packedValue & 0xffffffffffff;
|
||||
|
||||
Position = value & 0xffffffffffff;
|
||||
|
||||
Size = (ushort)(value >> 48);
|
||||
Size = (ushort)(packedValue >> 48);
|
||||
}
|
||||
}
|
||||
}
|
@@ -16,7 +16,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
||||
|
||||
public WaitingObject(IKFutureSchedulerObject schedulerObj, long timePoint)
|
||||
{
|
||||
Object = schedulerObj;
|
||||
Object = schedulerObj;
|
||||
TimePoint = timePoint;
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
||||
private bool _keepRunning;
|
||||
private long _enforceWakeupFromSpinWait;
|
||||
|
||||
private const long NanosecondsPerSecond = 1000000000L;
|
||||
private const long NanosecondsPerMillisecond = 1000000L;
|
||||
|
||||
public KTimeManager(KernelContext context)
|
||||
{
|
||||
_context = context;
|
||||
@@ -55,7 +58,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
||||
{
|
||||
_waitingObjects.Add(new WaitingObject(schedulerObj, timePoint));
|
||||
|
||||
if (timeout < 1000000)
|
||||
if (timeout < NanosecondsPerMillisecond)
|
||||
{
|
||||
Interlocked.Exchange(ref _enforceWakeupFromSpinWait, 1);
|
||||
}
|
||||
@@ -142,7 +145,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
||||
private WaitingObject GetNextWaitingObject()
|
||||
{
|
||||
WaitingObject selected = null;
|
||||
|
||||
|
||||
long lowestTimePoint = long.MaxValue;
|
||||
|
||||
for (int index = _waitingObjects.Count - 1; index >= 0; index--)
|
||||
@@ -161,7 +164,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
||||
|
||||
public static long ConvertNanosecondsToMilliseconds(long time)
|
||||
{
|
||||
time /= 1000000;
|
||||
time /= NanosecondsPerMillisecond;
|
||||
|
||||
if ((ulong)time > int.MaxValue)
|
||||
{
|
||||
@@ -173,18 +176,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
||||
|
||||
public static long ConvertMillisecondsToNanoseconds(long time)
|
||||
{
|
||||
return time * 1000000;
|
||||
return time * NanosecondsPerMillisecond;
|
||||
}
|
||||
|
||||
public static long ConvertNanosecondsToHostTicks(long ns)
|
||||
{
|
||||
long nsDiv = ns / 1000000000;
|
||||
long nsMod = ns % 1000000000;
|
||||
long tickDiv = PerformanceCounter.TicksPerSecond / 1000000000;
|
||||
long tickMod = PerformanceCounter.TicksPerSecond % 1000000000;
|
||||
long nsDiv = ns / NanosecondsPerSecond;
|
||||
long nsMod = ns % NanosecondsPerSecond;
|
||||
long tickDiv = PerformanceCounter.TicksPerSecond / NanosecondsPerSecond;
|
||||
long tickMod = PerformanceCounter.TicksPerSecond % NanosecondsPerSecond;
|
||||
|
||||
long baseTicks = (nsMod * tickMod + PerformanceCounter.TicksPerSecond - 1) / 1000000000;
|
||||
return (nsDiv * tickDiv) * 1000000000 + nsDiv * tickMod + nsMod * tickDiv + baseTicks;
|
||||
long baseTicks = (nsMod * tickMod + PerformanceCounter.TicksPerSecond - 1) / NanosecondsPerSecond;
|
||||
return (nsDiv * tickDiv) * NanosecondsPerSecond + nsDiv * tickMod + nsMod * tickDiv + baseTicks;
|
||||
}
|
||||
|
||||
public static long ConvertGuestTicksToNanoseconds(long ticks)
|
||||
|
@@ -2,7 +2,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
{
|
||||
internal class ProcessTamperInfo
|
||||
class ProcessTamperInfo
|
||||
{
|
||||
public KProcess Process { get; }
|
||||
public IEnumerable<string> BuildIds { get; }
|
||||
|
@@ -553,7 +553,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||
|
||||
KProcess currentProcess = KernelStatic.GetCurrentProcess();
|
||||
|
||||
KSynchronizationObject[] syncObjs = new KSynchronizationObject[handles.Length];
|
||||
KSynchronizationObject[] syncObjs = handles.Length == 0 ? Array.Empty<KSynchronizationObject>() : new KSynchronizationObject[handles.Length];
|
||||
|
||||
for (int index = 0; index < handles.Length; index++)
|
||||
{
|
||||
|
@@ -5,11 +5,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
class KPriorityQueue
|
||||
{
|
||||
private LinkedList<KThread>[][] _scheduledThreadsPerPrioPerCore;
|
||||
private LinkedList<KThread>[][] _suggestedThreadsPerPrioPerCore;
|
||||
private readonly LinkedList<KThread>[][] _scheduledThreadsPerPrioPerCore;
|
||||
private readonly LinkedList<KThread>[][] _suggestedThreadsPerPrioPerCore;
|
||||
|
||||
private long[] _scheduledPrioritiesPerCore;
|
||||
private long[] _suggestedPrioritiesPerCore;
|
||||
private readonly long[] _scheduledPrioritiesPerCore;
|
||||
private readonly long[] _suggestedPrioritiesPerCore;
|
||||
|
||||
public KPriorityQueue()
|
||||
{
|
||||
@@ -32,43 +32,134 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
_suggestedPrioritiesPerCore = new long[KScheduler.CpuCoresCount];
|
||||
}
|
||||
|
||||
public IEnumerable<KThread> SuggestedThreads(int core)
|
||||
public readonly ref struct KThreadEnumerable
|
||||
{
|
||||
return Iterate(_suggestedThreadsPerPrioPerCore, _suggestedPrioritiesPerCore, core);
|
||||
}
|
||||
readonly LinkedList<KThread>[][] _listPerPrioPerCore;
|
||||
readonly long[] _prios;
|
||||
readonly int _core;
|
||||
|
||||
public IEnumerable<KThread> ScheduledThreads(int core)
|
||||
{
|
||||
return Iterate(_scheduledThreadsPerPrioPerCore, _scheduledPrioritiesPerCore, core);
|
||||
}
|
||||
|
||||
private IEnumerable<KThread> Iterate(LinkedList<KThread>[][] listPerPrioPerCore, long[] prios, int core)
|
||||
{
|
||||
long prioMask = prios[core];
|
||||
|
||||
int prio = BitOperations.TrailingZeroCount(prioMask);
|
||||
|
||||
prioMask &= ~(1L << prio);
|
||||
|
||||
while (prio < KScheduler.PrioritiesCount)
|
||||
public KThreadEnumerable(LinkedList<KThread>[][] listPerPrioPerCore, long[] prios, int core)
|
||||
{
|
||||
LinkedList<KThread> list = listPerPrioPerCore[prio][core];
|
||||
_listPerPrioPerCore = listPerPrioPerCore;
|
||||
_prios = prios;
|
||||
_core = core;
|
||||
}
|
||||
|
||||
LinkedListNode<KThread> node = list.First;
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
return new Enumerator(_listPerPrioPerCore, _prios, _core);
|
||||
}
|
||||
|
||||
while (node != null)
|
||||
public ref struct Enumerator
|
||||
{
|
||||
private readonly LinkedList<KThread>[][] _listPerPrioPerCore;
|
||||
private readonly int _core;
|
||||
private long _prioMask;
|
||||
private int _prio;
|
||||
private LinkedList<KThread> _list;
|
||||
private LinkedListNode<KThread> _node;
|
||||
|
||||
public Enumerator(LinkedList<KThread>[][] listPerPrioPerCore, long[] prios, int core)
|
||||
{
|
||||
yield return node.Value;
|
||||
|
||||
node = node.Next;
|
||||
_listPerPrioPerCore = listPerPrioPerCore;
|
||||
_core = core;
|
||||
_prioMask = prios[core];
|
||||
_prio = BitOperations.TrailingZeroCount(_prioMask);
|
||||
_prioMask &= ~(1L << _prio);
|
||||
}
|
||||
|
||||
prio = BitOperations.TrailingZeroCount(prioMask);
|
||||
public KThread Current => _node?.Value;
|
||||
|
||||
prioMask &= ~(1L << prio);
|
||||
public bool MoveNext()
|
||||
{
|
||||
_node = _node?.Next;
|
||||
|
||||
if (_node == null)
|
||||
{
|
||||
if (!MoveNextListAndFirstNode())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return _node != null;
|
||||
}
|
||||
|
||||
private bool MoveNextListAndFirstNode()
|
||||
{
|
||||
if (_prio < KScheduler.PrioritiesCount)
|
||||
{
|
||||
_list = _listPerPrioPerCore[_prio][_core];
|
||||
|
||||
_node = _list.First;
|
||||
|
||||
_prio = BitOperations.TrailingZeroCount(_prioMask);
|
||||
|
||||
_prioMask &= ~(1L << _prio);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_list = null;
|
||||
_node = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public KThreadEnumerable ScheduledThreads(int core)
|
||||
{
|
||||
return new KThreadEnumerable(_scheduledThreadsPerPrioPerCore, _scheduledPrioritiesPerCore, core);
|
||||
}
|
||||
|
||||
public KThreadEnumerable SuggestedThreads(int core)
|
||||
{
|
||||
return new KThreadEnumerable(_suggestedThreadsPerPrioPerCore, _suggestedPrioritiesPerCore, core);
|
||||
}
|
||||
|
||||
public KThread ScheduledThreadsFirstOrDefault(int core)
|
||||
{
|
||||
return ScheduledThreadsElementAtOrDefault(core, 0);
|
||||
}
|
||||
|
||||
public KThread ScheduledThreadsElementAtOrDefault(int core, int index)
|
||||
{
|
||||
int currentIndex = 0;
|
||||
foreach (var scheduledThread in ScheduledThreads(core))
|
||||
{
|
||||
if (currentIndex == index)
|
||||
{
|
||||
return scheduledThread;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public KThread ScheduledThreadsWithDynamicPriorityFirstOrDefault(int core, int dynamicPriority)
|
||||
{
|
||||
foreach (var scheduledThread in ScheduledThreads(core))
|
||||
{
|
||||
if (scheduledThread.DynamicPriority == dynamicPriority)
|
||||
{
|
||||
return scheduledThread;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool HasScheduledThreads(int core)
|
||||
{
|
||||
return ScheduledThreadsFirstOrDefault(core) != null;
|
||||
}
|
||||
|
||||
public void TransferToCore(int prio, int dstCore, KThread thread)
|
||||
{
|
||||
int srcCore = thread.ActiveCore;
|
||||
|
@@ -1,8 +1,6 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
|
||||
@@ -17,6 +15,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
private static readonly int[] PreemptionPriorities = new int[] { 59, 59, 59, 63 };
|
||||
|
||||
private static readonly int[] _srcCoresHighestPrioThreads = new int[CpuCoresCount];
|
||||
|
||||
private readonly KernelContext _context;
|
||||
private readonly int _coreId;
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
for (int core = 0; core < CpuCoresCount; core++)
|
||||
{
|
||||
KThread thread = context.PriorityQueue.ScheduledThreads(core).FirstOrDefault();
|
||||
KThread thread = context.PriorityQueue.ScheduledThreadsFirstOrDefault(core);
|
||||
|
||||
if (thread != null &&
|
||||
thread.Owner != null &&
|
||||
@@ -115,12 +115,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
// If the core is not idle (there's already a thread running on it),
|
||||
// then we don't need to attempt load balancing.
|
||||
if (context.PriorityQueue.ScheduledThreads(core).Any())
|
||||
if (context.PriorityQueue.HasScheduledThreads(core))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int[] srcCoresHighestPrioThreads = new int[CpuCoresCount];
|
||||
Array.Fill(_srcCoresHighestPrioThreads, 0);
|
||||
|
||||
int srcCoresHighestPrioThreadsCount = 0;
|
||||
|
||||
@@ -136,7 +136,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
break;
|
||||
}
|
||||
|
||||
srcCoresHighestPrioThreads[srcCoresHighestPrioThreadsCount++] = suggested.ActiveCore;
|
||||
_srcCoresHighestPrioThreads[srcCoresHighestPrioThreadsCount++] = suggested.ActiveCore;
|
||||
}
|
||||
|
||||
// Not yet selected candidate found.
|
||||
@@ -158,9 +158,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
// (the first one that doesn't make the source core idle if moved).
|
||||
for (int index = 0; index < srcCoresHighestPrioThreadsCount; index++)
|
||||
{
|
||||
int srcCore = srcCoresHighestPrioThreads[index];
|
||||
int srcCore = _srcCoresHighestPrioThreads[index];
|
||||
|
||||
KThread src = context.PriorityQueue.ScheduledThreads(srcCore).ElementAtOrDefault(1);
|
||||
KThread src = context.PriorityQueue.ScheduledThreadsElementAtOrDefault(srcCore, 1);
|
||||
|
||||
if (src != null)
|
||||
{
|
||||
@@ -422,9 +422,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
private static void RotateScheduledQueue(KernelContext context, int core, int prio)
|
||||
{
|
||||
IEnumerable<KThread> scheduledThreads = context.PriorityQueue.ScheduledThreads(core);
|
||||
|
||||
KThread selectedThread = scheduledThreads.FirstOrDefault(x => x.DynamicPriority == prio);
|
||||
KThread selectedThread = context.PriorityQueue.ScheduledThreadsWithDynamicPriorityFirstOrDefault(core, prio);
|
||||
KThread nextThread = null;
|
||||
|
||||
// Yield priority queue.
|
||||
@@ -433,14 +431,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
nextThread = context.PriorityQueue.Reschedule(prio, core, selectedThread);
|
||||
}
|
||||
|
||||
IEnumerable<KThread> SuitableCandidates()
|
||||
static KThread FirstSuitableCandidateOrDefault(KernelContext context, int core, KThread selectedThread, KThread nextThread, Predicate< KThread> predicate)
|
||||
{
|
||||
foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core))
|
||||
{
|
||||
int suggestedCore = suggested.ActiveCore;
|
||||
if (suggestedCore >= 0)
|
||||
{
|
||||
KThread selectedSuggestedCore = context.PriorityQueue.ScheduledThreads(suggestedCore).FirstOrDefault();
|
||||
KThread selectedSuggestedCore = context.PriorityQueue.ScheduledThreadsFirstOrDefault(suggestedCore);
|
||||
|
||||
if (selectedSuggestedCore == suggested || (selectedSuggestedCore != null && selectedSuggestedCore.DynamicPriority < 2))
|
||||
{
|
||||
@@ -453,14 +451,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
nextThread == null ||
|
||||
nextThread.LastScheduledTime >= suggested.LastScheduledTime)
|
||||
{
|
||||
yield return suggested;
|
||||
if (predicate(suggested))
|
||||
{
|
||||
return suggested;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Select candidate threads that could run on this core.
|
||||
// Only take into account threads that are not yet selected.
|
||||
KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority == prio);
|
||||
KThread dst = FirstSuitableCandidateOrDefault(context, core, selectedThread, nextThread, x => x.DynamicPriority == prio);
|
||||
|
||||
if (dst != null)
|
||||
{
|
||||
@@ -469,11 +472,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
// If the priority of the currently selected thread is lower or same as the preemption priority,
|
||||
// then try to migrate a thread with lower priority.
|
||||
KThread bestCandidate = context.PriorityQueue.ScheduledThreads(core).FirstOrDefault();
|
||||
KThread bestCandidate = context.PriorityQueue.ScheduledThreadsFirstOrDefault(core);
|
||||
|
||||
if (bestCandidate != null && bestCandidate.DynamicPriority >= prio)
|
||||
{
|
||||
dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority < bestCandidate.DynamicPriority);
|
||||
dst = FirstSuitableCandidateOrDefault(context, core, selectedThread, nextThread, x => x.DynamicPriority < bestCandidate.DynamicPriority);
|
||||
|
||||
if (dst != null)
|
||||
{
|
||||
@@ -534,7 +537,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
// Move current thread to the end of the queue.
|
||||
KThread nextThread = context.PriorityQueue.Reschedule(prio, core, currentThread);
|
||||
|
||||
IEnumerable<KThread> SuitableCandidates()
|
||||
static KThread FirstSuitableCandidateOrDefault(KernelContext context, int core, KThread nextThread, int lessThanOrEqualPriority)
|
||||
{
|
||||
foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core))
|
||||
{
|
||||
@@ -554,12 +557,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
if (suggested.LastScheduledTime <= nextThread.LastScheduledTime ||
|
||||
suggested.DynamicPriority < nextThread.DynamicPriority)
|
||||
{
|
||||
yield return suggested;
|
||||
if (suggested.DynamicPriority <= lessThanOrEqualPriority)
|
||||
{
|
||||
return suggested;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority <= prio);
|
||||
KThread dst = FirstSuitableCandidateOrDefault(context, core, nextThread, prio);
|
||||
|
||||
if (dst != null)
|
||||
{
|
||||
@@ -596,7 +604,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
context.PriorityQueue.TransferToCore(currentThread.DynamicPriority, -1, currentThread);
|
||||
|
||||
if (!context.PriorityQueue.ScheduledThreads(core).Any())
|
||||
if (!context.PriorityQueue.HasScheduledThreads(core))
|
||||
{
|
||||
KThread selectedThread = null;
|
||||
|
||||
@@ -609,7 +617,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
continue;
|
||||
}
|
||||
|
||||
KThread firstCandidate = context.PriorityQueue.ScheduledThreads(suggestedCore).FirstOrDefault();
|
||||
KThread firstCandidate = context.PriorityQueue.ScheduledThreadsFirstOrDefault(suggestedCore);
|
||||
|
||||
if (firstCandidate == suggested)
|
||||
{
|
||||
|
@@ -59,7 +59,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
}
|
||||
else
|
||||
{
|
||||
LinkedListNode<KThread>[] syncNodes = new LinkedListNode<KThread>[syncObjs.Length];
|
||||
LinkedListNode<KThread>[] syncNodes = syncObjs.Length == 0 ? Array.Empty<LinkedListNode<KThread>>() : new LinkedListNode<KThread>[syncObjs.Length];
|
||||
|
||||
for (int index = 0; index < syncObjs.Length; index++)
|
||||
{
|
||||
|
@@ -10,6 +10,7 @@ using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.HLE.Loaders.Executables;
|
||||
using Ryujinx.HLE.Loaders.Mods;
|
||||
using Ryujinx.HLE.Loaders.Processes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
@@ -547,7 +548,7 @@ namespace Ryujinx.HLE.HOS
|
||||
return modLoadResult;
|
||||
}
|
||||
|
||||
if (nsos.Length != ApplicationLoader.ExeFsPrefixes.Length)
|
||||
if (nsos.Length != ProcessConst.ExeFsPrefixes.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("NSO Count is incorrect");
|
||||
}
|
||||
@@ -556,9 +557,9 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
foreach (var mod in exeMods)
|
||||
{
|
||||
for (int i = 0; i < ApplicationLoader.ExeFsPrefixes.Length; ++i)
|
||||
for (int i = 0; i < ProcessConst.ExeFsPrefixes.Length; ++i)
|
||||
{
|
||||
var nsoName = ApplicationLoader.ExeFsPrefixes[i];
|
||||
var nsoName = ProcessConst.ExeFsPrefixes[i];
|
||||
|
||||
FileInfo nsoFile = new FileInfo(Path.Combine(mod.Path.FullName, nsoName));
|
||||
if (nsoFile.Exists)
|
||||
@@ -596,7 +597,7 @@ namespace Ryujinx.HLE.HOS
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = ApplicationLoader.ExeFsPrefixes.Length - 1; i >= 0; --i)
|
||||
for (int i = ProcessConst.ExeFsPrefixes.Length - 1; i >= 0; --i)
|
||||
{
|
||||
if (modLoadResult.Stubs[1 << i] && !modLoadResult.Replaces[1 << i]) // Prioritizes replacements over stubs
|
||||
{
|
||||
|
@@ -190,7 +190,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
// TODO: Account actually calls nn::arp::detail::IReader::GetApplicationControlProperty() with the current Pid and store the result (NACP file) internally.
|
||||
// But since we use LibHac and we load one Application at a time, it's not necessary.
|
||||
|
||||
context.ResponseData.Write((byte)context.Device.Application.ControlData.Value.UserAccountSwitchLock);
|
||||
context.ResponseData.Write((byte)context.Device.Processes.ActiveApplication.ApplicationControlProperties.UserAccountSwitchLock);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAcc);
|
||||
|
||||
|
@@ -9,7 +9,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
||||
|
||||
public ILibraryAppletSelfAccessor(ServiceCtx context)
|
||||
{
|
||||
if (context.Device.Application.TitleId == 0x0100000000001009)
|
||||
if (context.Device.Processes.ActiveApplication.ProgramId == 0x0100000000001009)
|
||||
{
|
||||
// Create MiiEdit data.
|
||||
_appletStandalone = new AppletStandalone()
|
||||
@@ -25,7 +25,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException($"{context.Device.Application.TitleId} applet is not implemented.");
|
||||
throw new NotImplementedException($"{context.Device.Processes.ActiveApplication.ProgramId} applet is not implemented.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage
|
||||
@@ -10,7 +11,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage
|
||||
public static byte[] MakeLaunchParams(UserProfile userProfile)
|
||||
{
|
||||
// Size needs to be at least 0x88 bytes otherwise application errors.
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
using (MemoryStream ms = MemoryStreamManager.Shared.GetStream())
|
||||
{
|
||||
BinaryWriter writer = new BinaryWriter(ms);
|
||||
|
||||
|
@@ -115,28 +115,12 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
||||
Uid userId = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid();
|
||||
|
||||
// Mask out the low nibble of the program ID to get the application ID
|
||||
ApplicationId applicationId = new ApplicationId(context.Device.Application.TitleId & ~0xFul);
|
||||
ApplicationId applicationId = new ApplicationId(context.Device.Processes.ActiveApplication.ProgramId & ~0xFul);
|
||||
|
||||
BlitStruct<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData;
|
||||
|
||||
ref ApplicationControlProperty control = ref controlHolder.Value;
|
||||
|
||||
if (LibHac.Common.Utilities.IsZeros(controlHolder.ByteSpan))
|
||||
{
|
||||
// If the current application doesn't have a loaded control property, create a dummy one
|
||||
// and set the savedata sizes so a user savedata will be created.
|
||||
control = ref new BlitStruct<ApplicationControlProperty>(1).Value;
|
||||
|
||||
// The set sizes don't actually matter as long as they're non-zero because we use directory savedata.
|
||||
control.UserAccountSaveDataSize = 0x4000;
|
||||
control.UserAccountSaveDataJournalSize = 0x4000;
|
||||
|
||||
Logger.Warning?.Print(LogClass.ServiceAm,
|
||||
"No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
|
||||
}
|
||||
ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
|
||||
|
||||
LibHac.HorizonClient hos = context.Device.System.LibHacHorizonManager.AmClient;
|
||||
LibHac.Result result = hos.Fs.EnsureApplicationSaveData(out long requiredSize, applicationId, in control, in userId);
|
||||
LibHac.Result result = hos.Fs.EnsureApplicationSaveData(out long requiredSize, applicationId, in nacp, in userId);
|
||||
|
||||
context.ResponseData.Write(requiredSize);
|
||||
|
||||
@@ -153,7 +137,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
||||
// TODO: When above calls are implemented, switch to using ns:am
|
||||
|
||||
long desiredLanguageCode = context.Device.System.State.DesiredLanguageCode;
|
||||
int supportedLanguages = (int)context.Device.Application.ControlData.Value.SupportedLanguageFlag;
|
||||
int supportedLanguages = (int)context.Device.Processes.ActiveApplication.ApplicationControlProperties.SupportedLanguageFlag;
|
||||
int firstSupported = BitOperations.TrailingZeroCount(supportedLanguages);
|
||||
|
||||
if (firstSupported > (int)TitleLanguage.BrazilianPortuguese)
|
||||
@@ -196,7 +180,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
||||
public ResultCode GetDisplayVersion(ServiceCtx context)
|
||||
{
|
||||
// If an NACP isn't found, the buffer will be all '\0' which seems to be the correct implementation.
|
||||
context.ResponseData.Write(context.Device.Application.ControlData.Value.DisplayVersion);
|
||||
context.ResponseData.Write(context.Device.Processes.ActiveApplication.ApplicationControlProperties.DisplayVersion);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
@@ -251,13 +235,12 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
||||
long journalSize = context.RequestData.ReadInt64();
|
||||
|
||||
// Mask out the low nibble of the program ID to get the application ID
|
||||
ApplicationId applicationId = new ApplicationId(context.Device.Application.TitleId & ~0xFul);
|
||||
ApplicationId applicationId = new ApplicationId(context.Device.Processes.ActiveApplication.ProgramId & ~0xFul);
|
||||
|
||||
BlitStruct<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData;
|
||||
ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
|
||||
|
||||
LibHac.Result result = _horizon.Fs.CreateApplicationCacheStorage(out long requiredSize,
|
||||
out CacheStorageTargetMedia storageTarget, applicationId, in controlHolder.Value, index, saveSize,
|
||||
journalSize);
|
||||
out CacheStorageTargetMedia storageTarget, applicationId, in nacp, index, saveSize, journalSize);
|
||||
|
||||
if (result.IsFailure()) return (ResultCode)result.Value;
|
||||
|
||||
@@ -677,7 +660,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
||||
throw new InvalidSystemResourceException($"JIT (010000000000003B) system title not found! The JIT will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx#requirements for more information)");
|
||||
}
|
||||
|
||||
context.Device.Application.LoadServiceNca(filePath);
|
||||
context.Device.LoadNca(filePath);
|
||||
|
||||
// FIXME: Most likely not how this should be done?
|
||||
while (!context.Device.System.SmRegistry.IsServiceRegistered("jit:u"))
|
||||
|
@@ -33,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp
|
||||
|
||||
return new ApplicationLaunchProperty
|
||||
{
|
||||
TitleId = context.Device.Application.TitleId,
|
||||
TitleId = context.Device.Processes.ActiveApplication.ProgramId,
|
||||
Version = 0x00,
|
||||
BaseGameStorageId = (byte)StorageId.BuiltInSystem,
|
||||
UpdateGameStorageId = (byte)StorageId.None
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user