Compare commits
18 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
7d8e198c33 | ||
|
3d98e1361b | ||
|
141cf61ff7 | ||
|
3fe3598d41 | ||
|
59cdf310bd | ||
|
4e34170a84 | ||
|
d540af5dc0 | ||
|
f7c7b66fc0 | ||
|
28ba55598d | ||
|
9719b6a112 | ||
|
f70236f947 | ||
|
eafadf10c7 | ||
|
9b06ee7736 | ||
|
baba2c2467 | ||
|
286e5d39b2 | ||
|
dc529c1181 | ||
|
c7cf1cbc35 | ||
|
d8e487d018 |
24
.github/dependabot.yml
vendored
Normal file
24
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
labels:
|
||||||
|
- "infra"
|
||||||
|
reviewers:
|
||||||
|
- marysaka
|
||||||
|
commit-message:
|
||||||
|
prefix: "ci"
|
||||||
|
|
||||||
|
- package-ecosystem: nuget
|
||||||
|
directory: /
|
||||||
|
open-pull-requests-limit: 5
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
|
labels:
|
||||||
|
- "infra"
|
||||||
|
reviewers:
|
||||||
|
- marysaka
|
||||||
|
commit-message:
|
||||||
|
prefix: nuget
|
15
.github/workflows/build.yml
vendored
15
.github/workflows/build.yml
vendored
@@ -48,21 +48,22 @@ jobs:
|
|||||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||||
RYUJINX_BASE_VERSION: "1.1.0"
|
RYUJINX_BASE_VERSION: "1.1.0"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-dotnet@v1
|
- uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: 6.0.x
|
dotnet-version: 6.0.x
|
||||||
- name: Ensure NuGet Source
|
- name: Ensure NuGet Source
|
||||||
uses: fabriciomurta/ensure-nuget-source@v1
|
uses: fabriciomurta/ensure-nuget-source@v1
|
||||||
- name: Get git short hash
|
- name: Get git short hash
|
||||||
id: git_short_hash
|
id: git_short_hash
|
||||||
run: echo "::set-output name=result::$(git rev-parse --short "${{ github.sha }}")"
|
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||||
|
shell: bash
|
||||||
- name: Clear
|
- name: Clear
|
||||||
run: dotnet clean && dotnet nuget locals all --clear
|
run: dotnet clean && dotnet nuget locals all --clear
|
||||||
- name: Build
|
- 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
|
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
|
- name: Test
|
||||||
run: dotnet test -c "${{ matrix.configuration }}"
|
run: dotnet test --no-build -c "${{ matrix.configuration }}"
|
||||||
- name: Publish Ryujinx
|
- 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
|
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'
|
if: github.event_name == 'pull_request'
|
||||||
@@ -73,19 +74,19 @@ jobs:
|
|||||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava /p:Version="1.0.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Ava
|
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'
|
if: github.event_name == 'pull_request'
|
||||||
- name: Upload Ryujinx artifact
|
- name: Upload Ryujinx artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
||||||
path: publish
|
path: publish
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
- name: Upload Ryujinx.Headless.SDL2 artifact
|
- name: Upload Ryujinx.Headless.SDL2 artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
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
|
path: publish_sdl2_headless
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
- name: Upload Ryujinx.Ava artifact
|
- name: Upload Ryujinx.Ava artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
||||||
path: publish_ava
|
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'
|
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v3
|
- uses: actions/github-script@v6
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const {owner, repo} = context.repo;
|
const {owner, repo} = context.repo;
|
||||||
@@ -16,7 +16,7 @@ jobs:
|
|||||||
const pull_head_sha = '${{github.event.workflow_run.head_sha}}';
|
const pull_head_sha = '${{github.event.workflow_run.head_sha}}';
|
||||||
|
|
||||||
const issue_number = await (async () => {
|
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 await (const {data} of github.paginate.iterator(pulls)) {
|
||||||
for (const pull of data) {
|
for (const pull of data) {
|
||||||
if (pull.head.sha === pull_head_sha) {
|
if (pull.head.sha === pull_head_sha) {
|
||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
return core.error(`No matching pull request found`);
|
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) {
|
if (!artifacts.length) {
|
||||||
return core.error(`No artifacts found`);
|
return core.error(`No artifacts found`);
|
||||||
}
|
}
|
||||||
@@ -57,12 +57,12 @@ jobs:
|
|||||||
body += hidden_headless_artifacts;
|
body += hidden_headless_artifacts;
|
||||||
body += hidden_debug_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]');
|
const existing_comment = comments.find((c) => c.user.login === 'github-actions[bot]');
|
||||||
if (existing_comment) {
|
if (existing_comment) {
|
||||||
core.info(`Updating comment ${existing_comment.id}`);
|
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 {
|
} else {
|
||||||
core.info(`Creating a comment`);
|
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"
|
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "release-channel-master"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-dotnet@v1
|
- uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: 6.0.x
|
dotnet-version: 6.0.x
|
||||||
- name: Ensure NuGet Source
|
- name: Ensure NuGet Source
|
||||||
@@ -36,8 +36,8 @@ jobs:
|
|||||||
- name: Get version info
|
- name: Get version info
|
||||||
id: version_info
|
id: version_info
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=build_version::${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}"
|
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
||||||
echo "::set-output name=git_short_hash::$(git rev-parse --short "${{ github.sha }}")"
|
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||||
shell: bash
|
shell: bash
|
||||||
- name: Configure for release
|
- name: Configure for release
|
||||||
run: |
|
run: |
|
||||||
|
@@ -36,8 +36,8 @@
|
|||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
As of October 2022, Ryujinx has been tested on approximately 3,600 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).
|
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.
|
||||||
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!
|
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
|
## Usage
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ Ryujinx system files are stored in the `Ryujinx` folder. This folder is located
|
|||||||
|
|
||||||
- **GPU**
|
- **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**
|
- **Input**
|
||||||
|
|
||||||
|
@@ -34,7 +34,7 @@
|
|||||||
<PackageReference Include="Silk.NET.Vulkan" Version="2.10.1" />
|
<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.EXT" Version="2.10.1" />
|
||||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" 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="SharpZipLib" Version="1.3.3" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@@ -4,6 +4,7 @@ using Avalonia.Controls.ApplicationLifetimes;
|
|||||||
using Avalonia.Controls.Primitives;
|
using Avalonia.Controls.Primitives;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
|
using FluentAvalonia.Core;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Ui.Models;
|
using Ryujinx.Ava.Ui.Models;
|
||||||
@@ -27,7 +28,10 @@ namespace Ryujinx.Ava.Ui.Controls
|
|||||||
string secondaryButton,
|
string secondaryButton,
|
||||||
string closeButton,
|
string closeButton,
|
||||||
int iconSymbol,
|
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;
|
UserResult result = UserResult.None;
|
||||||
|
|
||||||
@@ -110,12 +114,19 @@ namespace Ryujinx.Ava.Ui.Controls
|
|||||||
contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
|
contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
|
||||||
{
|
{
|
||||||
result = UserResult.No;
|
result = UserResult.No;
|
||||||
|
contentDialog.PrimaryButtonClick -= deferCloseAction;
|
||||||
});
|
});
|
||||||
contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
|
contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
|
||||||
{
|
{
|
||||||
result = UserResult.Cancel;
|
result = UserResult.Cancel;
|
||||||
|
contentDialog.PrimaryButtonClick -= deferCloseAction;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (deferResetEvent != null)
|
||||||
|
{
|
||||||
|
contentDialog.PrimaryButtonClick += deferCloseAction;
|
||||||
|
}
|
||||||
|
|
||||||
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
|
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
|
||||||
|
|
||||||
overlay?.Close();
|
overlay?.Close();
|
||||||
@@ -143,35 +154,20 @@ namespace Ryujinx.Ava.Ui.Controls
|
|||||||
Func<Window, Task> doWhileDeferred = null)
|
Func<Window, Task> doWhileDeferred = null)
|
||||||
{
|
{
|
||||||
bool startedDeferring = false;
|
bool startedDeferring = false;
|
||||||
|
|
||||||
UserResult result = UserResult.None;
|
UserResult result = UserResult.None;
|
||||||
|
|
||||||
ContentDialog contentDialog = new ContentDialog
|
return await ShowContentDialog(
|
||||||
{
|
title,
|
||||||
Title = title,
|
primaryText,
|
||||||
PrimaryButtonText = primaryButton,
|
secondaryText,
|
||||||
SecondaryButtonText = secondaryButton,
|
primaryButton,
|
||||||
CloseButtonText = closeButton,
|
secondaryButton,
|
||||||
Content = CreateDialogTextContent(primaryText, secondaryText, iconSymbol),
|
closeButton,
|
||||||
PrimaryButtonCommand = MiniCommand.Create(() =>
|
iconSymbol,
|
||||||
{
|
primaryButton == LocaleManager.Instance["InputDialogYes"] ? UserResult.Yes : UserResult.Ok,
|
||||||
result = primaryButton == LocaleManager.Instance["InputDialogYes"] ? UserResult.Yes : UserResult.Ok;
|
deferResetEvent,
|
||||||
}),
|
doWhileDeferred,
|
||||||
};
|
DeferClose);
|
||||||
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;
|
|
||||||
|
|
||||||
async void DeferClose(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
async void DeferClose(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||||
{
|
{
|
||||||
@@ -180,7 +176,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
contentDialog.PrimaryButtonClick -= DeferClose;
|
sender.PrimaryButtonClick -= DeferClose;
|
||||||
|
|
||||||
startedDeferring = true;
|
startedDeferring = true;
|
||||||
|
|
||||||
@@ -188,7 +184,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
|||||||
|
|
||||||
result = primaryButton == LocaleManager.Instance["InputDialogYes"] ? UserResult.Yes : UserResult.Ok;
|
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
|
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
|
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||||||
private const ushort FileFormatVersionMajor = 1;
|
private const ushort FileFormatVersionMajor = 1;
|
||||||
private const ushort FileFormatVersionMinor = 2;
|
private const ushort FileFormatVersionMinor = 2;
|
||||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||||
private const uint CodeGenVersion = 3781;
|
private const uint CodeGenVersion = 3807;
|
||||||
|
|
||||||
private const string SharedTocFileName = "shared.toc";
|
private const string SharedTocFileName = "shared.toc";
|
||||||
private const string SharedDataFileName = "shared.data";
|
private const string SharedDataFileName = "shared.data";
|
||||||
|
@@ -7,7 +7,9 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg
|
|||||||
{
|
{
|
||||||
unsafe class FFmpegContext : IDisposable
|
unsafe class FFmpegContext : IDisposable
|
||||||
{
|
{
|
||||||
private readonly FFCodec.AVCodec_decode _decodeFrame;
|
private unsafe delegate int AVCodec_decode(AVCodecContext* avctx, void* outdata, int* got_frame_ptr, AVPacket* avpkt);
|
||||||
|
|
||||||
|
private readonly AVCodec_decode _decodeFrame;
|
||||||
private static readonly FFmpegApi.av_log_set_callback_callback _logFunc;
|
private static readonly FFmpegApi.av_log_set_callback_callback _logFunc;
|
||||||
private readonly AVCodec* _codec;
|
private readonly AVCodec* _codec;
|
||||||
private AVPacket* _packet;
|
private AVPacket* _packet;
|
||||||
@@ -53,17 +55,17 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg
|
|||||||
// libavcodec 59.24 changed AvCodec to move its private API and also move the codec function to an union.
|
// libavcodec 59.24 changed AvCodec to move its private API and also move the codec function to an union.
|
||||||
if (avCodecMajorVersion > 59 || (avCodecMajorVersion == 59 && avCodecMinorVersion > 24))
|
if (avCodecMajorVersion > 59 || (avCodecMajorVersion == 59 && avCodecMinorVersion > 24))
|
||||||
{
|
{
|
||||||
_decodeFrame = Marshal.GetDelegateForFunctionPointer<FFCodec.AVCodec_decode>(((FFCodec*)_codec)->CodecCallback);
|
_decodeFrame = Marshal.GetDelegateForFunctionPointer<AVCodec_decode>(((FFCodec<AVCodec>*)_codec)->CodecCallback);
|
||||||
}
|
}
|
||||||
// libavcodec 59.x changed AvCodec private API layout.
|
// libavcodec 59.x changed AvCodec private API layout.
|
||||||
else if (avCodecMajorVersion == 59)
|
else if (avCodecMajorVersion == 59)
|
||||||
{
|
{
|
||||||
_decodeFrame = Marshal.GetDelegateForFunctionPointer<FFCodec.AVCodec_decode>(((FFCodecLegacy<AVCodec>*)_codec)->Decode);
|
_decodeFrame = Marshal.GetDelegateForFunctionPointer<AVCodec_decode>(((FFCodecLegacy<AVCodec501>*)_codec)->Decode);
|
||||||
}
|
}
|
||||||
// libavcodec 58.x and lower
|
// libavcodec 58.x and lower
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_decodeFrame = Marshal.GetDelegateForFunctionPointer<FFCodec.AVCodec_decode>(((FFCodecLegacy<AVCodecLegacy>*)_codec)->Decode);
|
_decodeFrame = Marshal.GetDelegateForFunctionPointer<AVCodec_decode>(((FFCodecLegacy<AVCodec>*)_codec)->Decode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,6 +20,7 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
|
|||||||
public unsafe IntPtr PrivClass;
|
public unsafe IntPtr PrivClass;
|
||||||
public IntPtr Profiles;
|
public IntPtr Profiles;
|
||||||
public unsafe byte* WrapperName;
|
public unsafe byte* WrapperName;
|
||||||
|
public IntPtr ChLayouts;
|
||||||
#pragma warning restore CS0649
|
#pragma warning restore CS0649
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
|
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
|
||||||
{
|
{
|
||||||
struct AVCodecLegacy
|
struct AVCodec501
|
||||||
{
|
{
|
||||||
#pragma warning disable CS0649
|
#pragma warning disable CS0649
|
||||||
public unsafe byte* Name;
|
public unsafe byte* Name;
|
||||||
@@ -20,7 +20,6 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
|
|||||||
public unsafe IntPtr PrivClass;
|
public unsafe IntPtr PrivClass;
|
||||||
public IntPtr Profiles;
|
public IntPtr Profiles;
|
||||||
public unsafe byte* WrapperName;
|
public unsafe byte* WrapperName;
|
||||||
public IntPtr ChLayouts;
|
|
||||||
#pragma warning restore CS0649
|
#pragma warning restore CS0649
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -9,7 +9,7 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
|
|||||||
public unsafe IntPtr AvClass;
|
public unsafe IntPtr AvClass;
|
||||||
public int LogLevelOffset;
|
public int LogLevelOffset;
|
||||||
public int CodecType;
|
public int CodecType;
|
||||||
public unsafe AVCodecLegacy* Codec;
|
public unsafe AVCodec* Codec;
|
||||||
public AVCodecID CodecId;
|
public AVCodecID CodecId;
|
||||||
public uint CodecTag;
|
public uint CodecTag;
|
||||||
public IntPtr PrivData;
|
public IntPtr PrivData;
|
||||||
|
@@ -2,12 +2,10 @@
|
|||||||
|
|
||||||
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
|
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
|
||||||
{
|
{
|
||||||
struct FFCodec
|
struct FFCodec<T> where T: struct
|
||||||
{
|
{
|
||||||
public unsafe delegate int AVCodec_decode(AVCodecContext* avctx, void* outdata, int* got_frame_ptr, AVPacket* avpkt);
|
|
||||||
|
|
||||||
#pragma warning disable CS0649
|
#pragma warning disable CS0649
|
||||||
public AVCodec Base;
|
public T Base;
|
||||||
public int CapsInternalOrCbType;
|
public int CapsInternalOrCbType;
|
||||||
public int PrivDataSize;
|
public int PrivDataSize;
|
||||||
public IntPtr UpdateThreadContext;
|
public IntPtr UpdateThreadContext;
|
||||||
|
@@ -262,6 +262,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
|
|
||||||
Instruction ioVariable, elemIndex;
|
Instruction ioVariable, elemIndex;
|
||||||
|
|
||||||
|
Instruction invocationId = null;
|
||||||
|
|
||||||
|
if (Config.Stage == ShaderStage.TessellationControl && isOutAttr)
|
||||||
|
{
|
||||||
|
invocationId = Load(TypeS32(), Inputs[AttributeConsts.InvocationId]);
|
||||||
|
}
|
||||||
|
|
||||||
bool isUserAttr = attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd;
|
bool isUserAttr = attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd;
|
||||||
|
|
||||||
if (isUserAttr &&
|
if (isUserAttr &&
|
||||||
@@ -273,7 +280,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
elemIndex = Constant(TypeU32(), attrInfo.GetInnermostIndex());
|
elemIndex = Constant(TypeU32(), attrInfo.GetInnermostIndex());
|
||||||
var vecIndex = Constant(TypeU32(), (attr - AttributeConsts.UserAttributeBase) >> 4);
|
var vecIndex = Constant(TypeU32(), (attr - AttributeConsts.UserAttributeBase) >> 4);
|
||||||
|
|
||||||
if (AttributeInfo.IsArrayAttributeSpirv(Config.Stage, isOutAttr))
|
bool isArray = AttributeInfo.IsArrayAttributeSpirv(Config.Stage, isOutAttr);
|
||||||
|
|
||||||
|
if (invocationId != null && isArray)
|
||||||
|
{
|
||||||
|
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, index, vecIndex, elemIndex);
|
||||||
|
}
|
||||||
|
else if (invocationId != null)
|
||||||
|
{
|
||||||
|
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, vecIndex, elemIndex);
|
||||||
|
}
|
||||||
|
else if (isArray)
|
||||||
{
|
{
|
||||||
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index, vecIndex, elemIndex);
|
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index, vecIndex, elemIndex);
|
||||||
}
|
}
|
||||||
@@ -307,13 +324,30 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
bool isIndexed = AttributeInfo.IsArrayAttributeSpirv(Config.Stage, isOutAttr) && (!attrInfo.IsBuiltin || AttributeInfo.IsArrayBuiltIn(attr));
|
bool isIndexed = AttributeInfo.IsArrayAttributeSpirv(Config.Stage, isOutAttr) && (!attrInfo.IsBuiltin || AttributeInfo.IsArrayBuiltIn(attr));
|
||||||
|
|
||||||
if ((type & (AggregateType.Array | AggregateType.Vector)) == 0)
|
if ((type & (AggregateType.Array | AggregateType.Vector)) == 0)
|
||||||
|
{
|
||||||
|
if (invocationId != null)
|
||||||
|
{
|
||||||
|
return isIndexed
|
||||||
|
? AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, index)
|
||||||
|
: AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
return isIndexed ? AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index) : ioVariable;
|
return isIndexed ? AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index) : ioVariable;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
elemIndex = Constant(TypeU32(), attrInfo.GetInnermostIndex());
|
elemIndex = Constant(TypeU32(), attrInfo.GetInnermostIndex());
|
||||||
|
|
||||||
if (isIndexed)
|
if (invocationId != null && isIndexed)
|
||||||
|
{
|
||||||
|
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, index, elemIndex);
|
||||||
|
}
|
||||||
|
else if (invocationId != null)
|
||||||
|
{
|
||||||
|
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, elemIndex);
|
||||||
|
}
|
||||||
|
else if (isIndexed)
|
||||||
{
|
{
|
||||||
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index, elemIndex);
|
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index, elemIndex);
|
||||||
}
|
}
|
||||||
@@ -327,12 +361,29 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
{
|
{
|
||||||
var storageClass = isOutAttr ? StorageClass.Output : StorageClass.Input;
|
var storageClass = isOutAttr ? StorageClass.Output : StorageClass.Input;
|
||||||
|
|
||||||
|
Instruction invocationId = null;
|
||||||
|
|
||||||
|
if (Config.Stage == ShaderStage.TessellationControl && isOutAttr)
|
||||||
|
{
|
||||||
|
invocationId = Load(TypeS32(), Inputs[AttributeConsts.InvocationId]);
|
||||||
|
}
|
||||||
|
|
||||||
elemType = AggregateType.FP32;
|
elemType = AggregateType.FP32;
|
||||||
var ioVariable = isOutAttr ? OutputsArray : InputsArray;
|
var ioVariable = isOutAttr ? OutputsArray : InputsArray;
|
||||||
var vecIndex = ShiftRightLogical(TypeS32(), attrIndex, Constant(TypeS32(), 2));
|
var vecIndex = ShiftRightLogical(TypeS32(), attrIndex, Constant(TypeS32(), 2));
|
||||||
var elemIndex = BitwiseAnd(TypeS32(), attrIndex, Constant(TypeS32(), 3));
|
var elemIndex = BitwiseAnd(TypeS32(), attrIndex, Constant(TypeS32(), 3));
|
||||||
|
|
||||||
if (AttributeInfo.IsArrayAttributeSpirv(Config.Stage, isOutAttr))
|
bool isArray = AttributeInfo.IsArrayAttributeSpirv(Config.Stage, isOutAttr);
|
||||||
|
|
||||||
|
if (invocationId != null && isArray)
|
||||||
|
{
|
||||||
|
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, index, vecIndex, elemIndex);
|
||||||
|
}
|
||||||
|
else if (invocationId != null)
|
||||||
|
{
|
||||||
|
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, vecIndex, elemIndex);
|
||||||
|
}
|
||||||
|
else if (isArray)
|
||||||
{
|
{
|
||||||
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index, vecIndex, elemIndex);
|
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index, vecIndex, elemIndex);
|
||||||
}
|
}
|
||||||
|
@@ -473,6 +473,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
var attrType = context.TypeVector(context.TypeFP32(), (LiteralInteger)4);
|
var attrType = context.TypeVector(context.TypeFP32(), (LiteralInteger)4);
|
||||||
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)MaxAttributes));
|
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)MaxAttributes));
|
||||||
|
|
||||||
|
if (context.Config.Stage == ShaderStage.TessellationControl)
|
||||||
|
{
|
||||||
|
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), context.Config.ThreadsPerInputPrimitive));
|
||||||
|
}
|
||||||
|
|
||||||
var spvType = context.TypePointer(StorageClass.Output, attrType);
|
var spvType = context.TypePointer(StorageClass.Output, attrType);
|
||||||
var spvVar = context.Variable(spvType, StorageClass.Output);
|
var spvVar = context.Variable(spvType, StorageClass.Output);
|
||||||
|
|
||||||
@@ -543,6 +548,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context.Config.Stage == ShaderStage.TessellationControl && isOutAttr && !perPatch)
|
||||||
|
{
|
||||||
|
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), context.Config.ThreadsPerInputPrimitive));
|
||||||
|
}
|
||||||
|
|
||||||
var spvType = context.TypePointer(storageClass, attrType);
|
var spvType = context.TypePointer(storageClass, attrType);
|
||||||
var spvVar = context.Variable(spvType, storageClass);
|
var spvVar = context.Variable(spvType, storageClass);
|
||||||
|
|
||||||
@@ -634,6 +644,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)arraySize));
|
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)arraySize));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context.Config.Stage == ShaderStage.TessellationControl && isOutAttr)
|
||||||
|
{
|
||||||
|
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), context.Config.ThreadsPerInputPrimitive));
|
||||||
|
}
|
||||||
|
|
||||||
var spvType = context.TypePointer(storageClass, attrType);
|
var spvType = context.TypePointer(storageClass, attrType);
|
||||||
var spvVar = context.Variable(spvType, storageClass);
|
var spvVar = context.Variable(spvType, storageClass);
|
||||||
|
|
||||||
|
@@ -37,7 +37,12 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
|||||||
|
|
||||||
Config = config;
|
Config = config;
|
||||||
|
|
||||||
if (config.GpPassthrough)
|
if (config.Stage == ShaderStage.TessellationControl)
|
||||||
|
{
|
||||||
|
// Required to index outputs.
|
||||||
|
Info.Inputs.Add(AttributeConsts.InvocationId);
|
||||||
|
}
|
||||||
|
else if (config.GpPassthrough)
|
||||||
{
|
{
|
||||||
int passthroughAttributes = config.PassthroughAttributes;
|
int passthroughAttributes = config.PassthroughAttributes;
|
||||||
while (passthroughAttributes != 0)
|
while (passthroughAttributes != 0)
|
||||||
|
@@ -12,13 +12,12 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private const int MaxUpdateBufferSize = 0x10000;
|
private const int MaxUpdateBufferSize = 0x10000;
|
||||||
|
|
||||||
public const AccessFlags DefaultAccessFlags =
|
public const AccessFlags DefaultAccessFlags =
|
||||||
|
AccessFlags.AccessIndirectCommandReadBit |
|
||||||
AccessFlags.AccessShaderReadBit |
|
AccessFlags.AccessShaderReadBit |
|
||||||
AccessFlags.AccessShaderWriteBit |
|
AccessFlags.AccessShaderWriteBit |
|
||||||
AccessFlags.AccessTransferReadBit |
|
AccessFlags.AccessTransferReadBit |
|
||||||
AccessFlags.AccessTransferWriteBit |
|
AccessFlags.AccessTransferWriteBit |
|
||||||
AccessFlags.AccessUniformReadBit |
|
AccessFlags.AccessUniformReadBit;
|
||||||
AccessFlags.AccessShaderReadBit |
|
|
||||||
AccessFlags.AccessShaderWriteBit;
|
|
||||||
|
|
||||||
private readonly VulkanRenderer _gd;
|
private readonly VulkanRenderer _gd;
|
||||||
private readonly Device _device;
|
private readonly Device _device;
|
||||||
|
@@ -228,10 +228,26 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
|
Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CommandBufferBarrier()
|
public unsafe void CommandBufferBarrier()
|
||||||
{
|
{
|
||||||
// TODO: More specific barrier?
|
MemoryBarrier memoryBarrier = new MemoryBarrier()
|
||||||
Barrier();
|
{
|
||||||
|
SType = StructureType.MemoryBarrier,
|
||||||
|
SrcAccessMask = BufferHolder.DefaultAccessFlags,
|
||||||
|
DstAccessMask = AccessFlags.AccessIndirectCommandReadBit
|
||||||
|
};
|
||||||
|
|
||||||
|
Gd.Api.CmdPipelineBarrier(
|
||||||
|
CommandBuffer,
|
||||||
|
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||||
|
PipelineStageFlags.PipelineStageDrawIndirectBit,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
memoryBarrier,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
0,
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size)
|
public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size)
|
||||||
@@ -535,10 +551,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
vkBlend = new PipelineColorBlendAttachmentState();
|
vkBlend = new PipelineColorBlendAttachmentState();
|
||||||
}
|
}
|
||||||
|
|
||||||
_newState.BlendConstantR = blend.BlendConstant.Red;
|
DynamicState.SetBlendConstants(
|
||||||
_newState.BlendConstantG = blend.BlendConstant.Green;
|
blend.BlendConstant.Red,
|
||||||
_newState.BlendConstantB = blend.BlendConstant.Blue;
|
blend.BlendConstant.Green,
|
||||||
_newState.BlendConstantA = blend.BlendConstant.Alpha;
|
blend.BlendConstant.Blue,
|
||||||
|
blend.BlendConstant.Alpha);
|
||||||
|
|
||||||
SignalStateChange();
|
SignalStateChange();
|
||||||
}
|
}
|
||||||
|
@@ -135,11 +135,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
// It is assumed that Dynamic State is enabled when this conversion is used.
|
// 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.CullMode = state.CullEnable ? state.CullMode.Convert() : CullModeFlags.CullModeNone;
|
||||||
|
|
||||||
pipeline.DepthBoundsTestEnable = false; // Not implemented.
|
pipeline.DepthBoundsTestEnable = false; // Not implemented.
|
||||||
|
@@ -19,21 +19,34 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private uint _frontWriteMask;
|
private uint _frontWriteMask;
|
||||||
private uint _frontReference;
|
private uint _frontReference;
|
||||||
|
|
||||||
|
private Array4<float> _blendConstants;
|
||||||
|
|
||||||
public int ViewportsCount;
|
public int ViewportsCount;
|
||||||
public Array16<Viewport> Viewports;
|
public Array16<Viewport> Viewports;
|
||||||
|
|
||||||
private enum DirtyFlags
|
private enum DirtyFlags
|
||||||
{
|
{
|
||||||
None = 0,
|
None = 0,
|
||||||
DepthBias = 1 << 0,
|
Blend = 1 << 0,
|
||||||
Scissor = 1 << 1,
|
DepthBias = 1 << 1,
|
||||||
Stencil = 1 << 2,
|
Scissor = 1 << 2,
|
||||||
Viewport = 1 << 3,
|
Stencil = 1 << 3,
|
||||||
All = DepthBias | Scissor | Stencil | Viewport
|
Viewport = 1 << 4,
|
||||||
|
All = Blend | DepthBias | Scissor | Stencil | Viewport
|
||||||
}
|
}
|
||||||
|
|
||||||
private DirtyFlags _dirty;
|
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)
|
public void SetDepthBias(float slopeFactor, float constantFactor, float clamp)
|
||||||
{
|
{
|
||||||
_depthBiasSlopeFactor = slopeFactor;
|
_depthBiasSlopeFactor = slopeFactor;
|
||||||
@@ -87,6 +100,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
public void ReplayIfDirty(Vk api, CommandBuffer commandBuffer)
|
public void ReplayIfDirty(Vk api, CommandBuffer commandBuffer)
|
||||||
{
|
{
|
||||||
|
if (_dirty.HasFlag(DirtyFlags.Blend))
|
||||||
|
{
|
||||||
|
RecordBlend(api, commandBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
if (_dirty.HasFlag(DirtyFlags.DepthBias))
|
if (_dirty.HasFlag(DirtyFlags.DepthBias))
|
||||||
{
|
{
|
||||||
RecordDepthBias(api, commandBuffer);
|
RecordDepthBias(api, commandBuffer);
|
||||||
@@ -110,6 +128,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_dirty = DirtyFlags.None;
|
_dirty = DirtyFlags.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RecordBlend(Vk api, CommandBuffer commandBuffer)
|
||||||
|
{
|
||||||
|
api.CmdSetBlendConstants(commandBuffer, _blendConstants.AsSpan());
|
||||||
|
}
|
||||||
|
|
||||||
private void RecordDepthBias(Vk api, CommandBuffer commandBuffer)
|
private void RecordDepthBias(Vk api, CommandBuffer commandBuffer)
|
||||||
{
|
{
|
||||||
api.CmdSetDepthBias(commandBuffer, _depthBiasConstantFactor, _depthBiasClamp, _depthBiasSlopeFactor);
|
api.CmdSetDepthBias(commandBuffer, _depthBiasConstantFactor, _depthBiasClamp, _depthBiasSlopeFactor);
|
||||||
|
@@ -499,7 +499,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
colorBlendState.BlendConstants[3] = BlendConstantA;
|
colorBlendState.BlendConstants[3] = BlendConstantA;
|
||||||
|
|
||||||
bool supportsExtDynamicState = gd.Capabilities.SupportsExtendedDynamicState;
|
bool supportsExtDynamicState = gd.Capabilities.SupportsExtendedDynamicState;
|
||||||
int dynamicStatesCount = supportsExtDynamicState ? 8 : 7;
|
int dynamicStatesCount = supportsExtDynamicState ? 9 : 8;
|
||||||
|
|
||||||
DynamicState* dynamicStates = stackalloc DynamicState[dynamicStatesCount];
|
DynamicState* dynamicStates = stackalloc DynamicState[dynamicStatesCount];
|
||||||
|
|
||||||
@@ -510,10 +510,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
dynamicStates[4] = DynamicState.StencilCompareMask;
|
dynamicStates[4] = DynamicState.StencilCompareMask;
|
||||||
dynamicStates[5] = DynamicState.StencilWriteMask;
|
dynamicStates[5] = DynamicState.StencilWriteMask;
|
||||||
dynamicStates[6] = DynamicState.StencilReference;
|
dynamicStates[6] = DynamicState.StencilReference;
|
||||||
|
dynamicStates[7] = DynamicState.BlendConstants;
|
||||||
|
|
||||||
if (supportsExtDynamicState)
|
if (supportsExtDynamicState)
|
||||||
{
|
{
|
||||||
dynamicStates[7] = DynamicState.VertexInputBindingStrideExt;
|
dynamicStates[8] = DynamicState.VertexInputBindingStrideExt;
|
||||||
}
|
}
|
||||||
|
|
||||||
var pipelineDynamicStateCreateInfo = new PipelineDynamicStateCreateInfo()
|
var pipelineDynamicStateCreateInfo = new PipelineDynamicStateCreateInfo()
|
||||||
|
@@ -53,7 +53,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
"VUID-VkSubpassDependency-srcSubpass-00867"
|
"VUID-VkSubpassDependency-srcSubpass-00867"
|
||||||
};
|
};
|
||||||
|
|
||||||
internal static Instance CreateInstance(Vk api, GraphicsDebugLevel logLevel, string[] requiredExtensions, out ExtDebugReport debugReport, out DebugReportCallbackEXT debugReportCallback)
|
internal static Instance CreateInstance(Vk api, GraphicsDebugLevel logLevel, string[] requiredExtensions, out ExtDebugUtils debugUtils, out DebugUtilsMessengerEXT debugUtilsMessenger)
|
||||||
{
|
{
|
||||||
var enabledLayers = new List<string>();
|
var enabledLayers = new List<string>();
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
AddAvailableLayer("VK_LAYER_KHRONOS_validation");
|
AddAvailableLayer("VK_LAYER_KHRONOS_validation");
|
||||||
}
|
}
|
||||||
|
|
||||||
var enabledExtensions = requiredExtensions.Append(ExtDebugReport.ExtensionName).ToArray();
|
var enabledExtensions = requiredExtensions.Append(ExtDebugUtils.ExtensionName).ToArray();
|
||||||
|
|
||||||
var appName = Marshal.StringToHGlobalAnsi(AppName);
|
var appName = Marshal.StringToHGlobalAnsi(AppName);
|
||||||
|
|
||||||
@@ -139,22 +139,18 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
Marshal.FreeHGlobal(ppEnabledLayers[i]);
|
Marshal.FreeHGlobal(ppEnabledLayers[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateDebugCallbacks(api, logLevel, instance, out debugReport, out debugReportCallback);
|
CreateDebugMessenger(api, logLevel, instance, out debugUtils, out debugUtilsMessenger);
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe static uint DebugReport(
|
private unsafe static uint DebugMessenger(
|
||||||
uint flags,
|
DebugUtilsMessageSeverityFlagsEXT messageSeverity,
|
||||||
DebugReportObjectTypeEXT objectType,
|
DebugUtilsMessageTypeFlagsEXT messageTypes,
|
||||||
ulong @object,
|
DebugUtilsMessengerCallbackDataEXT* pCallbackData,
|
||||||
nuint location,
|
void* pUserData)
|
||||||
int messageCode,
|
|
||||||
byte* layerPrefix,
|
|
||||||
byte* message,
|
|
||||||
void* userData)
|
|
||||||
{
|
{
|
||||||
var msg = Marshal.PtrToStringAnsi((IntPtr)message);
|
var msg = Marshal.PtrToStringAnsi((IntPtr)pCallbackData->PMessage);
|
||||||
|
|
||||||
foreach (string excludedMessagePart in _excludedMessages)
|
foreach (string excludedMessagePart in _excludedMessages)
|
||||||
{
|
{
|
||||||
@@ -164,26 +160,20 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DebugReportFlagsEXT debugFlags = (DebugReportFlagsEXT)flags;
|
if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt))
|
||||||
|
|
||||||
if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportErrorBitExt))
|
|
||||||
{
|
{
|
||||||
Logger.Error?.Print(LogClass.Gpu, msg);
|
Logger.Error?.Print(LogClass.Gpu, msg);
|
||||||
//throw new Exception(msg);
|
//throw new Exception(msg);
|
||||||
}
|
}
|
||||||
else if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportWarningBitExt))
|
else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityWarningBitExt))
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Gpu, msg);
|
Logger.Warning?.Print(LogClass.Gpu, msg);
|
||||||
}
|
}
|
||||||
else if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportInformationBitExt))
|
else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityInfoBitExt))
|
||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.Gpu, msg);
|
Logger.Info?.Print(LogClass.Gpu, msg);
|
||||||
}
|
}
|
||||||
else if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportPerformanceWarningBitExt))
|
else // if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityVerboseBitExt))
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Gpu, msg);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
Logger.Debug?.Print(LogClass.Gpu, msg);
|
Logger.Debug?.Print(LogClass.Gpu, msg);
|
||||||
}
|
}
|
||||||
@@ -551,46 +541,59 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return new CommandBufferPool(api, device, queue, queueLock, queueFamilyIndex);
|
return new CommandBufferPool(api, device, queue, queueLock, queueFamilyIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal unsafe static void CreateDebugCallbacks(
|
internal unsafe static void CreateDebugMessenger(
|
||||||
Vk api,
|
Vk api,
|
||||||
GraphicsDebugLevel logLevel,
|
GraphicsDebugLevel logLevel,
|
||||||
Instance instance,
|
Instance instance,
|
||||||
out ExtDebugReport debugReport,
|
out ExtDebugUtils debugUtils,
|
||||||
out DebugReportCallbackEXT debugReportCallback)
|
out DebugUtilsMessengerEXT debugUtilsMessenger)
|
||||||
{
|
{
|
||||||
debugReport = default;
|
debugUtils = default;
|
||||||
|
|
||||||
if (logLevel != GraphicsDebugLevel.None)
|
if (logLevel != GraphicsDebugLevel.None)
|
||||||
{
|
{
|
||||||
if (!api.TryGetInstanceExtension(instance, out debugReport))
|
if (!api.TryGetInstanceExtension(instance, out debugUtils))
|
||||||
{
|
{
|
||||||
debugReportCallback = default;
|
debugUtilsMessenger = default;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var flags = logLevel switch
|
var filterLogType = logLevel switch
|
||||||
{
|
{
|
||||||
GraphicsDebugLevel.Error => DebugReportFlagsEXT.DebugReportErrorBitExt,
|
GraphicsDebugLevel.Error => DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeValidationBitExt,
|
||||||
GraphicsDebugLevel.Slowdowns => DebugReportFlagsEXT.DebugReportErrorBitExt | DebugReportFlagsEXT.DebugReportPerformanceWarningBitExt,
|
GraphicsDebugLevel.Slowdowns => DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeValidationBitExt |
|
||||||
GraphicsDebugLevel.All => DebugReportFlagsEXT.DebugReportInformationBitExt |
|
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypePerformanceBitExt,
|
||||||
DebugReportFlagsEXT.DebugReportWarningBitExt |
|
GraphicsDebugLevel.All => DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeGeneralBitExt |
|
||||||
DebugReportFlagsEXT.DebugReportPerformanceWarningBitExt |
|
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeValidationBitExt |
|
||||||
DebugReportFlagsEXT.DebugReportErrorBitExt |
|
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypePerformanceBitExt,
|
||||||
DebugReportFlagsEXT.DebugReportDebugBitExt,
|
|
||||||
_ => throw new ArgumentException($"Invalid log level \"{logLevel}\".")
|
_ => throw new ArgumentException($"Invalid log level \"{logLevel}\".")
|
||||||
};
|
};
|
||||||
var debugReportCallbackCreateInfo = new DebugReportCallbackCreateInfoEXT()
|
|
||||||
|
var filterLogSeverity = logLevel switch
|
||||||
{
|
{
|
||||||
SType = StructureType.DebugReportCallbackCreateInfoExt,
|
GraphicsDebugLevel.Error => DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt,
|
||||||
Flags = flags,
|
GraphicsDebugLevel.Slowdowns => DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt |
|
||||||
PfnCallback = new PfnDebugReportCallbackEXT(DebugReport)
|
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityWarningBitExt,
|
||||||
|
GraphicsDebugLevel.All => DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityInfoBitExt |
|
||||||
|
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityWarningBitExt |
|
||||||
|
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityVerboseBitExt |
|
||||||
|
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt,
|
||||||
|
_ => throw new ArgumentException($"Invalid log level \"{logLevel}\".")
|
||||||
};
|
};
|
||||||
|
|
||||||
debugReport.CreateDebugReportCallback(instance, in debugReportCallbackCreateInfo, null, out debugReportCallback).ThrowOnError();
|
var debugUtilsMessengerCreateInfo = new DebugUtilsMessengerCreateInfoEXT()
|
||||||
|
{
|
||||||
|
SType = StructureType.DebugUtilsMessengerCreateInfoExt,
|
||||||
|
MessageType = filterLogType,
|
||||||
|
MessageSeverity = filterLogSeverity,
|
||||||
|
PfnUserCallback = new PfnDebugUtilsMessengerCallbackEXT(DebugMessenger)
|
||||||
|
};
|
||||||
|
|
||||||
|
debugUtils.CreateDebugUtilsMessenger(instance, in debugUtilsMessengerCreateInfo, null, out debugUtilsMessenger).ThrowOnError();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
debugReportCallback = default;
|
debugUtilsMessenger = default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -33,7 +33,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
internal KhrPushDescriptor PushDescriptorApi { get; private set; }
|
internal KhrPushDescriptor PushDescriptorApi { get; private set; }
|
||||||
internal ExtTransformFeedback TransformFeedbackApi { get; private set; }
|
internal ExtTransformFeedback TransformFeedbackApi { get; private set; }
|
||||||
internal KhrDrawIndirectCount DrawIndirectCountApi { get; private set; }
|
internal KhrDrawIndirectCount DrawIndirectCountApi { get; private set; }
|
||||||
internal ExtDebugReport DebugReportApi { get; private set; }
|
internal ExtDebugUtils DebugUtilsApi { get; private set; }
|
||||||
|
|
||||||
internal uint QueueFamilyIndex { get; private set; }
|
internal uint QueueFamilyIndex { get; private set; }
|
||||||
internal Queue Queue { get; private set; }
|
internal Queue Queue { get; private set; }
|
||||||
@@ -57,7 +57,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private SyncManager _syncManager;
|
private SyncManager _syncManager;
|
||||||
|
|
||||||
private PipelineFull _pipeline;
|
private PipelineFull _pipeline;
|
||||||
private DebugReportCallbackEXT _debugReportCallback;
|
private DebugUtilsMessengerEXT _debugUtilsMessenger;
|
||||||
|
|
||||||
internal HelperShader HelperShader { get; private set; }
|
internal HelperShader HelperShader { get; private set; }
|
||||||
internal PipelineFull PipelineInternal => _pipeline;
|
internal PipelineFull PipelineInternal => _pipeline;
|
||||||
@@ -237,9 +237,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
Api = api;
|
Api = api;
|
||||||
|
|
||||||
_instance = VulkanInitialization.CreateInstance(api, logLevel, _getRequiredExtensions(), out ExtDebugReport debugReport, out _debugReportCallback);
|
_instance = VulkanInitialization.CreateInstance(api, logLevel, _getRequiredExtensions(), out ExtDebugUtils debugUtils, out _debugUtilsMessenger);
|
||||||
|
|
||||||
DebugReportApi = debugReport;
|
DebugUtilsApi = debugUtils;
|
||||||
|
|
||||||
if (api.TryGetInstanceExtension(_instance, out KhrSurface surfaceApi))
|
if (api.TryGetInstanceExtension(_instance, out KhrSurface surfaceApi))
|
||||||
{
|
{
|
||||||
@@ -584,9 +584,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
MemoryAllocator.Dispose();
|
MemoryAllocator.Dispose();
|
||||||
|
|
||||||
if (_debugReportCallback.Handle != 0)
|
if (_debugUtilsMessenger.Handle != 0)
|
||||||
{
|
{
|
||||||
DebugReportApi.DestroyDebugReportCallback(_instance, _debugReportCallback, null);
|
DebugUtilsApi.DestroyDebugUtilsMessenger(_instance, _debugUtilsMessenger, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var shader in Shaders)
|
foreach (var shader in Shaders)
|
||||||
|
@@ -32,6 +32,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
|||||||
private bool _handlesRequestToDisplay = false;
|
private bool _handlesRequestToDisplay = false;
|
||||||
private bool _autoSleepDisabled = false;
|
private bool _autoSleepDisabled = false;
|
||||||
private bool _albumImageTakenNotificationEnabled = false;
|
private bool _albumImageTakenNotificationEnabled = false;
|
||||||
|
private bool _recordVolumeMuted = false;
|
||||||
|
|
||||||
private uint _screenShotImageOrientation = 0;
|
private uint _screenShotImageOrientation = 0;
|
||||||
private uint _idleTimeDetectionExtension = 0;
|
private uint _idleTimeDetectionExtension = 0;
|
||||||
@@ -389,5 +390,18 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
|||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[CommandHipc(130)] // 13.0.0+
|
||||||
|
// SetRecordVolumeMuted(b8)
|
||||||
|
public ResultCode SetRecordVolumeMuted(ServiceCtx context)
|
||||||
|
{
|
||||||
|
bool recordVolumeMuted = context.RequestData.ReadBoolean();
|
||||||
|
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { recordVolumeMuted });
|
||||||
|
|
||||||
|
_recordVolumeMuted = recordVolumeMuted;
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -203,6 +203,18 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Irs
|
|||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[CommandHipc(318)] // 4.0.0+
|
||||||
|
// StopImageProcessorAsync(nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId, pid)
|
||||||
|
public ResultCode StopImageProcessorAsync(ServiceCtx context)
|
||||||
|
{
|
||||||
|
int irCameraHandle = context.RequestData.ReadInt32();
|
||||||
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle });
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
[CommandHipc(319)] // 4.0.0+
|
[CommandHipc(319)] // 4.0.0+
|
||||||
// ActivateIrsensorWithFunctionLevel(nn::applet::AppletResourceUserId, nn::irsensor::PackedFunctionLevel, pid)
|
// ActivateIrsensorWithFunctionLevel(nn::applet::AppletResourceUserId, nn::irsensor::PackedFunctionLevel, pid)
|
||||||
public ResultCode ActivateIrsensorWithFunctionLevel(ServiceCtx context)
|
public ResultCode ActivateIrsensorWithFunctionLevel(ServiceCtx context)
|
||||||
|
@@ -26,7 +26,7 @@
|
|||||||
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
|
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.15.0" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.25.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Due to Concentus. -->
|
<!-- Due to Concentus. -->
|
||||||
|
199
Ryujinx.Memory/Tracking/BitMap.cs
Normal file
199
Ryujinx.Memory/Tracking/BitMap.cs
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Memory.Tracking
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A bitmap that can check or set large ranges of true/false values at once.
|
||||||
|
/// </summary>
|
||||||
|
struct BitMap
|
||||||
|
{
|
||||||
|
public const int IntSize = 64;
|
||||||
|
|
||||||
|
private const int IntShift = 6;
|
||||||
|
private const int IntMask = IntSize - 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Masks representing the bitmap. Least significant bit first, 64-bits per mask.
|
||||||
|
/// </summary>
|
||||||
|
public readonly long[] Masks;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new bitmap.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="count">The number of bits to reserve</param>
|
||||||
|
public BitMap(int count)
|
||||||
|
{
|
||||||
|
Masks = new long[(count + IntMask) / IntSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if any bit in the bitmap is set.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if any bits are set, false otherwise</returns>
|
||||||
|
public bool AnySet()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Masks.Length; i++)
|
||||||
|
{
|
||||||
|
if (Masks[i] != 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if a bit in the bitmap is set.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bit">The bit index to check</param>
|
||||||
|
/// <returns>True if the bit is set, false otherwise</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool IsSet(int bit)
|
||||||
|
{
|
||||||
|
int wordIndex = bit >> IntShift;
|
||||||
|
int wordBit = bit & IntMask;
|
||||||
|
|
||||||
|
long wordMask = 1L << wordBit;
|
||||||
|
|
||||||
|
return (Masks[wordIndex] & wordMask) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if any bit in a range of bits in the bitmap are set. (inclusive)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="start">The first bit index to check</param>
|
||||||
|
/// <param name="end">The last bit index to check</param>
|
||||||
|
/// <returns>True if a bit is set, false otherwise</returns>
|
||||||
|
public bool IsSet(int start, int end)
|
||||||
|
{
|
||||||
|
if (start == end)
|
||||||
|
{
|
||||||
|
return IsSet(start);
|
||||||
|
}
|
||||||
|
|
||||||
|
int startIndex = start >> IntShift;
|
||||||
|
int startBit = start & IntMask;
|
||||||
|
long startMask = -1L << startBit;
|
||||||
|
|
||||||
|
int endIndex = end >> IntShift;
|
||||||
|
int endBit = end & IntMask;
|
||||||
|
long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
|
||||||
|
|
||||||
|
if (startIndex == endIndex)
|
||||||
|
{
|
||||||
|
return (Masks[startIndex] & startMask & endMask) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Masks[startIndex] & startMask) != 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = startIndex + 1; i < endIndex; i++)
|
||||||
|
{
|
||||||
|
if (Masks[i] != 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Masks[endIndex] & endMask) != 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set a bit at a specific index to 1.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bit">The bit index to set</param>
|
||||||
|
/// <returns>True if the bit is set, false if it was already set</returns>
|
||||||
|
public bool Set(int bit)
|
||||||
|
{
|
||||||
|
int wordIndex = bit >> IntShift;
|
||||||
|
int wordBit = bit & IntMask;
|
||||||
|
|
||||||
|
long wordMask = 1L << wordBit;
|
||||||
|
|
||||||
|
if ((Masks[wordIndex] & wordMask) != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Masks[wordIndex] |= wordMask;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set a range of bits in the bitmap to 1.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="start">The first bit index to set</param>
|
||||||
|
/// <param name="end">The last bit index to set</param>
|
||||||
|
public void SetRange(int start, int end)
|
||||||
|
{
|
||||||
|
if (start == end)
|
||||||
|
{
|
||||||
|
Set(start);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int startIndex = start >> IntShift;
|
||||||
|
int startBit = start & IntMask;
|
||||||
|
long startMask = -1L << startBit;
|
||||||
|
|
||||||
|
int endIndex = end >> IntShift;
|
||||||
|
int endBit = end & IntMask;
|
||||||
|
long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
|
||||||
|
|
||||||
|
if (startIndex == endIndex)
|
||||||
|
{
|
||||||
|
Masks[startIndex] |= startMask & endMask;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Masks[startIndex] |= startMask;
|
||||||
|
|
||||||
|
for (int i = startIndex + 1; i < endIndex; i++)
|
||||||
|
{
|
||||||
|
Masks[i] |= -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Masks[endIndex] |= endMask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear a bit at a specific index to 0.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bit">The bit index to clear</param>
|
||||||
|
/// <returns>True if the bit was set, false if it was not</returns>
|
||||||
|
public bool Clear(int bit)
|
||||||
|
{
|
||||||
|
int wordIndex = bit >> IntShift;
|
||||||
|
int wordBit = bit & IntMask;
|
||||||
|
|
||||||
|
long wordMask = 1L << wordBit;
|
||||||
|
|
||||||
|
bool wasSet = (Masks[wordIndex] & wordMask) != 0;
|
||||||
|
|
||||||
|
Masks[wordIndex] &= ~wordMask;
|
||||||
|
|
||||||
|
return wasSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear the bitmap entirely, setting all bits to 0.
|
||||||
|
/// </summary>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Masks.Length; i++)
|
||||||
|
{
|
||||||
|
Masks[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
161
Ryujinx.Memory/Tracking/ConcurrentBitmap.cs
Normal file
161
Ryujinx.Memory/Tracking/ConcurrentBitmap.cs
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Ryujinx.Memory.Tracking
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A bitmap that can be safely modified from multiple threads.
|
||||||
|
/// </summary>
|
||||||
|
internal class ConcurrentBitmap
|
||||||
|
{
|
||||||
|
public const int IntSize = 64;
|
||||||
|
|
||||||
|
public const int IntShift = 6;
|
||||||
|
public const int IntMask = IntSize - 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Masks representing the bitmap. Least significant bit first, 64-bits per mask.
|
||||||
|
/// </summary>
|
||||||
|
public readonly long[] Masks;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new multithreaded bitmap.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="count">The number of bits to reserve</param>
|
||||||
|
/// <param name="set">Whether the bits should be initially set or not</param>
|
||||||
|
public ConcurrentBitmap(int count, bool set)
|
||||||
|
{
|
||||||
|
Masks = new long[(count + IntMask) / IntSize];
|
||||||
|
|
||||||
|
if (set)
|
||||||
|
{
|
||||||
|
Array.Fill(Masks, -1L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if any bit in the bitmap is set.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if any bits are set, false otherwise</returns>
|
||||||
|
public bool AnySet()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Masks.Length; i++)
|
||||||
|
{
|
||||||
|
if (Volatile.Read(ref Masks[i]) != 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if a bit in the bitmap is set.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bit">The bit index to check</param>
|
||||||
|
/// <returns>True if the bit is set, false otherwise</returns>
|
||||||
|
public bool IsSet(int bit)
|
||||||
|
{
|
||||||
|
int wordIndex = bit >> IntShift;
|
||||||
|
int wordBit = bit & IntMask;
|
||||||
|
|
||||||
|
long wordMask = 1L << wordBit;
|
||||||
|
|
||||||
|
return (Volatile.Read(ref Masks[wordIndex]) & wordMask) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if any bit in a range of bits in the bitmap are set. (inclusive)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="start">The first bit index to check</param>
|
||||||
|
/// <param name="end">The last bit index to check</param>
|
||||||
|
/// <returns>True if a bit is set, false otherwise</returns>
|
||||||
|
public bool IsSet(int start, int end)
|
||||||
|
{
|
||||||
|
if (start == end)
|
||||||
|
{
|
||||||
|
return IsSet(start);
|
||||||
|
}
|
||||||
|
|
||||||
|
int startIndex = start >> IntShift;
|
||||||
|
int startBit = start & IntMask;
|
||||||
|
long startMask = -1L << startBit;
|
||||||
|
|
||||||
|
int endIndex = end >> IntShift;
|
||||||
|
int endBit = end & IntMask;
|
||||||
|
long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
|
||||||
|
|
||||||
|
long startValue = Volatile.Read(ref Masks[startIndex]);
|
||||||
|
|
||||||
|
if (startIndex == endIndex)
|
||||||
|
{
|
||||||
|
return (startValue & startMask & endMask) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((startValue & startMask) != 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = startIndex + 1; i < endIndex; i++)
|
||||||
|
{
|
||||||
|
if (Volatile.Read(ref Masks[i]) != 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long endValue = Volatile.Read(ref Masks[endIndex]);
|
||||||
|
|
||||||
|
if ((endValue & endMask) != 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set a bit at a specific index to either true or false.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bit">The bit index to set</param>
|
||||||
|
/// <param name="value">Whether the bit should be set or not</param>
|
||||||
|
public void Set(int bit, bool value)
|
||||||
|
{
|
||||||
|
int wordIndex = bit >> IntShift;
|
||||||
|
int wordBit = bit & IntMask;
|
||||||
|
|
||||||
|
long wordMask = 1L << wordBit;
|
||||||
|
|
||||||
|
long existing;
|
||||||
|
long newValue;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
existing = Volatile.Read(ref Masks[wordIndex]);
|
||||||
|
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
newValue = existing | wordMask;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newValue = existing & ~wordMask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (Interlocked.CompareExchange(ref Masks[wordIndex], newValue, existing) != existing);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear the bitmap entirely, setting all bits to 0.
|
||||||
|
/// </summary>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Masks.Length; i++)
|
||||||
|
{
|
||||||
|
Volatile.Write(ref Masks[i], 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -176,6 +176,26 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">CPU virtual address of the region</param>
|
||||||
|
/// <param name="size">Size of the region</param>
|
||||||
|
/// <param name="bitmap">The bitmap owning the dirty flag for this handle</param>
|
||||||
|
/// <param name="bit">The bit of this handle within the dirty flag</param>
|
||||||
|
/// <returns>The memory tracking handle</returns>
|
||||||
|
internal RegionHandle BeginTrackingBitmap(ulong address, ulong size, ConcurrentBitmap bitmap, int bit)
|
||||||
|
{
|
||||||
|
(address, size) = PageAlign(address, size);
|
||||||
|
|
||||||
|
lock (TrackingLock)
|
||||||
|
{
|
||||||
|
RegionHandle handle = new RegionHandle(this, address, size, bitmap, bit, _memoryManager.IsRangeMapped(address, size));
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Signal that a virtual memory event happened at the given location (one byte).
|
/// Signal that a virtual memory event happened at the given location (one byte).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@@ -1,11 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx.Memory.Tracking
|
namespace Ryujinx.Memory.Tracking
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A region handle that tracks a large region using many smaller handles, to provide
|
/// A region handle that tracks a large region using many smaller handles, to provide
|
||||||
/// granular tracking that can be used to track partial updates.
|
/// granular tracking that can be used to track partial updates. Backed by a bitmap
|
||||||
|
/// to improve performance when scanning large regions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class MultiRegionHandle : IMultiRegionHandle
|
public class MultiRegionHandle : IMultiRegionHandle
|
||||||
{
|
{
|
||||||
@@ -17,6 +21,12 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
private readonly ulong Granularity;
|
private readonly ulong Granularity;
|
||||||
private readonly ulong Size;
|
private readonly ulong Size;
|
||||||
|
|
||||||
|
private ConcurrentBitmap _dirtyBitmap;
|
||||||
|
|
||||||
|
private int _sequenceNumber;
|
||||||
|
private BitMap _sequenceNumberBitmap;
|
||||||
|
private int _uncheckedHandles;
|
||||||
|
|
||||||
public bool Dirty { get; private set; } = true;
|
public bool Dirty { get; private set; } = true;
|
||||||
|
|
||||||
internal MultiRegionHandle(MemoryTracking tracking, ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity)
|
internal MultiRegionHandle(MemoryTracking tracking, ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity)
|
||||||
@@ -24,6 +34,9 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
_handles = new RegionHandle[size / granularity];
|
_handles = new RegionHandle[size / granularity];
|
||||||
Granularity = granularity;
|
Granularity = granularity;
|
||||||
|
|
||||||
|
_dirtyBitmap = new ConcurrentBitmap(_handles.Length, true);
|
||||||
|
_sequenceNumberBitmap = new BitMap(_handles.Length);
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
if (handles != null)
|
if (handles != null)
|
||||||
@@ -40,15 +53,20 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
// Fill any gap left before this handle.
|
// Fill any gap left before this handle.
|
||||||
while (i < startIndex)
|
while (i < startIndex)
|
||||||
{
|
{
|
||||||
RegionHandle fillHandle = tracking.BeginTracking(address + (ulong)i * granularity, granularity);
|
RegionHandle fillHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i);
|
||||||
fillHandle.Parent = this;
|
fillHandle.Parent = this;
|
||||||
_handles[i++] = fillHandle;
|
_handles[i++] = fillHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handle.Size == granularity)
|
lock (tracking.TrackingLock)
|
||||||
|
{
|
||||||
|
if (handle is RegionHandle bitHandle && handle.Size == granularity)
|
||||||
{
|
{
|
||||||
handle.Parent = this;
|
handle.Parent = this;
|
||||||
_handles[i++] = handle;
|
|
||||||
|
bitHandle.ReplaceBitmap(_dirtyBitmap, i);
|
||||||
|
|
||||||
|
_handles[i++] = bitHandle;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -56,7 +74,7 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
|
|
||||||
while (i < endIndex)
|
while (i < endIndex)
|
||||||
{
|
{
|
||||||
RegionHandle splitHandle = tracking.BeginTracking(address + (ulong)i * granularity, granularity);
|
RegionHandle splitHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i);
|
||||||
splitHandle.Parent = this;
|
splitHandle.Parent = this;
|
||||||
|
|
||||||
splitHandle.Reprotect(handle.Dirty);
|
splitHandle.Reprotect(handle.Dirty);
|
||||||
@@ -74,19 +92,32 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fill any remaining space with new handles.
|
// Fill any remaining space with new handles.
|
||||||
while (i < _handles.Length)
|
while (i < _handles.Length)
|
||||||
{
|
{
|
||||||
RegionHandle handle = tracking.BeginTracking(address + (ulong)i * granularity, granularity);
|
RegionHandle handle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i);
|
||||||
handle.Parent = this;
|
handle.Parent = this;
|
||||||
_handles[i++] = handle;
|
_handles[i++] = handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_uncheckedHandles = _handles.Length;
|
||||||
|
|
||||||
Address = address;
|
Address = address;
|
||||||
Size = size;
|
Size = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SignalWrite()
|
||||||
|
{
|
||||||
|
Dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<RegionHandle> GetHandles()
|
||||||
|
{
|
||||||
|
return _handles;
|
||||||
|
}
|
||||||
|
|
||||||
public void ForceDirty(ulong address, ulong size)
|
public void ForceDirty(ulong address, ulong size)
|
||||||
{
|
{
|
||||||
Dirty = true;
|
Dirty = true;
|
||||||
@@ -96,21 +127,15 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
|
|
||||||
for (int i = startHandle; i <= lastHandle; i++)
|
for (int i = startHandle; i <= lastHandle; i++)
|
||||||
{
|
{
|
||||||
_handles[i].SequenceNumber--;
|
if (_sequenceNumberBitmap.Clear(i))
|
||||||
|
{
|
||||||
|
_uncheckedHandles++;
|
||||||
|
}
|
||||||
|
|
||||||
_handles[i].ForceDirty();
|
_handles[i].ForceDirty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<RegionHandle> GetHandles()
|
|
||||||
{
|
|
||||||
return _handles;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SignalWrite()
|
|
||||||
{
|
|
||||||
Dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void QueryModified(Action<ulong, ulong> modifiedAction)
|
public void QueryModified(Action<ulong, ulong> modifiedAction)
|
||||||
{
|
{
|
||||||
if (!Dirty)
|
if (!Dirty)
|
||||||
@@ -123,33 +148,95 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
QueryModified(Address, Size, modifiedAction);
|
QueryModified(Address, Size, modifiedAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void ParseDirtyBits(long dirtyBits, ref int baseBit, ref int prevHandle, ref ulong rgStart, ref ulong rgSize, Action<ulong, ulong> modifiedAction)
|
||||||
{
|
{
|
||||||
int startHandle = (int)((address - Address) / Granularity);
|
while (dirtyBits != 0)
|
||||||
int lastHandle = (int)((address + (size - 1) - Address) / Granularity);
|
|
||||||
|
|
||||||
ulong rgStart = _handles[startHandle].Address;
|
|
||||||
ulong rgSize = 0;
|
|
||||||
|
|
||||||
for (int i = startHandle; i <= lastHandle; i++)
|
|
||||||
{
|
{
|
||||||
RegionHandle handle = _handles[i];
|
int bit = BitOperations.TrailingZeroCount(dirtyBits);
|
||||||
|
|
||||||
|
dirtyBits &= ~(1L << bit);
|
||||||
|
|
||||||
|
int handleIndex = baseBit + bit;
|
||||||
|
|
||||||
|
RegionHandle handle = _handles[handleIndex];
|
||||||
|
|
||||||
|
if (handleIndex != prevHandle + 1)
|
||||||
|
{
|
||||||
|
// Submit handles scanned until the gap as dirty
|
||||||
|
if (rgSize != 0)
|
||||||
|
{
|
||||||
|
modifiedAction(rgStart, rgSize);
|
||||||
|
rgSize = 0;
|
||||||
|
}
|
||||||
|
rgStart = handle.Address;
|
||||||
|
}
|
||||||
|
|
||||||
if (handle.Dirty)
|
if (handle.Dirty)
|
||||||
{
|
{
|
||||||
rgSize += handle.Size;
|
rgSize += handle.Size;
|
||||||
handle.Reprotect();
|
handle.Reprotect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prevHandle = handleIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
baseBit += ConcurrentBitmap.IntSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction)
|
||||||
|
{
|
||||||
|
int startHandle = (int)((address - Address) / Granularity);
|
||||||
|
int lastHandle = (int)((address + (size - 1) - Address) / Granularity);
|
||||||
|
|
||||||
|
ulong rgStart = _handles[startHandle].Address;
|
||||||
|
|
||||||
|
if (startHandle == lastHandle)
|
||||||
|
{
|
||||||
|
RegionHandle handle = _handles[startHandle];
|
||||||
|
|
||||||
|
if (handle.Dirty)
|
||||||
|
{
|
||||||
|
handle.Reprotect();
|
||||||
|
modifiedAction(rgStart, handle.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong rgSize = 0;
|
||||||
|
|
||||||
|
long[] masks = _dirtyBitmap.Masks;
|
||||||
|
|
||||||
|
int startIndex = startHandle >> ConcurrentBitmap.IntShift;
|
||||||
|
int startBit = startHandle & ConcurrentBitmap.IntMask;
|
||||||
|
long startMask = -1L << startBit;
|
||||||
|
|
||||||
|
int endIndex = lastHandle >> ConcurrentBitmap.IntShift;
|
||||||
|
int endBit = lastHandle & ConcurrentBitmap.IntMask;
|
||||||
|
long endMask = (long)(ulong.MaxValue >> (ConcurrentBitmap.IntMask - endBit));
|
||||||
|
|
||||||
|
long startValue = Volatile.Read(ref masks[startIndex]);
|
||||||
|
|
||||||
|
int baseBit = startIndex << ConcurrentBitmap.IntShift;
|
||||||
|
int prevHandle = startHandle - 1;
|
||||||
|
|
||||||
|
if (startIndex == endIndex)
|
||||||
|
{
|
||||||
|
ParseDirtyBits(startValue & startMask & endMask, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Submit the region scanned so far as dirty
|
ParseDirtyBits(startValue & startMask, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
|
||||||
if (rgSize != 0)
|
|
||||||
|
for (int i = startIndex + 1; i < endIndex; i++)
|
||||||
{
|
{
|
||||||
modifiedAction(rgStart, rgSize);
|
ParseDirtyBits(Volatile.Read(ref masks[i]), ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
|
||||||
rgSize = 0;
|
|
||||||
}
|
|
||||||
rgStart = handle.EndAddress;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long endValue = Volatile.Read(ref masks[endIndex]);
|
||||||
|
|
||||||
|
ParseDirtyBits(endValue & endMask, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rgSize != 0)
|
if (rgSize != 0)
|
||||||
@@ -158,35 +245,120 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void ParseDirtyBits(long dirtyBits, long mask, int index, long[] seqMasks, ref int baseBit, ref int prevHandle, ref ulong rgStart, ref ulong rgSize, Action<ulong, ulong> modifiedAction)
|
||||||
|
{
|
||||||
|
long seqMask = mask & ~seqMasks[index];
|
||||||
|
dirtyBits &= seqMask;
|
||||||
|
|
||||||
|
while (dirtyBits != 0)
|
||||||
|
{
|
||||||
|
int bit = BitOperations.TrailingZeroCount(dirtyBits);
|
||||||
|
|
||||||
|
dirtyBits &= ~(1L << bit);
|
||||||
|
|
||||||
|
int handleIndex = baseBit + bit;
|
||||||
|
|
||||||
|
RegionHandle handle = _handles[handleIndex];
|
||||||
|
|
||||||
|
if (handleIndex != prevHandle + 1)
|
||||||
|
{
|
||||||
|
// Submit handles scanned until the gap as dirty
|
||||||
|
if (rgSize != 0)
|
||||||
|
{
|
||||||
|
modifiedAction(rgStart, rgSize);
|
||||||
|
rgSize = 0;
|
||||||
|
}
|
||||||
|
rgStart = handle.Address;
|
||||||
|
}
|
||||||
|
|
||||||
|
rgSize += handle.Size;
|
||||||
|
handle.Reprotect();
|
||||||
|
|
||||||
|
prevHandle = handleIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
seqMasks[index] |= mask;
|
||||||
|
_uncheckedHandles -= BitOperations.PopCount((ulong)seqMask);
|
||||||
|
|
||||||
|
baseBit += ConcurrentBitmap.IntSize;
|
||||||
|
}
|
||||||
|
|
||||||
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction, int sequenceNumber)
|
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction, int sequenceNumber)
|
||||||
{
|
{
|
||||||
int startHandle = (int)((address - Address) / Granularity);
|
int startHandle = (int)((address - Address) / Granularity);
|
||||||
int lastHandle = (int)((address + (size - 1) - Address) / Granularity);
|
int lastHandle = (int)((address + (size - 1) - Address) / Granularity);
|
||||||
|
|
||||||
ulong rgStart = _handles[startHandle].Address;
|
ulong rgStart = Address + (ulong)startHandle * Granularity;
|
||||||
|
|
||||||
|
if (sequenceNumber != _sequenceNumber)
|
||||||
|
{
|
||||||
|
if (_uncheckedHandles != _handles.Length)
|
||||||
|
{
|
||||||
|
_sequenceNumberBitmap.Clear();
|
||||||
|
_uncheckedHandles = _handles.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
_sequenceNumber = sequenceNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startHandle == lastHandle)
|
||||||
|
{
|
||||||
|
var handle = _handles[startHandle];
|
||||||
|
if (_sequenceNumberBitmap.Set(startHandle))
|
||||||
|
{
|
||||||
|
_uncheckedHandles--;
|
||||||
|
|
||||||
|
if (handle.DirtyOrVolatile())
|
||||||
|
{
|
||||||
|
handle.Reprotect();
|
||||||
|
|
||||||
|
modifiedAction(rgStart, handle.Size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_uncheckedHandles == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ulong rgSize = 0;
|
ulong rgSize = 0;
|
||||||
|
|
||||||
for (int i = startHandle; i <= lastHandle; i++)
|
long[] seqMasks = _sequenceNumberBitmap.Masks;
|
||||||
{
|
long[] masks = _dirtyBitmap.Masks;
|
||||||
RegionHandle handle = _handles[i];
|
|
||||||
|
|
||||||
if (sequenceNumber != handle.SequenceNumber && handle.DirtyOrVolatile())
|
int startIndex = startHandle >> ConcurrentBitmap.IntShift;
|
||||||
|
int startBit = startHandle & ConcurrentBitmap.IntMask;
|
||||||
|
long startMask = -1L << startBit;
|
||||||
|
|
||||||
|
int endIndex = lastHandle >> ConcurrentBitmap.IntShift;
|
||||||
|
int endBit = lastHandle & ConcurrentBitmap.IntMask;
|
||||||
|
long endMask = (long)(ulong.MaxValue >> (ConcurrentBitmap.IntMask - endBit));
|
||||||
|
|
||||||
|
long startValue = Volatile.Read(ref masks[startIndex]);
|
||||||
|
|
||||||
|
int baseBit = startIndex << ConcurrentBitmap.IntShift;
|
||||||
|
int prevHandle = startHandle - 1;
|
||||||
|
|
||||||
|
if (startIndex == endIndex)
|
||||||
{
|
{
|
||||||
rgSize += handle.Size;
|
ParseDirtyBits(startValue, startMask & endMask, startIndex, seqMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
|
||||||
handle.Reprotect();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Submit the region scanned so far as dirty
|
ParseDirtyBits(startValue, startMask, startIndex, seqMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
|
||||||
if (rgSize != 0)
|
|
||||||
|
for (int i = startIndex + 1; i < endIndex; i++)
|
||||||
{
|
{
|
||||||
modifiedAction(rgStart, rgSize);
|
ParseDirtyBits(Volatile.Read(ref masks[i]), -1L, i, seqMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
|
||||||
rgSize = 0;
|
|
||||||
}
|
|
||||||
rgStart = handle.EndAddress;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handle.SequenceNumber = sequenceNumber;
|
long endValue = Volatile.Read(ref masks[endIndex]);
|
||||||
|
|
||||||
|
ParseDirtyBits(endValue, endMask, endIndex, seqMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rgSize != 0)
|
if (rgSize != 0)
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
using Ryujinx.Memory.Range;
|
using System;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -10,7 +9,7 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
/// A tracking handle for a given region of virtual memory. The Dirty flag is updated whenever any changes are made,
|
/// A tracking handle for a given region of virtual memory. The Dirty flag is updated whenever any changes are made,
|
||||||
/// and an action can be performed when the region is read to or written from.
|
/// and an action can be performed when the region is read to or written from.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RegionHandle : IRegionHandle, IRange
|
public class RegionHandle : IRegionHandle
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If more than this number of checks have been performed on a dirty flag since its last reprotect,
|
/// If more than this number of checks have been performed on a dirty flag since its last reprotect,
|
||||||
@@ -23,7 +22,20 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static int VolatileThreshold = 5;
|
private static int VolatileThreshold = 5;
|
||||||
|
|
||||||
public bool Dirty { get; private set; }
|
public bool Dirty
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Bitmap.IsSet(DirtyBit);
|
||||||
|
}
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
Bitmap.Set(DirtyBit, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal int SequenceNumber { get; set; }
|
||||||
|
|
||||||
public bool Unmapped { get; private set; }
|
public bool Unmapped { get; private set; }
|
||||||
|
|
||||||
public ulong Address { get; }
|
public ulong Address { get; }
|
||||||
@@ -31,7 +43,6 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
public ulong EndAddress { get; }
|
public ulong EndAddress { get; }
|
||||||
|
|
||||||
internal IMultiRegionHandle Parent { get; set; }
|
internal IMultiRegionHandle Parent { get; set; }
|
||||||
internal int SequenceNumber { get; set; }
|
|
||||||
|
|
||||||
private event Action _onDirty;
|
private event Action _onDirty;
|
||||||
|
|
||||||
@@ -68,17 +79,26 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
|
|
||||||
internal RegionSignal PreAction => _preAction;
|
internal RegionSignal PreAction => _preAction;
|
||||||
|
|
||||||
|
internal ConcurrentBitmap Bitmap;
|
||||||
|
internal int DirtyBit;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new region handle. The handle is registered with the given tracking object,
|
/// Create a new bitmap backed region handle. The handle is registered with the given tracking object,
|
||||||
/// and will be notified of any changes to the specified region.
|
/// and will be notified of any changes to the specified region.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="tracking">Tracking object for the target memory block</param>
|
/// <param name="tracking">Tracking object for the target memory block</param>
|
||||||
/// <param name="address">Virtual address of the region to track</param>
|
/// <param name="address">Virtual address of the region to track</param>
|
||||||
/// <param name="size">Size of the region to track</param>
|
/// <param name="size">Size of the region to track</param>
|
||||||
|
/// <param name="bitmap">The bitmap the dirty flag for this handle is stored in</param>
|
||||||
|
/// <param name="bit">The bit index representing the dirty flag for this handle</param>
|
||||||
/// <param name="mapped">True if the region handle starts mapped</param>
|
/// <param name="mapped">True if the region handle starts mapped</param>
|
||||||
internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, bool mapped = true)
|
internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, ConcurrentBitmap bitmap, int bit, bool mapped = true)
|
||||||
{
|
{
|
||||||
|
Bitmap = bitmap;
|
||||||
|
DirtyBit = bit;
|
||||||
|
|
||||||
Dirty = mapped;
|
Dirty = mapped;
|
||||||
|
|
||||||
Unmapped = !mapped;
|
Unmapped = !mapped;
|
||||||
Address = address;
|
Address = address;
|
||||||
Size = size;
|
Size = size;
|
||||||
@@ -92,6 +112,54 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new region handle. The handle is registered with the given tracking object,
|
||||||
|
/// and will be notified of any changes to the specified region.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tracking">Tracking object for the target memory block</param>
|
||||||
|
/// <param name="address">Virtual address of the region to track</param>
|
||||||
|
/// <param name="size">Size of the region to track</param>
|
||||||
|
/// <param name="mapped">True if the region handle starts mapped</param>
|
||||||
|
internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, bool mapped = true)
|
||||||
|
{
|
||||||
|
Bitmap = new ConcurrentBitmap(1, mapped);
|
||||||
|
|
||||||
|
Unmapped = !mapped;
|
||||||
|
Address = address;
|
||||||
|
Size = size;
|
||||||
|
EndAddress = address + size;
|
||||||
|
|
||||||
|
_tracking = tracking;
|
||||||
|
_regions = tracking.GetVirtualRegionsForHandle(address, size);
|
||||||
|
foreach (var region in _regions)
|
||||||
|
{
|
||||||
|
region.Handles.Add(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Replace the bitmap and bit index used to track dirty state.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The tracking lock should be held when this is called, to ensure neither bitmap is modified.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="bitmap">The bitmap the dirty flag for this handle is stored in</param>
|
||||||
|
/// <param name="bit">The bit index representing the dirty flag for this handle</param>
|
||||||
|
internal void ReplaceBitmap(ConcurrentBitmap bitmap, int bit)
|
||||||
|
{
|
||||||
|
// Assumes the tracking lock is held, so nothing else can signal right now.
|
||||||
|
|
||||||
|
var oldBitmap = Bitmap;
|
||||||
|
var oldBit = DirtyBit;
|
||||||
|
|
||||||
|
bitmap.Set(bit, Dirty);
|
||||||
|
|
||||||
|
Bitmap = bitmap;
|
||||||
|
DirtyBit = bit;
|
||||||
|
|
||||||
|
Dirty |= oldBitmap.IsSet(oldBit);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clear the volatile state of this handle.
|
/// Clear the volatile state of this handle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -108,7 +176,7 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
public bool DirtyOrVolatile()
|
public bool DirtyOrVolatile()
|
||||||
{
|
{
|
||||||
_checkCount++;
|
_checkCount++;
|
||||||
return Dirty || _volatile;
|
return _volatile || Dirty;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -195,6 +263,8 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void ForceDirty()
|
public void ForceDirty()
|
||||||
{
|
{
|
||||||
|
_checkCount++;
|
||||||
|
|
||||||
Dirty = true;
|
Dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,9 +3,10 @@ using System;
|
|||||||
|
|
||||||
namespace Ryujinx.Tests.Unicorn
|
namespace Ryujinx.Tests.Unicorn
|
||||||
{
|
{
|
||||||
public class UnicornAArch32
|
public class UnicornAArch32 : IDisposable
|
||||||
{
|
{
|
||||||
internal readonly IntPtr uc;
|
internal readonly IntPtr uc;
|
||||||
|
private bool _isDisposed = false;
|
||||||
|
|
||||||
public IndexedProperty<int, uint> R
|
public IndexedProperty<int, uint> R
|
||||||
{
|
{
|
||||||
@@ -106,8 +107,23 @@ namespace Ryujinx.Tests.Unicorn
|
|||||||
}
|
}
|
||||||
|
|
||||||
~UnicornAArch32()
|
~UnicornAArch32()
|
||||||
|
{
|
||||||
|
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));
|
Interface.Checked(Native.Interface.uc_close(uc));
|
||||||
|
_isDisposed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RunForCount(ulong count)
|
public void RunForCount(ulong count)
|
||||||
|
@@ -3,9 +3,10 @@ using System;
|
|||||||
|
|
||||||
namespace Ryujinx.Tests.Unicorn
|
namespace Ryujinx.Tests.Unicorn
|
||||||
{
|
{
|
||||||
public class UnicornAArch64
|
public class UnicornAArch64 : IDisposable
|
||||||
{
|
{
|
||||||
internal readonly IntPtr uc;
|
internal readonly IntPtr uc;
|
||||||
|
private bool _isDisposed = false;
|
||||||
|
|
||||||
public IndexedProperty<int, ulong> X
|
public IndexedProperty<int, ulong> X
|
||||||
{
|
{
|
||||||
@@ -95,8 +96,23 @@ namespace Ryujinx.Tests.Unicorn
|
|||||||
}
|
}
|
||||||
|
|
||||||
~UnicornAArch64()
|
~UnicornAArch64()
|
||||||
|
{
|
||||||
|
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));
|
Interface.Checked(Native.Interface.uc_close(uc));
|
||||||
|
_isDisposed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RunForCount(ulong count)
|
public void RunForCount(ulong count)
|
||||||
|
@@ -80,6 +80,12 @@ namespace Ryujinx.Tests.Cpu
|
|||||||
[TearDown]
|
[TearDown]
|
||||||
public void Teardown()
|
public void Teardown()
|
||||||
{
|
{
|
||||||
|
if (_unicornAvailable)
|
||||||
|
{
|
||||||
|
_unicornEmu.Dispose();
|
||||||
|
_unicornEmu = null;
|
||||||
|
}
|
||||||
|
|
||||||
_memory.DecrementReferenceCount();
|
_memory.DecrementReferenceCount();
|
||||||
_context.Dispose();
|
_context.Dispose();
|
||||||
_ram.Dispose();
|
_ram.Dispose();
|
||||||
|
@@ -76,6 +76,12 @@ namespace Ryujinx.Tests.Cpu
|
|||||||
[TearDown]
|
[TearDown]
|
||||||
public void Teardown()
|
public void Teardown()
|
||||||
{
|
{
|
||||||
|
if (_unicornAvailable)
|
||||||
|
{
|
||||||
|
_unicornEmu.Dispose();
|
||||||
|
_unicornEmu = null;
|
||||||
|
}
|
||||||
|
|
||||||
_memory.DecrementReferenceCount();
|
_memory.DecrementReferenceCount();
|
||||||
_context.Dispose();
|
_context.Dispose();
|
||||||
_ram.Dispose();
|
_ram.Dispose();
|
||||||
|
@@ -23,7 +23,7 @@
|
|||||||
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
||||||
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
||||||
<PackageReference Include="OpenTK.Graphics" Version="4.7.2" />
|
<PackageReference Include="OpenTK.Graphics" Version="4.7.2" />
|
||||||
<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="SharpZipLib" Version="1.3.3" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
Reference in New Issue
Block a user