Compare commits
47 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
7d8e198c33 | ||
|
3d98e1361b | ||
|
141cf61ff7 | ||
|
3fe3598d41 | ||
|
59cdf310bd | ||
|
4e34170a84 | ||
|
d540af5dc0 | ||
|
f7c7b66fc0 | ||
|
28ba55598d | ||
|
9719b6a112 | ||
|
f70236f947 | ||
|
eafadf10c7 | ||
|
9b06ee7736 | ||
|
baba2c2467 | ||
|
286e5d39b2 | ||
|
dc529c1181 | ||
|
c7cf1cbc35 | ||
|
d8e487d018 | ||
|
5fdc46ac7f | ||
|
1e5b45f580 | ||
|
62585755fd | ||
|
56621615b1 | ||
|
2099a3e84b | ||
|
7d26e4ac7b | ||
|
8d41402fa6 | ||
|
5af8ce7c38 | ||
|
77c4291c34 | ||
|
6e92b7a378 | ||
|
9b852c7481 | ||
|
c40c3905e2 | ||
|
a6cd044f0f | ||
|
f5a1de6ac5 | ||
|
2aeb5b00e3 | ||
|
60ba7b71f2 | ||
|
7c1d2bbb98 | ||
|
beacf8c1c8 | ||
|
0dbe45ae37 | ||
|
2b50e52e48 | ||
|
49eadbc209 | ||
|
2df16ded9b | ||
|
e43390c723 | ||
|
5af1327068 | ||
|
88a8d1e567 | ||
|
bf77d1cab9 | ||
|
1ca0517c99 | ||
|
599d485bff | ||
|
60e16c15b6 |
24
.github/dependabot.yml
vendored
Normal file
24
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
labels:
|
||||
- "infra"
|
||||
reviewers:
|
||||
- marysaka
|
||||
commit-message:
|
||||
prefix: "ci"
|
||||
|
||||
- package-ecosystem: nuget
|
||||
directory: /
|
||||
open-pull-requests-limit: 5
|
||||
schedule:
|
||||
interval: daily
|
||||
labels:
|
||||
- "infra"
|
||||
reviewers:
|
||||
- marysaka
|
||||
commit-message:
|
||||
prefix: nuget
|
15
.github/workflows/build.yml
vendored
15
.github/workflows/build.yml
vendored
@@ -48,21 +48,22 @@ jobs:
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
RYUJINX_BASE_VERSION: "1.1.0"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-dotnet@v1
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 6.0.x
|
||||
- name: Ensure NuGet Source
|
||||
uses: fabriciomurta/ensure-nuget-source@v1
|
||||
- name: Get git short hash
|
||||
id: git_short_hash
|
||||
run: echo "::set-output name=result::$(git rev-parse --short "${{ github.sha }}")"
|
||||
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
- name: Clear
|
||||
run: dotnet clean && dotnet nuget locals all --clear
|
||||
- name: Build
|
||||
run: dotnet build -c "${{ matrix.configuration }}" /p:Version="${{ env.RYUJINX_BASE_VERSION }}" /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER
|
||||
- name: Test
|
||||
run: dotnet test -c "${{ matrix.configuration }}"
|
||||
run: dotnet test --no-build -c "${{ matrix.configuration }}"
|
||||
- name: Publish Ryujinx
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish /p:Version="${{ env.RYUJINX_BASE_VERSION }}" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx --self-contained
|
||||
if: github.event_name == 'pull_request'
|
||||
@@ -73,19 +74,19 @@ jobs:
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava /p:Version="1.0.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Ava
|
||||
if: github.event_name == 'pull_request'
|
||||
- name: Upload Ryujinx artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
||||
path: publish
|
||||
if: github.event_name == 'pull_request'
|
||||
- name: Upload Ryujinx.Headless.SDL2 artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
||||
path: publish_sdl2_headless
|
||||
if: github.event_name == 'pull_request'
|
||||
- name: Upload Ryujinx.Ava artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
||||
path: publish_ava
|
||||
|
12
.github/workflows/nightly_pr_comment.yml
vendored
12
.github/workflows/nightly_pr_comment.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v3
|
||||
- uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const {owner, repo} = context.repo;
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
const pull_head_sha = '${{github.event.workflow_run.head_sha}}';
|
||||
|
||||
const issue_number = await (async () => {
|
||||
const pulls = await github.pulls.list({owner, repo});
|
||||
const pulls = await github.rest.pulls.list({owner, repo});
|
||||
for await (const {data} of github.paginate.iterator(pulls)) {
|
||||
for (const pull of data) {
|
||||
if (pull.head.sha === pull_head_sha) {
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
return core.error(`No matching pull request found`);
|
||||
}
|
||||
|
||||
const {data: {artifacts}} = await github.actions.listWorkflowRunArtifacts({owner, repo, run_id});
|
||||
const {data: {artifacts}} = await github.rest.actions.listWorkflowRunArtifacts({owner, repo, run_id});
|
||||
if (!artifacts.length) {
|
||||
return core.error(`No artifacts found`);
|
||||
}
|
||||
@@ -57,12 +57,12 @@ jobs:
|
||||
body += hidden_headless_artifacts;
|
||||
body += hidden_debug_artifacts;
|
||||
|
||||
const {data: comments} = await github.issues.listComments({repo, owner, issue_number});
|
||||
const {data: comments} = await github.rest.issues.listComments({repo, owner, issue_number});
|
||||
const existing_comment = comments.find((c) => c.user.login === 'github-actions[bot]');
|
||||
if (existing_comment) {
|
||||
core.info(`Updating comment ${existing_comment.id}`);
|
||||
await github.issues.updateComment({repo, owner, comment_id: existing_comment.id, body});
|
||||
await github.rest.issues.updateComment({repo, owner, comment_id: existing_comment.id, body});
|
||||
} else {
|
||||
core.info(`Creating a comment`);
|
||||
await github.issues.createComment({repo, owner, issue_number, body});
|
||||
await github.rest.issues.createComment({repo, owner, issue_number, body});
|
||||
}
|
||||
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -25,8 +25,8 @@ jobs:
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "release-channel-master"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-dotnet@v1
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 6.0.x
|
||||
- name: Ensure NuGet Source
|
||||
@@ -36,8 +36,8 @@ jobs:
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "::set-output name=build_version::${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}"
|
||||
echo "::set-output name=git_short_hash::$(git rev-parse --short "${{ github.sha }}")"
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
- name: Configure for release
|
||||
run: |
|
||||
|
44
ARMeilleure/Decoders/OpCode32SimdCvtTB.cs
Normal file
44
ARMeilleure/Decoders/OpCode32SimdCvtTB.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCode32SimdCvtTB : OpCode32, IOpCode32Simd
|
||||
{
|
||||
public int Vd { get; }
|
||||
public int Vm { get; }
|
||||
public bool Op { get; } // Convert to Half / Convert from Half
|
||||
public bool T { get; } // Top / Bottom
|
||||
public int Size { get; } // Double / Single
|
||||
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCvtTB(inst, address, opCode, false);
|
||||
public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCvtTB(inst, address, opCode, true);
|
||||
|
||||
public OpCode32SimdCvtTB(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode)
|
||||
{
|
||||
IsThumb = isThumb;
|
||||
|
||||
Op = ((opCode >> 16) & 0x1) != 0;
|
||||
T = ((opCode >> 7) & 0x1) != 0;
|
||||
Size = ((opCode >> 8) & 0x1);
|
||||
|
||||
RegisterSize = Size == 1 ? RegisterSize.Int64 : RegisterSize.Int32;
|
||||
|
||||
if (Size == 1)
|
||||
{
|
||||
if (Op)
|
||||
{
|
||||
Vm = ((opCode >> 1) & 0x10) | ((opCode >> 0) & 0xf);
|
||||
Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e);
|
||||
}
|
||||
else
|
||||
{
|
||||
Vm = ((opCode >> 5) & 0x1) | ((opCode << 1) & 0x1e);
|
||||
Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Vm = ((opCode >> 5) & 0x1) | ((opCode << 1) & 0x1e);
|
||||
Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -828,6 +828,7 @@ namespace ARMeilleure.Decoders
|
||||
SetVfp("<<<<11101x11110xxxxx101x11x0xxxx", InstName.Vcvt, InstEmit32.Vcvt_FI, OpCode32SimdCvtFI.Create, OpCode32SimdCvtFI.CreateT32); // FP32 to int.
|
||||
SetVfp("<<<<11101x111000xxxx101xx1x0xxxx", InstName.Vcvt, InstEmit32.Vcvt_FI, OpCode32SimdCvtFI.Create, OpCode32SimdCvtFI.CreateT32); // Int to FP32.
|
||||
SetVfp("111111101x1111xxxxxx101xx1x0xxxx", InstName.Vcvt, InstEmit32.Vcvt_RM, OpCode32SimdCvtFI.Create, OpCode32SimdCvtFI.CreateT32); // The many FP32 to int encodings (fp).
|
||||
SetVfp("<<<<11101x11001xxxxx101xx1x0xxxx", InstName.Vcvt, InstEmit32.Vcvt_TB, OpCode32SimdCvtTB.Create, OpCode32SimdCvtTB.CreateT32);
|
||||
SetVfp("<<<<11101x00xxxxxxxx101xx0x0xxxx", InstName.Vdiv, InstEmit32.Vdiv_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32);
|
||||
SetVfp("<<<<11101xx0xxxxxxxx1011x0x10000", InstName.Vdup, InstEmit32.Vdup, OpCode32SimdDupGP.Create, OpCode32SimdDupGP.CreateT32);
|
||||
SetVfp("<<<<11101x10xxxxxxxx101xx0x0xxxx", InstName.Vfma, InstEmit32.Vfma_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32);
|
||||
|
@@ -1617,18 +1617,32 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
public static void Frinta_S(ArmEmitterContext context)
|
||||
{
|
||||
EmitScalarUnaryOpF(context, (op1) =>
|
||||
if (Optimizations.UseSse41)
|
||||
{
|
||||
return EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1);
|
||||
});
|
||||
EmitSse41ScalarRoundOpF(context, FPRoundingMode.ToNearestAway);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitScalarUnaryOpF(context, (op1) =>
|
||||
{
|
||||
return EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void Frinta_V(ArmEmitterContext context)
|
||||
{
|
||||
EmitVectorUnaryOpF(context, (op1) =>
|
||||
if (Optimizations.UseSse41)
|
||||
{
|
||||
return EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1);
|
||||
});
|
||||
EmitSse41VectorRoundOpF(context, FPRoundingMode.ToNearestAway);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorUnaryOpF(context, (op1) =>
|
||||
{
|
||||
return EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void Frinti_S(ArmEmitterContext context)
|
||||
@@ -3516,9 +3530,18 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
Operand n = GetVec(op.Rn);
|
||||
|
||||
Intrinsic inst = (op.Size & 1) != 0 ? Intrinsic.X86Roundsd : Intrinsic.X86Roundss;
|
||||
Operand res;
|
||||
|
||||
Operand res = context.AddIntrinsic(inst, n, Const(X86GetRoundControl(roundMode)));
|
||||
if (roundMode != FPRoundingMode.ToNearestAway)
|
||||
{
|
||||
Intrinsic inst = (op.Size & 1) != 0 ? Intrinsic.X86Roundsd : Intrinsic.X86Roundss;
|
||||
|
||||
res = context.AddIntrinsic(inst, n, Const(X86GetRoundControl(roundMode)));
|
||||
}
|
||||
else
|
||||
{
|
||||
res = EmitSse41RoundToNearestWithTiesToAwayOpF(context, n, scalar: true);
|
||||
}
|
||||
|
||||
if ((op.Size & 1) != 0)
|
||||
{
|
||||
@@ -3538,9 +3561,18 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
Operand n = GetVec(op.Rn);
|
||||
|
||||
Intrinsic inst = (op.Size & 1) != 0 ? Intrinsic.X86Roundpd : Intrinsic.X86Roundps;
|
||||
Operand res;
|
||||
|
||||
Operand res = context.AddIntrinsic(inst, n, Const(X86GetRoundControl(roundMode)));
|
||||
if (roundMode != FPRoundingMode.ToNearestAway)
|
||||
{
|
||||
Intrinsic inst = (op.Size & 1) != 0 ? Intrinsic.X86Roundpd : Intrinsic.X86Roundps;
|
||||
|
||||
res = context.AddIntrinsic(inst, n, Const(X86GetRoundControl(roundMode)));
|
||||
}
|
||||
else
|
||||
{
|
||||
res = EmitSse41RoundToNearestWithTiesToAwayOpF(context, n, scalar: false);
|
||||
}
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
|
@@ -164,32 +164,74 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
public static void Fcvtas_Gp(ArmEmitterContext context)
|
||||
{
|
||||
EmitFcvt_s_Gp(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1));
|
||||
if (Optimizations.UseSse41)
|
||||
{
|
||||
EmitSse41Fcvts_Gp(context, FPRoundingMode.ToNearestAway, isFixed: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitFcvt_s_Gp(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1));
|
||||
}
|
||||
}
|
||||
|
||||
public static void Fcvtas_S(ArmEmitterContext context)
|
||||
{
|
||||
EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1), signed: true, scalar: true);
|
||||
if (Optimizations.UseSse41)
|
||||
{
|
||||
EmitSse41FcvtsOpF(context, FPRoundingMode.ToNearestAway, scalar: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1), signed: true, scalar: true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Fcvtas_V(ArmEmitterContext context)
|
||||
{
|
||||
EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1), signed: true, scalar: false);
|
||||
if (Optimizations.UseSse41)
|
||||
{
|
||||
EmitSse41FcvtsOpF(context, FPRoundingMode.ToNearestAway, scalar: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1), signed: true, scalar: false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Fcvtau_Gp(ArmEmitterContext context)
|
||||
{
|
||||
EmitFcvt_u_Gp(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1));
|
||||
if (Optimizations.UseSse41)
|
||||
{
|
||||
EmitSse41Fcvtu_Gp(context, FPRoundingMode.ToNearestAway, isFixed: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitFcvt_u_Gp(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1));
|
||||
}
|
||||
}
|
||||
|
||||
public static void Fcvtau_S(ArmEmitterContext context)
|
||||
{
|
||||
EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1), signed: false, scalar: true);
|
||||
if (Optimizations.UseSse41)
|
||||
{
|
||||
EmitSse41FcvtuOpF(context, FPRoundingMode.ToNearestAway, scalar: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1), signed: false, scalar: true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Fcvtau_V(ArmEmitterContext context)
|
||||
{
|
||||
EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1), signed: false, scalar: false);
|
||||
if (Optimizations.UseSse41)
|
||||
{
|
||||
EmitSse41FcvtuOpF(context, FPRoundingMode.ToNearestAway, scalar: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1), signed: false, scalar: false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Fcvtl_V(ArmEmitterContext context)
|
||||
@@ -1223,7 +1265,14 @@ namespace ARMeilleure.Instructions
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Mulps, nRes, fpScaledMask);
|
||||
}
|
||||
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundps, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
if (roundMode != FPRoundingMode.ToNearestAway)
|
||||
{
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundps, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
}
|
||||
else
|
||||
{
|
||||
nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar);
|
||||
}
|
||||
|
||||
Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRes);
|
||||
|
||||
@@ -1265,7 +1314,14 @@ namespace ARMeilleure.Instructions
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Mulpd, nRes, fpScaledMask);
|
||||
}
|
||||
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundpd, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
if (roundMode != FPRoundingMode.ToNearestAway)
|
||||
{
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundpd, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
}
|
||||
else
|
||||
{
|
||||
nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar);
|
||||
}
|
||||
|
||||
Operand nLong = EmitSse2CvtDoubleToInt64OpF(context, nRes, scalar);
|
||||
|
||||
@@ -1314,7 +1370,14 @@ namespace ARMeilleure.Instructions
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Mulps, nRes, fpScaledMask);
|
||||
}
|
||||
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundps, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
if (roundMode != FPRoundingMode.ToNearestAway)
|
||||
{
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundps, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
}
|
||||
else
|
||||
{
|
||||
nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar);
|
||||
}
|
||||
|
||||
Operand zero = context.VectorZero();
|
||||
|
||||
@@ -1369,7 +1432,14 @@ namespace ARMeilleure.Instructions
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Mulpd, nRes, fpScaledMask);
|
||||
}
|
||||
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundpd, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
if (roundMode != FPRoundingMode.ToNearestAway)
|
||||
{
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundpd, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
}
|
||||
else
|
||||
{
|
||||
nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar);
|
||||
}
|
||||
|
||||
Operand zero = context.VectorZero();
|
||||
|
||||
@@ -1424,7 +1494,14 @@ namespace ARMeilleure.Instructions
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Mulss, nRes, fpScaledMask);
|
||||
}
|
||||
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundss, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
if (roundMode != FPRoundingMode.ToNearestAway)
|
||||
{
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundss, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
}
|
||||
else
|
||||
{
|
||||
nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar: true);
|
||||
}
|
||||
|
||||
Operand nIntOrLong = op.RegisterSize == RegisterSize.Int32
|
||||
? context.AddIntrinsicInt (Intrinsic.X86Cvtss2si, nRes)
|
||||
@@ -1464,7 +1541,14 @@ namespace ARMeilleure.Instructions
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Mulsd, nRes, fpScaledMask);
|
||||
}
|
||||
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundsd, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
if (roundMode != FPRoundingMode.ToNearestAway)
|
||||
{
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundsd, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
}
|
||||
else
|
||||
{
|
||||
nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar: true);
|
||||
}
|
||||
|
||||
Operand nIntOrLong = op.RegisterSize == RegisterSize.Int32
|
||||
? context.AddIntrinsicInt (Intrinsic.X86Cvtsd2si, nRes)
|
||||
@@ -1512,7 +1596,14 @@ namespace ARMeilleure.Instructions
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Mulss, nRes, fpScaledMask);
|
||||
}
|
||||
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundss, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
if (roundMode != FPRoundingMode.ToNearestAway)
|
||||
{
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundss, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
}
|
||||
else
|
||||
{
|
||||
nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar: true);
|
||||
}
|
||||
|
||||
Operand zero = context.VectorZero();
|
||||
|
||||
@@ -1567,7 +1658,14 @@ namespace ARMeilleure.Instructions
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Mulsd, nRes, fpScaledMask);
|
||||
}
|
||||
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundsd, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
if (roundMode != FPRoundingMode.ToNearestAway)
|
||||
{
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundsd, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
}
|
||||
else
|
||||
{
|
||||
nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar: true);
|
||||
}
|
||||
|
||||
Operand zero = context.VectorZero();
|
||||
|
||||
|
@@ -203,6 +203,9 @@ namespace ARMeilleure.Instructions
|
||||
FPRoundingMode roundMode;
|
||||
switch (rm)
|
||||
{
|
||||
case 0b00:
|
||||
roundMode = FPRoundingMode.ToNearestAway;
|
||||
break;
|
||||
case 0b01:
|
||||
roundMode = FPRoundingMode.ToNearest;
|
||||
break;
|
||||
@@ -228,7 +231,7 @@ namespace ARMeilleure.Instructions
|
||||
bool unsigned = op.Opc == 0;
|
||||
int rm = op.Opc2 & 3;
|
||||
|
||||
if (Optimizations.UseSse41 && rm != 0b00)
|
||||
if (Optimizations.UseSse41)
|
||||
{
|
||||
EmitSse41ConvertInt32(context, RMToRoundMode(rm), !unsigned);
|
||||
}
|
||||
@@ -258,6 +261,74 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vcvt_TB(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdCvtTB op = (OpCode32SimdCvtTB)context.CurrOp;
|
||||
|
||||
if (Optimizations.UseF16c)
|
||||
{
|
||||
Debug.Assert(!Optimizations.ForceLegacySse);
|
||||
|
||||
if (op.Op)
|
||||
{
|
||||
Operand res = ExtractScalar(context, op.Size == 1 ? OperandType.FP64 : OperandType.FP32, op.Vm);
|
||||
if (op.Size == 1)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Cvtsd2ss, context.VectorZero(), res);
|
||||
}
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vcvtps2ph, res, Const(X86GetRoundControl(FPRoundingMode.ToNearest)));
|
||||
res = context.VectorExtract16(res, 0);
|
||||
InsertScalar16(context, op.Vd, op.T, res);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand res = context.VectorCreateScalar(ExtractScalar16(context, op.Vm, op.T));
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vcvtph2ps, res);
|
||||
if (op.Size == 1)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Cvtss2sd, context.VectorZero(), res);
|
||||
}
|
||||
res = context.VectorExtract(op.Size == 1 ? OperandType.I64 : OperandType.I32, res, 0);
|
||||
InsertScalar(context, op.Vd, res);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (op.Op)
|
||||
{
|
||||
// Convert to half.
|
||||
|
||||
Operand src = ExtractScalar(context, op.Size == 1 ? OperandType.FP64 : OperandType.FP32, op.Vm);
|
||||
|
||||
MethodInfo method = op.Size == 1
|
||||
? typeof(SoftFloat64_16).GetMethod(nameof(SoftFloat64_16.FPConvert))
|
||||
: typeof(SoftFloat32_16).GetMethod(nameof(SoftFloat32_16.FPConvert));
|
||||
|
||||
context.StoreToContext();
|
||||
Operand res = context.Call(method, src);
|
||||
context.LoadFromContext();
|
||||
|
||||
InsertScalar16(context, op.Vd, op.T, res);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Convert from half.
|
||||
|
||||
Operand src = ExtractScalar16(context, op.Vm, op.T);
|
||||
|
||||
MethodInfo method = op.Size == 1
|
||||
? typeof(SoftFloat16_64).GetMethod(nameof(SoftFloat16_64.FPConvert))
|
||||
: typeof(SoftFloat16_32).GetMethod(nameof(SoftFloat16_32.FPConvert));
|
||||
|
||||
context.StoreToContext();
|
||||
Operand res = context.Call(method, src);
|
||||
context.LoadFromContext();
|
||||
|
||||
InsertScalar(context, op.Vd, res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// VRINTA/M/N/P (floating-point).
|
||||
public static void Vrint_RM(ArmEmitterContext context)
|
||||
{
|
||||
@@ -267,15 +338,21 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
int rm = op.Opc2 & 3;
|
||||
|
||||
if (Optimizations.UseSse2 && rm != 0b00)
|
||||
if (Optimizations.UseSse41)
|
||||
{
|
||||
EmitScalarUnaryOpSimd32(context, (m) =>
|
||||
{
|
||||
Intrinsic inst = (op.Size & 1) == 0 ? Intrinsic.X86Roundss : Intrinsic.X86Roundsd;
|
||||
|
||||
FPRoundingMode roundMode = RMToRoundMode(rm);
|
||||
|
||||
return context.AddIntrinsic(inst, m, Const(X86GetRoundControl(roundMode)));
|
||||
if (roundMode != FPRoundingMode.ToNearestAway)
|
||||
{
|
||||
Intrinsic inst = (op.Size & 1) == 0 ? Intrinsic.X86Roundss : Intrinsic.X86Roundsd;
|
||||
return context.AddIntrinsic(inst, m, Const(X86GetRoundControl(roundMode)));
|
||||
}
|
||||
else
|
||||
{
|
||||
return EmitSse41RoundToNearestWithTiesToAwayOpF(context, m, scalar: true);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
@@ -305,7 +382,17 @@ namespace ARMeilleure.Instructions
|
||||
// VRINTA (vector).
|
||||
public static void Vrinta_V(ArmEmitterContext context)
|
||||
{
|
||||
EmitVectorUnaryOpF32(context, (m) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, m));
|
||||
if (Optimizations.UseSse41)
|
||||
{
|
||||
EmitVectorUnaryOpSimd32(context, (m) =>
|
||||
{
|
||||
return EmitSse41RoundToNearestWithTiesToAwayOpF(context, m, scalar: false);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorUnaryOpF32(context, (m) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, m));
|
||||
}
|
||||
}
|
||||
|
||||
// VRINTM (vector).
|
||||
@@ -413,7 +500,14 @@ namespace ARMeilleure.Instructions
|
||||
Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, n, n, Const((int)CmpCondition.OrderedQ));
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n);
|
||||
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundss, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
if (roundMode != FPRoundingMode.ToNearestAway)
|
||||
{
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundss, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
}
|
||||
else
|
||||
{
|
||||
nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar: true);
|
||||
}
|
||||
|
||||
Operand zero = context.VectorZero();
|
||||
|
||||
@@ -464,7 +558,14 @@ namespace ARMeilleure.Instructions
|
||||
Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, n, n, Const((int)CmpCondition.OrderedQ));
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n);
|
||||
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundsd, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
if (roundMode != FPRoundingMode.ToNearestAway)
|
||||
{
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundsd, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
}
|
||||
else
|
||||
{
|
||||
nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar: true);
|
||||
}
|
||||
|
||||
Operand zero = context.VectorZero();
|
||||
|
||||
|
@@ -33,6 +33,14 @@ namespace ARMeilleure.Instructions
|
||||
};
|
||||
|
||||
public static readonly long ZeroMask = 128L << 56 | 128L << 48 | 128L << 40 | 128L << 32 | 128L << 24 | 128L << 16 | 128L << 8 | 128L << 0;
|
||||
|
||||
public static ulong X86GetGf2p8LogicalShiftLeft(int shift)
|
||||
{
|
||||
ulong identity = (0b00000001UL << 56) | (0b00000010UL << 48) | (0b00000100UL << 40) | (0b00001000UL << 32) |
|
||||
(0b00010000UL << 24) | (0b00100000UL << 16) | (0b01000000UL << 8) | (0b10000000UL << 0);
|
||||
|
||||
return shift >= 0 ? identity >> (shift * 8) : identity << (-shift * 8);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region "X86 SSE Intrinsics"
|
||||
@@ -243,19 +251,44 @@ namespace ARMeilleure.Instructions
|
||||
throw new ArgumentException($"Invalid rounding mode \"{roundMode}\".");
|
||||
}
|
||||
|
||||
public static ulong X86GetGf2p8LogicalShiftLeft(int shift)
|
||||
public static Operand EmitSse41RoundToNearestWithTiesToAwayOpF(ArmEmitterContext context, Operand n, bool scalar)
|
||||
{
|
||||
ulong identity =
|
||||
(0b00000001UL << 56) |
|
||||
(0b00000010UL << 48) |
|
||||
(0b00000100UL << 40) |
|
||||
(0b00001000UL << 32) |
|
||||
(0b00010000UL << 24) |
|
||||
(0b00100000UL << 16) |
|
||||
(0b01000000UL << 8) |
|
||||
(0b10000000UL << 0);
|
||||
Debug.Assert(n.Type == OperandType.V128);
|
||||
|
||||
return shift >= 0 ? identity >> (shift * 8) : identity << (-shift * 8);
|
||||
Operand nCopy = context.Copy(n);
|
||||
|
||||
Operand rC = Const(X86GetRoundControl(FPRoundingMode.TowardsZero));
|
||||
|
||||
IOpCodeSimd op = (IOpCodeSimd)context.CurrOp;
|
||||
|
||||
if ((op.Size & 1) == 0)
|
||||
{
|
||||
Operand signMask = scalar ? X86GetScalar(context, int.MinValue) : X86GetAllElements(context, int.MinValue);
|
||||
signMask = context.AddIntrinsic(Intrinsic.X86Pand, signMask, nCopy);
|
||||
|
||||
// 0x3EFFFFFF == BitConverter.SingleToInt32Bits(0.5f) - 1
|
||||
Operand valueMask = scalar ? X86GetScalar(context, 0x3EFFFFFF) : X86GetAllElements(context, 0x3EFFFFFF);
|
||||
valueMask = context.AddIntrinsic(Intrinsic.X86Por, valueMask, signMask);
|
||||
|
||||
nCopy = context.AddIntrinsic(scalar ? Intrinsic.X86Addss : Intrinsic.X86Addps, nCopy, valueMask);
|
||||
|
||||
nCopy = context.AddIntrinsic(scalar ? Intrinsic.X86Roundss : Intrinsic.X86Roundps, nCopy, rC);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand signMask = scalar ? X86GetScalar(context, long.MinValue) : X86GetAllElements(context, long.MinValue);
|
||||
signMask = context.AddIntrinsic(Intrinsic.X86Pand, signMask, nCopy);
|
||||
|
||||
// 0x3FDFFFFFFFFFFFFFL == BitConverter.DoubleToInt64Bits(0.5d) - 1L
|
||||
Operand valueMask = scalar ? X86GetScalar(context, 0x3FDFFFFFFFFFFFFFL) : X86GetAllElements(context, 0x3FDFFFFFFFFFFFFFL);
|
||||
valueMask = context.AddIntrinsic(Intrinsic.X86Por, valueMask, signMask);
|
||||
|
||||
nCopy = context.AddIntrinsic(scalar ? Intrinsic.X86Addsd : Intrinsic.X86Addpd, nCopy, valueMask);
|
||||
|
||||
nCopy = context.AddIntrinsic(scalar ? Intrinsic.X86Roundsd : Intrinsic.X86Roundpd, nCopy, rC);
|
||||
}
|
||||
|
||||
return nCopy;
|
||||
}
|
||||
|
||||
public static Operand EmitCountSetBits8(ArmEmitterContext context, Operand op) // "size" is 8 (SIMD&FP Inst.).
|
||||
|
@@ -70,6 +70,22 @@ namespace ARMeilleure.Instructions
|
||||
context.Copy(vec, insert);
|
||||
}
|
||||
|
||||
public static Operand ExtractScalar16(ArmEmitterContext context, int reg, bool top)
|
||||
{
|
||||
return context.VectorExtract16(GetVecA32(reg >> 2), ((reg & 3) << 1) | (top ? 1 : 0));
|
||||
}
|
||||
|
||||
public static void InsertScalar16(ArmEmitterContext context, int reg, bool top, Operand value)
|
||||
{
|
||||
Debug.Assert(value.Type == OperandType.FP32 || value.Type == OperandType.I32);
|
||||
|
||||
Operand vec, insert;
|
||||
vec = GetVecA32(reg >> 2);
|
||||
insert = context.VectorInsert16(vec, value, ((reg & 3) << 1) | (top ? 1 : 0));
|
||||
|
||||
context.Copy(vec, insert);
|
||||
}
|
||||
|
||||
public static Operand ExtractElement(ArmEmitterContext context, int reg, int size, bool signed)
|
||||
{
|
||||
return EmitVectorExtract32(context, reg >> (4 - size), reg & ((16 >> size) - 1), size, signed);
|
||||
|
@@ -2,9 +2,10 @@ namespace ARMeilleure.State
|
||||
{
|
||||
public enum FPRoundingMode
|
||||
{
|
||||
ToNearest = 0,
|
||||
ToNearest = 0, // With ties to even.
|
||||
TowardsPlusInfinity = 1,
|
||||
TowardsMinusInfinity = 2,
|
||||
TowardsZero = 3
|
||||
TowardsZero = 3,
|
||||
ToNearestAway = 4 // With ties to away.
|
||||
}
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||
|
||||
private const uint InternalVersion = 3710; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
private const uint InternalVersion = 3713; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
|
||||
private const string ActualDir = "0";
|
||||
private const string BackupDir = "1";
|
||||
|
@@ -455,13 +455,16 @@ namespace ARMeilleure.Translation
|
||||
|
||||
public void InvalidateJitCacheRegion(ulong address, ulong size)
|
||||
{
|
||||
// If rejit is running, stop it as it may be trying to rejit a function on the invalidated region.
|
||||
ClearRejitQueue(allowRequeue: true);
|
||||
|
||||
ulong[] overlapAddresses = Array.Empty<ulong>();
|
||||
|
||||
int overlapsCount = Functions.GetOverlaps(address, size, ref overlapAddresses);
|
||||
|
||||
if (overlapsCount != 0)
|
||||
{
|
||||
// If rejit is running, stop it as it may be trying to rejit a function on the invalidated region.
|
||||
ClearRejitQueue(allowRequeue: true);
|
||||
}
|
||||
|
||||
for (int index = 0; index < overlapsCount; index++)
|
||||
{
|
||||
ulong overlapAddress = overlapAddresses[index];
|
||||
|
@@ -36,8 +36,8 @@
|
||||
|
||||
## Compatibility
|
||||
|
||||
As of September 2022, Ryujinx has been tested on approximately 3,600 titles; over 3,400 boot past menus and into gameplay, with roughly 2,700 of those being considered playable. You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues).
|
||||
Anyone is free to submit a new game test or update an existing game test entry; simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. Use the search function to see if a game has been tested already!
|
||||
As of October 2022, Ryujinx has been tested on approximately 3,700 titles; over 3,500 boot past menus and into gameplay, with roughly 3,000 of those being considered playable.
|
||||
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). Anyone is free to submit a new game test or update an existing game test entry; simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. Use the search function to see if a game has been tested already!
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -90,7 +90,7 @@ Ryujinx system files are stored in the `Ryujinx` folder. This folder is located
|
||||
|
||||
- **GPU**
|
||||
|
||||
The GPU emulator emulates the Switch's Maxwell GPU using the OpenGL API (version 4.5 minimum) through a custom build of OpenTK. There are currently four graphics enhancements available to the end user in Ryujinx: disk shader caching, resolution scaling, aspect ratio adjustment and anisotropic filtering. These enhancements can be adjusted or toggled as desired in the GUI.
|
||||
The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum) or Vulkan APIs through a custom build of OpenTK or Silk.NET respectively. There are currently four graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Aspect Ratio Adjustment, and Anisotropic Filtering. These enhancements can be adjusted or toggled as desired in the GUI.
|
||||
|
||||
- **Input**
|
||||
|
||||
|
@@ -49,8 +49,8 @@
|
||||
"GameListContextMenuManageTitleUpdatesToolTip": "Apre la finestra di gestione aggiornamenti del gioco",
|
||||
"GameListContextMenuManageDlc": "Gestici DLC",
|
||||
"GameListContextMenuManageDlcToolTip": "Apre la finestra di gestione DLC",
|
||||
"GameListContextMenuOpenModsDirectory": "Apri cartella delle mods",
|
||||
"GameListContextMenuOpenModsDirectoryToolTip": "Apre la cartella che contiene le mods dell'applicazione",
|
||||
"GameListContextMenuOpenModsDirectory": "Apri cartella delle mod",
|
||||
"GameListContextMenuOpenModsDirectoryToolTip": "Apre la cartella che contiene le mod dell'applicazione",
|
||||
"GameListContextMenuCacheManagement": "Gestione della cache",
|
||||
"GameListContextMenuCacheManagementPurgePptc": "Pulisci PPTC cache",
|
||||
"GameListContextMenuCacheManagementPurgePptcToolTip": "Elimina la PPTC cache dell'applicazione",
|
||||
@@ -81,7 +81,7 @@
|
||||
"SettingsTabGeneralRemove": "Rimuovi",
|
||||
"SettingsTabSystem": "Sistema",
|
||||
"SettingsTabSystemCore": "Core",
|
||||
"SettingsTabSystemSystemRegion": "Regione di sistema:",
|
||||
"SettingsTabSystemSystemRegion": "Regione del sistema:",
|
||||
"SettingsTabSystemSystemRegionJapan": "Giappone",
|
||||
"SettingsTabSystemSystemRegionUSA": "Stati Uniti d'America",
|
||||
"SettingsTabSystemSystemRegionEurope": "Europa",
|
||||
@@ -89,7 +89,7 @@
|
||||
"SettingsTabSystemSystemRegionChina": "Cina",
|
||||
"SettingsTabSystemSystemRegionKorea": "Corea",
|
||||
"SettingsTabSystemSystemRegionTaiwan": "Taiwan",
|
||||
"SettingsTabSystemSystemLanguage": "Lingua di sistema:",
|
||||
"SettingsTabSystemSystemLanguage": "Lingua del sistema:",
|
||||
"SettingsTabSystemSystemLanguageJapanese": "Giapponese",
|
||||
"SettingsTabSystemSystemLanguageAmericanEnglish": "Inglese americano",
|
||||
"SettingsTabSystemSystemLanguageFrench": "Francese",
|
||||
@@ -107,8 +107,8 @@
|
||||
"SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Spagnolo latino americano",
|
||||
"SettingsTabSystemSystemLanguageSimplifiedChinese": "Cinese semplificato",
|
||||
"SettingsTabSystemSystemLanguageTraditionalChinese": "Cinese tradizionale",
|
||||
"SettingsTabSystemSystemTimeZone": "Fuso orario di sistema:",
|
||||
"SettingsTabSystemSystemTime": "Data e ora di sistema:",
|
||||
"SettingsTabSystemSystemTimeZone": "Fuso orario del sistema:",
|
||||
"SettingsTabSystemSystemTime": "Data e ora del sistema:",
|
||||
"SettingsTabSystemEnableVsync": "Attiva VSync",
|
||||
"SettingsTabSystemEnablePptc": "Attiva PPTC (Profiled Persistent Translation Cache)",
|
||||
"SettingsTabSystemEnableFsIntegrityChecks": "Attiva controlli d'integrità FS",
|
||||
@@ -155,7 +155,7 @@
|
||||
"SettingsTabLoggingEnableTraceLogs": "Attiva Trace Logs",
|
||||
"SettingsTabLoggingEnableGuestLogs": "Attiva Guest Logs",
|
||||
"SettingsTabLoggingEnableFsAccessLogs": "Attiva Fs Access Logs",
|
||||
"SettingsTabLoggingFsGlobalAccessLogMode": "Modalità di log accesso globale Fs:",
|
||||
"SettingsTabLoggingFsGlobalAccessLogMode": "Modalità log accesso globale Fs:",
|
||||
"SettingsTabLoggingDeveloperOptions": "Opzioni da sviluppatore (AVVISO: Ridurrà le prestazioni)",
|
||||
"SettingsTabLoggingOpenglLogLevel": "Livello di log OpenGL:",
|
||||
"SettingsTabLoggingOpenglLogLevelNone": "Nessuno",
|
||||
@@ -240,7 +240,7 @@
|
||||
"ControllerSettingsRightSR": "SR",
|
||||
"ControllerSettingsExtraButtonsLeft": "Tasto sinitro",
|
||||
"ControllerSettingsExtraButtonsRight": "Tasto destro",
|
||||
"ControllerSettingsMisc": "Miscellanee",
|
||||
"ControllerSettingsMisc": "Varie",
|
||||
"ControllerSettingsTriggerThreshold": "Sensibilità dei grilletti:",
|
||||
"ControllerSettingsMotion": "Movimento",
|
||||
"ControllerSettingsMotionUseCemuhookCompatibleMotion": "Usa sensore compatibile con CemuHook",
|
||||
@@ -261,7 +261,7 @@
|
||||
"UserProfilesClose": "Chiudi",
|
||||
"ProfileImageSelectionTitle": "Selezione dell'immagine profilo",
|
||||
"ProfileImageSelectionHeader": "Scegli un'immagine profilo",
|
||||
"ProfileImageSelectionNote": "Puoi importare un'immagine profilo personalizzata o selezionare un avatar dal firmware di sistema",
|
||||
"ProfileImageSelectionNote": "Puoi importare un'immagine profilo personalizzata o selezionare un avatar dal firmware del sistema",
|
||||
"ProfileImageSelectionImportImage": "Importa file immagine",
|
||||
"ProfileImageSelectionSelectAvatar": "Seleziona avatar dal firmware",
|
||||
"InputDialogTitle": "Input Dialog",
|
||||
@@ -293,7 +293,7 @@
|
||||
"ControllerSettingsRumbleStrongMultiplier": "Moltiplicatore vibrazione forte",
|
||||
"ControllerSettingsRumbleWeakMultiplier": "Moltiplicatore vibrazione debole",
|
||||
"DialogMessageSaveNotAvailableMessage": "Non ci sono dati di salvataggio per {0} [{1:x16}]",
|
||||
"DialogMessageSaveNotAvailableCreateSaveMessage": "Vuoi creare dei dat di salvataggio per questo gioco?",
|
||||
"DialogMessageSaveNotAvailableCreateSaveMessage": "Vuoi creare dei dati di salvataggio per questo gioco?",
|
||||
"DialogConfirmationTitle": "Ryujinx - Conferma",
|
||||
"DialogUpdaterTitle": "Ryujinx - Updater",
|
||||
"DialogErrorTitle": "Ryujinx - Errore",
|
||||
@@ -318,7 +318,7 @@
|
||||
"DialogUpdaterDownloadingMessage": "Download dell'aggiornamento...",
|
||||
"DialogUpdaterExtractionMessage": "Estrazione dell'aggiornamento...",
|
||||
"DialogUpdaterRenamingMessage": "Rinominazione dell'aggiornamento...",
|
||||
"DialogUpdaterAddingFilesMessage": "Aggiunta nuovo aggiornamento...",
|
||||
"DialogUpdaterAddingFilesMessage": "Aggiunta del nuovo aggiornamento...",
|
||||
"DialogUpdaterCompleteMessage": "Aggiornamento completato!",
|
||||
"DialogUpdaterRestartMessage": "Vuoi riavviare Ryujinx adesso?",
|
||||
"DialogUpdaterArchNotSupportedMessage": "Non stai usando un'architettura di sistema supportata!",
|
||||
@@ -331,7 +331,7 @@
|
||||
"DialogThemeRestartMessage": "Il tema è stato salvato. E' richiesto un riavvio per applicare un tema.",
|
||||
"DialogThemeRestartSubMessage": "Vuoi riavviare?",
|
||||
"DialogFirmwareInstallEmbeddedMessage": "Vuoi installare il firmware incorporato in questo gioco? (Firmware {0})",
|
||||
"DialogFirmwareInstallEmbeddedSuccessMessage": "Non è stato trovato alcun firmware installato, ma Ryujinx è riuscito di installare il firmware {0} dal gioco fornito.\nL'emulatore si avvierà adesso.",
|
||||
"DialogFirmwareInstallEmbeddedSuccessMessage": "Non è stato trovato alcun firmware installato, ma Ryujinx è riuscito ad installare il firmware {0} dal gioco fornito.\nL'emulatore si avvierà adesso.",
|
||||
"DialogFirmwareNoFirmwareInstalledMessage": "Nessun firmware installato",
|
||||
"DialogFirmwareInstalledMessage": "Il firmware {0} è stato installato",
|
||||
"DialogOpenSettingsWindowLabel": "Apri finestra delle impostazioni",
|
||||
@@ -355,14 +355,14 @@
|
||||
"DialogShaderDeletionMessage": "Stai per eliminare la Shader cache per :\n\n{0}\n\nSei sicuro di voler proseguire?",
|
||||
"DialogShaderDeletionErrorMessage": "Errore nell'eliminazione della Shader cache a {0}: {1}",
|
||||
"DialogRyujinxErrorMessage": "Ryujinx ha incontrato un errore",
|
||||
"DialogInvalidTitleIdErrorMessage": "Errore UI: Il gioco selezionato non ha un title ID valido",
|
||||
"DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Un firmware di sistema valido non è stato trovato in {0}.",
|
||||
"DialogInvalidTitleIdErrorMessage": "Errore UI: Il gioco selezionato non ha un ID titolo valido",
|
||||
"DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Un firmware del sistema valido non è stato trovato in {0}.",
|
||||
"DialogFirmwareInstallerFirmwareInstallTitle": "Installa firmware {0}",
|
||||
"DialogFirmwareInstallerFirmwareInstallMessage": "La versione di sistema {0} sarà installata.",
|
||||
"DialogFirmwareInstallerFirmwareInstallMessage": "La versione del sistema {0} sarà installata.",
|
||||
"DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nQuesta sostituirà l'attuale versione di sistema {0}.",
|
||||
"DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nVuoi continuare?",
|
||||
"DialogFirmwareInstallerFirmwareInstallWaitMessage": "Installazione del firmware...",
|
||||
"DialogFirmwareInstallerFirmwareInstallSuccessMessage": "La versione di sistema {0} è stata installata.",
|
||||
"DialogFirmwareInstallerFirmwareInstallSuccessMessage": "La versione del sistema {0} è stata installata.",
|
||||
"DialogUserProfileDeletionWarningMessage": "Non ci sarebbero altri profili da aprire se il profilo selezionato viene cancellato",
|
||||
"DialogUserProfileDeletionConfirmMessage": "Vuoi eliminare il profilo selezionato?",
|
||||
"DialogControllerSettingsModifiedConfirmMessage": "Le attuali impostazioni del controller sono state aggiornate.",
|
||||
@@ -557,4 +557,40 @@
|
||||
"SettingsXamlThemeFile" : "File del tema xaml",
|
||||
"SettingsTabHotkeysResScaleUpHotkey": "Aumentare la risoluzione:",
|
||||
"SettingsTabHotkeysResScaleDownHotkey": "Diminuire la risoluzione:"
|
||||
"AvatarWindowTitle": "Gestisci account - Avatar"
|
||||
"Amiibo": "Amiibo",
|
||||
"Unknown": "Sconosciuto",
|
||||
"Usage": "Utilizzo",
|
||||
"Writable": "Scrivibile",
|
||||
"SelectDlcDialogTitle": "Seleziona file dei DLC",
|
||||
"SelectUpdateDialogTitle": "Seleziona file di aggiornamento",
|
||||
"UserProfileWindowTitle": "Gestisci profili degli utenti",
|
||||
"CheatWindowTitle": "Gestisci cheat dei giochi",
|
||||
"DlcWindowTitle": "Gestisci DLC dei giochi",
|
||||
"UpdateWindowTitle": "Gestisci aggiornamenti dei giochi",
|
||||
"CheatWindowHeading": "Cheat disponibiili per {0} [{1}]",
|
||||
"DlcWindowHeading": "DLC disponibili per {0} [{1}]",
|
||||
"UserProfilesEditProfile": "Modifica selezionati",
|
||||
"Cancel": "Annulla",
|
||||
"Save": "Salva",
|
||||
"Discard": "Scarta",
|
||||
"UserProfilesSetProfileImage": "Imposta immagine profilo",
|
||||
"UserProfileEmptyNameError": "È richiesto un nome",
|
||||
"UserProfileNoImageError": "Dev'essere impostata un'immagine profilo",
|
||||
"GameUpdateWindowHeading": "Aggiornamenti disponibili per {0} [{1}]",
|
||||
"UserProfilesName": "Name:",
|
||||
"UserProfilesUserId": "User Id:",
|
||||
"SettingsTabGraphicsBackend": "Backend grafica",
|
||||
"SettingsTabGraphicsBackendTooltip": "Backend grafica da usare",
|
||||
"SettingsEnableTextureRecompression": "Abilita Ricompressione Texture",
|
||||
"SettingsEnableTextureRecompressionTooltip": "Comprime alcune texture per ridurre l'utilizzo della VRAM.\n\nL'utilizzo è consigliato con GPU con meno di 4GB di VRAM.\n\nLascia su OFF se non sei sicuro.",
|
||||
"SettingsTabGraphicsPreferredGpu": "GPU preferita",
|
||||
"SettingsTabGraphicsPreferredGpuTooltip": "Seleziona la scheda grafica che verrà usata con la backend grafica Vulkan.\n\nNon influenza la GPU che userà OpenGL.\n\nImposta la GPU contrassegnata come \"dGPU\" se non sei sicuro. Se non ce n'è una, lascia intatta quest'impostazione.",
|
||||
"SettingsAppRequiredRestartMessage": "È richiesto un riavvio di Ryujinx",
|
||||
"SettingsGpuBackendRestartMessage": "Le impostazioni della backend grafica o della GPU sono state modificate. Questo richiederà un riavvio perché le modifiche siano applicate",
|
||||
"SettingsGpuBackendRestartSubMessage": "Vuoi riavviare ora?",
|
||||
"RyujinxUpdaterMessage": "Vuoi aggiornare Ryujinx all'ultima versione?",
|
||||
"SettingsTabHotkeysVolumeUpHotkey": "Aumentare il volume:",
|
||||
"SettingsTabHotkeysVolumeDownHotkey": "Diminuire il volume:",
|
||||
"VolumeShort": "Vol"
|
||||
}
|
||||
|
@@ -588,5 +588,9 @@
|
||||
"SettingsTabGraphicsPreferredGpuTooltip": "Wybierz kartę graficzną, która będzie używana z backendem graficznym Vulkan.\n\nNie wpływa na GPU używane przez OpenGL.\n\nW razie wątpliwości ustaw flagę GPU jako \"dGPU\". Jeśli żadnej nie ma, pozostaw nietknięte.",
|
||||
"SettingsAppRequiredRestartMessage": "Wymagane Zrestartowanie Ryujinx",
|
||||
"SettingsGpuBackendRestartMessage": "Zmieniono ustawienia Backendu Graficznego lub GPU. Będzie to wymagało ponownego uruchomienia",
|
||||
"SettingsGpuBackendRestartSubMessage": "Czy chcesz zrestartować teraz?"
|
||||
"SettingsGpuBackendRestartSubMessage": "Czy chcesz zrestartować teraz?",
|
||||
"RyujinxUpdaterMessage": "Czy chcesz zaktualizować Ryujinx do najnowszej wersji?",
|
||||
"SettingsTabHotkeysVolumeUpHotkey": "Zwiększ Głośność:",
|
||||
"SettingsTabHotkeysVolumeDownHotkey": "Zmniejsz Głośność:",
|
||||
"VolumeShort": "Głoś"
|
||||
}
|
||||
|
@@ -278,7 +278,7 @@ namespace Ryujinx.Modules
|
||||
{
|
||||
string ryuName = Path.GetFileName(Environment.ProcessPath);
|
||||
string ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ryuName);
|
||||
string ryuArg = string.Join(" ", Environment.GetCommandLineArgs().Skip(1).ToArray());
|
||||
var ryuArg = Environment.GetCommandLineArgs().Skip(1);
|
||||
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
|
@@ -34,7 +34,7 @@
|
||||
<PackageReference Include="Silk.NET.Vulkan" Version="2.10.1" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.10.1" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.10.1" />
|
||||
<PackageReference Include="SPB" Version="0.0.4-build24" />
|
||||
<PackageReference Include="SPB" Version="0.0.4-build27" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||
</ItemGroup>
|
||||
|
@@ -4,6 +4,7 @@ using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Threading;
|
||||
using FluentAvalonia.Core;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Ui.Models;
|
||||
@@ -27,7 +28,10 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
string secondaryButton,
|
||||
string closeButton,
|
||||
int iconSymbol,
|
||||
UserResult primaryButtonResult = UserResult.Ok)
|
||||
UserResult primaryButtonResult = UserResult.Ok,
|
||||
ManualResetEvent deferResetEvent = null,
|
||||
Func<Window, Task> doWhileDeferred = null,
|
||||
TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null)
|
||||
{
|
||||
UserResult result = UserResult.None;
|
||||
|
||||
@@ -110,12 +114,19 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
|
||||
{
|
||||
result = UserResult.No;
|
||||
contentDialog.PrimaryButtonClick -= deferCloseAction;
|
||||
});
|
||||
contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
|
||||
{
|
||||
result = UserResult.Cancel;
|
||||
contentDialog.PrimaryButtonClick -= deferCloseAction;
|
||||
});
|
||||
|
||||
if (deferResetEvent != null)
|
||||
{
|
||||
contentDialog.PrimaryButtonClick += deferCloseAction;
|
||||
}
|
||||
|
||||
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
|
||||
|
||||
overlay?.Close();
|
||||
@@ -143,35 +154,20 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
Func<Window, Task> doWhileDeferred = null)
|
||||
{
|
||||
bool startedDeferring = false;
|
||||
|
||||
UserResult result = UserResult.None;
|
||||
|
||||
ContentDialog contentDialog = new ContentDialog
|
||||
{
|
||||
Title = title,
|
||||
PrimaryButtonText = primaryButton,
|
||||
SecondaryButtonText = secondaryButton,
|
||||
CloseButtonText = closeButton,
|
||||
Content = CreateDialogTextContent(primaryText, secondaryText, iconSymbol),
|
||||
PrimaryButtonCommand = MiniCommand.Create(() =>
|
||||
{
|
||||
result = primaryButton == LocaleManager.Instance["InputDialogYes"] ? UserResult.Yes : UserResult.Ok;
|
||||
}),
|
||||
};
|
||||
contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
|
||||
{
|
||||
contentDialog.PrimaryButtonClick -= DeferClose;
|
||||
result = UserResult.No;
|
||||
});
|
||||
contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
|
||||
{
|
||||
contentDialog.PrimaryButtonClick -= DeferClose;
|
||||
result = UserResult.Cancel;
|
||||
});
|
||||
contentDialog.PrimaryButtonClick += DeferClose;
|
||||
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
|
||||
|
||||
return result;
|
||||
return await ShowContentDialog(
|
||||
title,
|
||||
primaryText,
|
||||
secondaryText,
|
||||
primaryButton,
|
||||
secondaryButton,
|
||||
closeButton,
|
||||
iconSymbol,
|
||||
primaryButton == LocaleManager.Instance["InputDialogYes"] ? UserResult.Yes : UserResult.Ok,
|
||||
deferResetEvent,
|
||||
doWhileDeferred,
|
||||
DeferClose);
|
||||
|
||||
async void DeferClose(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
@@ -180,7 +176,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
return;
|
||||
}
|
||||
|
||||
contentDialog.PrimaryButtonClick -= DeferClose;
|
||||
sender.PrimaryButtonClick -= DeferClose;
|
||||
|
||||
startedDeferring = true;
|
||||
|
||||
@@ -188,7 +184,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
|
||||
result = primaryButton == LocaleManager.Instance["InputDialogYes"] ? UserResult.Yes : UserResult.Ok;
|
||||
|
||||
contentDialog.PrimaryButtonClick -= DeferClose;
|
||||
sender.PrimaryButtonClick -= DeferClose;
|
||||
|
||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
Task.Run(() =>
|
||||
|
@@ -295,8 +295,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
if (_validTzRegions.Contains(location))
|
||||
{
|
||||
TimeZone = location;
|
||||
|
||||
OnPropertyChanged(nameof(TimeZone));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -164,7 +164,7 @@
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="pl_PL"
|
||||
Header="Polish" />
|
||||
Header="Polski" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="ru_RU"
|
||||
|
@@ -37,7 +37,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||
InitializeComponent();
|
||||
Load();
|
||||
|
||||
FuncMultiValueConverter<string, string> converter = new(parts => string.Format("{0} {1} {2}", parts.ToArray()));
|
||||
FuncMultiValueConverter<string, string> converter = new(parts => string.Format("{0} {1} {2}", parts.ToArray()).Trim());
|
||||
MultiBinding tzMultiBinding = new() { Converter = converter };
|
||||
tzMultiBinding.Bindings.Add(new Binding("UtcDifference"));
|
||||
tzMultiBinding.Bindings.Add(new Binding("Location"));
|
||||
|
89
Ryujinx.Common/Memory/SpanOrArray.cs
Normal file
89
Ryujinx.Common/Memory/SpanOrArray.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Common.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// A struct that can represent both a Span and Array.
|
||||
/// This is useful to keep the Array representation when possible to avoid copies.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Element Type</typeparam>
|
||||
public ref struct SpanOrArray<T> where T : unmanaged
|
||||
{
|
||||
public readonly T[] Array;
|
||||
public readonly ReadOnlySpan<T> Span;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new SpanOrArray from an array.
|
||||
/// </summary>
|
||||
/// <param name="array">Array to store</param>
|
||||
public SpanOrArray(T[] array)
|
||||
{
|
||||
Array = array;
|
||||
Span = ReadOnlySpan<T>.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new SpanOrArray from a readonly span.
|
||||
/// </summary>
|
||||
/// <param name="array">Span to store</param>
|
||||
public SpanOrArray(ReadOnlySpan<T> span)
|
||||
{
|
||||
Array = null;
|
||||
Span = span;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the contained array, or convert the span if necessary.
|
||||
/// </summary>
|
||||
/// <returns>An array containing the data</returns>
|
||||
public T[] ToArray()
|
||||
{
|
||||
return Array ?? Span.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a ReadOnlySpan from either the array or ReadOnlySpan.
|
||||
/// </summary>
|
||||
/// <returns>A ReadOnlySpan containing the data</returns>
|
||||
public ReadOnlySpan<T> AsSpan()
|
||||
{
|
||||
return Array ?? Span;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cast an array to a SpanOrArray.
|
||||
/// </summary>
|
||||
/// <param name="array">Source array</param>
|
||||
public static implicit operator SpanOrArray<T>(T[] array)
|
||||
{
|
||||
return new SpanOrArray<T>(array);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cast a ReadOnlySpan to a SpanOrArray.
|
||||
/// </summary>
|
||||
/// <param name="span">Source ReadOnlySpan</param>
|
||||
public static implicit operator SpanOrArray<T>(ReadOnlySpan<T> span)
|
||||
{
|
||||
return new SpanOrArray<T>(span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cast a Span to a SpanOrArray.
|
||||
/// </summary>
|
||||
/// <param name="span">Source Span</param>
|
||||
public static implicit operator SpanOrArray<T>(Span<T> span)
|
||||
{
|
||||
return new SpanOrArray<T>(span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cast a SpanOrArray to a ReadOnlySpan
|
||||
/// </summary>
|
||||
/// <param name="spanOrArray">Source SpanOrArray</param>
|
||||
public static implicit operator ReadOnlySpan<T>(SpanOrArray<T> spanOrArray)
|
||||
{
|
||||
return spanOrArray.AsSpan();
|
||||
}
|
||||
}
|
||||
}
|
@@ -129,7 +129,7 @@ namespace Ryujinx.Common
|
||||
|
||||
private static (Assembly, string) ResolveManifestPath(string filename)
|
||||
{
|
||||
var segments = filename.Split(new[] { '/' }, 2, StringSplitOptions.RemoveEmptyEntries);
|
||||
var segments = filename.Split('/', 2, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (segments.Length >= 2)
|
||||
{
|
||||
|
@@ -1,3 +1,4 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
@@ -17,9 +18,9 @@ namespace Ryujinx.Graphics.GAL
|
||||
ReadOnlySpan<byte> GetData();
|
||||
ReadOnlySpan<byte> GetData(int layer, int level);
|
||||
|
||||
void SetData(ReadOnlySpan<byte> data);
|
||||
void SetData(ReadOnlySpan<byte> data, int layer, int level);
|
||||
void SetData(ReadOnlySpan<byte> data, int layer, int level, Rectangle<int> region);
|
||||
void SetData(SpanOrArray<byte> data);
|
||||
void SetData(SpanOrArray<byte> data, int layer, int level);
|
||||
void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region);
|
||||
void SetStorage(BufferRange buffer);
|
||||
void Release();
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using System;
|
||||
|
||||
@@ -107,19 +108,19 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
||||
}
|
||||
}
|
||||
|
||||
public void SetData(ReadOnlySpan<byte> data)
|
||||
public void SetData(SpanOrArray<byte> data)
|
||||
{
|
||||
_renderer.New<TextureSetDataCommand>().Set(Ref(this), Ref(data.ToArray()));
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void SetData(ReadOnlySpan<byte> data, int layer, int level)
|
||||
public void SetData(SpanOrArray<byte> data, int layer, int level)
|
||||
{
|
||||
_renderer.New<TextureSetDataSliceCommand>().Set(Ref(this), Ref(data.ToArray()), layer, level);
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void SetData(ReadOnlySpan<byte> data, int layer, int level, Rectangle<int> region)
|
||||
public void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region)
|
||||
{
|
||||
_renderer.New<TextureSetDataSliceRegionCommand>().Set(Ref(this), Ref(data.ToArray()), layer, level, region);
|
||||
_renderer.QueueCommand();
|
||||
|
@@ -229,7 +229,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
ReadOnlySpan<byte> data;
|
||||
byte[] data;
|
||||
if (srcLinear)
|
||||
{
|
||||
data = LayoutConverter.ConvertLinearStridedToLinear(
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
@@ -136,11 +137,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
public LinkedListNode<Texture> CacheNode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Event to fire when texture data is disposed.
|
||||
/// </summary>
|
||||
public event Action<Texture> Disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Physical memory ranges where the texture data is located.
|
||||
/// </summary>
|
||||
@@ -720,9 +716,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
}
|
||||
|
||||
data = ConvertToHostCompatibleFormat(data);
|
||||
SpanOrArray<byte> result = ConvertToHostCompatibleFormat(data);
|
||||
|
||||
HostTexture.SetData(data);
|
||||
HostTexture.SetData(result);
|
||||
|
||||
_hasData = true;
|
||||
}
|
||||
@@ -731,7 +727,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// Uploads new texture data to the host GPU.
|
||||
/// </summary>
|
||||
/// <param name="data">New data</param>
|
||||
public void SetData(ReadOnlySpan<byte> data)
|
||||
public void SetData(SpanOrArray<byte> data)
|
||||
{
|
||||
BlacklistScale();
|
||||
|
||||
@@ -750,7 +746,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="data">New data</param>
|
||||
/// <param name="layer">Target layer</param>
|
||||
/// <param name="level">Target level</param>
|
||||
public void SetData(ReadOnlySpan<byte> data, int layer, int level)
|
||||
public void SetData(SpanOrArray<byte> data, int layer, int level)
|
||||
{
|
||||
BlacklistScale();
|
||||
|
||||
@@ -786,7 +782,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="level">Mip level to convert</param>
|
||||
/// <param name="single">True to convert a single slice</param>
|
||||
/// <returns>Converted data</returns>
|
||||
public ReadOnlySpan<byte> ConvertToHostCompatibleFormat(ReadOnlySpan<byte> data, int level = 0, bool single = false)
|
||||
public SpanOrArray<byte> ConvertToHostCompatibleFormat(ReadOnlySpan<byte> data, int level = 0, bool single = false)
|
||||
{
|
||||
int width = Info.Width;
|
||||
int height = Info.Height;
|
||||
@@ -799,9 +795,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
height = Math.Max(height >> level, 1);
|
||||
depth = Math.Max(depth >> level, 1);
|
||||
|
||||
SpanOrArray<byte> result;
|
||||
|
||||
if (Info.IsLinear)
|
||||
{
|
||||
data = LayoutConverter.ConvertLinearStridedToLinear(
|
||||
result = LayoutConverter.ConvertLinearStridedToLinear(
|
||||
width,
|
||||
height,
|
||||
Info.FormatInfo.BlockWidth,
|
||||
@@ -813,7 +811,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
else
|
||||
{
|
||||
data = LayoutConverter.ConvertBlockLinearToLinear(
|
||||
result = LayoutConverter.ConvertBlockLinearToLinear(
|
||||
width,
|
||||
height,
|
||||
depth,
|
||||
@@ -836,7 +834,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
if (!_context.Capabilities.SupportsAstcCompression && Format.IsAstc())
|
||||
{
|
||||
if (!AstcDecoder.TryDecodeToRgba8P(
|
||||
data.ToArray(),
|
||||
result.ToArray(),
|
||||
Info.FormatInfo.BlockWidth,
|
||||
Info.FormatInfo.BlockHeight,
|
||||
width,
|
||||
@@ -856,11 +854,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
decoded = BCnEncoder.EncodeBC7(decoded, width, height, depth, levels, layers);
|
||||
}
|
||||
|
||||
data = decoded;
|
||||
result = decoded;
|
||||
}
|
||||
else if (!_context.Capabilities.SupportsR4G4Format && Format == Format.R4G4Unorm)
|
||||
{
|
||||
data = PixelConverter.ConvertR4G4ToR4G4B4A4(data);
|
||||
result = PixelConverter.ConvertR4G4ToR4G4B4A4(result);
|
||||
}
|
||||
else if (!TextureCompatibility.HostSupportsBcFormat(Format, Target, _context.Capabilities))
|
||||
{
|
||||
@@ -868,36 +866,36 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
case Format.Bc1RgbaSrgb:
|
||||
case Format.Bc1RgbaUnorm:
|
||||
data = BCnDecoder.DecodeBC1(data, width, height, depth, levels, layers);
|
||||
result = BCnDecoder.DecodeBC1(result, width, height, depth, levels, layers);
|
||||
break;
|
||||
case Format.Bc2Srgb:
|
||||
case Format.Bc2Unorm:
|
||||
data = BCnDecoder.DecodeBC2(data, width, height, depth, levels, layers);
|
||||
result = BCnDecoder.DecodeBC2(result, width, height, depth, levels, layers);
|
||||
break;
|
||||
case Format.Bc3Srgb:
|
||||
case Format.Bc3Unorm:
|
||||
data = BCnDecoder.DecodeBC3(data, width, height, depth, levels, layers);
|
||||
result = BCnDecoder.DecodeBC3(result, width, height, depth, levels, layers);
|
||||
break;
|
||||
case Format.Bc4Snorm:
|
||||
case Format.Bc4Unorm:
|
||||
data = BCnDecoder.DecodeBC4(data, width, height, depth, levels, layers, Format == Format.Bc4Snorm);
|
||||
result = BCnDecoder.DecodeBC4(result, width, height, depth, levels, layers, Format == Format.Bc4Snorm);
|
||||
break;
|
||||
case Format.Bc5Snorm:
|
||||
case Format.Bc5Unorm:
|
||||
data = BCnDecoder.DecodeBC5(data, width, height, depth, levels, layers, Format == Format.Bc5Snorm);
|
||||
result = BCnDecoder.DecodeBC5(result, width, height, depth, levels, layers, Format == Format.Bc5Snorm);
|
||||
break;
|
||||
case Format.Bc6HSfloat:
|
||||
case Format.Bc6HUfloat:
|
||||
data = BCnDecoder.DecodeBC6(data, width, height, depth, levels, layers, Format == Format.Bc6HSfloat);
|
||||
result = BCnDecoder.DecodeBC6(result, width, height, depth, levels, layers, Format == Format.Bc6HSfloat);
|
||||
break;
|
||||
case Format.Bc7Srgb:
|
||||
case Format.Bc7Unorm:
|
||||
data = BCnDecoder.DecodeBC7(data, width, height, depth, levels, layers);
|
||||
result = BCnDecoder.DecodeBC7(result, width, height, depth, levels, layers);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1445,7 +1443,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
DisposeTextures();
|
||||
|
||||
HostTexture = hostTexture;
|
||||
InvalidatedSequence++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1600,6 +1597,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
private void DisposeTextures()
|
||||
{
|
||||
InvalidatedSequence++;
|
||||
|
||||
_currentData = null;
|
||||
HostTexture.Release();
|
||||
|
||||
@@ -1634,8 +1633,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
DisposeTextures();
|
||||
|
||||
Disposed?.Invoke(this);
|
||||
|
||||
if (Group.Storage == this)
|
||||
{
|
||||
Group.Dispose();
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using Ryujinx.Cpu.Tracking;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Cpu.Tracking;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
@@ -348,9 +349,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
ReadOnlySpan<byte> data = _physicalMemory.GetSpan(Storage.Range.GetSlice((ulong)offset, (ulong)size));
|
||||
|
||||
data = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel, true);
|
||||
SpanOrArray<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel, true);
|
||||
|
||||
Storage.SetData(data, info.BaseLayer, info.BaseLevel);
|
||||
Storage.SetData(result, info.BaseLayer, info.BaseLevel);
|
||||
|
||||
offsetIndex++;
|
||||
}
|
||||
|
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private const ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 2;
|
||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||
private const uint CodeGenVersion = 3732;
|
||||
private const uint CodeGenVersion = 3807;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
|
@@ -7,7 +7,9 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg
|
||||
{
|
||||
unsafe class FFmpegContext : IDisposable
|
||||
{
|
||||
private readonly FFCodec.AVCodec_decode _decodeFrame;
|
||||
private unsafe delegate int AVCodec_decode(AVCodecContext* avctx, void* outdata, int* got_frame_ptr, AVPacket* avpkt);
|
||||
|
||||
private readonly AVCodec_decode _decodeFrame;
|
||||
private static readonly FFmpegApi.av_log_set_callback_callback _logFunc;
|
||||
private readonly AVCodec* _codec;
|
||||
private AVPacket* _packet;
|
||||
@@ -53,17 +55,17 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg
|
||||
// libavcodec 59.24 changed AvCodec to move its private API and also move the codec function to an union.
|
||||
if (avCodecMajorVersion > 59 || (avCodecMajorVersion == 59 && avCodecMinorVersion > 24))
|
||||
{
|
||||
_decodeFrame = Marshal.GetDelegateForFunctionPointer<FFCodec.AVCodec_decode>(((FFCodec*)_codec)->CodecCallback);
|
||||
_decodeFrame = Marshal.GetDelegateForFunctionPointer<AVCodec_decode>(((FFCodec<AVCodec>*)_codec)->CodecCallback);
|
||||
}
|
||||
// libavcodec 59.x changed AvCodec private API layout.
|
||||
else if (avCodecMajorVersion == 59)
|
||||
{
|
||||
_decodeFrame = Marshal.GetDelegateForFunctionPointer<FFCodec.AVCodec_decode>(((FFCodecLegacy<AVCodec>*)_codec)->Decode);
|
||||
_decodeFrame = Marshal.GetDelegateForFunctionPointer<AVCodec_decode>(((FFCodecLegacy<AVCodec501>*)_codec)->Decode);
|
||||
}
|
||||
// libavcodec 58.x and lower
|
||||
else
|
||||
{
|
||||
_decodeFrame = Marshal.GetDelegateForFunctionPointer<FFCodec.AVCodec_decode>(((FFCodecLegacy<AVCodecLegacy>*)_codec)->Decode);
|
||||
_decodeFrame = Marshal.GetDelegateForFunctionPointer<AVCodec_decode>(((FFCodecLegacy<AVCodec>*)_codec)->Decode);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -20,6 +20,7 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
|
||||
public unsafe IntPtr PrivClass;
|
||||
public IntPtr Profiles;
|
||||
public unsafe byte* WrapperName;
|
||||
public IntPtr ChLayouts;
|
||||
#pragma warning restore CS0649
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
|
||||
{
|
||||
struct AVCodecLegacy
|
||||
struct AVCodec501
|
||||
{
|
||||
#pragma warning disable CS0649
|
||||
public unsafe byte* Name;
|
||||
@@ -20,7 +20,6 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
|
||||
public unsafe IntPtr PrivClass;
|
||||
public IntPtr Profiles;
|
||||
public unsafe byte* WrapperName;
|
||||
public IntPtr ChLayouts;
|
||||
#pragma warning restore CS0649
|
||||
}
|
||||
}
|
@@ -9,7 +9,7 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
|
||||
public unsafe IntPtr AvClass;
|
||||
public int LogLevelOffset;
|
||||
public int CodecType;
|
||||
public unsafe AVCodecLegacy* Codec;
|
||||
public unsafe AVCodec* Codec;
|
||||
public AVCodecID CodecId;
|
||||
public uint CodecTag;
|
||||
public IntPtr PrivData;
|
||||
|
@@ -2,12 +2,10 @@
|
||||
|
||||
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
|
||||
{
|
||||
struct FFCodec
|
||||
struct FFCodec<T> where T: struct
|
||||
{
|
||||
public unsafe delegate int AVCodec_decode(AVCodecContext* avctx, void* outdata, int* got_frame_ptr, AVPacket* avpkt);
|
||||
|
||||
#pragma warning disable CS0649
|
||||
public AVCodec Base;
|
||||
public T Base;
|
||||
public int CapsInternalOrCbType;
|
||||
public int PrivDataSize;
|
||||
public IntPtr UpdateThreadContext;
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
|
||||
@@ -48,17 +49,19 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
return GetData();
|
||||
}
|
||||
|
||||
public void SetData(ReadOnlySpan<byte> data)
|
||||
public void SetData(SpanOrArray<byte> data)
|
||||
{
|
||||
Buffer.SetData(_buffer, _bufferOffset, data.Slice(0, Math.Min(data.Length, _bufferSize)));
|
||||
var dataSpan = data.AsSpan();
|
||||
|
||||
Buffer.SetData(_buffer, _bufferOffset, dataSpan.Slice(0, Math.Min(dataSpan.Length, _bufferSize)));
|
||||
}
|
||||
|
||||
public void SetData(ReadOnlySpan<byte> data, int layer, int level)
|
||||
public void SetData(SpanOrArray<byte> data, int layer, int level)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void SetData(ReadOnlySpan<byte> data, int layer, int level, Rectangle<int> region)
|
||||
public void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
|
||||
@@ -317,32 +318,36 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
}
|
||||
}
|
||||
|
||||
public void SetData(ReadOnlySpan<byte> data)
|
||||
public void SetData(SpanOrArray<byte> data)
|
||||
{
|
||||
var dataSpan = data.AsSpan();
|
||||
|
||||
if (Format == Format.S8UintD24Unorm)
|
||||
{
|
||||
data = FormatConverter.ConvertS8D24ToD24S8(data);
|
||||
dataSpan = FormatConverter.ConvertS8D24ToD24S8(dataSpan);
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* ptr = data)
|
||||
fixed (byte* ptr = dataSpan)
|
||||
{
|
||||
ReadFrom((IntPtr)ptr, data.Length);
|
||||
ReadFrom((IntPtr)ptr, dataSpan.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetData(ReadOnlySpan<byte> data, int layer, int level)
|
||||
public void SetData(SpanOrArray<byte> data, int layer, int level)
|
||||
{
|
||||
var dataSpan = data.AsSpan();
|
||||
|
||||
if (Format == Format.S8UintD24Unorm)
|
||||
{
|
||||
data = FormatConverter.ConvertS8D24ToD24S8(data);
|
||||
dataSpan = FormatConverter.ConvertS8D24ToD24S8(dataSpan);
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* ptr = data)
|
||||
fixed (byte* ptr = dataSpan)
|
||||
{
|
||||
int width = Math.Max(Info.Width >> level, 1);
|
||||
int height = Math.Max(Info.Height >> level, 1);
|
||||
@@ -352,11 +357,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
}
|
||||
}
|
||||
|
||||
public void SetData(ReadOnlySpan<byte> data, int layer, int level, Rectangle<int> region)
|
||||
public void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region)
|
||||
{
|
||||
var dataSpan = data.AsSpan();
|
||||
|
||||
if (Format == Format.S8UintD24Unorm)
|
||||
{
|
||||
data = FormatConverter.ConvertS8D24ToD24S8(data);
|
||||
dataSpan = FormatConverter.ConvertS8D24ToD24S8(dataSpan);
|
||||
}
|
||||
|
||||
int wInBlocks = BitUtils.DivRoundUp(region.Width, Info.BlockWidth);
|
||||
@@ -364,7 +371,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* ptr = data)
|
||||
fixed (byte* ptr = dataSpan)
|
||||
{
|
||||
ReadFrom2D(
|
||||
(IntPtr)ptr,
|
||||
|
@@ -262,6 +262,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
|
||||
Instruction ioVariable, elemIndex;
|
||||
|
||||
Instruction invocationId = null;
|
||||
|
||||
if (Config.Stage == ShaderStage.TessellationControl && isOutAttr)
|
||||
{
|
||||
invocationId = Load(TypeS32(), Inputs[AttributeConsts.InvocationId]);
|
||||
}
|
||||
|
||||
bool isUserAttr = attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd;
|
||||
|
||||
if (isUserAttr &&
|
||||
@@ -273,7 +280,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
elemIndex = Constant(TypeU32(), attrInfo.GetInnermostIndex());
|
||||
var vecIndex = Constant(TypeU32(), (attr - AttributeConsts.UserAttributeBase) >> 4);
|
||||
|
||||
if (AttributeInfo.IsArrayAttributeSpirv(Config.Stage, isOutAttr))
|
||||
bool isArray = AttributeInfo.IsArrayAttributeSpirv(Config.Stage, isOutAttr);
|
||||
|
||||
if (invocationId != null && isArray)
|
||||
{
|
||||
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, index, vecIndex, elemIndex);
|
||||
}
|
||||
else if (invocationId != null)
|
||||
{
|
||||
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, vecIndex, elemIndex);
|
||||
}
|
||||
else if (isArray)
|
||||
{
|
||||
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index, vecIndex, elemIndex);
|
||||
}
|
||||
@@ -308,12 +325,29 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
|
||||
if ((type & (AggregateType.Array | AggregateType.Vector)) == 0)
|
||||
{
|
||||
return isIndexed ? AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index) : ioVariable;
|
||||
if (invocationId != null)
|
||||
{
|
||||
return isIndexed
|
||||
? AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, index)
|
||||
: AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId);
|
||||
}
|
||||
else
|
||||
{
|
||||
return isIndexed ? AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index) : ioVariable;
|
||||
}
|
||||
}
|
||||
|
||||
elemIndex = Constant(TypeU32(), attrInfo.GetInnermostIndex());
|
||||
|
||||
if (isIndexed)
|
||||
if (invocationId != null && isIndexed)
|
||||
{
|
||||
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, index, elemIndex);
|
||||
}
|
||||
else if (invocationId != null)
|
||||
{
|
||||
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, elemIndex);
|
||||
}
|
||||
else if (isIndexed)
|
||||
{
|
||||
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index, elemIndex);
|
||||
}
|
||||
@@ -327,12 +361,29 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
{
|
||||
var storageClass = isOutAttr ? StorageClass.Output : StorageClass.Input;
|
||||
|
||||
Instruction invocationId = null;
|
||||
|
||||
if (Config.Stage == ShaderStage.TessellationControl && isOutAttr)
|
||||
{
|
||||
invocationId = Load(TypeS32(), Inputs[AttributeConsts.InvocationId]);
|
||||
}
|
||||
|
||||
elemType = AggregateType.FP32;
|
||||
var ioVariable = isOutAttr ? OutputsArray : InputsArray;
|
||||
var vecIndex = ShiftRightLogical(TypeS32(), attrIndex, Constant(TypeS32(), 2));
|
||||
var elemIndex = BitwiseAnd(TypeS32(), attrIndex, Constant(TypeS32(), 3));
|
||||
|
||||
if (AttributeInfo.IsArrayAttributeSpirv(Config.Stage, isOutAttr))
|
||||
bool isArray = AttributeInfo.IsArrayAttributeSpirv(Config.Stage, isOutAttr);
|
||||
|
||||
if (invocationId != null && isArray)
|
||||
{
|
||||
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, index, vecIndex, elemIndex);
|
||||
}
|
||||
else if (invocationId != null)
|
||||
{
|
||||
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, vecIndex, elemIndex);
|
||||
}
|
||||
else if (isArray)
|
||||
{
|
||||
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index, vecIndex, elemIndex);
|
||||
}
|
||||
|
@@ -473,6 +473,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
var attrType = context.TypeVector(context.TypeFP32(), (LiteralInteger)4);
|
||||
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)MaxAttributes));
|
||||
|
||||
if (context.Config.Stage == ShaderStage.TessellationControl)
|
||||
{
|
||||
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), context.Config.ThreadsPerInputPrimitive));
|
||||
}
|
||||
|
||||
var spvType = context.TypePointer(StorageClass.Output, attrType);
|
||||
var spvVar = context.Variable(spvType, StorageClass.Output);
|
||||
|
||||
@@ -543,6 +548,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
}
|
||||
}
|
||||
|
||||
if (context.Config.Stage == ShaderStage.TessellationControl && isOutAttr && !perPatch)
|
||||
{
|
||||
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), context.Config.ThreadsPerInputPrimitive));
|
||||
}
|
||||
|
||||
var spvType = context.TypePointer(storageClass, attrType);
|
||||
var spvVar = context.Variable(spvType, storageClass);
|
||||
|
||||
@@ -634,6 +644,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)arraySize));
|
||||
}
|
||||
|
||||
if (context.Config.Stage == ShaderStage.TessellationControl && isOutAttr)
|
||||
{
|
||||
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), context.Config.ThreadsPerInputPrimitive));
|
||||
}
|
||||
|
||||
var spvType = context.TypePointer(storageClass, attrType);
|
||||
var spvVar = context.Variable(spvType, storageClass);
|
||||
|
||||
|
@@ -377,6 +377,8 @@ namespace Ryujinx.Graphics.Shader.Decoders
|
||||
|
||||
if (lastOp.Name == InstName.Brx && block.Successors.Count == (hasNext ? 1 : 0))
|
||||
{
|
||||
HashSet<ulong> visited = new HashSet<ulong>();
|
||||
|
||||
InstBrx opBrx = new InstBrx(lastOp.RawOpCode);
|
||||
ulong baseOffset = lastOp.GetAbsoluteAddress();
|
||||
|
||||
@@ -392,9 +394,14 @@ namespace Ryujinx.Graphics.Shader.Decoders
|
||||
for (int i = 0; i < cbOffsetsCount; i++)
|
||||
{
|
||||
uint targetOffset = config.ConstantBuffer1Read(cbBaseOffset + i * 4);
|
||||
Block target = getBlock(baseOffset + targetOffset);
|
||||
target.Predecessors.Add(block);
|
||||
block.Successors.Add(target);
|
||||
ulong targetAddress = baseOffset + targetOffset;
|
||||
|
||||
if (visited.Add(targetAddress))
|
||||
{
|
||||
Block target = getBlock(targetAddress);
|
||||
target.Predecessors.Add(block);
|
||||
block.Successors.Add(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -41,24 +41,82 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
|
||||
Operand address = context.IAdd(Register(op.SrcA, RegisterType.Gpr), Const(offset));
|
||||
|
||||
// Sorting the target addresses in descending order improves the code,
|
||||
// since it will always check the most distant targets first, then the
|
||||
// near ones. This can be easily transformed into if/else statements.
|
||||
var sortedTargets = context.CurrBlock.Successors.Skip(startIndex).OrderByDescending(x => x.Address);
|
||||
var targets = context.CurrBlock.Successors.Skip(startIndex);
|
||||
|
||||
Block lastTarget = sortedTargets.LastOrDefault();
|
||||
bool allTargetsSinglePred = true;
|
||||
int total = context.CurrBlock.Successors.Count - startIndex;
|
||||
int count = 0;
|
||||
|
||||
foreach (Block possibleTarget in sortedTargets)
|
||||
foreach (var target in targets.OrderBy(x => x.Address))
|
||||
{
|
||||
Operand label = context.GetLabel(possibleTarget.Address);
|
||||
|
||||
if (possibleTarget != lastTarget)
|
||||
if (++count < total && (target.Predecessors.Count > 1 || target.Address <= context.CurrBlock.Address))
|
||||
{
|
||||
context.BranchIfTrue(label, context.ICompareEqual(address, Const((int)possibleTarget.Address)));
|
||||
allTargetsSinglePred = false;
|
||||
break;
|
||||
}
|
||||
else
|
||||
}
|
||||
|
||||
if (allTargetsSinglePred)
|
||||
{
|
||||
// Chain blocks, each target block will check if the BRX target address
|
||||
// matches its own address, if not, it jumps to the next target which will do the same check,
|
||||
// until it reaches the last possible target, which executed unconditionally.
|
||||
// We can only do this if the BRX block is the only predecessor of all target blocks.
|
||||
// Additionally, this is not supported for blocks located before the current block,
|
||||
// since it will be too late to insert a label, but this is something that can be improved
|
||||
// in the future if necessary.
|
||||
|
||||
var sortedTargets = targets.OrderBy(x => x.Address);
|
||||
|
||||
Block currentTarget = null;
|
||||
ulong firstTargetAddress = 0;
|
||||
|
||||
foreach (Block nextTarget in sortedTargets)
|
||||
{
|
||||
context.Branch(label);
|
||||
if (currentTarget != null)
|
||||
{
|
||||
if (currentTarget.Address != nextTarget.Address)
|
||||
{
|
||||
context.SetBrxTarget(currentTarget.Address, address, (int)currentTarget.Address, nextTarget.Address);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
firstTargetAddress = nextTarget.Address;
|
||||
}
|
||||
|
||||
currentTarget = nextTarget;
|
||||
}
|
||||
|
||||
context.Branch(context.GetLabel(firstTargetAddress));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Emit the branches sequentially.
|
||||
// This generates slightly worse code, but should work for all cases.
|
||||
|
||||
var sortedTargets = targets.OrderByDescending(x => x.Address);
|
||||
ulong lastTargetAddress = ulong.MaxValue;
|
||||
|
||||
count = 0;
|
||||
|
||||
foreach (Block target in sortedTargets)
|
||||
{
|
||||
Operand label = context.GetLabel(target.Address);
|
||||
|
||||
if (++count < total)
|
||||
{
|
||||
if (target.Address != lastTargetAddress)
|
||||
{
|
||||
context.BranchIfTrue(label, context.ICompareEqual(address, Const((int)target.Address)));
|
||||
}
|
||||
|
||||
lastTargetAddress = target.Address;
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Branch(label);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -37,7 +37,12 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||
|
||||
Config = config;
|
||||
|
||||
if (config.GpPassthrough)
|
||||
if (config.Stage == ShaderStage.TessellationControl)
|
||||
{
|
||||
// Required to index outputs.
|
||||
Info.Inputs.Add(AttributeConsts.InvocationId);
|
||||
}
|
||||
else if (config.GpPassthrough)
|
||||
{
|
||||
int passthroughAttributes = config.PassthroughAttributes;
|
||||
while (passthroughAttributes != 0)
|
||||
|
@@ -21,8 +21,33 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
public int OperationsCount => _operations.Count;
|
||||
|
||||
private struct BrxTarget
|
||||
{
|
||||
public readonly Operand Selector;
|
||||
public readonly int ExpectedValue;
|
||||
public readonly ulong NextTargetAddress;
|
||||
|
||||
public BrxTarget(Operand selector, int expectedValue, ulong nextTargetAddress)
|
||||
{
|
||||
Selector = selector;
|
||||
ExpectedValue = expectedValue;
|
||||
NextTargetAddress = nextTargetAddress;
|
||||
}
|
||||
}
|
||||
|
||||
private class BlockLabel
|
||||
{
|
||||
public readonly Operand Label;
|
||||
public BrxTarget BrxTarget;
|
||||
|
||||
public BlockLabel(Operand label)
|
||||
{
|
||||
Label = label;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<Operation> _operations;
|
||||
private readonly Dictionary<ulong, Operand> _labels;
|
||||
private readonly Dictionary<ulong, BlockLabel> _labels;
|
||||
|
||||
public EmitterContext(DecodedProgram program, ShaderConfig config, bool isNonMain)
|
||||
{
|
||||
@@ -30,7 +55,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
Config = config;
|
||||
IsNonMain = isNonMain;
|
||||
_operations = new List<Operation>();
|
||||
_labels = new Dictionary<ulong, Operand>();
|
||||
_labels = new Dictionary<ulong, BlockLabel>();
|
||||
|
||||
EmitStart();
|
||||
}
|
||||
@@ -158,14 +183,40 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
public Operand GetLabel(ulong address)
|
||||
{
|
||||
if (!_labels.TryGetValue(address, out Operand label))
|
||||
{
|
||||
label = Label();
|
||||
return EnsureBlockLabel(address).Label;
|
||||
}
|
||||
|
||||
_labels.Add(address, label);
|
||||
public void SetBrxTarget(ulong address, Operand selector, int targetValue, ulong nextTargetAddress)
|
||||
{
|
||||
BlockLabel blockLabel = EnsureBlockLabel(address);
|
||||
Debug.Assert(blockLabel.BrxTarget.Selector == null);
|
||||
blockLabel.BrxTarget = new BrxTarget(selector, targetValue, nextTargetAddress);
|
||||
}
|
||||
|
||||
public void EnterBlock(ulong address)
|
||||
{
|
||||
BlockLabel blockLabel = EnsureBlockLabel(address);
|
||||
|
||||
MarkLabel(blockLabel.Label);
|
||||
|
||||
BrxTarget brxTarget = blockLabel.BrxTarget;
|
||||
|
||||
if (brxTarget.Selector != null)
|
||||
{
|
||||
this.BranchIfFalse(GetLabel(brxTarget.NextTargetAddress), this.ICompareEqual(brxTarget.Selector, Const(brxTarget.ExpectedValue)));
|
||||
}
|
||||
}
|
||||
|
||||
private BlockLabel EnsureBlockLabel(ulong address)
|
||||
{
|
||||
if (!_labels.TryGetValue(address, out BlockLabel blockLabel))
|
||||
{
|
||||
blockLabel = new BlockLabel(Label());
|
||||
|
||||
_labels.Add(address, blockLabel);
|
||||
}
|
||||
|
||||
return label;
|
||||
return blockLabel;
|
||||
}
|
||||
|
||||
public void PrepareForVertexReturn()
|
||||
|
@@ -306,7 +306,10 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
config._perPatchAttributeLocations = locationsMap;
|
||||
}
|
||||
|
||||
if (config.Stage != ShaderStage.Fragment)
|
||||
// We don't consider geometry shaders using the geometry shader passthrough feature
|
||||
// as being the last because when this feature is used, it can't actually modify any of the outputs,
|
||||
// so the stage that comes before it is the last one that can do modifications.
|
||||
if (config.Stage != ShaderStage.Fragment && (config.Stage != ShaderStage.Geometry || !config.GpPassthrough))
|
||||
{
|
||||
LastInVertexPipeline = false;
|
||||
}
|
||||
|
@@ -162,7 +162,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
{
|
||||
context.CurrBlock = block;
|
||||
|
||||
context.MarkLabel(context.GetLabel(block.Address));
|
||||
context.EnterBlock(block.Address);
|
||||
|
||||
EmitOps(context, block);
|
||||
}
|
||||
|
@@ -93,7 +93,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
};
|
||||
}
|
||||
|
||||
public static Span<byte> ConvertBlockLinearToLinear(
|
||||
public static byte[] ConvertBlockLinearToLinear(
|
||||
int width,
|
||||
int height,
|
||||
int depth,
|
||||
@@ -119,7 +119,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
blockHeight,
|
||||
bytesPerPixel);
|
||||
|
||||
Span<byte> output = new byte[outSize];
|
||||
byte[] output = new byte[outSize];
|
||||
|
||||
int outOffs = 0;
|
||||
|
||||
@@ -246,7 +246,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
return output;
|
||||
}
|
||||
|
||||
public static Span<byte> ConvertLinearStridedToLinear(
|
||||
public static byte[] ConvertLinearStridedToLinear(
|
||||
int width,
|
||||
int height,
|
||||
int blockWidth,
|
||||
@@ -262,14 +262,15 @@ namespace Ryujinx.Graphics.Texture
|
||||
int outStride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment);
|
||||
lineSize = Math.Min(lineSize, outStride);
|
||||
|
||||
Span<byte> output = new byte[h * outStride];
|
||||
byte[] output = new byte[h * outStride];
|
||||
Span<byte> outSpan = output;
|
||||
|
||||
int outOffs = 0;
|
||||
int inOffs = 0;
|
||||
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
data.Slice(inOffs, lineSize).CopyTo(output.Slice(outOffs, lineSize));
|
||||
data.Slice(inOffs, lineSize).CopyTo(outSpan.Slice(outOffs, lineSize));
|
||||
|
||||
inOffs += stride;
|
||||
outOffs += outStride;
|
||||
|
@@ -12,13 +12,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private const int MaxUpdateBufferSize = 0x10000;
|
||||
|
||||
public const AccessFlags DefaultAccessFlags =
|
||||
AccessFlags.AccessIndirectCommandReadBit |
|
||||
AccessFlags.AccessShaderReadBit |
|
||||
AccessFlags.AccessShaderWriteBit |
|
||||
AccessFlags.AccessTransferReadBit |
|
||||
AccessFlags.AccessTransferWriteBit |
|
||||
AccessFlags.AccessUniformReadBit |
|
||||
AccessFlags.AccessShaderReadBit |
|
||||
AccessFlags.AccessShaderWriteBit;
|
||||
AccessFlags.AccessUniformReadBit;
|
||||
|
||||
private readonly VulkanRenderer _gd;
|
||||
private readonly Device _device;
|
||||
@@ -109,12 +108,34 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
if (isWrite)
|
||||
{
|
||||
_cachedConvertedBuffers.Clear();
|
||||
SignalWrite(0, Size);
|
||||
}
|
||||
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, int offset, int size, bool isWrite = false)
|
||||
{
|
||||
if (isWrite)
|
||||
{
|
||||
SignalWrite(offset, size);
|
||||
}
|
||||
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
public void SignalWrite(int offset, int size)
|
||||
{
|
||||
if (offset == 0 && size == Size)
|
||||
{
|
||||
_cachedConvertedBuffers.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
_cachedConvertedBuffers.ClearRange(offset, size);
|
||||
}
|
||||
}
|
||||
|
||||
public BufferHandle GetHandle()
|
||||
{
|
||||
var handle = _bufferHandle;
|
||||
@@ -183,6 +204,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
data.Slice(0, dataSize).CopyTo(new Span<byte>((void*)(_map + offset), dataSize));
|
||||
|
||||
SignalWrite(offset, dataSize);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -240,7 +263,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
endRenderPass?.Invoke();
|
||||
|
||||
var dstBuffer = GetBuffer(cbs.CommandBuffer, true).Get(cbs, dstOffset, data.Length).Value;
|
||||
var dstBuffer = GetBuffer(cbs.CommandBuffer, dstOffset, data.Length, true).Get(cbs, dstOffset, data.Length).Value;
|
||||
|
||||
InsertBufferBarrier(
|
||||
_gd,
|
||||
@@ -362,9 +385,26 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_waitable.WaitForFences(_gd.Api, _device, offset, size);
|
||||
}
|
||||
|
||||
private bool BoundToRange(int offset, ref int size)
|
||||
{
|
||||
if (offset >= Size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
size = Math.Min(Size - offset, size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBufferI8ToI16(CommandBufferScoped cbs, int offset, int size)
|
||||
{
|
||||
var key = new I8ToI16CacheKey();
|
||||
if (!BoundToRange(offset, ref size))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var key = new I8ToI16CacheKey(_gd);
|
||||
|
||||
if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder))
|
||||
{
|
||||
@@ -373,6 +413,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_gd.PipelineInternal.EndRenderPass();
|
||||
_gd.HelperShader.ConvertI8ToI16(_gd, cbs, this, holder, offset, size);
|
||||
|
||||
key.SetBuffer(holder.GetBuffer());
|
||||
|
||||
_cachedConvertedBuffers.Add(offset, size, key, holder);
|
||||
}
|
||||
|
||||
@@ -381,6 +423,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public Auto<DisposableBuffer> GetAlignedVertexBuffer(CommandBufferScoped cbs, int offset, int size, int stride, int alignment)
|
||||
{
|
||||
if (!BoundToRange(offset, ref size))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var key = new AlignedVertexBufferCacheKey(_gd, stride, alignment);
|
||||
|
||||
if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder))
|
||||
@@ -402,6 +449,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public Auto<DisposableBuffer> GetBufferTopologyConversion(CommandBufferScoped cbs, int offset, int size, IndexBufferPattern pattern, int indexSize)
|
||||
{
|
||||
if (!BoundToRange(offset, ref size))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var key = new TopologyConversionCacheKey(_gd, pattern, indexSize);
|
||||
|
||||
if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder))
|
||||
@@ -417,6 +469,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_gd.PipelineInternal.EndRenderPass();
|
||||
_gd.HelperShader.ConvertIndexBuffer(_gd, cbs, this, holder, pattern, indexSize, offset, indexCount);
|
||||
|
||||
key.SetBuffer(holder.GetBuffer());
|
||||
|
||||
_cachedConvertedBuffers.Add(offset, size, key, holder);
|
||||
}
|
||||
|
||||
|
@@ -124,6 +124,16 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return null;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, BufferHandle handle, int offset, int size, bool isWrite)
|
||||
{
|
||||
if (TryGetBuffer(handle, out var holder))
|
||||
{
|
||||
return holder.GetBuffer(commandBuffer, offset, size, isWrite);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBufferI8ToI16(CommandBufferScoped cbs, BufferHandle handle, int offset, int size)
|
||||
{
|
||||
if (TryGetBuffer(handle, out var holder))
|
||||
|
@@ -25,6 +25,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return other is I8ToI16CacheKey;
|
||||
}
|
||||
|
||||
public void SetBuffer(Auto<DisposableBuffer> buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_gd.PipelineInternal.DirtyIndexBuffer(_buffer);
|
||||
@@ -160,6 +165,44 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearRange(int offset, int size)
|
||||
{
|
||||
if (_ranges != null && _ranges.Count > 0)
|
||||
{
|
||||
int end = offset + size;
|
||||
|
||||
List<ulong> toRemove = null;
|
||||
|
||||
foreach (KeyValuePair<ulong, List<Entry>> range in _ranges)
|
||||
{
|
||||
(int rOffset, int rSize) = UnpackRange(range.Key);
|
||||
|
||||
int rEnd = rOffset + rSize;
|
||||
|
||||
if (rEnd > offset && rOffset < end)
|
||||
{
|
||||
List<Entry> entries = range.Value;
|
||||
|
||||
foreach (Entry entry in entries)
|
||||
{
|
||||
entry.Key.Dispose();
|
||||
entry.Value.Dispose();
|
||||
}
|
||||
|
||||
(toRemove ??= new List<ulong>()).Add(range.Key);
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove != null)
|
||||
{
|
||||
foreach (ulong range in toRemove)
|
||||
{
|
||||
_ranges.Remove(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Entry> GetEntries(int offset, int size)
|
||||
{
|
||||
if (_ranges == null)
|
||||
@@ -184,6 +227,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return (uint)offset | ((ulong)size << 32);
|
||||
}
|
||||
|
||||
private static (int offset, int size) UnpackRange(ulong range)
|
||||
{
|
||||
return ((int)range, (int)(range >> 32));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Clear();
|
||||
|
@@ -38,7 +38,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public int GetPrimitiveCount(int vertexCount)
|
||||
{
|
||||
return Math.Max(0, ((vertexCount - BaseIndex) + IndexStride - 1) / IndexStride);
|
||||
return Math.Max(0, (vertexCount - BaseIndex) / IndexStride);
|
||||
}
|
||||
|
||||
public int GetConvertedCount(int indexCount)
|
||||
|
@@ -49,7 +49,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
else
|
||||
{
|
||||
autoBuffer = gd.BufferManager.GetBuffer(cbs.CommandBuffer, _handle, false, out int _);
|
||||
autoBuffer = gd.BufferManager.GetBuffer(cbs.CommandBuffer, _handle, false, out int bufferSize);
|
||||
|
||||
if (_offset >= bufferSize)
|
||||
{
|
||||
autoBuffer = null;
|
||||
}
|
||||
|
||||
offset = _offset;
|
||||
size = _size;
|
||||
|
@@ -21,7 +21,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
protected readonly AutoFlushCounter AutoFlush;
|
||||
|
||||
private PipelineDynamicState _dynamicState;
|
||||
protected PipelineDynamicState DynamicState;
|
||||
private PipelineState _newState;
|
||||
private bool _stateDirty;
|
||||
private GAL.PrimitiveTopology _topology;
|
||||
@@ -95,7 +95,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
using var emptyVb = gd.BufferManager.Create(gd, EmptyVbSize);
|
||||
emptyVb.SetData(0, new byte[EmptyVbSize]);
|
||||
_vertexBuffers[0] = new VertexBufferState(emptyVb.GetBuffer(), 0, EmptyVbSize, 0);
|
||||
_vertexBuffers[0] = new VertexBufferState(emptyVb.GetBuffer(), 0, 0, EmptyVbSize, 0);
|
||||
_vertexBuffersDirty = ulong.MaxValue >> (64 - _vertexBuffers.Length);
|
||||
|
||||
ClearScissor = new Rectangle<int>(0, 0, 0xffff, 0xffff);
|
||||
@@ -150,7 +150,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
EndRenderPass();
|
||||
|
||||
var dst = Gd.BufferManager.GetBuffer(CommandBuffer, destination, true).Get(Cbs, offset, size).Value;
|
||||
var dst = Gd.BufferManager.GetBuffer(CommandBuffer, destination, offset, size, true).Get(Cbs, offset, size).Value;
|
||||
|
||||
BufferHolder.InsertBufferBarrier(
|
||||
Gd,
|
||||
@@ -228,18 +228,34 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
|
||||
}
|
||||
|
||||
public void CommandBufferBarrier()
|
||||
public unsafe void CommandBufferBarrier()
|
||||
{
|
||||
// TODO: More specific barrier?
|
||||
Barrier();
|
||||
MemoryBarrier memoryBarrier = new MemoryBarrier()
|
||||
{
|
||||
SType = StructureType.MemoryBarrier,
|
||||
SrcAccessMask = BufferHolder.DefaultAccessFlags,
|
||||
DstAccessMask = AccessFlags.AccessIndirectCommandReadBit
|
||||
};
|
||||
|
||||
Gd.Api.CmdPipelineBarrier(
|
||||
CommandBuffer,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.PipelineStageDrawIndirectBit,
|
||||
0,
|
||||
1,
|
||||
memoryBarrier,
|
||||
0,
|
||||
null,
|
||||
0,
|
||||
null);
|
||||
}
|
||||
|
||||
public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size)
|
||||
{
|
||||
EndRenderPass();
|
||||
|
||||
var src = Gd.BufferManager.GetBuffer(CommandBuffer, source, false);
|
||||
var dst = Gd.BufferManager.GetBuffer(CommandBuffer, destination, true);
|
||||
var src = Gd.BufferManager.GetBuffer(CommandBuffer, source, srcOffset, size, false);
|
||||
var dst = Gd.BufferManager.GetBuffer(CommandBuffer, destination, dstOffset, size, true);
|
||||
|
||||
BufferHolder.Copy(Gd, Cbs, src, dst, srcOffset, dstOffset, size);
|
||||
}
|
||||
@@ -388,7 +404,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var oldDepthTestEnable = _newState.DepthTestEnable;
|
||||
var oldDepthWriteEnable = _newState.DepthWriteEnable;
|
||||
var oldTopology = _newState.Topology;
|
||||
var oldViewports = _dynamicState.Viewports;
|
||||
var oldViewports = DynamicState.Viewports;
|
||||
var oldViewportsCount = _newState.ViewportsCount;
|
||||
|
||||
_newState.CullMode = CullModeFlags.CullModeNone;
|
||||
@@ -411,9 +427,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_newState.DepthWriteEnable = oldDepthWriteEnable;
|
||||
_newState.Topology = oldTopology;
|
||||
|
||||
_dynamicState.Viewports = oldViewports;
|
||||
_dynamicState.ViewportsCount = (int)oldViewportsCount;
|
||||
_dynamicState.SetViewportsDirty();
|
||||
DynamicState.Viewports = oldViewports;
|
||||
DynamicState.ViewportsCount = (int)oldViewportsCount;
|
||||
DynamicState.SetViewportsDirty();
|
||||
|
||||
_newState.ViewportsCount = oldViewportsCount;
|
||||
SignalStateChange();
|
||||
@@ -448,8 +464,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
ResumeTransformFeedbackInternal();
|
||||
DrawCount++;
|
||||
|
||||
var buffer = Gd.BufferManager.GetBuffer(CommandBuffer, indirectBuffer.Handle, true).Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
|
||||
var countBuffer = Gd.BufferManager.GetBuffer(CommandBuffer, parameterBuffer.Handle, true).Get(Cbs, parameterBuffer.Offset, parameterBuffer.Size).Value;
|
||||
var buffer = Gd.BufferManager
|
||||
.GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, true)
|
||||
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
|
||||
|
||||
var countBuffer = Gd.BufferManager
|
||||
.GetBuffer(CommandBuffer, parameterBuffer.Handle, parameterBuffer.Offset, parameterBuffer.Size, true)
|
||||
.Get(Cbs, parameterBuffer.Offset, parameterBuffer.Size).Value;
|
||||
|
||||
Gd.DrawIndirectCountApi.CmdDrawIndirectCount(
|
||||
CommandBuffer,
|
||||
@@ -478,8 +499,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
ResumeTransformFeedbackInternal();
|
||||
DrawCount++;
|
||||
|
||||
var buffer = Gd.BufferManager.GetBuffer(CommandBuffer, indirectBuffer.Handle, true).Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
|
||||
var countBuffer = Gd.BufferManager.GetBuffer(CommandBuffer, parameterBuffer.Handle, true).Get(Cbs, parameterBuffer.Offset, parameterBuffer.Size).Value;
|
||||
var buffer = Gd.BufferManager
|
||||
.GetBuffer(CommandBuffer, indirectBuffer.Handle, parameterBuffer.Offset, parameterBuffer.Size, true)
|
||||
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
|
||||
|
||||
var countBuffer = Gd.BufferManager
|
||||
.GetBuffer(CommandBuffer, parameterBuffer.Handle, parameterBuffer.Offset, parameterBuffer.Size, true)
|
||||
.Get(Cbs, parameterBuffer.Offset, parameterBuffer.Size).Value;
|
||||
|
||||
Gd.DrawIndirectCountApi.CmdDrawIndexedIndirectCount(
|
||||
CommandBuffer,
|
||||
@@ -525,17 +551,18 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
vkBlend = new PipelineColorBlendAttachmentState();
|
||||
}
|
||||
|
||||
_newState.BlendConstantR = blend.BlendConstant.Red;
|
||||
_newState.BlendConstantG = blend.BlendConstant.Green;
|
||||
_newState.BlendConstantB = blend.BlendConstant.Blue;
|
||||
_newState.BlendConstantA = blend.BlendConstant.Alpha;
|
||||
DynamicState.SetBlendConstants(
|
||||
blend.BlendConstant.Red,
|
||||
blend.BlendConstant.Green,
|
||||
blend.BlendConstant.Blue,
|
||||
blend.BlendConstant.Alpha);
|
||||
|
||||
SignalStateChange();
|
||||
}
|
||||
|
||||
public void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp)
|
||||
{
|
||||
_dynamicState.SetDepthBias(factor, units, clamp);
|
||||
DynamicState.SetDepthBias(factor, units, clamp);
|
||||
|
||||
_newState.DepthBiasEnable = enables != 0;
|
||||
SignalStateChange();
|
||||
@@ -753,10 +780,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var offset = new Offset2D(region.X, region.Y);
|
||||
var extent = new Extent2D((uint)region.Width, (uint)region.Height);
|
||||
|
||||
_dynamicState.SetScissor(i, new Rect2D(offset, extent));
|
||||
DynamicState.SetScissor(i, new Rect2D(offset, extent));
|
||||
}
|
||||
|
||||
_dynamicState.ScissorsCount = count;
|
||||
DynamicState.ScissorsCount = count;
|
||||
|
||||
_newState.ScissorsCount = (uint)count;
|
||||
SignalStateChange();
|
||||
@@ -764,7 +791,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void SetStencilTest(StencilTestDescriptor stencilTest)
|
||||
{
|
||||
_dynamicState.SetStencilMasks(
|
||||
DynamicState.SetStencilMasks(
|
||||
(uint)stencilTest.BackFuncMask,
|
||||
(uint)stencilTest.BackMask,
|
||||
(uint)stencilTest.BackFuncRef,
|
||||
@@ -813,7 +840,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
if (range.Handle != BufferHandle.Null)
|
||||
{
|
||||
_transformFeedbackBuffers[i] = new BufferState(Gd.BufferManager.GetBuffer(CommandBuffer, range.Handle, true), range.Offset, range.Size);
|
||||
_transformFeedbackBuffers[i] =
|
||||
new BufferState(Gd.BufferManager.GetBuffer(CommandBuffer, range.Handle, range.Offset, range.Size, true), range.Offset, range.Size);
|
||||
_transformFeedbackBuffers[i].BindTransformFeedbackBuffer(Gd, Cbs, (uint)i);
|
||||
}
|
||||
else
|
||||
@@ -975,7 +1003,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
var viewport = viewports[i];
|
||||
|
||||
_dynamicState.SetViewport(i, new Silk.NET.Vulkan.Viewport(
|
||||
DynamicState.SetViewport(i, new Silk.NET.Vulkan.Viewport(
|
||||
viewport.Region.X,
|
||||
viewport.Region.Y,
|
||||
viewport.Region.Width == 0f ? 1f : viewport.Region.Width,
|
||||
@@ -984,7 +1012,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Clamp(viewport.DepthFar)));
|
||||
}
|
||||
|
||||
_dynamicState.ViewportsCount = count;
|
||||
DynamicState.ViewportsCount = count;
|
||||
|
||||
float disableTransformF = disableTransform ? 1.0f : 0.0f;
|
||||
if (SupportBufferUpdater.Data.ViewportInverse.W != disableTransformF || disableTransform)
|
||||
@@ -1063,7 +1091,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_vertexBuffersDirty = ulong.MaxValue >> (64 - _vertexBuffers.Length);
|
||||
|
||||
_descriptorSetUpdater.SignalCommandBufferChange();
|
||||
_dynamicState.ForceAllDirty();
|
||||
DynamicState.ForceAllDirty();
|
||||
_currentPipelineHandle = 0;
|
||||
}
|
||||
|
||||
@@ -1201,7 +1229,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private void RecreatePipelineIfNeeded(PipelineBindPoint pbp)
|
||||
{
|
||||
_dynamicState.ReplayIfDirty(Gd.Api, CommandBuffer);
|
||||
DynamicState.ReplayIfDirty(Gd.Api, CommandBuffer);
|
||||
|
||||
// Commit changes to the support buffer before drawing.
|
||||
SupportBufferUpdater.Commit();
|
||||
@@ -1232,7 +1260,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_vertexBuffers[i].BindVertexBuffer(Gd, Cbs, (uint)i, ref _newState);
|
||||
|
||||
_vertexBuffersDirty &= ~(1u << i);
|
||||
_vertexBuffersDirty &= ~(1UL << i);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -135,11 +135,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
// It is assumed that Dynamic State is enabled when this conversion is used.
|
||||
|
||||
pipeline.BlendConstantA = state.BlendDescriptors[0].BlendConstant.Alpha;
|
||||
pipeline.BlendConstantB = state.BlendDescriptors[0].BlendConstant.Blue;
|
||||
pipeline.BlendConstantG = state.BlendDescriptors[0].BlendConstant.Green;
|
||||
pipeline.BlendConstantR = state.BlendDescriptors[0].BlendConstant.Red;
|
||||
|
||||
pipeline.CullMode = state.CullEnable ? state.CullMode.Convert() : CullModeFlags.CullModeNone;
|
||||
|
||||
pipeline.DepthBoundsTestEnable = false; // Not implemented.
|
||||
|
@@ -19,21 +19,34 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private uint _frontWriteMask;
|
||||
private uint _frontReference;
|
||||
|
||||
private Array4<float> _blendConstants;
|
||||
|
||||
public int ViewportsCount;
|
||||
public Array16<Viewport> Viewports;
|
||||
|
||||
private enum DirtyFlags
|
||||
{
|
||||
None = 0,
|
||||
DepthBias = 1 << 0,
|
||||
Scissor = 1 << 1,
|
||||
Stencil = 1 << 2,
|
||||
Viewport = 1 << 3,
|
||||
All = DepthBias | Scissor | Stencil | Viewport
|
||||
Blend = 1 << 0,
|
||||
DepthBias = 1 << 1,
|
||||
Scissor = 1 << 2,
|
||||
Stencil = 1 << 3,
|
||||
Viewport = 1 << 4,
|
||||
All = Blend | DepthBias | Scissor | Stencil | Viewport
|
||||
}
|
||||
|
||||
private DirtyFlags _dirty;
|
||||
|
||||
public void SetBlendConstants(float r, float g, float b, float a)
|
||||
{
|
||||
_blendConstants[0] = r;
|
||||
_blendConstants[1] = g;
|
||||
_blendConstants[2] = b;
|
||||
_blendConstants[3] = a;
|
||||
|
||||
_dirty |= DirtyFlags.Blend;
|
||||
}
|
||||
|
||||
public void SetDepthBias(float slopeFactor, float constantFactor, float clamp)
|
||||
{
|
||||
_depthBiasSlopeFactor = slopeFactor;
|
||||
@@ -87,6 +100,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void ReplayIfDirty(Vk api, CommandBuffer commandBuffer)
|
||||
{
|
||||
if (_dirty.HasFlag(DirtyFlags.Blend))
|
||||
{
|
||||
RecordBlend(api, commandBuffer);
|
||||
}
|
||||
|
||||
if (_dirty.HasFlag(DirtyFlags.DepthBias))
|
||||
{
|
||||
RecordDepthBias(api, commandBuffer);
|
||||
@@ -110,6 +128,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_dirty = DirtyFlags.None;
|
||||
}
|
||||
|
||||
private void RecordBlend(Vk api, CommandBuffer commandBuffer)
|
||||
{
|
||||
api.CmdSetBlendConstants(commandBuffer, _blendConstants.AsSpan());
|
||||
}
|
||||
|
||||
private void RecordDepthBias(Vk api, CommandBuffer commandBuffer)
|
||||
{
|
||||
api.CmdSetDepthBias(commandBuffer, _depthBiasConstantFactor, _depthBiasClamp, _depthBiasSlopeFactor);
|
||||
|
@@ -204,6 +204,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
|
||||
SignalCommandBufferChange();
|
||||
|
||||
DynamicState.ReplayIfDirty(Gd.Api, CommandBuffer);
|
||||
}
|
||||
|
||||
public void FlushCommandsImpl()
|
||||
|
@@ -499,7 +499,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
colorBlendState.BlendConstants[3] = BlendConstantA;
|
||||
|
||||
bool supportsExtDynamicState = gd.Capabilities.SupportsExtendedDynamicState;
|
||||
int dynamicStatesCount = supportsExtDynamicState ? 8 : 7;
|
||||
int dynamicStatesCount = supportsExtDynamicState ? 9 : 8;
|
||||
|
||||
DynamicState* dynamicStates = stackalloc DynamicState[dynamicStatesCount];
|
||||
|
||||
@@ -510,10 +510,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
dynamicStates[4] = DynamicState.StencilCompareMask;
|
||||
dynamicStates[5] = DynamicState.StencilWriteMask;
|
||||
dynamicStates[6] = DynamicState.StencilReference;
|
||||
dynamicStates[7] = DynamicState.BlendConstants;
|
||||
|
||||
if (supportsExtDynamicState)
|
||||
{
|
||||
dynamicStates[7] = DynamicState.VertexInputBindingStrideExt;
|
||||
dynamicStates[8] = DynamicState.VertexInputBindingStrideExt;
|
||||
}
|
||||
|
||||
var pipelineDynamicStateCreateInfo = new PipelineDynamicStateCreateInfo()
|
||||
|
@@ -64,6 +64,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
};
|
||||
|
||||
samplerCreateInfo.PNext = &customBorderColor;
|
||||
samplerCreateInfo.BorderColor = BorderColor.FloatCustomExt;
|
||||
}
|
||||
|
||||
gd.Api.CreateSampler(device, samplerCreateInfo, null, out var sampler).ThrowOnError();
|
||||
|
@@ -87,7 +87,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private void PushDataImpl(CommandBufferScoped cbs, BufferHolder dst, int dstOffset, ReadOnlySpan<byte> data)
|
||||
{
|
||||
var srcBuffer = _buffer.GetBuffer();
|
||||
var dstBuffer = dst.GetBuffer();
|
||||
var dstBuffer = dst.GetBuffer(cbs.CommandBuffer, dstOffset, data.Length, true);
|
||||
|
||||
int offset = _freeOffset;
|
||||
int capacity = BufferSize - offset;
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -90,17 +91,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_bufferView = null;
|
||||
}
|
||||
|
||||
public void SetData(ReadOnlySpan<byte> data)
|
||||
public void SetData(SpanOrArray<byte> data)
|
||||
{
|
||||
_gd.SetBufferData(_bufferHandle, _offset, data);
|
||||
}
|
||||
|
||||
public void SetData(ReadOnlySpan<byte> data, int layer, int level)
|
||||
public void SetData(SpanOrArray<byte> data, int layer, int level)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void SetData(ReadOnlySpan<byte> data, int layer, int level, Rectangle<int> region)
|
||||
public void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
@@ -480,6 +480,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
if (--_viewsCount == 0)
|
||||
{
|
||||
_gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_imageAuto, _size);
|
||||
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -514,10 +515,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
dst.Info,
|
||||
srcRegion,
|
||||
dstRegion,
|
||||
src.FirstLevel,
|
||||
dst.FirstLevel,
|
||||
src.FirstLayer,
|
||||
dst.FirstLayer,
|
||||
src.FirstLevel,
|
||||
dst.FirstLevel,
|
||||
layers,
|
||||
levels,
|
||||
linearFilter,
|
||||
@@ -873,17 +874,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return GetDataFromBuffer(result, size, result);
|
||||
}
|
||||
|
||||
public void SetData(ReadOnlySpan<byte> data)
|
||||
public void SetData(SpanOrArray<byte> data)
|
||||
{
|
||||
SetData(data, 0, 0, Info.GetLayers(), Info.Levels, singleSlice: false);
|
||||
}
|
||||
|
||||
public void SetData(ReadOnlySpan<byte> data, int layer, int level)
|
||||
public void SetData(SpanOrArray<byte> data, int layer, int level)
|
||||
{
|
||||
SetData(data, layer, level, 1, 1, singleSlice: true);
|
||||
}
|
||||
|
||||
public void SetData(ReadOnlySpan<byte> data, int layer, int level, Rectangle<int> region)
|
||||
public void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region)
|
||||
{
|
||||
SetData(data, layer, level, 1, 1, singleSlice: true, region);
|
||||
}
|
||||
|
@@ -57,37 +57,48 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
if (gd.NeedsVertexBufferAlignment(AttributeScalarAlignment, out int alignment) && (_stride % alignment) != 0)
|
||||
{
|
||||
autoBuffer = gd.BufferManager.GetAlignedVertexBuffer(cbs, _handle, _offset, _size, _stride, alignment);
|
||||
int stride = (_stride + (alignment - 1)) & -alignment;
|
||||
|
||||
var buffer = autoBuffer.Get(cbs, _offset, _size).Value;
|
||||
if (autoBuffer != null)
|
||||
{
|
||||
int stride = (_stride + (alignment - 1)) & -alignment;
|
||||
int newSize = (_size / _stride) * stride;
|
||||
|
||||
if (gd.Capabilities.SupportsExtendedDynamicState)
|
||||
{
|
||||
gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2(
|
||||
cbs.CommandBuffer,
|
||||
binding,
|
||||
1,
|
||||
buffer,
|
||||
0,
|
||||
(ulong)(_size / _stride) * (ulong)stride,
|
||||
(ulong)stride);
|
||||
}
|
||||
else
|
||||
{
|
||||
gd.Api.CmdBindVertexBuffers(cbs.CommandBuffer, binding, 1, buffer, 0);
|
||||
var buffer = autoBuffer.Get(cbs, 0, newSize).Value;
|
||||
|
||||
if (gd.Capabilities.SupportsExtendedDynamicState)
|
||||
{
|
||||
gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2(
|
||||
cbs.CommandBuffer,
|
||||
binding,
|
||||
1,
|
||||
buffer,
|
||||
0,
|
||||
(ulong)newSize,
|
||||
(ulong)stride);
|
||||
}
|
||||
else
|
||||
{
|
||||
gd.Api.CmdBindVertexBuffers(cbs.CommandBuffer, binding, 1, buffer, 0);
|
||||
}
|
||||
|
||||
_buffer = autoBuffer;
|
||||
}
|
||||
|
||||
_buffer = autoBuffer;
|
||||
state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)stride;
|
||||
state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)_stride;
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
autoBuffer = gd.BufferManager.GetBuffer(cbs.CommandBuffer, _handle, false, out int _);
|
||||
autoBuffer = gd.BufferManager.GetBuffer(cbs.CommandBuffer, _handle, false, out int size);
|
||||
|
||||
// The original stride must be reapplied in case it was rewritten.
|
||||
state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)_stride;
|
||||
|
||||
if (_offset >= size)
|
||||
{
|
||||
autoBuffer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -53,7 +53,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
"VUID-VkSubpassDependency-srcSubpass-00867"
|
||||
};
|
||||
|
||||
internal static Instance CreateInstance(Vk api, GraphicsDebugLevel logLevel, string[] requiredExtensions, out ExtDebugReport debugReport, out DebugReportCallbackEXT debugReportCallback)
|
||||
internal static Instance CreateInstance(Vk api, GraphicsDebugLevel logLevel, string[] requiredExtensions, out ExtDebugUtils debugUtils, out DebugUtilsMessengerEXT debugUtilsMessenger)
|
||||
{
|
||||
var enabledLayers = new List<string>();
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
AddAvailableLayer("VK_LAYER_KHRONOS_validation");
|
||||
}
|
||||
|
||||
var enabledExtensions = requiredExtensions.Append(ExtDebugReport.ExtensionName).ToArray();
|
||||
var enabledExtensions = requiredExtensions.Append(ExtDebugUtils.ExtensionName).ToArray();
|
||||
|
||||
var appName = Marshal.StringToHGlobalAnsi(AppName);
|
||||
|
||||
@@ -139,22 +139,18 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Marshal.FreeHGlobal(ppEnabledLayers[i]);
|
||||
}
|
||||
|
||||
CreateDebugCallbacks(api, logLevel, instance, out debugReport, out debugReportCallback);
|
||||
CreateDebugMessenger(api, logLevel, instance, out debugUtils, out debugUtilsMessenger);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private unsafe static uint DebugReport(
|
||||
uint flags,
|
||||
DebugReportObjectTypeEXT objectType,
|
||||
ulong @object,
|
||||
nuint location,
|
||||
int messageCode,
|
||||
byte* layerPrefix,
|
||||
byte* message,
|
||||
void* userData)
|
||||
private unsafe static uint DebugMessenger(
|
||||
DebugUtilsMessageSeverityFlagsEXT messageSeverity,
|
||||
DebugUtilsMessageTypeFlagsEXT messageTypes,
|
||||
DebugUtilsMessengerCallbackDataEXT* pCallbackData,
|
||||
void* pUserData)
|
||||
{
|
||||
var msg = Marshal.PtrToStringAnsi((IntPtr)message);
|
||||
var msg = Marshal.PtrToStringAnsi((IntPtr)pCallbackData->PMessage);
|
||||
|
||||
foreach (string excludedMessagePart in _excludedMessages)
|
||||
{
|
||||
@@ -164,26 +160,20 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
DebugReportFlagsEXT debugFlags = (DebugReportFlagsEXT)flags;
|
||||
|
||||
if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportErrorBitExt))
|
||||
if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt))
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, msg);
|
||||
//throw new Exception(msg);
|
||||
}
|
||||
else if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportWarningBitExt))
|
||||
else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityWarningBitExt))
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, msg);
|
||||
}
|
||||
else if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportInformationBitExt))
|
||||
else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityInfoBitExt))
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Gpu, msg);
|
||||
}
|
||||
else if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportPerformanceWarningBitExt))
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, msg);
|
||||
}
|
||||
else
|
||||
else // if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityVerboseBitExt))
|
||||
{
|
||||
Logger.Debug?.Print(LogClass.Gpu, msg);
|
||||
}
|
||||
@@ -551,46 +541,59 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return new CommandBufferPool(api, device, queue, queueLock, queueFamilyIndex);
|
||||
}
|
||||
|
||||
internal unsafe static void CreateDebugCallbacks(
|
||||
internal unsafe static void CreateDebugMessenger(
|
||||
Vk api,
|
||||
GraphicsDebugLevel logLevel,
|
||||
Instance instance,
|
||||
out ExtDebugReport debugReport,
|
||||
out DebugReportCallbackEXT debugReportCallback)
|
||||
out ExtDebugUtils debugUtils,
|
||||
out DebugUtilsMessengerEXT debugUtilsMessenger)
|
||||
{
|
||||
debugReport = default;
|
||||
debugUtils = default;
|
||||
|
||||
if (logLevel != GraphicsDebugLevel.None)
|
||||
{
|
||||
if (!api.TryGetInstanceExtension(instance, out debugReport))
|
||||
if (!api.TryGetInstanceExtension(instance, out debugUtils))
|
||||
{
|
||||
debugReportCallback = default;
|
||||
debugUtilsMessenger = default;
|
||||
return;
|
||||
}
|
||||
|
||||
var flags = logLevel switch
|
||||
var filterLogType = logLevel switch
|
||||
{
|
||||
GraphicsDebugLevel.Error => DebugReportFlagsEXT.DebugReportErrorBitExt,
|
||||
GraphicsDebugLevel.Slowdowns => DebugReportFlagsEXT.DebugReportErrorBitExt | DebugReportFlagsEXT.DebugReportPerformanceWarningBitExt,
|
||||
GraphicsDebugLevel.All => DebugReportFlagsEXT.DebugReportInformationBitExt |
|
||||
DebugReportFlagsEXT.DebugReportWarningBitExt |
|
||||
DebugReportFlagsEXT.DebugReportPerformanceWarningBitExt |
|
||||
DebugReportFlagsEXT.DebugReportErrorBitExt |
|
||||
DebugReportFlagsEXT.DebugReportDebugBitExt,
|
||||
GraphicsDebugLevel.Error => DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeValidationBitExt,
|
||||
GraphicsDebugLevel.Slowdowns => DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeValidationBitExt |
|
||||
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypePerformanceBitExt,
|
||||
GraphicsDebugLevel.All => DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeGeneralBitExt |
|
||||
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeValidationBitExt |
|
||||
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypePerformanceBitExt,
|
||||
_ => throw new ArgumentException($"Invalid log level \"{logLevel}\".")
|
||||
};
|
||||
var debugReportCallbackCreateInfo = new DebugReportCallbackCreateInfoEXT()
|
||||
|
||||
var filterLogSeverity = logLevel switch
|
||||
{
|
||||
SType = StructureType.DebugReportCallbackCreateInfoExt,
|
||||
Flags = flags,
|
||||
PfnCallback = new PfnDebugReportCallbackEXT(DebugReport)
|
||||
GraphicsDebugLevel.Error => DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt,
|
||||
GraphicsDebugLevel.Slowdowns => DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityWarningBitExt,
|
||||
GraphicsDebugLevel.All => DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityInfoBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityWarningBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityVerboseBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt,
|
||||
_ => throw new ArgumentException($"Invalid log level \"{logLevel}\".")
|
||||
};
|
||||
|
||||
debugReport.CreateDebugReportCallback(instance, in debugReportCallbackCreateInfo, null, out debugReportCallback).ThrowOnError();
|
||||
var debugUtilsMessengerCreateInfo = new DebugUtilsMessengerCreateInfoEXT()
|
||||
{
|
||||
SType = StructureType.DebugUtilsMessengerCreateInfoExt,
|
||||
MessageType = filterLogType,
|
||||
MessageSeverity = filterLogSeverity,
|
||||
PfnUserCallback = new PfnDebugUtilsMessengerCallbackEXT(DebugMessenger)
|
||||
};
|
||||
|
||||
debugUtils.CreateDebugUtilsMessenger(instance, in debugUtilsMessengerCreateInfo, null, out debugUtilsMessenger).ThrowOnError();
|
||||
}
|
||||
else
|
||||
{
|
||||
debugReportCallback = default;
|
||||
debugUtilsMessenger = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -33,7 +33,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
internal KhrPushDescriptor PushDescriptorApi { get; private set; }
|
||||
internal ExtTransformFeedback TransformFeedbackApi { get; private set; }
|
||||
internal KhrDrawIndirectCount DrawIndirectCountApi { get; private set; }
|
||||
internal ExtDebugReport DebugReportApi { get; private set; }
|
||||
internal ExtDebugUtils DebugUtilsApi { get; private set; }
|
||||
|
||||
internal uint QueueFamilyIndex { get; private set; }
|
||||
internal Queue Queue { get; private set; }
|
||||
@@ -57,7 +57,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private SyncManager _syncManager;
|
||||
|
||||
private PipelineFull _pipeline;
|
||||
private DebugReportCallbackEXT _debugReportCallback;
|
||||
private DebugUtilsMessengerEXT _debugUtilsMessenger;
|
||||
|
||||
internal HelperShader HelperShader { get; private set; }
|
||||
internal PipelineFull PipelineInternal => _pipeline;
|
||||
@@ -237,9 +237,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
Api = api;
|
||||
|
||||
_instance = VulkanInitialization.CreateInstance(api, logLevel, _getRequiredExtensions(), out ExtDebugReport debugReport, out _debugReportCallback);
|
||||
_instance = VulkanInitialization.CreateInstance(api, logLevel, _getRequiredExtensions(), out ExtDebugUtils debugUtils, out _debugUtilsMessenger);
|
||||
|
||||
DebugReportApi = debugReport;
|
||||
DebugUtilsApi = debugUtils;
|
||||
|
||||
if (api.TryGetInstanceExtension(_instance, out KhrSurface surfaceApi))
|
||||
{
|
||||
@@ -310,7 +310,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
internal TextureView CreateTextureView(TextureCreateInfo info, float scale)
|
||||
{
|
||||
// This should be disposed when all views are destroyed.
|
||||
using var storage = CreateTextureStorage(info, scale);
|
||||
var storage = CreateTextureStorage(info, scale);
|
||||
return storage.CreateView(info, 0, 0);
|
||||
}
|
||||
|
||||
@@ -584,9 +584,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
MemoryAllocator.Dispose();
|
||||
|
||||
if (_debugReportCallback.Handle != 0)
|
||||
if (_debugUtilsMessenger.Handle != 0)
|
||||
{
|
||||
DebugReportApi.DestroyDebugReportCallback(_instance, _debugReportCallback, null);
|
||||
DebugUtilsApi.DestroyDebugUtilsMessenger(_instance, _debugUtilsMessenger, null);
|
||||
}
|
||||
|
||||
foreach (var shader in Shaders)
|
||||
|
@@ -918,7 +918,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
|
||||
return -1;
|
||||
}
|
||||
|
||||
return int.Parse(part.Substring(0, numberLength));
|
||||
return int.Parse(part.AsSpan(0, numberLength));
|
||||
}
|
||||
|
||||
private string ParseNumber(bool isSigned = false)
|
||||
|
@@ -2540,11 +2540,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
|
||||
for (int attempt = 0; attempt < 8; attempt++)
|
||||
{
|
||||
address = BitUtils.AlignDown(regionStart + GetRandomValue(0, aslrMaxOffset) * (ulong)alignment, alignment);
|
||||
ulong aslrAddress = BitUtils.AlignDown(regionStart + GetRandomValue(0, aslrMaxOffset) * (ulong)alignment, alignment);
|
||||
ulong aslrEndAddr = aslrAddress + totalNeededSize;
|
||||
|
||||
ulong endAddr = address + totalNeededSize;
|
||||
|
||||
KMemoryInfo info = _blockManager.FindBlock(address).GetInfo();
|
||||
KMemoryInfo info = _blockManager.FindBlock(aslrAddress).GetInfo();
|
||||
|
||||
if (info.State != MemoryState.Unmapped)
|
||||
{
|
||||
@@ -2554,11 +2553,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
ulong currBaseAddr = info.Address + reservedPagesCount * PageSize;
|
||||
ulong currEndAddr = info.Address + info.Size;
|
||||
|
||||
if (address >= regionStart &&
|
||||
address >= currBaseAddr &&
|
||||
endAddr - 1 <= regionEndAddr - 1 &&
|
||||
endAddr - 1 <= currEndAddr - 1)
|
||||
if (aslrAddress >= regionStart &&
|
||||
aslrAddress >= currBaseAddr &&
|
||||
aslrEndAddr - 1 <= regionEndAddr - 1 &&
|
||||
aslrEndAddr - 1 <= currEndAddr - 1)
|
||||
{
|
||||
address = aslrAddress;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2603,7 +2603,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
|
||||
ulong totalNeededSize = reservedSize + neededPagesCount * PageSize;
|
||||
|
||||
ulong regionEndAddr = regionStart + regionPagesCount * PageSize;
|
||||
ulong regionEndAddr = (regionStart + regionPagesCount * PageSize) - 1;
|
||||
|
||||
KMemoryBlock currBlock = _blockManager.FindBlock(regionStart);
|
||||
|
||||
|
@@ -32,6 +32,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
private bool _handlesRequestToDisplay = false;
|
||||
private bool _autoSleepDisabled = false;
|
||||
private bool _albumImageTakenNotificationEnabled = false;
|
||||
private bool _recordVolumeMuted = false;
|
||||
|
||||
private uint _screenShotImageOrientation = 0;
|
||||
private uint _idleTimeDetectionExtension = 0;
|
||||
@@ -389,5 +390,18 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(130)] // 13.0.0+
|
||||
// SetRecordVolumeMuted(b8)
|
||||
public ResultCode SetRecordVolumeMuted(ServiceCtx context)
|
||||
{
|
||||
bool recordVolumeMuted = context.RequestData.ReadBoolean();
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { recordVolumeMuted });
|
||||
|
||||
_recordVolumeMuted = recordVolumeMuted;
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -38,18 +38,17 @@ namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator
|
||||
// Read() -> (u32, buffer<nn::bcat::DeliveryCacheDirectoryEntry, 6>)
|
||||
public ResultCode Read(ServiceCtx context)
|
||||
{
|
||||
ulong position = context.Request.ReceiveBuff[0].Position;
|
||||
ulong size = context.Request.ReceiveBuff[0].Size;
|
||||
ulong bufferAddress = context.Request.ReceiveBuff[0].Position;
|
||||
ulong bufferLen = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
byte[] data = new byte[size];
|
||||
using (var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true))
|
||||
{
|
||||
Result result = _base.Get.Read(out int entriesRead, MemoryMarshal.Cast<byte, DeliveryCacheDirectoryEntry>(region.Memory.Span));
|
||||
|
||||
Result result = _base.Get.Read(out int entriesRead, MemoryMarshal.Cast<byte, DeliveryCacheDirectoryEntry>(data));
|
||||
context.ResponseData.Write(entriesRead);
|
||||
|
||||
context.Memory.Write(position, data);
|
||||
|
||||
context.ResponseData.Write(entriesRead);
|
||||
|
||||
return (ResultCode)result.Value;
|
||||
return (ResultCode)result.Value;
|
||||
}
|
||||
}
|
||||
|
||||
[CommandHipc(2)]
|
||||
|
@@ -38,20 +38,19 @@ namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator
|
||||
// Read(u64) -> (u64, buffer<bytes, 6>)
|
||||
public ResultCode Read(ServiceCtx context)
|
||||
{
|
||||
ulong position = context.Request.ReceiveBuff[0].Position;
|
||||
ulong size = context.Request.ReceiveBuff[0].Size;
|
||||
ulong bufferAddress = context.Request.ReceiveBuff[0].Position;
|
||||
ulong bufferLen = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
long offset = context.RequestData.ReadInt64();
|
||||
|
||||
byte[] data = new byte[size];
|
||||
using (var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true))
|
||||
{
|
||||
Result result = _base.Get.Read(out long bytesRead, offset, region.Memory.Span);
|
||||
|
||||
Result result = _base.Get.Read(out long bytesRead, offset, data);
|
||||
context.ResponseData.Write(bytesRead);
|
||||
|
||||
context.Memory.Write(position, data);
|
||||
|
||||
context.ResponseData.Write(bytesRead);
|
||||
|
||||
return (ResultCode)result.Value;
|
||||
return (ResultCode)result.Value;
|
||||
}
|
||||
}
|
||||
|
||||
[CommandHipc(2)]
|
||||
|
@@ -50,18 +50,17 @@ namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator
|
||||
// EnumerateDeliveryCacheDirectory() -> (u32, buffer<nn::bcat::DirectoryName, 6>)
|
||||
public ResultCode EnumerateDeliveryCacheDirectory(ServiceCtx context)
|
||||
{
|
||||
ulong position = context.Request.ReceiveBuff[0].Position;
|
||||
ulong size = context.Request.ReceiveBuff[0].Size;
|
||||
ulong bufferAddress = context.Request.ReceiveBuff[0].Position;
|
||||
ulong bufferLen = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
byte[] data = new byte[size];
|
||||
using (var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true))
|
||||
{
|
||||
Result result = _base.Get.EnumerateDeliveryCacheDirectory(out int count, MemoryMarshal.Cast<byte, DirectoryName>(region.Memory.Span));
|
||||
|
||||
Result result = _base.Get.EnumerateDeliveryCacheDirectory(out int count, MemoryMarshal.Cast<byte, DirectoryName>(data));
|
||||
context.ResponseData.Write(count);
|
||||
|
||||
context.Memory.Write(position, data);
|
||||
|
||||
context.ResponseData.Write(count);
|
||||
|
||||
return (ResultCode)result.Value;
|
||||
return (ResultCode)result.Value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
|
@@ -17,17 +17,17 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
|
||||
// Read() -> (u64 count, buffer<nn::fssrv::sf::IDirectoryEntry, 6, 0> entries)
|
||||
public ResultCode Read(ServiceCtx context)
|
||||
{
|
||||
ulong bufferPosition = context.Request.ReceiveBuff[0].Position;
|
||||
ulong bufferAddress = context.Request.ReceiveBuff[0].Position;
|
||||
ulong bufferLen = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
byte[] entryBuffer = new byte[bufferLen];
|
||||
using (var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true))
|
||||
{
|
||||
Result result = _baseDirectory.Get.Read(out long entriesRead, new OutBuffer(region.Memory.Span));
|
||||
|
||||
Result result = _baseDirectory.Get.Read(out long entriesRead, new OutBuffer(entryBuffer));
|
||||
context.ResponseData.Write(entriesRead);
|
||||
|
||||
context.Memory.Write(bufferPosition, entryBuffer);
|
||||
context.ResponseData.Write(entriesRead);
|
||||
|
||||
return (ResultCode)result.Value;
|
||||
return (ResultCode)result.Value;
|
||||
}
|
||||
}
|
||||
|
||||
[CommandHipc(1)]
|
||||
|
@@ -19,7 +19,8 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
|
||||
// Read(u32 readOption, u64 offset, u64 size) -> (u64 out_size, buffer<u8, 0x46, 0> out_buf)
|
||||
public ResultCode Read(ServiceCtx context)
|
||||
{
|
||||
ulong position = context.Request.ReceiveBuff[0].Position;
|
||||
ulong bufferAddress = context.Request.ReceiveBuff[0].Position;
|
||||
ulong bufferLen = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
ReadOption readOption = context.RequestData.ReadStruct<ReadOption>();
|
||||
context.RequestData.BaseStream.Position += 4;
|
||||
@@ -27,15 +28,14 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
|
||||
long offset = context.RequestData.ReadInt64();
|
||||
long size = context.RequestData.ReadInt64();
|
||||
|
||||
byte[] data = new byte[context.Request.ReceiveBuff[0].Size];
|
||||
using (var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true))
|
||||
{
|
||||
Result result = _baseFile.Get.Read(out long bytesRead, offset, new OutBuffer(region.Memory.Span), size, readOption);
|
||||
|
||||
Result result = _baseFile.Get.Read(out long bytesRead, offset, new OutBuffer(data), size, readOption);
|
||||
context.ResponseData.Write(bytesRead);
|
||||
|
||||
context.Memory.Write(position, data);
|
||||
|
||||
context.ResponseData.Write(bytesRead);
|
||||
|
||||
return (ResultCode)result.Value;
|
||||
return (ResultCode)result.Value;
|
||||
}
|
||||
}
|
||||
|
||||
[CommandHipc(1)]
|
||||
|
@@ -197,13 +197,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
|
||||
context.ResponseData.Write(timestamp.Created);
|
||||
context.ResponseData.Write(timestamp.Modified);
|
||||
context.ResponseData.Write(timestamp.Accessed);
|
||||
|
||||
byte[] data = new byte[8];
|
||||
|
||||
// is valid?
|
||||
data[0] = 1;
|
||||
|
||||
context.ResponseData.Write(data);
|
||||
context.ResponseData.Write(1L); // Is valid?
|
||||
|
||||
return (ResultCode)result.Value;
|
||||
}
|
||||
|
@@ -23,21 +23,21 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
|
||||
|
||||
if (context.Request.ReceiveBuff.Count > 0)
|
||||
{
|
||||
IpcBuffDesc buffDesc = context.Request.ReceiveBuff[0];
|
||||
ulong bufferAddress = context.Request.ReceiveBuff[0].Position;
|
||||
ulong bufferLen = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
// Use smaller length to avoid overflows.
|
||||
if (size > buffDesc.Size)
|
||||
if (size > bufferLen)
|
||||
{
|
||||
size = buffDesc.Size;
|
||||
size = bufferLen;
|
||||
}
|
||||
|
||||
byte[] data = new byte[size];
|
||||
using (var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true))
|
||||
{
|
||||
Result result = _baseStorage.Get.Read((long)offset, new OutBuffer(region.Memory.Span), (long)size);
|
||||
|
||||
Result result = _baseStorage.Get.Read((long)offset, new OutBuffer(data), (long)size);
|
||||
|
||||
context.Memory.Write(buffDesc.Position, data);
|
||||
|
||||
return (ResultCode)result.Value;
|
||||
return (ResultCode)result.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
|
@@ -12,6 +12,7 @@ using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
using static Ryujinx.HLE.Utilities.StringUtils;
|
||||
@@ -500,16 +501,16 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
||||
SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64();
|
||||
SaveDataFilter filter = context.RequestData.ReadStruct<SaveDataFilter>();
|
||||
|
||||
ulong bufferPosition = context.Request.ReceiveBuff[0].Position;
|
||||
ulong bufferAddress = context.Request.ReceiveBuff[0].Position;
|
||||
ulong bufferLen = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
byte[] infoBuffer = new byte[bufferLen];
|
||||
using (var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true))
|
||||
{
|
||||
Result result = _baseFileSystemProxy.Get.FindSaveDataWithFilter(out long count, new OutBuffer(region.Memory.Span), spaceId, in filter);
|
||||
if (result.IsFailure()) return (ResultCode)result.Value;
|
||||
|
||||
Result result = _baseFileSystemProxy.Get.FindSaveDataWithFilter(out long count, new OutBuffer(infoBuffer), spaceId, in filter);
|
||||
if (result.IsFailure()) return (ResultCode)result.Value;
|
||||
|
||||
context.Memory.Write(bufferPosition, infoBuffer);
|
||||
context.ResponseData.Write(count);
|
||||
context.ResponseData.Write(count);
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
@@ -787,6 +788,26 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(205)]
|
||||
// OpenDataStorageWithProgramIndex(u8 program_index) -> object<nn::fssrv::sf::IStorage>
|
||||
public ResultCode OpenDataStorageWithProgramIndex(ServiceCtx context)
|
||||
{
|
||||
byte programIndex = context.RequestData.ReadByte();
|
||||
|
||||
if ((context.Device.Application.TitleId & 0xf) != programIndex)
|
||||
{
|
||||
throw new NotImplementedException($"Accessing storage from other programs is not supported (program index = {programIndex}).");
|
||||
}
|
||||
|
||||
var storage = context.Device.FileSystem.RomFs.AsStorage(true);
|
||||
using var sharedStorage = new SharedRef<LibHac.Fs.IStorage>(storage);
|
||||
using var sfStorage = new SharedRef<IStorage>(new StorageInterfaceAdapter(ref sharedStorage.Ref()));
|
||||
|
||||
MakeObject(context, new FileSystemProxy.IStorage(ref sfStorage.Ref()));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(400)]
|
||||
// OpenDataStorageByCurrentProcess() -> object<nn::fssrv::sf::IStorage> dataStorage
|
||||
public ResultCode OpenDeviceOperator(ServiceCtx context)
|
||||
|
@@ -17,17 +17,17 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
||||
// ReadSaveDataInfo() -> (u64, buffer<unknown, 6>)
|
||||
public ResultCode ReadSaveDataInfo(ServiceCtx context)
|
||||
{
|
||||
ulong bufferPosition = context.Request.ReceiveBuff[0].Position;
|
||||
ulong bufferAddress = context.Request.ReceiveBuff[0].Position;
|
||||
ulong bufferLen = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
byte[] infoBuffer = new byte[bufferLen];
|
||||
using (var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true))
|
||||
{
|
||||
Result result = _baseReader.Get.Read(out long readCount, new OutBuffer(region.Memory.Span));
|
||||
|
||||
Result result = _baseReader.Get.Read(out long readCount, new OutBuffer(infoBuffer));
|
||||
context.ResponseData.Write(readCount);
|
||||
|
||||
context.Memory.Write(bufferPosition, infoBuffer);
|
||||
context.ResponseData.Write(readCount);
|
||||
|
||||
return (ResultCode)result.Value;
|
||||
return (ResultCode)result.Value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
|
@@ -203,6 +203,18 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Irs
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(318)] // 4.0.0+
|
||||
// StopImageProcessorAsync(nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId, pid)
|
||||
public ResultCode StopImageProcessorAsync(ServiceCtx context)
|
||||
{
|
||||
int irCameraHandle = context.RequestData.ReadInt32();
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(319)] // 4.0.0+
|
||||
// ActivateIrsensorWithFunctionLevel(nn::applet::AppletResourceUserId, nn::irsensor::PackedFunctionLevel, pid)
|
||||
public ResultCode ActivateIrsensorWithFunctionLevel(ServiceCtx context)
|
||||
@@ -225,4 +237,4 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Irs
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,22 +5,48 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.NfcManager
|
||||
class INfc : IpcService
|
||||
{
|
||||
private NfcPermissionLevel _permissionLevel;
|
||||
private State _state;
|
||||
|
||||
public INfc(NfcPermissionLevel permissionLevel)
|
||||
{
|
||||
_permissionLevel = permissionLevel;
|
||||
_state = State.NonInitialized;
|
||||
}
|
||||
|
||||
[CommandHipc(0)]
|
||||
[CommandHipc(400)] // 4.0.0+
|
||||
// Initialize()
|
||||
// Initialize(u64, u64, pid, buffer<unknown, 5>)
|
||||
public ResultCode Initialize(ServiceCtx context)
|
||||
{
|
||||
_state = State.Initialized;
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceNfc, new { _permissionLevel });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(1)]
|
||||
[CommandHipc(401)] // 4.0.0+
|
||||
// Finalize()
|
||||
public ResultCode Finalize(ServiceCtx context)
|
||||
{
|
||||
_state = State.NonInitialized;
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceNfc, new { _permissionLevel });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(2)]
|
||||
[CommandHipc(402)] // 4.0.0+
|
||||
// GetState() -> u32
|
||||
public ResultCode GetState(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write((int)_state);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(3)]
|
||||
[CommandHipc(403)] // 4.0.0+
|
||||
// IsNfcEnabled() -> b8
|
||||
|
8
Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/State.cs
Normal file
8
Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/State.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.NfcManager
|
||||
{
|
||||
enum State
|
||||
{
|
||||
NonInitialized,
|
||||
Initialized
|
||||
}
|
||||
}
|
@@ -816,11 +816,11 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
||||
Reserved = new Array57<byte>()
|
||||
};
|
||||
|
||||
modelInfo.CharacterId = BinaryPrimitives.ReverseEndianness(ushort.Parse(context.Device.System.NfpDevices[i].AmiiboId.Substring(0, 4), NumberStyles.HexNumber));
|
||||
modelInfo.CharacterVariant = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.Substring(4, 2), NumberStyles.HexNumber);
|
||||
modelInfo.Series = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.Substring(12, 2), NumberStyles.HexNumber);
|
||||
modelInfo.ModelNumber = ushort.Parse(context.Device.System.NfpDevices[i].AmiiboId.Substring(8, 4), NumberStyles.HexNumber);
|
||||
modelInfo.Type = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.Substring(6, 2), NumberStyles.HexNumber);
|
||||
modelInfo.CharacterId = BinaryPrimitives.ReverseEndianness(ushort.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(0, 4), NumberStyles.HexNumber));
|
||||
modelInfo.CharacterVariant = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(4, 2), NumberStyles.HexNumber);
|
||||
modelInfo.Series = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(12, 2), NumberStyles.HexNumber);
|
||||
modelInfo.ModelNumber = ushort.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(8, 4), NumberStyles.HexNumber);
|
||||
modelInfo.Type = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(6, 2), NumberStyles.HexNumber);
|
||||
|
||||
context.Memory.Write(outputPosition, modelInfo);
|
||||
|
||||
|
@@ -3,7 +3,6 @@ using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
||||
{
|
||||
@@ -11,13 +10,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
||||
{
|
||||
private const int FlagNotFreedYet = 1;
|
||||
|
||||
private static ConcurrentDictionary<ulong, IdDictionary> _maps = new ConcurrentDictionary<ulong, IdDictionary>();
|
||||
private static NvMapIdDictionary _maps = new NvMapIdDictionary();
|
||||
|
||||
public NvMapDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner)
|
||||
{
|
||||
IdDictionary dict = _maps.GetOrAdd(Owner, (key) => new IdDictionary());
|
||||
|
||||
dict.Add(0, new NvMapHandle());
|
||||
}
|
||||
|
||||
public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments)
|
||||
@@ -232,19 +228,12 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
||||
|
||||
private int CreateHandleFromMap(NvMapHandle map)
|
||||
{
|
||||
IdDictionary dict = _maps.GetOrAdd(Owner, (key) => new IdDictionary());
|
||||
|
||||
return dict.Add(map);
|
||||
return _maps.Add(map);
|
||||
}
|
||||
|
||||
private static bool DeleteMapWithHandle(ulong pid, int handle)
|
||||
{
|
||||
if (_maps.TryGetValue(pid, out IdDictionary dict))
|
||||
{
|
||||
return dict.Delete(handle) != null;
|
||||
}
|
||||
|
||||
return false;
|
||||
return _maps.Delete(handle) != null;
|
||||
}
|
||||
|
||||
public static void IncrementMapRefCount(ulong pid, int handle)
|
||||
@@ -277,12 +266,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
||||
|
||||
public static NvMapHandle GetMapFromHandle(ulong pid, int handle)
|
||||
{
|
||||
if (_maps.TryGetValue(pid, out IdDictionary dict))
|
||||
{
|
||||
return dict.GetData<NvMapHandle>(handle);
|
||||
}
|
||||
|
||||
return null;
|
||||
return _maps.Get(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
||||
{
|
||||
class NvMapIdDictionary
|
||||
{
|
||||
private readonly ConcurrentDictionary<int, NvMapHandle> _nvmapHandles;
|
||||
private int _id;
|
||||
|
||||
public ICollection<NvMapHandle> Values => _nvmapHandles.Values;
|
||||
|
||||
public NvMapIdDictionary()
|
||||
{
|
||||
_nvmapHandles = new ConcurrentDictionary<int, NvMapHandle>();
|
||||
}
|
||||
|
||||
public int Add(NvMapHandle handle)
|
||||
{
|
||||
int id = Interlocked.Add(ref _id, 4);
|
||||
|
||||
if (id != 0 && _nvmapHandles.TryAdd(id, handle))
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("NvMap ID overflow.");
|
||||
}
|
||||
|
||||
public NvMapHandle Get(int id)
|
||||
{
|
||||
if (_nvmapHandles.TryGetValue(id, out NvMapHandle handle))
|
||||
{
|
||||
return handle;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public NvMapHandle Delete(int id)
|
||||
{
|
||||
if (_nvmapHandles.TryRemove(id, out NvMapHandle handle))
|
||||
{
|
||||
return handle;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ICollection<NvMapHandle> Clear()
|
||||
{
|
||||
ICollection<NvMapHandle> values = _nvmapHandles.Values;
|
||||
|
||||
_nvmapHandles.Clear();
|
||||
|
||||
return values;
|
||||
}
|
||||
}
|
||||
}
|
@@ -235,6 +235,13 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
|
||||
shouldBlockAfterOperation = true;
|
||||
}
|
||||
|
||||
if (!Socket.IsBound)
|
||||
{
|
||||
receiveSize = -1;
|
||||
|
||||
return LinuxError.EOPNOTSUPP;
|
||||
}
|
||||
|
||||
receiveSize = Socket.ReceiveFrom(buffer[..size], ConvertBsdSocketFlags(flags), ref temp);
|
||||
|
||||
remoteEndPoint = (IPEndPoint)temp;
|
||||
@@ -519,4 +526,4 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -142,14 +142,13 @@ namespace Ryujinx.HLE.HOS.Services.Ssl.SslService
|
||||
// GetHostName(buffer<bytes, 6>) -> u32
|
||||
public ResultCode GetHostName(ServiceCtx context)
|
||||
{
|
||||
ulong hostNameDataPosition = context.Request.ReceiveBuff[0].Position;
|
||||
ulong hostNameDataSize = context.Request.ReceiveBuff[0].Size;
|
||||
ulong bufferAddress = context.Request.ReceiveBuff[0].Position;
|
||||
ulong bufferLen = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
byte[] hostNameData = new byte[hostNameDataSize];
|
||||
|
||||
Encoding.ASCII.GetBytes(_hostName, hostNameData);
|
||||
|
||||
context.Memory.Write(hostNameDataPosition, hostNameData);
|
||||
using (var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true))
|
||||
{
|
||||
Encoding.ASCII.GetBytes(_hostName, region.Memory.Span);
|
||||
}
|
||||
|
||||
context.ResponseData.Write((uint)_hostName.Length);
|
||||
|
||||
@@ -350,7 +349,11 @@ namespace Ryujinx.HLE.HOS.Services.Ssl.SslService
|
||||
// GetSessionCacheMode() -> nn::ssl::sf::SessionCacheMode
|
||||
public ResultCode GetSessionCacheMode(ServiceCtx context)
|
||||
{
|
||||
throw new ServiceNotImplementedException(this, context);
|
||||
context.ResponseData.Write((uint)_sessionCacheMode);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { _sessionCacheMode });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(19)]
|
||||
|
@@ -13,7 +13,7 @@ namespace Ryujinx.HLE.HOS.Tamper.Conditions
|
||||
|
||||
public bool Evaluate()
|
||||
{
|
||||
return (_input.Value & _mask) != 0;
|
||||
return (_input.Value & _mask) == _mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -122,9 +122,8 @@ namespace Ryujinx.HLE.HOS.Tamper
|
||||
for (int nybbleIndex = 0; nybbleIndex < wordSize; nybbleIndex++)
|
||||
{
|
||||
int index = wordIndex * wordSize + nybbleIndex;
|
||||
string byteData = word.Substring(nybbleIndex, 1);
|
||||
|
||||
instruction[index] = byte.Parse(byteData, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||
instruction[index] = byte.Parse(word.AsSpan(nybbleIndex, 1), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -132,7 +132,7 @@ namespace Ryujinx.HLE.Loaders.Mods
|
||||
{
|
||||
if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X'))
|
||||
{
|
||||
return int.TryParse(str.Substring(2), System.Globalization.NumberStyles.HexNumber, null, out value);
|
||||
return int.TryParse(str.AsSpan(2), System.Globalization.NumberStyles.HexNumber, null, out value);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@@ -26,7 +26,7 @@
|
||||
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.15.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.25.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Due to Concentus. -->
|
||||
|
@@ -54,7 +54,7 @@ namespace Ryujinx.HLE.Utilities
|
||||
|
||||
for (int index = 0; index < bytesInHex; index++)
|
||||
{
|
||||
output[index] = byte.Parse(hexString.Substring(index * 2, 2), NumberStyles.HexNumber);
|
||||
output[index] = byte.Parse(hexString.AsSpan(index * 2, 2), NumberStyles.HexNumber);
|
||||
}
|
||||
|
||||
return output;
|
||||
|
@@ -51,7 +51,16 @@ namespace Ryujinx.Input.HLE
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_device.Hid.RefreshInputConfig(_inputConfig);
|
||||
List<InputConfig> validInputs = new List<InputConfig>();
|
||||
foreach (var inputConfigEntry in _inputConfig)
|
||||
{
|
||||
if (_controllers[(int)inputConfigEntry.PlayerIndex] != null)
|
||||
{
|
||||
validInputs.Add(inputConfigEntry);
|
||||
}
|
||||
}
|
||||
|
||||
_device.Hid.RefreshInputConfig(validInputs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +112,8 @@ namespace Ryujinx.Input.HLE
|
||||
_controllers[i] = null;
|
||||
}
|
||||
|
||||
List<InputConfig> validInputs = new List<InputConfig>();
|
||||
|
||||
foreach (InputConfig inputConfigEntry in inputConfig)
|
||||
{
|
||||
NpadController controller = new NpadController(_cemuHookClient);
|
||||
@@ -116,6 +127,7 @@ namespace Ryujinx.Input.HLE
|
||||
else
|
||||
{
|
||||
_controllers[(int)inputConfigEntry.PlayerIndex] = controller;
|
||||
validInputs.Add(inputConfigEntry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +135,7 @@ namespace Ryujinx.Input.HLE
|
||||
_enableKeyboard = enableKeyboard;
|
||||
_enableMouse = enableMouse;
|
||||
|
||||
_device.Hid.RefreshInputConfig(inputConfig);
|
||||
_device.Hid.RefreshInputConfig(validInputs);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -92,5 +92,31 @@ namespace Ryujinx.Memory.Tests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Test_AliasMapLeak()
|
||||
{
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
// Memory aliasing tests fail on CI at the moment.
|
||||
return;
|
||||
}
|
||||
|
||||
ulong pageSize = 4096;
|
||||
ulong size = 100000 * pageSize; // The mappings limit on Linux is usually around 65K, so let's make sure we are above that.
|
||||
|
||||
using MemoryBlock backing = new MemoryBlock(pageSize, MemoryAllocationFlags.Mirrorable);
|
||||
using MemoryBlock toAlias = new MemoryBlock(size, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible);
|
||||
|
||||
for (ulong offset = 0; offset < size; offset += pageSize)
|
||||
{
|
||||
toAlias.MapView(backing, 0, offset, pageSize);
|
||||
|
||||
toAlias.Write(offset, 0xbadc0de);
|
||||
Assert.AreEqual(0xbadc0de, backing.Read<int>(0));
|
||||
|
||||
toAlias.UnmapView(backing, offset, pageSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -177,7 +177,7 @@ namespace Ryujinx.Memory
|
||||
|
||||
public static void UnmapView(IntPtr location, ulong size)
|
||||
{
|
||||
mmap(location, size, MmapProts.PROT_NONE, MmapFlags.MAP_FIXED, -1, 0);
|
||||
mmap(location, size, MmapProts.PROT_NONE, MmapFlags.MAP_FIXED | MmapFlags.MAP_PRIVATE | MmapFlags.MAP_ANONYMOUS | MmapFlags.MAP_NORESERVE, -1, 0);
|
||||
}
|
||||
}
|
||||
}
|
199
Ryujinx.Memory/Tracking/BitMap.cs
Normal file
199
Ryujinx.Memory/Tracking/BitMap.cs
Normal file
@@ -0,0 +1,199 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Memory.Tracking
|
||||
{
|
||||
/// <summary>
|
||||
/// A bitmap that can check or set large ranges of true/false values at once.
|
||||
/// </summary>
|
||||
struct BitMap
|
||||
{
|
||||
public const int IntSize = 64;
|
||||
|
||||
private const int IntShift = 6;
|
||||
private const int IntMask = IntSize - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Masks representing the bitmap. Least significant bit first, 64-bits per mask.
|
||||
/// </summary>
|
||||
public readonly long[] Masks;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new bitmap.
|
||||
/// </summary>
|
||||
/// <param name="count">The number of bits to reserve</param>
|
||||
public BitMap(int count)
|
||||
{
|
||||
Masks = new long[(count + IntMask) / IntSize];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if any bit in the bitmap is set.
|
||||
/// </summary>
|
||||
/// <returns>True if any bits are set, false otherwise</returns>
|
||||
public bool AnySet()
|
||||
{
|
||||
for (int i = 0; i < Masks.Length; i++)
|
||||
{
|
||||
if (Masks[i] != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a bit in the bitmap is set.
|
||||
/// </summary>
|
||||
/// <param name="bit">The bit index to check</param>
|
||||
/// <returns>True if the bit is set, false otherwise</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsSet(int bit)
|
||||
{
|
||||
int wordIndex = bit >> IntShift;
|
||||
int wordBit = bit & IntMask;
|
||||
|
||||
long wordMask = 1L << wordBit;
|
||||
|
||||
return (Masks[wordIndex] & wordMask) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if any bit in a range of bits in the bitmap are set. (inclusive)
|
||||
/// </summary>
|
||||
/// <param name="start">The first bit index to check</param>
|
||||
/// <param name="end">The last bit index to check</param>
|
||||
/// <returns>True if a bit is set, false otherwise</returns>
|
||||
public bool IsSet(int start, int end)
|
||||
{
|
||||
if (start == end)
|
||||
{
|
||||
return IsSet(start);
|
||||
}
|
||||
|
||||
int startIndex = start >> IntShift;
|
||||
int startBit = start & IntMask;
|
||||
long startMask = -1L << startBit;
|
||||
|
||||
int endIndex = end >> IntShift;
|
||||
int endBit = end & IntMask;
|
||||
long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
|
||||
|
||||
if (startIndex == endIndex)
|
||||
{
|
||||
return (Masks[startIndex] & startMask & endMask) != 0;
|
||||
}
|
||||
|
||||
if ((Masks[startIndex] & startMask) != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = startIndex + 1; i < endIndex; i++)
|
||||
{
|
||||
if (Masks[i] != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((Masks[endIndex] & endMask) != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a bit at a specific index to 1.
|
||||
/// </summary>
|
||||
/// <param name="bit">The bit index to set</param>
|
||||
/// <returns>True if the bit is set, false if it was already set</returns>
|
||||
public bool Set(int bit)
|
||||
{
|
||||
int wordIndex = bit >> IntShift;
|
||||
int wordBit = bit & IntMask;
|
||||
|
||||
long wordMask = 1L << wordBit;
|
||||
|
||||
if ((Masks[wordIndex] & wordMask) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Masks[wordIndex] |= wordMask;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a range of bits in the bitmap to 1.
|
||||
/// </summary>
|
||||
/// <param name="start">The first bit index to set</param>
|
||||
/// <param name="end">The last bit index to set</param>
|
||||
public void SetRange(int start, int end)
|
||||
{
|
||||
if (start == end)
|
||||
{
|
||||
Set(start);
|
||||
return;
|
||||
}
|
||||
|
||||
int startIndex = start >> IntShift;
|
||||
int startBit = start & IntMask;
|
||||
long startMask = -1L << startBit;
|
||||
|
||||
int endIndex = end >> IntShift;
|
||||
int endBit = end & IntMask;
|
||||
long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
|
||||
|
||||
if (startIndex == endIndex)
|
||||
{
|
||||
Masks[startIndex] |= startMask & endMask;
|
||||
}
|
||||
else
|
||||
{
|
||||
Masks[startIndex] |= startMask;
|
||||
|
||||
for (int i = startIndex + 1; i < endIndex; i++)
|
||||
{
|
||||
Masks[i] |= -1;
|
||||
}
|
||||
|
||||
Masks[endIndex] |= endMask;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear a bit at a specific index to 0.
|
||||
/// </summary>
|
||||
/// <param name="bit">The bit index to clear</param>
|
||||
/// <returns>True if the bit was set, false if it was not</returns>
|
||||
public bool Clear(int bit)
|
||||
{
|
||||
int wordIndex = bit >> IntShift;
|
||||
int wordBit = bit & IntMask;
|
||||
|
||||
long wordMask = 1L << wordBit;
|
||||
|
||||
bool wasSet = (Masks[wordIndex] & wordMask) != 0;
|
||||
|
||||
Masks[wordIndex] &= ~wordMask;
|
||||
|
||||
return wasSet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the bitmap entirely, setting all bits to 0.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
for (int i = 0; i < Masks.Length; i++)
|
||||
{
|
||||
Masks[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
161
Ryujinx.Memory/Tracking/ConcurrentBitmap.cs
Normal file
161
Ryujinx.Memory/Tracking/ConcurrentBitmap.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Memory.Tracking
|
||||
{
|
||||
/// <summary>
|
||||
/// A bitmap that can be safely modified from multiple threads.
|
||||
/// </summary>
|
||||
internal class ConcurrentBitmap
|
||||
{
|
||||
public const int IntSize = 64;
|
||||
|
||||
public const int IntShift = 6;
|
||||
public const int IntMask = IntSize - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Masks representing the bitmap. Least significant bit first, 64-bits per mask.
|
||||
/// </summary>
|
||||
public readonly long[] Masks;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new multithreaded bitmap.
|
||||
/// </summary>
|
||||
/// <param name="count">The number of bits to reserve</param>
|
||||
/// <param name="set">Whether the bits should be initially set or not</param>
|
||||
public ConcurrentBitmap(int count, bool set)
|
||||
{
|
||||
Masks = new long[(count + IntMask) / IntSize];
|
||||
|
||||
if (set)
|
||||
{
|
||||
Array.Fill(Masks, -1L);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if any bit in the bitmap is set.
|
||||
/// </summary>
|
||||
/// <returns>True if any bits are set, false otherwise</returns>
|
||||
public bool AnySet()
|
||||
{
|
||||
for (int i = 0; i < Masks.Length; i++)
|
||||
{
|
||||
if (Volatile.Read(ref Masks[i]) != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a bit in the bitmap is set.
|
||||
/// </summary>
|
||||
/// <param name="bit">The bit index to check</param>
|
||||
/// <returns>True if the bit is set, false otherwise</returns>
|
||||
public bool IsSet(int bit)
|
||||
{
|
||||
int wordIndex = bit >> IntShift;
|
||||
int wordBit = bit & IntMask;
|
||||
|
||||
long wordMask = 1L << wordBit;
|
||||
|
||||
return (Volatile.Read(ref Masks[wordIndex]) & wordMask) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if any bit in a range of bits in the bitmap are set. (inclusive)
|
||||
/// </summary>
|
||||
/// <param name="start">The first bit index to check</param>
|
||||
/// <param name="end">The last bit index to check</param>
|
||||
/// <returns>True if a bit is set, false otherwise</returns>
|
||||
public bool IsSet(int start, int end)
|
||||
{
|
||||
if (start == end)
|
||||
{
|
||||
return IsSet(start);
|
||||
}
|
||||
|
||||
int startIndex = start >> IntShift;
|
||||
int startBit = start & IntMask;
|
||||
long startMask = -1L << startBit;
|
||||
|
||||
int endIndex = end >> IntShift;
|
||||
int endBit = end & IntMask;
|
||||
long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
|
||||
|
||||
long startValue = Volatile.Read(ref Masks[startIndex]);
|
||||
|
||||
if (startIndex == endIndex)
|
||||
{
|
||||
return (startValue & startMask & endMask) != 0;
|
||||
}
|
||||
|
||||
if ((startValue & startMask) != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = startIndex + 1; i < endIndex; i++)
|
||||
{
|
||||
if (Volatile.Read(ref Masks[i]) != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
long endValue = Volatile.Read(ref Masks[endIndex]);
|
||||
|
||||
if ((endValue & endMask) != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a bit at a specific index to either true or false.
|
||||
/// </summary>
|
||||
/// <param name="bit">The bit index to set</param>
|
||||
/// <param name="value">Whether the bit should be set or not</param>
|
||||
public void Set(int bit, bool value)
|
||||
{
|
||||
int wordIndex = bit >> IntShift;
|
||||
int wordBit = bit & IntMask;
|
||||
|
||||
long wordMask = 1L << wordBit;
|
||||
|
||||
long existing;
|
||||
long newValue;
|
||||
|
||||
do
|
||||
{
|
||||
existing = Volatile.Read(ref Masks[wordIndex]);
|
||||
|
||||
if (value)
|
||||
{
|
||||
newValue = existing | wordMask;
|
||||
}
|
||||
else
|
||||
{
|
||||
newValue = existing & ~wordMask;
|
||||
}
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref Masks[wordIndex], newValue, existing) != existing);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the bitmap entirely, setting all bits to 0.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
for (int i = 0; i < Masks.Length; i++)
|
||||
{
|
||||
Volatile.Write(ref Masks[i], 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -176,6 +176,26 @@ namespace Ryujinx.Memory.Tracking
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with.
|
||||
/// </summary>
|
||||
/// <param name="address">CPU virtual address of the region</param>
|
||||
/// <param name="size">Size of the region</param>
|
||||
/// <param name="bitmap">The bitmap owning the dirty flag for this handle</param>
|
||||
/// <param name="bit">The bit of this handle within the dirty flag</param>
|
||||
/// <returns>The memory tracking handle</returns>
|
||||
internal RegionHandle BeginTrackingBitmap(ulong address, ulong size, ConcurrentBitmap bitmap, int bit)
|
||||
{
|
||||
(address, size) = PageAlign(address, size);
|
||||
|
||||
lock (TrackingLock)
|
||||
{
|
||||
RegionHandle handle = new RegionHandle(this, address, size, bitmap, bit, _memoryManager.IsRangeMapped(address, size));
|
||||
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal that a virtual memory event happened at the given location (one byte).
|
||||
/// </summary>
|
||||
|
@@ -1,11 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Memory.Tracking
|
||||
{
|
||||
/// <summary>
|
||||
/// A region handle that tracks a large region using many smaller handles, to provide
|
||||
/// granular tracking that can be used to track partial updates.
|
||||
/// granular tracking that can be used to track partial updates. Backed by a bitmap
|
||||
/// to improve performance when scanning large regions.
|
||||
/// </summary>
|
||||
public class MultiRegionHandle : IMultiRegionHandle
|
||||
{
|
||||
@@ -17,6 +21,12 @@ namespace Ryujinx.Memory.Tracking
|
||||
private readonly ulong Granularity;
|
||||
private readonly ulong Size;
|
||||
|
||||
private ConcurrentBitmap _dirtyBitmap;
|
||||
|
||||
private int _sequenceNumber;
|
||||
private BitMap _sequenceNumberBitmap;
|
||||
private int _uncheckedHandles;
|
||||
|
||||
public bool Dirty { get; private set; } = true;
|
||||
|
||||
internal MultiRegionHandle(MemoryTracking tracking, ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity)
|
||||
@@ -24,6 +34,9 @@ namespace Ryujinx.Memory.Tracking
|
||||
_handles = new RegionHandle[size / granularity];
|
||||
Granularity = granularity;
|
||||
|
||||
_dirtyBitmap = new ConcurrentBitmap(_handles.Length, true);
|
||||
_sequenceNumberBitmap = new BitMap(_handles.Length);
|
||||
|
||||
int i = 0;
|
||||
|
||||
if (handles != null)
|
||||
@@ -40,37 +53,43 @@ namespace Ryujinx.Memory.Tracking
|
||||
// Fill any gap left before this handle.
|
||||
while (i < startIndex)
|
||||
{
|
||||
RegionHandle fillHandle = tracking.BeginTracking(address + (ulong)i * granularity, granularity);
|
||||
RegionHandle fillHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i);
|
||||
fillHandle.Parent = this;
|
||||
_handles[i++] = fillHandle;
|
||||
}
|
||||
|
||||
if (handle.Size == granularity)
|
||||
lock (tracking.TrackingLock)
|
||||
{
|
||||
handle.Parent = this;
|
||||
_handles[i++] = handle;
|
||||
}
|
||||
else
|
||||
{
|
||||
int endIndex = (int)((handle.EndAddress - address) / granularity);
|
||||
|
||||
while (i < endIndex)
|
||||
if (handle is RegionHandle bitHandle && handle.Size == granularity)
|
||||
{
|
||||
RegionHandle splitHandle = tracking.BeginTracking(address + (ulong)i * granularity, granularity);
|
||||
splitHandle.Parent = this;
|
||||
handle.Parent = this;
|
||||
|
||||
splitHandle.Reprotect(handle.Dirty);
|
||||
bitHandle.ReplaceBitmap(_dirtyBitmap, i);
|
||||
|
||||
RegionSignal signal = handle.PreAction;
|
||||
if (signal != null)
|
||||
_handles[i++] = bitHandle;
|
||||
}
|
||||
else
|
||||
{
|
||||
int endIndex = (int)((handle.EndAddress - address) / granularity);
|
||||
|
||||
while (i < endIndex)
|
||||
{
|
||||
splitHandle.RegisterAction(signal);
|
||||
RegionHandle splitHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i);
|
||||
splitHandle.Parent = this;
|
||||
|
||||
splitHandle.Reprotect(handle.Dirty);
|
||||
|
||||
RegionSignal signal = handle.PreAction;
|
||||
if (signal != null)
|
||||
{
|
||||
splitHandle.RegisterAction(signal);
|
||||
}
|
||||
|
||||
_handles[i++] = splitHandle;
|
||||
}
|
||||
|
||||
_handles[i++] = splitHandle;
|
||||
handle.Dispose();
|
||||
}
|
||||
|
||||
handle.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,15 +97,27 @@ namespace Ryujinx.Memory.Tracking
|
||||
// Fill any remaining space with new handles.
|
||||
while (i < _handles.Length)
|
||||
{
|
||||
RegionHandle handle = tracking.BeginTracking(address + (ulong)i * granularity, granularity);
|
||||
RegionHandle handle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i);
|
||||
handle.Parent = this;
|
||||
_handles[i++] = handle;
|
||||
}
|
||||
|
||||
_uncheckedHandles = _handles.Length;
|
||||
|
||||
Address = address;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public void SignalWrite()
|
||||
{
|
||||
Dirty = true;
|
||||
}
|
||||
|
||||
public IEnumerable<RegionHandle> GetHandles()
|
||||
{
|
||||
return _handles;
|
||||
}
|
||||
|
||||
public void ForceDirty(ulong address, ulong size)
|
||||
{
|
||||
Dirty = true;
|
||||
@@ -96,21 +127,15 @@ namespace Ryujinx.Memory.Tracking
|
||||
|
||||
for (int i = startHandle; i <= lastHandle; i++)
|
||||
{
|
||||
_handles[i].SequenceNumber--;
|
||||
if (_sequenceNumberBitmap.Clear(i))
|
||||
{
|
||||
_uncheckedHandles++;
|
||||
}
|
||||
|
||||
_handles[i].ForceDirty();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<RegionHandle> GetHandles()
|
||||
{
|
||||
return _handles;
|
||||
}
|
||||
|
||||
public void SignalWrite()
|
||||
{
|
||||
Dirty = true;
|
||||
}
|
||||
|
||||
public void QueryModified(Action<ulong, ulong> modifiedAction)
|
||||
{
|
||||
if (!Dirty)
|
||||
@@ -123,33 +148,95 @@ namespace Ryujinx.Memory.Tracking
|
||||
QueryModified(Address, Size, modifiedAction);
|
||||
}
|
||||
|
||||
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ParseDirtyBits(long dirtyBits, ref int baseBit, ref int prevHandle, ref ulong rgStart, ref ulong rgSize, Action<ulong, ulong> modifiedAction)
|
||||
{
|
||||
int startHandle = (int)((address - Address) / Granularity);
|
||||
int lastHandle = (int)((address + (size - 1) - Address) / Granularity);
|
||||
|
||||
ulong rgStart = _handles[startHandle].Address;
|
||||
ulong rgSize = 0;
|
||||
|
||||
for (int i = startHandle; i <= lastHandle; i++)
|
||||
while (dirtyBits != 0)
|
||||
{
|
||||
RegionHandle handle = _handles[i];
|
||||
int bit = BitOperations.TrailingZeroCount(dirtyBits);
|
||||
|
||||
dirtyBits &= ~(1L << bit);
|
||||
|
||||
int handleIndex = baseBit + bit;
|
||||
|
||||
RegionHandle handle = _handles[handleIndex];
|
||||
|
||||
if (handleIndex != prevHandle + 1)
|
||||
{
|
||||
// Submit handles scanned until the gap as dirty
|
||||
if (rgSize != 0)
|
||||
{
|
||||
modifiedAction(rgStart, rgSize);
|
||||
rgSize = 0;
|
||||
}
|
||||
rgStart = handle.Address;
|
||||
}
|
||||
|
||||
if (handle.Dirty)
|
||||
{
|
||||
rgSize += handle.Size;
|
||||
handle.Reprotect();
|
||||
}
|
||||
else
|
||||
|
||||
prevHandle = handleIndex;
|
||||
}
|
||||
|
||||
baseBit += ConcurrentBitmap.IntSize;
|
||||
}
|
||||
|
||||
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction)
|
||||
{
|
||||
int startHandle = (int)((address - Address) / Granularity);
|
||||
int lastHandle = (int)((address + (size - 1) - Address) / Granularity);
|
||||
|
||||
ulong rgStart = _handles[startHandle].Address;
|
||||
|
||||
if (startHandle == lastHandle)
|
||||
{
|
||||
RegionHandle handle = _handles[startHandle];
|
||||
|
||||
if (handle.Dirty)
|
||||
{
|
||||
// Submit the region scanned so far as dirty
|
||||
if (rgSize != 0)
|
||||
{
|
||||
modifiedAction(rgStart, rgSize);
|
||||
rgSize = 0;
|
||||
}
|
||||
rgStart = handle.EndAddress;
|
||||
handle.Reprotect();
|
||||
modifiedAction(rgStart, handle.Size);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ulong rgSize = 0;
|
||||
|
||||
long[] masks = _dirtyBitmap.Masks;
|
||||
|
||||
int startIndex = startHandle >> ConcurrentBitmap.IntShift;
|
||||
int startBit = startHandle & ConcurrentBitmap.IntMask;
|
||||
long startMask = -1L << startBit;
|
||||
|
||||
int endIndex = lastHandle >> ConcurrentBitmap.IntShift;
|
||||
int endBit = lastHandle & ConcurrentBitmap.IntMask;
|
||||
long endMask = (long)(ulong.MaxValue >> (ConcurrentBitmap.IntMask - endBit));
|
||||
|
||||
long startValue = Volatile.Read(ref masks[startIndex]);
|
||||
|
||||
int baseBit = startIndex << ConcurrentBitmap.IntShift;
|
||||
int prevHandle = startHandle - 1;
|
||||
|
||||
if (startIndex == endIndex)
|
||||
{
|
||||
ParseDirtyBits(startValue & startMask & endMask, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
|
||||
}
|
||||
else
|
||||
{
|
||||
ParseDirtyBits(startValue & startMask, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
|
||||
|
||||
for (int i = startIndex + 1; i < endIndex; i++)
|
||||
{
|
||||
ParseDirtyBits(Volatile.Read(ref masks[i]), ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
|
||||
}
|
||||
|
||||
long endValue = Volatile.Read(ref masks[endIndex]);
|
||||
|
||||
ParseDirtyBits(endValue & endMask, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
|
||||
}
|
||||
|
||||
if (rgSize != 0)
|
||||
@@ -158,35 +245,120 @@ namespace Ryujinx.Memory.Tracking
|
||||
}
|
||||
}
|
||||
|
||||
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction, int sequenceNumber)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ParseDirtyBits(long dirtyBits, long mask, int index, long[] seqMasks, ref int baseBit, ref int prevHandle, ref ulong rgStart, ref ulong rgSize, Action<ulong, ulong> modifiedAction)
|
||||
{
|
||||
int startHandle = (int)((address - Address) / Granularity);
|
||||
int lastHandle = (int)((address + (size - 1) - Address) / Granularity);
|
||||
long seqMask = mask & ~seqMasks[index];
|
||||
dirtyBits &= seqMask;
|
||||
|
||||
ulong rgStart = _handles[startHandle].Address;
|
||||
ulong rgSize = 0;
|
||||
|
||||
for (int i = startHandle; i <= lastHandle; i++)
|
||||
while (dirtyBits != 0)
|
||||
{
|
||||
RegionHandle handle = _handles[i];
|
||||
int bit = BitOperations.TrailingZeroCount(dirtyBits);
|
||||
|
||||
if (sequenceNumber != handle.SequenceNumber && handle.DirtyOrVolatile())
|
||||
dirtyBits &= ~(1L << bit);
|
||||
|
||||
int handleIndex = baseBit + bit;
|
||||
|
||||
RegionHandle handle = _handles[handleIndex];
|
||||
|
||||
if (handleIndex != prevHandle + 1)
|
||||
{
|
||||
rgSize += handle.Size;
|
||||
handle.Reprotect();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Submit the region scanned so far as dirty
|
||||
// Submit handles scanned until the gap as dirty
|
||||
if (rgSize != 0)
|
||||
{
|
||||
modifiedAction(rgStart, rgSize);
|
||||
rgSize = 0;
|
||||
}
|
||||
rgStart = handle.EndAddress;
|
||||
rgStart = handle.Address;
|
||||
}
|
||||
|
||||
handle.SequenceNumber = sequenceNumber;
|
||||
rgSize += handle.Size;
|
||||
handle.Reprotect();
|
||||
|
||||
prevHandle = handleIndex;
|
||||
}
|
||||
|
||||
seqMasks[index] |= mask;
|
||||
_uncheckedHandles -= BitOperations.PopCount((ulong)seqMask);
|
||||
|
||||
baseBit += ConcurrentBitmap.IntSize;
|
||||
}
|
||||
|
||||
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction, int sequenceNumber)
|
||||
{
|
||||
int startHandle = (int)((address - Address) / Granularity);
|
||||
int lastHandle = (int)((address + (size - 1) - Address) / Granularity);
|
||||
|
||||
ulong rgStart = Address + (ulong)startHandle * Granularity;
|
||||
|
||||
if (sequenceNumber != _sequenceNumber)
|
||||
{
|
||||
if (_uncheckedHandles != _handles.Length)
|
||||
{
|
||||
_sequenceNumberBitmap.Clear();
|
||||
_uncheckedHandles = _handles.Length;
|
||||
}
|
||||
|
||||
_sequenceNumber = sequenceNumber;
|
||||
}
|
||||
|
||||
if (startHandle == lastHandle)
|
||||
{
|
||||
var handle = _handles[startHandle];
|
||||
if (_sequenceNumberBitmap.Set(startHandle))
|
||||
{
|
||||
_uncheckedHandles--;
|
||||
|
||||
if (handle.DirtyOrVolatile())
|
||||
{
|
||||
handle.Reprotect();
|
||||
|
||||
modifiedAction(rgStart, handle.Size);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_uncheckedHandles == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ulong rgSize = 0;
|
||||
|
||||
long[] seqMasks = _sequenceNumberBitmap.Masks;
|
||||
long[] masks = _dirtyBitmap.Masks;
|
||||
|
||||
int startIndex = startHandle >> ConcurrentBitmap.IntShift;
|
||||
int startBit = startHandle & ConcurrentBitmap.IntMask;
|
||||
long startMask = -1L << startBit;
|
||||
|
||||
int endIndex = lastHandle >> ConcurrentBitmap.IntShift;
|
||||
int endBit = lastHandle & ConcurrentBitmap.IntMask;
|
||||
long endMask = (long)(ulong.MaxValue >> (ConcurrentBitmap.IntMask - endBit));
|
||||
|
||||
long startValue = Volatile.Read(ref masks[startIndex]);
|
||||
|
||||
int baseBit = startIndex << ConcurrentBitmap.IntShift;
|
||||
int prevHandle = startHandle - 1;
|
||||
|
||||
if (startIndex == endIndex)
|
||||
{
|
||||
ParseDirtyBits(startValue, startMask & endMask, startIndex, seqMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
|
||||
}
|
||||
else
|
||||
{
|
||||
ParseDirtyBits(startValue, startMask, startIndex, seqMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
|
||||
|
||||
for (int i = startIndex + 1; i < endIndex; i++)
|
||||
{
|
||||
ParseDirtyBits(Volatile.Read(ref masks[i]), -1L, i, seqMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
|
||||
}
|
||||
|
||||
long endValue = Volatile.Read(ref masks[endIndex]);
|
||||
|
||||
ParseDirtyBits(endValue, endMask, endIndex, seqMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
|
||||
}
|
||||
|
||||
if (rgSize != 0)
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@@ -10,7 +9,7 @@ namespace Ryujinx.Memory.Tracking
|
||||
/// A tracking handle for a given region of virtual memory. The Dirty flag is updated whenever any changes are made,
|
||||
/// and an action can be performed when the region is read to or written from.
|
||||
/// </summary>
|
||||
public class RegionHandle : IRegionHandle, IRange
|
||||
public class RegionHandle : IRegionHandle
|
||||
{
|
||||
/// <summary>
|
||||
/// If more than this number of checks have been performed on a dirty flag since its last reprotect,
|
||||
@@ -23,7 +22,20 @@ namespace Ryujinx.Memory.Tracking
|
||||
/// </summary>
|
||||
private static int VolatileThreshold = 5;
|
||||
|
||||
public bool Dirty { get; private set; }
|
||||
public bool Dirty
|
||||
{
|
||||
get
|
||||
{
|
||||
return Bitmap.IsSet(DirtyBit);
|
||||
}
|
||||
protected set
|
||||
{
|
||||
Bitmap.Set(DirtyBit, value);
|
||||
}
|
||||
}
|
||||
|
||||
internal int SequenceNumber { get; set; }
|
||||
|
||||
public bool Unmapped { get; private set; }
|
||||
|
||||
public ulong Address { get; }
|
||||
@@ -31,7 +43,6 @@ namespace Ryujinx.Memory.Tracking
|
||||
public ulong EndAddress { get; }
|
||||
|
||||
internal IMultiRegionHandle Parent { get; set; }
|
||||
internal int SequenceNumber { get; set; }
|
||||
|
||||
private event Action _onDirty;
|
||||
|
||||
@@ -68,17 +79,26 @@ namespace Ryujinx.Memory.Tracking
|
||||
|
||||
internal RegionSignal PreAction => _preAction;
|
||||
|
||||
internal ConcurrentBitmap Bitmap;
|
||||
internal int DirtyBit;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new region handle. The handle is registered with the given tracking object,
|
||||
/// Create a new bitmap backed region handle. The handle is registered with the given tracking object,
|
||||
/// and will be notified of any changes to the specified region.
|
||||
/// </summary>
|
||||
/// <param name="tracking">Tracking object for the target memory block</param>
|
||||
/// <param name="address">Virtual address of the region to track</param>
|
||||
/// <param name="size">Size of the region to track</param>
|
||||
/// <param name="bitmap">The bitmap the dirty flag for this handle is stored in</param>
|
||||
/// <param name="bit">The bit index representing the dirty flag for this handle</param>
|
||||
/// <param name="mapped">True if the region handle starts mapped</param>
|
||||
internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, bool mapped = true)
|
||||
internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, ConcurrentBitmap bitmap, int bit, bool mapped = true)
|
||||
{
|
||||
Bitmap = bitmap;
|
||||
DirtyBit = bit;
|
||||
|
||||
Dirty = mapped;
|
||||
|
||||
Unmapped = !mapped;
|
||||
Address = address;
|
||||
Size = size;
|
||||
@@ -92,6 +112,54 @@ namespace Ryujinx.Memory.Tracking
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new region handle. The handle is registered with the given tracking object,
|
||||
/// and will be notified of any changes to the specified region.
|
||||
/// </summary>
|
||||
/// <param name="tracking">Tracking object for the target memory block</param>
|
||||
/// <param name="address">Virtual address of the region to track</param>
|
||||
/// <param name="size">Size of the region to track</param>
|
||||
/// <param name="mapped">True if the region handle starts mapped</param>
|
||||
internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, bool mapped = true)
|
||||
{
|
||||
Bitmap = new ConcurrentBitmap(1, mapped);
|
||||
|
||||
Unmapped = !mapped;
|
||||
Address = address;
|
||||
Size = size;
|
||||
EndAddress = address + size;
|
||||
|
||||
_tracking = tracking;
|
||||
_regions = tracking.GetVirtualRegionsForHandle(address, size);
|
||||
foreach (var region in _regions)
|
||||
{
|
||||
region.Handles.Add(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace the bitmap and bit index used to track dirty state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The tracking lock should be held when this is called, to ensure neither bitmap is modified.
|
||||
/// </remarks>
|
||||
/// <param name="bitmap">The bitmap the dirty flag for this handle is stored in</param>
|
||||
/// <param name="bit">The bit index representing the dirty flag for this handle</param>
|
||||
internal void ReplaceBitmap(ConcurrentBitmap bitmap, int bit)
|
||||
{
|
||||
// Assumes the tracking lock is held, so nothing else can signal right now.
|
||||
|
||||
var oldBitmap = Bitmap;
|
||||
var oldBit = DirtyBit;
|
||||
|
||||
bitmap.Set(bit, Dirty);
|
||||
|
||||
Bitmap = bitmap;
|
||||
DirtyBit = bit;
|
||||
|
||||
Dirty |= oldBitmap.IsSet(oldBit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the volatile state of this handle.
|
||||
/// </summary>
|
||||
@@ -108,7 +176,7 @@ namespace Ryujinx.Memory.Tracking
|
||||
public bool DirtyOrVolatile()
|
||||
{
|
||||
_checkCount++;
|
||||
return Dirty || _volatile;
|
||||
return _volatile || Dirty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -195,6 +263,8 @@ namespace Ryujinx.Memory.Tracking
|
||||
/// </summary>
|
||||
public void ForceDirty()
|
||||
{
|
||||
_checkCount++;
|
||||
|
||||
Dirty = true;
|
||||
}
|
||||
|
||||
|
@@ -3,9 +3,10 @@ using System;
|
||||
|
||||
namespace Ryujinx.Tests.Unicorn
|
||||
{
|
||||
public class UnicornAArch32
|
||||
public class UnicornAArch32 : IDisposable
|
||||
{
|
||||
internal readonly IntPtr uc;
|
||||
private bool _isDisposed = false;
|
||||
|
||||
public IndexedProperty<int, uint> R
|
||||
{
|
||||
@@ -107,7 +108,22 @@ namespace Ryujinx.Tests.Unicorn
|
||||
|
||||
~UnicornAArch32()
|
||||
{
|
||||
Interface.Checked(Native.Interface.uc_close(uc));
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
Interface.Checked(Native.Interface.uc_close(uc));
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void RunForCount(ulong count)
|
||||
|
@@ -3,9 +3,10 @@ using System;
|
||||
|
||||
namespace Ryujinx.Tests.Unicorn
|
||||
{
|
||||
public class UnicornAArch64
|
||||
public class UnicornAArch64 : IDisposable
|
||||
{
|
||||
internal readonly IntPtr uc;
|
||||
private bool _isDisposed = false;
|
||||
|
||||
public IndexedProperty<int, ulong> X
|
||||
{
|
||||
@@ -96,7 +97,22 @@ namespace Ryujinx.Tests.Unicorn
|
||||
|
||||
~UnicornAArch64()
|
||||
{
|
||||
Interface.Checked(Native.Interface.uc_close(uc));
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
Interface.Checked(Native.Interface.uc_close(uc));
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void RunForCount(ulong count)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user