Compare commits
45 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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 | ||
|
2068445939 | ||
|
a4fc9f8050 | ||
|
5437d6cb13 | ||
|
7539e26144 | ||
|
1c3697b6a4 | ||
|
81f848e54f | ||
|
358a781639 |
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
|
14
.github/workflows/build.yml
vendored
14
.github/workflows/build.yml
vendored
@@ -48,21 +48,21 @@ 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
|
||||
- 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 +73,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**
|
||||
|
||||
|
@@ -58,6 +58,8 @@ namespace Ryujinx.Ava
|
||||
private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping.
|
||||
private const int TargetFps = 60;
|
||||
|
||||
private const float VolumeDelta = 0.05f;
|
||||
|
||||
private static readonly Cursor InvisibleCursor = new Cursor(StandardCursorType.None);
|
||||
|
||||
private readonly long _ticksPerFrame;
|
||||
@@ -73,6 +75,7 @@ namespace Ryujinx.Ava
|
||||
private bool _isStopped;
|
||||
private bool _isActive;
|
||||
private long _lastCursorMoveTime;
|
||||
private float _newVolume;
|
||||
private long _ticks = 0;
|
||||
|
||||
private KeyboardHotkeyState _prevHotkeyState;
|
||||
@@ -1003,6 +1006,18 @@ namespace Ryujinx.Ava
|
||||
GraphicsConfig.ResScale =
|
||||
(MaxResolutionScale + GraphicsConfig.ResScale - 2) % MaxResolutionScale + 1;
|
||||
break;
|
||||
case KeyboardHotkeyState.VolumeUp:
|
||||
_newVolume = MathF.Round((Device.GetVolume() + VolumeDelta), 2);
|
||||
Device.SetVolume(_newVolume);
|
||||
|
||||
_parent.ViewModel.Volume = Device.GetVolume();
|
||||
break;
|
||||
case KeyboardHotkeyState.VolumeDown:
|
||||
_newVolume = MathF.Round((Device.GetVolume() - VolumeDelta), 2);
|
||||
Device.SetVolume(_newVolume);
|
||||
|
||||
_parent.ViewModel.Volume = Device.GetVolume();
|
||||
break;
|
||||
case KeyboardHotkeyState.None:
|
||||
(_keyboardInterface as AvaloniaKeyboard).Clear();
|
||||
break;
|
||||
@@ -1068,6 +1083,14 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
state = KeyboardHotkeyState.ResScaleDown;
|
||||
}
|
||||
else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.VolumeUp))
|
||||
{
|
||||
state = KeyboardHotkeyState.VolumeUp;
|
||||
}
|
||||
else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.VolumeDown))
|
||||
{
|
||||
state = KeyboardHotkeyState.VolumeDown;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
@@ -574,12 +574,12 @@
|
||||
"Discard": "Discard",
|
||||
"UserProfilesSetProfileImage": "Set Profile Image",
|
||||
"UserProfileEmptyNameError": "Name is required",
|
||||
"UserProfileNoImageError": "Profile image must be set",
|
||||
"UserProfileNoImageError": "Profile image must be set",
|
||||
"GameUpdateWindowHeading": "Updates Available for {0} [{1}]",
|
||||
"SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:",
|
||||
"SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:",
|
||||
"UserProfilesName": "Name:",
|
||||
"UserProfilesUserId" : "User Id:",
|
||||
"UserProfilesUserId": "User Id:",
|
||||
"SettingsTabGraphicsBackend": "Graphics Backend",
|
||||
"SettingsTabGraphicsBackendTooltip": "Graphics Backend to use",
|
||||
"SettingsEnableTextureRecompression": "Enable Texture Recompression",
|
||||
@@ -589,5 +589,8 @@
|
||||
"SettingsAppRequiredRestartMessage": "Ryujinx Restart Required",
|
||||
"SettingsGpuBackendRestartMessage": "Graphics Backend or Gpu settings have been modified. This will require a restart to be applied",
|
||||
"SettingsGpuBackendRestartSubMessage": "Do you want to restart now?",
|
||||
"RyujinxUpdaterMessage": "Do you want to update Ryujinx to the latest version?",
|
||||
"SettingsTabHotkeysVolumeUpHotkey": "Increase Volume:",
|
||||
"SettingsTabHotkeysVolumeDownHotkey": "Decrease Volume:",
|
||||
"VolumeShort": "Vol"
|
||||
}
|
||||
|
@@ -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ś"
|
||||
}
|
||||
|
@@ -9,6 +9,8 @@
|
||||
Pause,
|
||||
ToggleMute,
|
||||
ResScaleUp,
|
||||
ResScaleDown
|
||||
ResScaleDown,
|
||||
VolumeUp,
|
||||
VolumeDown
|
||||
}
|
||||
}
|
@@ -1,4 +1,6 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using ICSharpCode.SharpZipLib.GZip;
|
||||
using ICSharpCode.SharpZipLib.Tar;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
@@ -11,11 +13,13 @@ using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
@@ -40,6 +44,8 @@ namespace Ryujinx.Modules
|
||||
|
||||
private static readonly string[] WindowsDependencyDirs = Array.Empty<string>();
|
||||
|
||||
public static bool UpdateSuccessful { get; private set; }
|
||||
|
||||
public static async Task BeginParse(MainWindow mainWindow, bool showVersionUpToDate)
|
||||
{
|
||||
if (Running)
|
||||
@@ -198,11 +204,18 @@ namespace Ryujinx.Modules
|
||||
_buildSize = -1;
|
||||
}
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
// Show a message asking the user if they want to update
|
||||
UpdaterWindow updateDialog = new(mainWindow, newVersion, _buildUrl);
|
||||
await updateDialog.ShowDialog(mainWindow);
|
||||
var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance["RyujinxUpdater"],
|
||||
LocaleManager.Instance["RyujinxUpdaterMessage"],
|
||||
$"{Program.Version} -> {newVersion}");
|
||||
|
||||
if (shouldUpdate)
|
||||
{
|
||||
UpdateRyujinx(mainWindow, _buildUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -216,8 +229,10 @@ namespace Ryujinx.Modules
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void UpdateRyujinx(UpdaterWindow updateDialog, string downloadUrl)
|
||||
public static async void UpdateRyujinx(Window parent, string downloadUrl)
|
||||
{
|
||||
UpdateSuccessful = false;
|
||||
|
||||
// Empty update dir, although it shouldn't ever have anything inside it
|
||||
if (Directory.Exists(UpdateDir))
|
||||
{
|
||||
@@ -228,25 +243,56 @@ namespace Ryujinx.Modules
|
||||
|
||||
string updateFile = Path.Combine(UpdateDir, "update.bin");
|
||||
|
||||
// Download the update .zip
|
||||
updateDialog.MainText.Text = LocaleManager.Instance["UpdaterDownloading"];
|
||||
updateDialog.ProgressBar.Value = 0;
|
||||
updateDialog.ProgressBar.Maximum = 100;
|
||||
var taskDialog = new TaskDialog()
|
||||
{
|
||||
Header = LocaleManager.Instance["RyujinxUpdater"],
|
||||
SubHeader = LocaleManager.Instance["UpdaterDownloading"],
|
||||
IconSource = new SymbolIconSource { Symbol = Symbol.Download },
|
||||
Buttons = { },
|
||||
ShowProgressBar = true
|
||||
};
|
||||
|
||||
Task.Run(() =>
|
||||
taskDialog.XamlRoot = parent;
|
||||
|
||||
taskDialog.Opened += (s, e) =>
|
||||
{
|
||||
if (_buildSize >= 0)
|
||||
{
|
||||
DoUpdateWithMultipleThreads(updateDialog, downloadUrl, updateFile);
|
||||
DoUpdateWithMultipleThreads(taskDialog, downloadUrl, updateFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile);
|
||||
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
await taskDialog.ShowAsync(true);
|
||||
|
||||
if (UpdateSuccessful)
|
||||
{
|
||||
var shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance["RyujinxUpdater"],
|
||||
LocaleManager.Instance["DialogUpdaterCompleteMessage"],
|
||||
LocaleManager.Instance["DialogUpdaterRestartMessage"]);
|
||||
|
||||
if (shouldRestart)
|
||||
{
|
||||
string ryuName = Path.GetFileName(Environment.ProcessPath);
|
||||
string ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ryuName);
|
||||
var ryuArg = Environment.GetCommandLineArgs().Skip(1);
|
||||
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
chmod(ryuExe, Convert.ToUInt32("0777", 8));
|
||||
}
|
||||
|
||||
Process.Start(ryuExe, ryuArg);
|
||||
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DoUpdateWithMultipleThreads(UpdaterWindow updateDialog, string downloadUrl, string updateFile)
|
||||
private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
||||
{
|
||||
// Multi-Threaded Updater
|
||||
long chunkSize = _buildSize / ConnectionCount;
|
||||
@@ -290,7 +336,7 @@ namespace Ryujinx.Modules
|
||||
Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
|
||||
Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
|
||||
|
||||
updateDialog.ProgressBar.Value = totalProgressPercentage / ConnectionCount;
|
||||
taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal);
|
||||
};
|
||||
|
||||
client.DownloadDataCompleted += (_, args) =>
|
||||
@@ -301,6 +347,8 @@ namespace Ryujinx.Modules
|
||||
{
|
||||
webClients[index].Dispose();
|
||||
|
||||
taskDialog.Hide();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -320,14 +368,14 @@ namespace Ryujinx.Modules
|
||||
|
||||
try
|
||||
{
|
||||
InstallUpdate(updateDialog, updateFile);
|
||||
InstallUpdate(taskDialog, updateFile);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, e.Message);
|
||||
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
|
||||
|
||||
DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile);
|
||||
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -348,7 +396,7 @@ namespace Ryujinx.Modules
|
||||
webClients[j].CancelAsync();
|
||||
}
|
||||
|
||||
DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile);
|
||||
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -356,7 +404,7 @@ namespace Ryujinx.Modules
|
||||
}
|
||||
}
|
||||
|
||||
private static void DoUpdateWithSingleThreadWorker(UpdaterWindow updateDialog, string downloadUrl, string updateFile)
|
||||
private static void DoUpdateWithSingleThreadWorker(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
||||
{
|
||||
using (HttpClient client = new HttpClient())
|
||||
{
|
||||
@@ -384,19 +432,26 @@ namespace Ryujinx.Modules
|
||||
|
||||
byteWritten += readSize;
|
||||
|
||||
updateDialog.ProgressBar.Value = ((double)byteWritten / totalBytes) * 100;
|
||||
taskDialog.SetProgressBarState(GetPercentage(byteWritten, totalBytes), TaskDialogProgressState.Normal);
|
||||
|
||||
updateFileStream.Write(buffer, 0, readSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InstallUpdate(updateDialog, updateFile);
|
||||
InstallUpdate(taskDialog, updateFile);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DoUpdateWithSingleThread(UpdaterWindow updateDialog, string downloadUrl, string updateFile)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static double GetPercentage(double value, double max)
|
||||
{
|
||||
Thread worker = new Thread(() => DoUpdateWithSingleThreadWorker(updateDialog, downloadUrl, updateFile));
|
||||
return max == 0 ? 0 : value / max * 100;
|
||||
}
|
||||
|
||||
private static void DoUpdateWithSingleThread(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
||||
{
|
||||
Thread worker = new Thread(() => DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile));
|
||||
worker.Name = "Updater.SingleThreadWorker";
|
||||
worker.Start();
|
||||
}
|
||||
@@ -414,11 +469,11 @@ namespace Ryujinx.Modules
|
||||
}
|
||||
}
|
||||
|
||||
private static async void InstallUpdate(UpdaterWindow updateDialog, string updateFile)
|
||||
private static async void InstallUpdate(TaskDialog taskDialog, string updateFile)
|
||||
{
|
||||
// Extract Update
|
||||
updateDialog.MainText.Text = LocaleManager.Instance["UpdaterExtracting"];
|
||||
updateDialog.ProgressBar.Value = 0;
|
||||
taskDialog.SubHeader = LocaleManager.Instance["UpdaterExtracting"];
|
||||
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
||||
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
@@ -426,8 +481,6 @@ namespace Ryujinx.Modules
|
||||
using (Stream gzipStream = new GZipInputStream(inStream))
|
||||
using (TarInputStream tarStream = new TarInputStream(gzipStream, Encoding.ASCII))
|
||||
{
|
||||
updateDialog.ProgressBar.Maximum = inStream.Length;
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
TarEntry tarEntry;
|
||||
@@ -450,12 +503,12 @@ namespace Ryujinx.Modules
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
updateDialog.ProgressBar.Value += entry.Size;
|
||||
taskDialog.SetProgressBarState(GetPercentage(entry.Size, inStream.Length), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
updateDialog.ProgressBar.Value = inStream.Length;
|
||||
taskDialog.SetProgressBarState(100, TaskDialogProgressState.Normal);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -463,12 +516,12 @@ namespace Ryujinx.Modules
|
||||
using (Stream inStream = File.OpenRead(updateFile))
|
||||
using (ZipFile zipFile = new ZipFile(inStream))
|
||||
{
|
||||
updateDialog.ProgressBar.Maximum = zipFile.Count;
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
double count = 0;
|
||||
foreach (ZipEntry zipEntry in zipFile)
|
||||
{
|
||||
count++;
|
||||
if (zipEntry.IsDirectory) continue;
|
||||
|
||||
string outPath = Path.Combine(UpdateDir, zipEntry.Name);
|
||||
@@ -485,7 +538,7 @@ namespace Ryujinx.Modules
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
updateDialog.ProgressBar.Value++;
|
||||
taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -497,22 +550,23 @@ namespace Ryujinx.Modules
|
||||
|
||||
List<string> allFiles = EnumerateFilesToDelete().ToList();
|
||||
|
||||
updateDialog.MainText.Text = LocaleManager.Instance["UpdaterRenaming"];
|
||||
updateDialog.ProgressBar.Value = 0;
|
||||
updateDialog.ProgressBar.Maximum = allFiles.Count;
|
||||
taskDialog.SubHeader = LocaleManager.Instance["UpdaterRenaming"];
|
||||
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
||||
|
||||
// Replace old files
|
||||
await Task.Run(() =>
|
||||
{
|
||||
double count = 0;
|
||||
foreach (string file in allFiles)
|
||||
{
|
||||
count++;
|
||||
try
|
||||
{
|
||||
File.Move(file, file + ".ryuold");
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
updateDialog.ProgressBar.Value++;
|
||||
taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
catch
|
||||
@@ -523,23 +577,20 @@ namespace Ryujinx.Modules
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
updateDialog.MainText.Text = LocaleManager.Instance["UpdaterAddingFiles"];
|
||||
updateDialog.ProgressBar.Value = 0;
|
||||
updateDialog.ProgressBar.Maximum = Directory.GetFiles(UpdatePublishDir, "*", SearchOption.AllDirectories).Length;
|
||||
taskDialog.SubHeader = LocaleManager.Instance["UpdaterAddingFiles"];
|
||||
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
||||
});
|
||||
|
||||
MoveAllFilesOver(UpdatePublishDir, HomeDir, updateDialog);
|
||||
MoveAllFilesOver(UpdatePublishDir, HomeDir, taskDialog);
|
||||
});
|
||||
|
||||
Directory.Delete(UpdateDir, true);
|
||||
|
||||
SetUnixPermissions();
|
||||
|
||||
updateDialog.MainText.Text = LocaleManager.Instance["DialogUpdaterCompleteMessage"];
|
||||
updateDialog.SecondaryText.Text = LocaleManager.Instance["DialogUpdaterRestartMessage"];
|
||||
UpdateSuccessful = true;
|
||||
|
||||
updateDialog.ProgressBar.IsVisible = false;
|
||||
updateDialog.ButtonBox.IsVisible = true;
|
||||
taskDialog.Hide();
|
||||
}
|
||||
|
||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
@@ -618,8 +669,9 @@ namespace Ryujinx.Modules
|
||||
return files;
|
||||
}
|
||||
|
||||
private static void MoveAllFilesOver(string root, string dest, UpdaterWindow dialog)
|
||||
private static void MoveAllFilesOver(string root, string dest, TaskDialog taskDialog)
|
||||
{
|
||||
var total = Directory.GetFiles(root, "*", SearchOption.AllDirectories).Length;
|
||||
foreach (string directory in Directory.GetDirectories(root))
|
||||
{
|
||||
string dirName = Path.GetFileName(directory);
|
||||
@@ -629,16 +681,18 @@ namespace Ryujinx.Modules
|
||||
Directory.CreateDirectory(Path.Combine(dest, dirName));
|
||||
}
|
||||
|
||||
MoveAllFilesOver(directory, Path.Combine(dest, dirName), dialog);
|
||||
MoveAllFilesOver(directory, Path.Combine(dest, dirName), taskDialog);
|
||||
}
|
||||
|
||||
double count = 0;
|
||||
foreach (string file in Directory.GetFiles(root))
|
||||
{
|
||||
count++;
|
||||
File.Move(file, Path.Combine(dest, Path.GetFileName(file)), true);
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
dialog.ProgressBar.Value++;
|
||||
taskDialog.SetProgressBarState(GetPercentage(count, total), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -6,7 +6,6 @@
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
Title="Ryujinx - About"
|
||||
Width="850"
|
||||
Height="550"
|
||||
MinWidth="500"
|
||||
|
@@ -164,7 +164,7 @@
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="pl_PL"
|
||||
Header="Polish" />
|
||||
Header="Polski" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="ru_RU"
|
||||
|
@@ -257,6 +257,22 @@
|
||||
TextAlignment="Center" />
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysVolumeUpHotkey}" Width="230" />
|
||||
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
|
||||
<TextBlock
|
||||
Text="{Binding KeyboardHotkeys.VolumeUp, Mode=TwoWay, Converter={StaticResource Key}}"
|
||||
TextAlignment="Center" />
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysVolumeDownHotkey}" Width="230" />
|
||||
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
|
||||
<TextBlock
|
||||
Text="{Binding KeyboardHotkeys.VolumeDown, Mode=TwoWay, Converter={StaticResource Key}}"
|
||||
TextAlignment="Center" />
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
|
@@ -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"));
|
||||
|
@@ -1,66 +0,0 @@
|
||||
<window:StyleableWindow
|
||||
x:Class="Ryujinx.Ava.Ui.Windows.UpdaterWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
Title="Ryujinx Updater"
|
||||
Width="500"
|
||||
Height="500"
|
||||
MinWidth="500"
|
||||
MinHeight="500"
|
||||
d:DesignHeight="350"
|
||||
d:DesignWidth="400"
|
||||
CanResize="False"
|
||||
SizeToContent="Height"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
mc:Ignorable="d">
|
||||
<Grid
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Name="MainText"
|
||||
Grid.Row="1"
|
||||
Height="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
TextAlignment="Center" />
|
||||
<TextBlock
|
||||
Name="SecondaryText"
|
||||
Grid.Row="2"
|
||||
Height="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
TextAlignment="Center" />
|
||||
<ProgressBar
|
||||
Name="ProgressBar"
|
||||
Grid.Row="3"
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsVisible="False"
|
||||
Maximum="100"
|
||||
Minimum="0" />
|
||||
<StackPanel
|
||||
Name="ButtonBox"
|
||||
Grid.Row="4"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="False"
|
||||
Orientation="Horizontal"
|
||||
Spacing="20">
|
||||
<Button MinWidth="50" Command="{Binding YesPressed}">
|
||||
<TextBlock Text="{locale:Locale InputDialogYes}" TextAlignment="Center" />
|
||||
</Button>
|
||||
<Button MinWidth="50" Command="{Binding NoPressed}">
|
||||
<TextBlock Text="{locale:Locale InputDialogNo}" TextAlignment="Center" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</window:StyleableWindow>
|
@@ -1,73 +0,0 @@
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Modules;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Windows
|
||||
{
|
||||
public partial class UpdaterWindow : StyleableWindow
|
||||
{
|
||||
private readonly string _buildUrl;
|
||||
private readonly MainWindow _mainWindow;
|
||||
private readonly Version _newVersion;
|
||||
private bool _restartQuery;
|
||||
|
||||
public UpdaterWindow()
|
||||
{
|
||||
DataContext = this;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
Title = LocaleManager.Instance["RyujinxUpdater"];
|
||||
}
|
||||
|
||||
public UpdaterWindow(MainWindow mainWindow, Version newVersion, string buildUrl) : this()
|
||||
{
|
||||
_mainWindow = mainWindow;
|
||||
_newVersion = newVersion;
|
||||
_buildUrl = buildUrl;
|
||||
}
|
||||
|
||||
[DllImport("libc", SetLastError = true)]
|
||||
private static extern int chmod(string path, uint mode);
|
||||
|
||||
public void YesPressed()
|
||||
{
|
||||
if (_restartQuery)
|
||||
{
|
||||
string ryuName = OperatingSystem.IsWindows() ? "Ryujinx.Ava.exe" : "Ryujinx.Ava";
|
||||
string ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ryuName);
|
||||
string ryuArg = string.Join(" ", Environment.GetCommandLineArgs().AsEnumerable().Skip(1).ToArray());
|
||||
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
chmod(ryuExe, 0777);
|
||||
}
|
||||
|
||||
Process.Start(ryuExe, ryuArg);
|
||||
|
||||
Environment.Exit(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
ButtonBox.IsVisible = false;
|
||||
ProgressBar.IsVisible = true;
|
||||
|
||||
SecondaryText.Text = "";
|
||||
_restartQuery = true;
|
||||
|
||||
Updater.UpdateRyujinx(this, _buildUrl);
|
||||
}
|
||||
}
|
||||
|
||||
public void NoPressed()
|
||||
{
|
||||
_mainWindow.UpdateMenuItem.IsEnabled = true;
|
||||
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
@@ -9,5 +9,7 @@
|
||||
public Key ToggleMute { get; set; }
|
||||
public Key ResScaleUp { get; set; }
|
||||
public Key ResScaleDown { get; set; }
|
||||
public Key VolumeUp { get; set; }
|
||||
public Key VolumeDown { get; set; }
|
||||
}
|
||||
}
|
||||
|
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 = 3728;
|
||||
private const uint CodeGenVersion = 3781;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
|
@@ -123,7 +123,8 @@ namespace Ryujinx.Graphics.Gpu
|
||||
/// <param name="releaseCallback">Texture release callback</param>
|
||||
/// <param name="userObj">User defined object passed to the release callback</param>
|
||||
/// <exception cref="ArgumentException">Thrown when <paramref name="pid"/> is invalid</exception>
|
||||
public void EnqueueFrameThreadSafe(
|
||||
/// <returns>True if the frame was added to the queue, false otherwise</returns>
|
||||
public bool EnqueueFrameThreadSafe(
|
||||
ulong pid,
|
||||
ulong address,
|
||||
int width,
|
||||
@@ -140,7 +141,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||
{
|
||||
if (!_context.PhysicalMemoryRegistry.TryGetValue(pid, out var physicalMemory))
|
||||
{
|
||||
throw new ArgumentException("The PID is invalid or the process was not registered", nameof(pid));
|
||||
return false;
|
||||
}
|
||||
|
||||
FormatInfo formatInfo = new FormatInfo(format, 1, 1, bytesPerPixel, 4);
|
||||
@@ -184,6 +185,8 @@ namespace Ryujinx.Graphics.Gpu
|
||||
acquireCallback,
|
||||
releaseCallback,
|
||||
userObj));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -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,
|
||||
|
@@ -257,7 +257,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
|
||||
DeclareSupportUniformBlock(context, context.Config.Stage, scaleElements);
|
||||
|
||||
if (context.Config.UsedFeatures.HasFlag(FeatureFlags.IntegerSampling))
|
||||
if (context.Config.UsedFeatures.HasFlag(FeatureFlags.IntegerSampling) && scaleElements != 0)
|
||||
{
|
||||
AppendHelperFunction(context, $"Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_{stage}.glsl");
|
||||
context.AppendLine();
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -279,16 +279,16 @@ namespace Ryujinx.Graphics.Shader.Decoders
|
||||
Add("1110101110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SuatomB2, InstEmit.SuatomB2, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc);
|
||||
Add("1110101011010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SuatomCasB, InstEmit.SuatomCasB, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc | InstProps.SPd);
|
||||
Add("1110101x1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SuatomCas, InstEmit.SuatomCas, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.SPd);
|
||||
Add("111010110001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SuldDB, InstEmit.SuldDB, InstProps.Rd | InstProps.Ra | InstProps.Rc | InstProps.SPd | InstProps.TexB);
|
||||
Add("1110101100010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SuldDB, InstEmit.SuldDB, InstProps.Rd | InstProps.Ra | InstProps.Rc | InstProps.SPd | InstProps.TexB);
|
||||
Add("1110101100011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SuldD, InstEmit.SuldD, InstProps.Rd | InstProps.Ra | InstProps.SPd | InstProps.Tex);
|
||||
Add("11101011000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SuldB, InstEmit.SuldB, InstProps.Rd | InstProps.Ra | InstProps.Rc | InstProps.SPd | InstProps.TexB);
|
||||
Add("11101011000x1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Suld, InstEmit.Suld, InstProps.Rd | InstProps.Ra | InstProps.SPd | InstProps.Tex);
|
||||
Add("111010110101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SuredB, InstEmit.SuredB, InstProps.Rd | InstProps.Ra | InstProps.Rc);
|
||||
Add("1110101100000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SuldB, InstEmit.SuldB, InstProps.Rd | InstProps.Ra | InstProps.Rc | InstProps.SPd | InstProps.TexB);
|
||||
Add("1110101100001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Suld, InstEmit.Suld, InstProps.Rd | InstProps.Ra | InstProps.SPd | InstProps.Tex);
|
||||
Add("1110101101010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SuredB, InstEmit.SuredB, InstProps.Rd | InstProps.Ra | InstProps.Rc);
|
||||
Add("1110101101011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Sured, InstEmit.Sured, InstProps.Rd | InstProps.Ra);
|
||||
Add("111010110011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SustDB, InstEmit.SustDB, InstProps.Rd | InstProps.Ra | InstProps.Rc | InstProps.TexB);
|
||||
Add("1110101100110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SustDB, InstEmit.SustDB, InstProps.Rd | InstProps.Ra | InstProps.Rc | InstProps.TexB);
|
||||
Add("1110101100111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SustD, InstEmit.SustD, InstProps.Rd | InstProps.Ra | InstProps.Tex);
|
||||
Add("11101011001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SustB, InstEmit.SustB, InstProps.Rd | InstProps.Ra | InstProps.Rc | InstProps.TexB);
|
||||
Add("11101011001x1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Sust, InstEmit.Sust, InstProps.Rd | InstProps.Ra | InstProps.Tex);
|
||||
Add("1110101100100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SustB, InstEmit.SustB, InstProps.Rd | InstProps.Ra | InstProps.Rc | InstProps.TexB);
|
||||
Add("1110101100101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Sust, InstEmit.Sust, InstProps.Rd | InstProps.Ra | InstProps.Tex);
|
||||
Add("1111000011111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Sync, InstEmit.Sync, InstProps.Bra);
|
||||
Add("11000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Tex, InstEmit.Tex, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.TPd | InstProps.Tex);
|
||||
Add("1101111010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.TexB, InstEmit.TexB, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.TPd | InstProps.TexB);
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -123,7 +123,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
{
|
||||
InstSuldB op = context.GetOp<InstSuldB>();
|
||||
|
||||
EmitSuld(context, op.CacheOp, op.Dim, 0, 0, op.Rgba, op.SrcA, op.Dest, 0, useComponents: true, false, isBindless: true);
|
||||
EmitSuld(context, op.CacheOp, op.Dim, 0, 0, op.Rgba, op.SrcA, op.Dest, op.SrcC, useComponents: true, false, isBindless: true);
|
||||
}
|
||||
|
||||
public static void Suld(EmitterContext context)
|
||||
|
@@ -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;
|
||||
|
@@ -109,12 +109,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 +205,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
data.Slice(0, dataSize).CopyTo(new Span<byte>((void*)(_map + offset), dataSize));
|
||||
|
||||
SignalWrite(offset, dataSize);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -240,7 +264,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 +386,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 +414,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 +424,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 +450,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 +470,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);
|
||||
}
|
||||
|
||||
|
@@ -38,6 +38,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private readonly IdList<BufferHolder> _buffers;
|
||||
|
||||
public int BufferCount { get; private set; }
|
||||
|
||||
public StagingBuffer StagingBuffer { get; }
|
||||
|
||||
public BufferManager(VulkanRenderer gd, PhysicalDevice physicalDevice, Device device)
|
||||
@@ -56,6 +58,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return BufferHandle.Null;
|
||||
}
|
||||
|
||||
BufferCount++;
|
||||
|
||||
ulong handle64 = (uint)_buffers.Add(holder);
|
||||
|
||||
return Unsafe.As<ulong, BufferHandle>(ref handle64);
|
||||
@@ -120,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))
|
||||
|
@@ -24,6 +24,11 @@
|
||||
|
||||
public void Add(int cbIndex, int offset, int size)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Some usages can be out of bounds (vertex buffer on amd), so bound if necessary.
|
||||
if (offset + size > _size)
|
||||
{
|
||||
@@ -39,6 +44,11 @@
|
||||
|
||||
public bool OverlapsWith(int cbIndex, int offset, int size)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int cbBase = cbIndex * _bitsPerCb;
|
||||
int start = cbBase + offset / _granularity;
|
||||
int end = cbBase + (offset + size - 1) / _granularity;
|
||||
|
@@ -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,
|
||||
@@ -238,8 +238,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
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 +388,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 +411,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 +448,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 +483,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 +535,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 +764,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 +775,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 +824,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 +987,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 +996,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 +1075,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_vertexBuffersDirty = ulong.MaxValue >> (64 - _vertexBuffers.Length);
|
||||
|
||||
_descriptorSetUpdater.SignalCommandBufferChange();
|
||||
_dynamicState.ForceAllDirty();
|
||||
DynamicState.ForceAllDirty();
|
||||
_currentPipelineHandle = 0;
|
||||
}
|
||||
|
||||
@@ -1201,7 +1213,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 +1244,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;
|
||||
@@ -16,6 +17,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private Auto<DisposableBufferView> _bufferView;
|
||||
private Dictionary<GAL.Format, Auto<DisposableBufferView>> _selfManagedViews;
|
||||
|
||||
private int _bufferCount;
|
||||
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
|
||||
@@ -88,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();
|
||||
}
|
||||
@@ -107,7 +110,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
if (_bufferHandle == buffer.Handle &&
|
||||
_offset == buffer.Offset &&
|
||||
_size == buffer.Size)
|
||||
_size == buffer.Size &&
|
||||
_bufferCount == _gd.BufferManager.BufferCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -115,6 +119,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_bufferHandle = buffer.Handle;
|
||||
_offset = buffer.Offset;
|
||||
_size = buffer.Size;
|
||||
_bufferCount = _gd.BufferManager.BufferCount;
|
||||
|
||||
ReleaseImpl();
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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)
|
||||
|
@@ -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;
|
||||
|
@@ -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)]
|
||||
|
@@ -453,7 +453,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
Item = item
|
||||
};
|
||||
|
||||
_device.Gpu.Window.EnqueueFrameThreadSafe(
|
||||
if (_device.Gpu.Window.EnqueueFrameThreadSafe(
|
||||
layer.Owner,
|
||||
frameBufferAddress,
|
||||
frameBufferWidth,
|
||||
@@ -466,20 +466,25 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
crop,
|
||||
AcquireBuffer,
|
||||
ReleaseBuffer,
|
||||
textureCallbackInformation);
|
||||
|
||||
if (item.Fence.FenceCount == 0)
|
||||
textureCallbackInformation))
|
||||
{
|
||||
_device.Gpu.Window.SignalFrameReady();
|
||||
_device.Gpu.GPFifo.Interrupt();
|
||||
}
|
||||
else
|
||||
{
|
||||
item.Fence.RegisterCallback(_device.Gpu, (x) =>
|
||||
if (item.Fence.FenceCount == 0)
|
||||
{
|
||||
_device.Gpu.Window.SignalFrameReady();
|
||||
_device.Gpu.GPFifo.Interrupt();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
item.Fence.RegisterCallback(_device.Gpu, (x) =>
|
||||
{
|
||||
_device.Gpu.Window.SignalFrameReady();
|
||||
_device.Gpu.GPFifo.Interrupt();
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ReleaseBuffer(textureCallbackInformation);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
{
|
||||
|
@@ -124,7 +124,7 @@ namespace Ryujinx.HLE
|
||||
|
||||
public void SetVolume(float volume)
|
||||
{
|
||||
System.SetVolume(volume);
|
||||
System.SetVolume(Math.Clamp(volume, 0, 1));
|
||||
}
|
||||
|
||||
public float GetVolume()
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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)
|
||||
|
@@ -80,6 +80,12 @@ namespace Ryujinx.Tests.Cpu
|
||||
[TearDown]
|
||||
public void Teardown()
|
||||
{
|
||||
if (_unicornAvailable)
|
||||
{
|
||||
_unicornEmu.Dispose();
|
||||
_unicornEmu = null;
|
||||
}
|
||||
|
||||
_memory.DecrementReferenceCount();
|
||||
_context.Dispose();
|
||||
_ram.Dispose();
|
||||
|
@@ -76,6 +76,12 @@ namespace Ryujinx.Tests.Cpu
|
||||
[TearDown]
|
||||
public void Teardown()
|
||||
{
|
||||
if (_unicornAvailable)
|
||||
{
|
||||
_unicornEmu.Dispose();
|
||||
_unicornEmu = null;
|
||||
}
|
||||
|
||||
_memory.DecrementReferenceCount();
|
||||
_context.Dispose();
|
||||
_ram.Dispose();
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user