Compare commits

...

18 Commits

Author SHA1 Message Date
Mary-nyan
7d8e198c33 fix: Support FFmpeg 5.1.x for decoding (#3816)
For some reason FFmpeg 5.1.x reverted part of the changes made in 5.0.x
on AVCodec.

This fix decoding issues with it.
2022-11-02 09:26:50 +01:00
riperiperi
3d98e1361b GPU: Use a bitmap to track buffer modified flags. (#3775)
* Initial implementation

* Some improvements.

* Fix incorrect cast

* Performance improvement and improved correctness

* Add very fast path when all handles are checked.

* Slightly faster

* Add comment

* De-virtualize region handle

All region handles are now bitmap backed.

* Remove non-bitmap tracking

* Remove unused methods

* Add docs, remove unused methods

* Address Feedback

* Rename file
2022-10-29 22:07:37 +00:00
TSRBerry
141cf61ff7 CI: Run git_short_hash inside of bash (#3808) 2022-10-29 19:00:08 +00:00
Wunk
3fe3598d41 Vulkan: Replace VK_EXT_debug_report usage with VK_EXT_debug_utils (#3802)
* Vulkan: Replace `VK_EXT_debug_report` usage with `VK_EXT_debug_utils`

[VK_EXT_debug_report](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_debug_report.html)
has been depreciated for quite some time now in favor of the much more
featureful
[VK_EXT_debug_utils](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_debug_utils.html)
extension.

This PR converts our debug-report-callback into the newer
debug-messenger pattern.

`VK_EXT_debug_utils` adds some additional diagnostic tooling for marking
debug-label scopes for queue-operations, command-buffers, and assigning
name-labels to vulkan objects to aid in debugging(for a later PR).

* Vulkan: Fix `DebugMessenger` severity-flag classification

Extension bits between the two flags, for reference:

https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDebugUtilsMessageSeverityFlagBitsEXT.html

https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDebugReportFlagBitsEXT.html
2022-10-29 14:09:25 -03:00
gdkchan
59cdf310bd SPIR-V: Fix tessellation control shader output types (#3807)
* SPIR-V: Fix tessellation control shader output types

* Shader cache version bump
2022-10-29 13:45:30 -03:00
dependabot[bot]
4e34170a84 nuget: bump System.IdentityModel.Tokens.Jwt from 6.15.0 to 6.25.0 (#3806)
Bumps [System.IdentityModel.Tokens.Jwt](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 6.15.0 to 6.25.0.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/commits)

---
updated-dependencies:
- dependency-name: System.IdentityModel.Tokens.Jwt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-29 10:46:46 +02:00
Luke Warner
d540af5dc0 AppletAE: stub SetRecordVolumeMuted (#3804)
* Update IIrSensorServer.cs

* Update IIrSensorServer.cs

* Apply suggestions from code review

Addressed formatting feedback

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update IIrSensorServer.cs

* Update ISelfController.cs

* Update ISelfController.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2022-10-27 23:15:57 +00:00
Luke Warner
f7c7b66fc0 hid/irs: Stub StopImageProcessorAsync (#3799)
* Update IIrSensorServer.cs

* Update IIrSensorServer.cs

* Apply suggestions from code review

Addressed formatting feedback

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update IIrSensorServer.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2022-10-27 21:36:37 +00:00
gdkchan
28ba55598d Vulkan: Fix indirect buffer barrier (#3798) 2022-10-26 14:53:11 -03:00
riperiperi
9719b6a112 Vulkan: Use dynamic state for blend constants (#3793) 2022-10-25 23:49:23 +00:00
VocalFan
f70236f947 Updated Compatibility info and GPU info (Vulkan, SPIRV, and Texture Recompression) (#3568)
* Updated Compatibility info and GPU info (Vulkan, SPIRV, and Texture Recompression)

* Added Mutant's changes.

Co-authored-by: MutantAura <44103205+MutantAura@users.noreply.github.com>

* Five to four.

* Fixed github's terrible conflict diffs

Co-authored-by: MutantAura <44103205+MutantAura@users.noreply.github.com>
2022-10-24 16:40:39 +00:00
merry
eafadf10c7 Ryujinx.Tests.Unicorn: Implement IDisposable (#3794)
Dispose unicorn when done
2022-10-23 23:51:54 +00:00
Mary-nyan
9b06ee7736 Attempt to fix issues since github-script v6 upgrade 2022-10-23 17:15:15 +02:00
Emmanuel Hansen
baba2c2467 Avalonia: Use overlay dialog for controller applet (#3777)
* use overlay dialog for controller applet

* Update Ryujinx.Ava/Ui/Controls/ContentDialogHelper.cs

Co-authored-by: riperiperi <rhy3756547@hotmail.com>

Co-authored-by: riperiperi <rhy3756547@hotmail.com>
2022-10-23 11:15:45 +02:00
dependabot[bot]
286e5d39b2 nuget: bump SPB from 0.0.4-build24 to 0.0.4-build27 (#3791)
Bumps [SPB](https://github.com/Thog/SPB) from 0.0.4-build24 to 0.0.4-build27.
- [Release notes](https://github.com/Thog/SPB/releases)
- [Commits](https://github.com/Thog/SPB/commits)

---
updated-dependencies:
- dependency-name: SPB
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-22 10:32:12 +02:00
TSRBerry
dc529c1181 ci: Add updates for nuget packages to dependabot (#3786) 2022-10-21 14:23:16 +02:00
TSRBerry
c7cf1cbc35 CI: Update workflows (#3774)
* ci: Update workflows

* gha: Add no-build switch to test action
2022-10-21 10:31:38 +02:00
TSRBerry
d8e487d018 gha: Add dependabot.yml (#3778) 2022-10-21 10:16:28 +02:00
36 changed files with 1044 additions and 222 deletions

24
.github/dependabot.yml vendored Normal file
View 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

View File

@@ -48,21 +48,22 @@ jobs:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
RYUJINX_BASE_VERSION: "1.1.0"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-dotnet@v1
- uses: actions/checkout@v3
- uses: actions/setup-dotnet@v3
with:
dotnet-version: 6.0.x
- name: Ensure NuGet Source
uses: fabriciomurta/ensure-nuget-source@v1
- name: Get git short hash
id: git_short_hash
run: echo "::set-output name=result::$(git rev-parse --short "${{ github.sha }}")"
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
shell: bash
- name: Clear
run: dotnet clean && dotnet nuget locals all --clear
- name: Build
run: dotnet build -c "${{ matrix.configuration }}" /p:Version="${{ env.RYUJINX_BASE_VERSION }}" /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER
- name: Test
run: dotnet test -c "${{ matrix.configuration }}"
run: dotnet test --no-build -c "${{ matrix.configuration }}"
- name: Publish Ryujinx
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish /p:Version="${{ env.RYUJINX_BASE_VERSION }}" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx --self-contained
if: github.event_name == 'pull_request'
@@ -73,19 +74,19 @@ jobs:
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava /p:Version="1.0.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Ava
if: github.event_name == 'pull_request'
- name: Upload Ryujinx artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
path: publish
if: github.event_name == 'pull_request'
- name: Upload Ryujinx.Headless.SDL2 artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
path: publish_sdl2_headless
if: github.event_name == 'pull_request'
- name: Upload Ryujinx.Ava artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
path: publish_ava

View File

@@ -8,7 +8,7 @@ jobs:
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v3
- uses: actions/github-script@v6
with:
script: |
const {owner, repo} = context.repo;
@@ -16,7 +16,7 @@ jobs:
const pull_head_sha = '${{github.event.workflow_run.head_sha}}';
const issue_number = await (async () => {
const pulls = await github.pulls.list({owner, repo});
const pulls = await github.rest.pulls.list({owner, repo});
for await (const {data} of github.paginate.iterator(pulls)) {
for (const pull of data) {
if (pull.head.sha === pull_head_sha) {
@@ -31,7 +31,7 @@ jobs:
return core.error(`No matching pull request found`);
}
const {data: {artifacts}} = await github.actions.listWorkflowRunArtifacts({owner, repo, run_id});
const {data: {artifacts}} = await github.rest.actions.listWorkflowRunArtifacts({owner, repo, run_id});
if (!artifacts.length) {
return core.error(`No artifacts found`);
}
@@ -57,12 +57,12 @@ jobs:
body += hidden_headless_artifacts;
body += hidden_debug_artifacts;
const {data: comments} = await github.issues.listComments({repo, owner, issue_number});
const {data: comments} = await github.rest.issues.listComments({repo, owner, issue_number});
const existing_comment = comments.find((c) => c.user.login === 'github-actions[bot]');
if (existing_comment) {
core.info(`Updating comment ${existing_comment.id}`);
await github.issues.updateComment({repo, owner, comment_id: existing_comment.id, body});
await github.rest.issues.updateComment({repo, owner, comment_id: existing_comment.id, body});
} else {
core.info(`Creating a comment`);
await github.issues.createComment({repo, owner, issue_number, body});
await github.rest.issues.createComment({repo, owner, issue_number, body});
}

View File

@@ -25,8 +25,8 @@ jobs:
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "release-channel-master"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-dotnet@v1
- uses: actions/checkout@v3
- uses: actions/setup-dotnet@v3
with:
dotnet-version: 6.0.x
- name: Ensure NuGet Source
@@ -36,8 +36,8 @@ jobs:
- name: Get version info
id: version_info
run: |
echo "::set-output name=build_version::${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}"
echo "::set-output name=git_short_hash::$(git rev-parse --short "${{ github.sha }}")"
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
shell: bash
- name: Configure for release
run: |

View File

@@ -36,8 +36,8 @@
## 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).
Anyone is free to submit a new game test or update an existing game test entry; simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. Use the search function to see if a game has been tested already!
As of October 2022, Ryujinx has been tested on approximately 3,700 titles; over 3,500 boot past menus and into gameplay, with roughly 3,000 of those being considered playable.
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). Anyone is free to submit a new game test or update an existing game test entry; simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. Use the search function to see if a game has been tested already!
## Usage
@@ -90,7 +90,7 @@ Ryujinx system files are stored in the `Ryujinx` folder. This folder is located
- **GPU**
The GPU emulator emulates the Switch's Maxwell GPU using the OpenGL API (version 4.5 minimum) through a custom build of OpenTK. There are currently four graphics enhancements available to the end user in Ryujinx: disk shader caching, resolution scaling, aspect ratio adjustment and anisotropic filtering. These enhancements can be adjusted or toggled as desired in the GUI.
The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum) or Vulkan APIs through a custom build of OpenTK or Silk.NET respectively. There are currently four graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Aspect Ratio Adjustment, and Anisotropic Filtering. These enhancements can be adjusted or toggled as desired in the GUI.
- **Input**

View File

@@ -34,7 +34,7 @@
<PackageReference Include="Silk.NET.Vulkan" Version="2.10.1" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.10.1" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.10.1" />
<PackageReference Include="SPB" Version="0.0.4-build24" />
<PackageReference Include="SPB" Version="0.0.4-build27" />
<PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
</ItemGroup>

View File

@@ -4,6 +4,7 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Primitives;
using Avalonia.Media;
using Avalonia.Threading;
using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Models;
@@ -27,7 +28,10 @@ namespace Ryujinx.Ava.Ui.Controls
string secondaryButton,
string closeButton,
int iconSymbol,
UserResult primaryButtonResult = UserResult.Ok)
UserResult primaryButtonResult = UserResult.Ok,
ManualResetEvent deferResetEvent = null,
Func<Window, Task> doWhileDeferred = null,
TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null)
{
UserResult result = UserResult.None;
@@ -110,12 +114,19 @@ namespace Ryujinx.Ava.Ui.Controls
contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
{
result = UserResult.No;
contentDialog.PrimaryButtonClick -= deferCloseAction;
});
contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
{
result = UserResult.Cancel;
contentDialog.PrimaryButtonClick -= deferCloseAction;
});
if (deferResetEvent != null)
{
contentDialog.PrimaryButtonClick += deferCloseAction;
}
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
overlay?.Close();
@@ -143,35 +154,20 @@ namespace Ryujinx.Ava.Ui.Controls
Func<Window, Task> doWhileDeferred = null)
{
bool startedDeferring = false;
UserResult result = UserResult.None;
ContentDialog contentDialog = new ContentDialog
{
Title = title,
PrimaryButtonText = primaryButton,
SecondaryButtonText = secondaryButton,
CloseButtonText = closeButton,
Content = CreateDialogTextContent(primaryText, secondaryText, iconSymbol),
PrimaryButtonCommand = MiniCommand.Create(() =>
{
result = primaryButton == LocaleManager.Instance["InputDialogYes"] ? UserResult.Yes : UserResult.Ok;
}),
};
contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
{
contentDialog.PrimaryButtonClick -= DeferClose;
result = UserResult.No;
});
contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
{
contentDialog.PrimaryButtonClick -= DeferClose;
result = UserResult.Cancel;
});
contentDialog.PrimaryButtonClick += DeferClose;
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
return result;
return await ShowContentDialog(
title,
primaryText,
secondaryText,
primaryButton,
secondaryButton,
closeButton,
iconSymbol,
primaryButton == LocaleManager.Instance["InputDialogYes"] ? UserResult.Yes : UserResult.Ok,
deferResetEvent,
doWhileDeferred,
DeferClose);
async void DeferClose(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
@@ -180,7 +176,7 @@ namespace Ryujinx.Ava.Ui.Controls
return;
}
contentDialog.PrimaryButtonClick -= DeferClose;
sender.PrimaryButtonClick -= DeferClose;
startedDeferring = true;
@@ -188,7 +184,7 @@ namespace Ryujinx.Ava.Ui.Controls
result = primaryButton == LocaleManager.Instance["InputDialogYes"] ? UserResult.Yes : UserResult.Ok;
contentDialog.PrimaryButtonClick -= DeferClose;
sender.PrimaryButtonClick -= DeferClose;
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(() =>

View File

@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 3781;
private const uint CodeGenVersion = 3807;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";

View File

@@ -7,7 +7,9 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg
{
unsafe class FFmpegContext : IDisposable
{
private readonly FFCodec.AVCodec_decode _decodeFrame;
private unsafe delegate int AVCodec_decode(AVCodecContext* avctx, void* outdata, int* got_frame_ptr, AVPacket* avpkt);
private readonly AVCodec_decode _decodeFrame;
private static readonly FFmpegApi.av_log_set_callback_callback _logFunc;
private readonly AVCodec* _codec;
private AVPacket* _packet;
@@ -53,17 +55,17 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg
// libavcodec 59.24 changed AvCodec to move its private API and also move the codec function to an union.
if (avCodecMajorVersion > 59 || (avCodecMajorVersion == 59 && avCodecMinorVersion > 24))
{
_decodeFrame = Marshal.GetDelegateForFunctionPointer<FFCodec.AVCodec_decode>(((FFCodec*)_codec)->CodecCallback);
_decodeFrame = Marshal.GetDelegateForFunctionPointer<AVCodec_decode>(((FFCodec<AVCodec>*)_codec)->CodecCallback);
}
// libavcodec 59.x changed AvCodec private API layout.
else if (avCodecMajorVersion == 59)
{
_decodeFrame = Marshal.GetDelegateForFunctionPointer<FFCodec.AVCodec_decode>(((FFCodecLegacy<AVCodec>*)_codec)->Decode);
_decodeFrame = Marshal.GetDelegateForFunctionPointer<AVCodec_decode>(((FFCodecLegacy<AVCodec501>*)_codec)->Decode);
}
// libavcodec 58.x and lower
else
{
_decodeFrame = Marshal.GetDelegateForFunctionPointer<FFCodec.AVCodec_decode>(((FFCodecLegacy<AVCodecLegacy>*)_codec)->Decode);
_decodeFrame = Marshal.GetDelegateForFunctionPointer<AVCodec_decode>(((FFCodecLegacy<AVCodec>*)_codec)->Decode);
}
}

View File

@@ -20,6 +20,7 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
public unsafe IntPtr PrivClass;
public IntPtr Profiles;
public unsafe byte* WrapperName;
public IntPtr ChLayouts;
#pragma warning restore CS0649
}
}

View File

@@ -2,7 +2,7 @@
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
struct AVCodecLegacy
struct AVCodec501
{
#pragma warning disable CS0649
public unsafe byte* Name;
@@ -20,7 +20,6 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
public unsafe IntPtr PrivClass;
public IntPtr Profiles;
public unsafe byte* WrapperName;
public IntPtr ChLayouts;
#pragma warning restore CS0649
}
}

View File

@@ -9,7 +9,7 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
public unsafe IntPtr AvClass;
public int LogLevelOffset;
public int CodecType;
public unsafe AVCodecLegacy* Codec;
public unsafe AVCodec* Codec;
public AVCodecID CodecId;
public uint CodecTag;
public IntPtr PrivData;

View File

@@ -2,12 +2,10 @@
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
struct FFCodec
struct FFCodec<T> where T: struct
{
public unsafe delegate int AVCodec_decode(AVCodecContext* avctx, void* outdata, int* got_frame_ptr, AVPacket* avpkt);
#pragma warning disable CS0649
public AVCodec Base;
public T Base;
public int CapsInternalOrCbType;
public int PrivDataSize;
public IntPtr UpdateThreadContext;

View File

@@ -262,6 +262,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
Instruction ioVariable, elemIndex;
Instruction invocationId = null;
if (Config.Stage == ShaderStage.TessellationControl && isOutAttr)
{
invocationId = Load(TypeS32(), Inputs[AttributeConsts.InvocationId]);
}
bool isUserAttr = attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd;
if (isUserAttr &&
@@ -273,7 +280,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
elemIndex = Constant(TypeU32(), attrInfo.GetInnermostIndex());
var vecIndex = Constant(TypeU32(), (attr - AttributeConsts.UserAttributeBase) >> 4);
if (AttributeInfo.IsArrayAttributeSpirv(Config.Stage, isOutAttr))
bool isArray = AttributeInfo.IsArrayAttributeSpirv(Config.Stage, isOutAttr);
if (invocationId != null && isArray)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, index, vecIndex, elemIndex);
}
else if (invocationId != null)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, vecIndex, elemIndex);
}
else if (isArray)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index, vecIndex, elemIndex);
}
@@ -308,12 +325,29 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if ((type & (AggregateType.Array | AggregateType.Vector)) == 0)
{
return isIndexed ? AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index) : ioVariable;
if (invocationId != null)
{
return isIndexed
? AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, index)
: AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId);
}
else
{
return isIndexed ? AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index) : ioVariable;
}
}
elemIndex = Constant(TypeU32(), attrInfo.GetInnermostIndex());
if (isIndexed)
if (invocationId != null && isIndexed)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, index, elemIndex);
}
else if (invocationId != null)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, elemIndex);
}
else if (isIndexed)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index, elemIndex);
}
@@ -327,12 +361,29 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
var storageClass = isOutAttr ? StorageClass.Output : StorageClass.Input;
Instruction invocationId = null;
if (Config.Stage == ShaderStage.TessellationControl && isOutAttr)
{
invocationId = Load(TypeS32(), Inputs[AttributeConsts.InvocationId]);
}
elemType = AggregateType.FP32;
var ioVariable = isOutAttr ? OutputsArray : InputsArray;
var vecIndex = ShiftRightLogical(TypeS32(), attrIndex, Constant(TypeS32(), 2));
var elemIndex = BitwiseAnd(TypeS32(), attrIndex, Constant(TypeS32(), 3));
if (AttributeInfo.IsArrayAttributeSpirv(Config.Stage, isOutAttr))
bool isArray = AttributeInfo.IsArrayAttributeSpirv(Config.Stage, isOutAttr);
if (invocationId != null && isArray)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, index, vecIndex, elemIndex);
}
else if (invocationId != null)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, vecIndex, elemIndex);
}
else if (isArray)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index, vecIndex, elemIndex);
}

View File

@@ -473,6 +473,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var attrType = context.TypeVector(context.TypeFP32(), (LiteralInteger)4);
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)MaxAttributes));
if (context.Config.Stage == ShaderStage.TessellationControl)
{
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), context.Config.ThreadsPerInputPrimitive));
}
var spvType = context.TypePointer(StorageClass.Output, attrType);
var spvVar = context.Variable(spvType, StorageClass.Output);
@@ -543,6 +548,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
}
if (context.Config.Stage == ShaderStage.TessellationControl && isOutAttr && !perPatch)
{
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), context.Config.ThreadsPerInputPrimitive));
}
var spvType = context.TypePointer(storageClass, attrType);
var spvVar = context.Variable(spvType, storageClass);
@@ -634,6 +644,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)arraySize));
}
if (context.Config.Stage == ShaderStage.TessellationControl && isOutAttr)
{
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), context.Config.ThreadsPerInputPrimitive));
}
var spvType = context.TypePointer(storageClass, attrType);
var spvVar = context.Variable(spvType, storageClass);

View File

@@ -37,7 +37,12 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Config = config;
if (config.GpPassthrough)
if (config.Stage == ShaderStage.TessellationControl)
{
// Required to index outputs.
Info.Inputs.Add(AttributeConsts.InvocationId);
}
else if (config.GpPassthrough)
{
int passthroughAttributes = config.PassthroughAttributes;
while (passthroughAttributes != 0)

View File

@@ -12,13 +12,12 @@ namespace Ryujinx.Graphics.Vulkan
private const int MaxUpdateBufferSize = 0x10000;
public const AccessFlags DefaultAccessFlags =
AccessFlags.AccessIndirectCommandReadBit |
AccessFlags.AccessShaderReadBit |
AccessFlags.AccessShaderWriteBit |
AccessFlags.AccessTransferReadBit |
AccessFlags.AccessTransferWriteBit |
AccessFlags.AccessUniformReadBit |
AccessFlags.AccessShaderReadBit |
AccessFlags.AccessShaderWriteBit;
AccessFlags.AccessUniformReadBit;
private readonly VulkanRenderer _gd;
private readonly Device _device;

View File

@@ -228,10 +228,26 @@ namespace Ryujinx.Graphics.Vulkan
Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
}
public void CommandBufferBarrier()
public unsafe void CommandBufferBarrier()
{
// TODO: More specific barrier?
Barrier();
MemoryBarrier memoryBarrier = new MemoryBarrier()
{
SType = StructureType.MemoryBarrier,
SrcAccessMask = BufferHolder.DefaultAccessFlags,
DstAccessMask = AccessFlags.AccessIndirectCommandReadBit
};
Gd.Api.CmdPipelineBarrier(
CommandBuffer,
PipelineStageFlags.PipelineStageAllCommandsBit,
PipelineStageFlags.PipelineStageDrawIndirectBit,
0,
1,
memoryBarrier,
0,
null,
0,
null);
}
public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size)
@@ -535,10 +551,11 @@ namespace Ryujinx.Graphics.Vulkan
vkBlend = new PipelineColorBlendAttachmentState();
}
_newState.BlendConstantR = blend.BlendConstant.Red;
_newState.BlendConstantG = blend.BlendConstant.Green;
_newState.BlendConstantB = blend.BlendConstant.Blue;
_newState.BlendConstantA = blend.BlendConstant.Alpha;
DynamicState.SetBlendConstants(
blend.BlendConstant.Red,
blend.BlendConstant.Green,
blend.BlendConstant.Blue,
blend.BlendConstant.Alpha);
SignalStateChange();
}
@@ -823,7 +840,7 @@ namespace Ryujinx.Graphics.Vulkan
if (range.Handle != BufferHandle.Null)
{
_transformFeedbackBuffers[i] =
_transformFeedbackBuffers[i] =
new BufferState(Gd.BufferManager.GetBuffer(CommandBuffer, range.Handle, range.Offset, range.Size, true), range.Offset, range.Size);
_transformFeedbackBuffers[i].BindTransformFeedbackBuffer(Gd, Cbs, (uint)i);
}

View File

@@ -135,11 +135,6 @@ namespace Ryujinx.Graphics.Vulkan
// It is assumed that Dynamic State is enabled when this conversion is used.
pipeline.BlendConstantA = state.BlendDescriptors[0].BlendConstant.Alpha;
pipeline.BlendConstantB = state.BlendDescriptors[0].BlendConstant.Blue;
pipeline.BlendConstantG = state.BlendDescriptors[0].BlendConstant.Green;
pipeline.BlendConstantR = state.BlendDescriptors[0].BlendConstant.Red;
pipeline.CullMode = state.CullEnable ? state.CullMode.Convert() : CullModeFlags.CullModeNone;
pipeline.DepthBoundsTestEnable = false; // Not implemented.

View File

@@ -19,21 +19,34 @@ namespace Ryujinx.Graphics.Vulkan
private uint _frontWriteMask;
private uint _frontReference;
private Array4<float> _blendConstants;
public int ViewportsCount;
public Array16<Viewport> Viewports;
private enum DirtyFlags
{
None = 0,
DepthBias = 1 << 0,
Scissor = 1 << 1,
Stencil = 1 << 2,
Viewport = 1 << 3,
All = DepthBias | Scissor | Stencil | Viewport
Blend = 1 << 0,
DepthBias = 1 << 1,
Scissor = 1 << 2,
Stencil = 1 << 3,
Viewport = 1 << 4,
All = Blend | DepthBias | Scissor | Stencil | Viewport
}
private DirtyFlags _dirty;
public void SetBlendConstants(float r, float g, float b, float a)
{
_blendConstants[0] = r;
_blendConstants[1] = g;
_blendConstants[2] = b;
_blendConstants[3] = a;
_dirty |= DirtyFlags.Blend;
}
public void SetDepthBias(float slopeFactor, float constantFactor, float clamp)
{
_depthBiasSlopeFactor = slopeFactor;
@@ -87,6 +100,11 @@ namespace Ryujinx.Graphics.Vulkan
public void ReplayIfDirty(Vk api, CommandBuffer commandBuffer)
{
if (_dirty.HasFlag(DirtyFlags.Blend))
{
RecordBlend(api, commandBuffer);
}
if (_dirty.HasFlag(DirtyFlags.DepthBias))
{
RecordDepthBias(api, commandBuffer);
@@ -110,6 +128,11 @@ namespace Ryujinx.Graphics.Vulkan
_dirty = DirtyFlags.None;
}
private void RecordBlend(Vk api, CommandBuffer commandBuffer)
{
api.CmdSetBlendConstants(commandBuffer, _blendConstants.AsSpan());
}
private void RecordDepthBias(Vk api, CommandBuffer commandBuffer)
{
api.CmdSetDepthBias(commandBuffer, _depthBiasConstantFactor, _depthBiasClamp, _depthBiasSlopeFactor);

View File

@@ -499,7 +499,7 @@ namespace Ryujinx.Graphics.Vulkan
colorBlendState.BlendConstants[3] = BlendConstantA;
bool supportsExtDynamicState = gd.Capabilities.SupportsExtendedDynamicState;
int dynamicStatesCount = supportsExtDynamicState ? 8 : 7;
int dynamicStatesCount = supportsExtDynamicState ? 9 : 8;
DynamicState* dynamicStates = stackalloc DynamicState[dynamicStatesCount];
@@ -510,10 +510,11 @@ namespace Ryujinx.Graphics.Vulkan
dynamicStates[4] = DynamicState.StencilCompareMask;
dynamicStates[5] = DynamicState.StencilWriteMask;
dynamicStates[6] = DynamicState.StencilReference;
dynamicStates[7] = DynamicState.BlendConstants;
if (supportsExtDynamicState)
{
dynamicStates[7] = DynamicState.VertexInputBindingStrideExt;
dynamicStates[8] = DynamicState.VertexInputBindingStrideExt;
}
var pipelineDynamicStateCreateInfo = new PipelineDynamicStateCreateInfo()

View File

@@ -53,7 +53,7 @@ namespace Ryujinx.Graphics.Vulkan
"VUID-VkSubpassDependency-srcSubpass-00867"
};
internal static Instance CreateInstance(Vk api, GraphicsDebugLevel logLevel, string[] requiredExtensions, out ExtDebugReport debugReport, out DebugReportCallbackEXT debugReportCallback)
internal static Instance CreateInstance(Vk api, GraphicsDebugLevel logLevel, string[] requiredExtensions, out ExtDebugUtils debugUtils, out DebugUtilsMessengerEXT debugUtilsMessenger)
{
var enabledLayers = new List<string>();
@@ -89,7 +89,7 @@ namespace Ryujinx.Graphics.Vulkan
AddAvailableLayer("VK_LAYER_KHRONOS_validation");
}
var enabledExtensions = requiredExtensions.Append(ExtDebugReport.ExtensionName).ToArray();
var enabledExtensions = requiredExtensions.Append(ExtDebugUtils.ExtensionName).ToArray();
var appName = Marshal.StringToHGlobalAnsi(AppName);
@@ -139,22 +139,18 @@ namespace Ryujinx.Graphics.Vulkan
Marshal.FreeHGlobal(ppEnabledLayers[i]);
}
CreateDebugCallbacks(api, logLevel, instance, out debugReport, out debugReportCallback);
CreateDebugMessenger(api, logLevel, instance, out debugUtils, out debugUtilsMessenger);
return instance;
}
private unsafe static uint DebugReport(
uint flags,
DebugReportObjectTypeEXT objectType,
ulong @object,
nuint location,
int messageCode,
byte* layerPrefix,
byte* message,
void* userData)
private unsafe static uint DebugMessenger(
DebugUtilsMessageSeverityFlagsEXT messageSeverity,
DebugUtilsMessageTypeFlagsEXT messageTypes,
DebugUtilsMessengerCallbackDataEXT* pCallbackData,
void* pUserData)
{
var msg = Marshal.PtrToStringAnsi((IntPtr)message);
var msg = Marshal.PtrToStringAnsi((IntPtr)pCallbackData->PMessage);
foreach (string excludedMessagePart in _excludedMessages)
{
@@ -164,26 +160,20 @@ namespace Ryujinx.Graphics.Vulkan
}
}
DebugReportFlagsEXT debugFlags = (DebugReportFlagsEXT)flags;
if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportErrorBitExt))
if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt))
{
Logger.Error?.Print(LogClass.Gpu, msg);
//throw new Exception(msg);
}
else if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportWarningBitExt))
else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityWarningBitExt))
{
Logger.Warning?.Print(LogClass.Gpu, msg);
}
else if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportInformationBitExt))
else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityInfoBitExt))
{
Logger.Info?.Print(LogClass.Gpu, msg);
}
else if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportPerformanceWarningBitExt))
{
Logger.Warning?.Print(LogClass.Gpu, msg);
}
else
else // if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityVerboseBitExt))
{
Logger.Debug?.Print(LogClass.Gpu, msg);
}
@@ -551,46 +541,59 @@ namespace Ryujinx.Graphics.Vulkan
return new CommandBufferPool(api, device, queue, queueLock, queueFamilyIndex);
}
internal unsafe static void CreateDebugCallbacks(
internal unsafe static void CreateDebugMessenger(
Vk api,
GraphicsDebugLevel logLevel,
Instance instance,
out ExtDebugReport debugReport,
out DebugReportCallbackEXT debugReportCallback)
out ExtDebugUtils debugUtils,
out DebugUtilsMessengerEXT debugUtilsMessenger)
{
debugReport = default;
debugUtils = default;
if (logLevel != GraphicsDebugLevel.None)
{
if (!api.TryGetInstanceExtension(instance, out debugReport))
if (!api.TryGetInstanceExtension(instance, out debugUtils))
{
debugReportCallback = default;
debugUtilsMessenger = default;
return;
}
var flags = logLevel switch
var filterLogType = logLevel switch
{
GraphicsDebugLevel.Error => DebugReportFlagsEXT.DebugReportErrorBitExt,
GraphicsDebugLevel.Slowdowns => DebugReportFlagsEXT.DebugReportErrorBitExt | DebugReportFlagsEXT.DebugReportPerformanceWarningBitExt,
GraphicsDebugLevel.All => DebugReportFlagsEXT.DebugReportInformationBitExt |
DebugReportFlagsEXT.DebugReportWarningBitExt |
DebugReportFlagsEXT.DebugReportPerformanceWarningBitExt |
DebugReportFlagsEXT.DebugReportErrorBitExt |
DebugReportFlagsEXT.DebugReportDebugBitExt,
GraphicsDebugLevel.Error => DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeValidationBitExt,
GraphicsDebugLevel.Slowdowns => DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeValidationBitExt |
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypePerformanceBitExt,
GraphicsDebugLevel.All => DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeGeneralBitExt |
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeValidationBitExt |
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypePerformanceBitExt,
_ => throw new ArgumentException($"Invalid log level \"{logLevel}\".")
};
var debugReportCallbackCreateInfo = new DebugReportCallbackCreateInfoEXT()
var filterLogSeverity = logLevel switch
{
SType = StructureType.DebugReportCallbackCreateInfoExt,
Flags = flags,
PfnCallback = new PfnDebugReportCallbackEXT(DebugReport)
GraphicsDebugLevel.Error => DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt,
GraphicsDebugLevel.Slowdowns => DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt |
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityWarningBitExt,
GraphicsDebugLevel.All => DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityInfoBitExt |
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityWarningBitExt |
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityVerboseBitExt |
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt,
_ => throw new ArgumentException($"Invalid log level \"{logLevel}\".")
};
debugReport.CreateDebugReportCallback(instance, in debugReportCallbackCreateInfo, null, out debugReportCallback).ThrowOnError();
var debugUtilsMessengerCreateInfo = new DebugUtilsMessengerCreateInfoEXT()
{
SType = StructureType.DebugUtilsMessengerCreateInfoExt,
MessageType = filterLogType,
MessageSeverity = filterLogSeverity,
PfnUserCallback = new PfnDebugUtilsMessengerCallbackEXT(DebugMessenger)
};
debugUtils.CreateDebugUtilsMessenger(instance, in debugUtilsMessengerCreateInfo, null, out debugUtilsMessenger).ThrowOnError();
}
else
{
debugReportCallback = default;
debugUtilsMessenger = default;
}
}
}

View File

@@ -33,7 +33,7 @@ namespace Ryujinx.Graphics.Vulkan
internal KhrPushDescriptor PushDescriptorApi { get; private set; }
internal ExtTransformFeedback TransformFeedbackApi { get; private set; }
internal KhrDrawIndirectCount DrawIndirectCountApi { get; private set; }
internal ExtDebugReport DebugReportApi { get; private set; }
internal ExtDebugUtils DebugUtilsApi { get; private set; }
internal uint QueueFamilyIndex { get; private set; }
internal Queue Queue { get; private set; }
@@ -57,7 +57,7 @@ namespace Ryujinx.Graphics.Vulkan
private SyncManager _syncManager;
private PipelineFull _pipeline;
private DebugReportCallbackEXT _debugReportCallback;
private DebugUtilsMessengerEXT _debugUtilsMessenger;
internal HelperShader HelperShader { get; private set; }
internal PipelineFull PipelineInternal => _pipeline;
@@ -237,9 +237,9 @@ namespace Ryujinx.Graphics.Vulkan
Api = api;
_instance = VulkanInitialization.CreateInstance(api, logLevel, _getRequiredExtensions(), out ExtDebugReport debugReport, out _debugReportCallback);
_instance = VulkanInitialization.CreateInstance(api, logLevel, _getRequiredExtensions(), out ExtDebugUtils debugUtils, out _debugUtilsMessenger);
DebugReportApi = debugReport;
DebugUtilsApi = debugUtils;
if (api.TryGetInstanceExtension(_instance, out KhrSurface surfaceApi))
{
@@ -584,9 +584,9 @@ namespace Ryujinx.Graphics.Vulkan
MemoryAllocator.Dispose();
if (_debugReportCallback.Handle != 0)
if (_debugUtilsMessenger.Handle != 0)
{
DebugReportApi.DestroyDebugReportCallback(_instance, _debugReportCallback, null);
DebugUtilsApi.DestroyDebugUtilsMessenger(_instance, _debugUtilsMessenger, null);
}
foreach (var shader in Shaders)

View File

@@ -32,6 +32,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
private bool _handlesRequestToDisplay = false;
private bool _autoSleepDisabled = false;
private bool _albumImageTakenNotificationEnabled = false;
private bool _recordVolumeMuted = false;
private uint _screenShotImageOrientation = 0;
private uint _idleTimeDetectionExtension = 0;
@@ -389,5 +390,18 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
return ResultCode.Success;
}
[CommandHipc(130)] // 13.0.0+
// SetRecordVolumeMuted(b8)
public ResultCode SetRecordVolumeMuted(ServiceCtx context)
{
bool recordVolumeMuted = context.RequestData.ReadBoolean();
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { recordVolumeMuted });
_recordVolumeMuted = recordVolumeMuted;
return ResultCode.Success;
}
}
}
}

View File

@@ -203,6 +203,18 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Irs
return ResultCode.Success;
}
[CommandHipc(318)] // 4.0.0+
// StopImageProcessorAsync(nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId, pid)
public ResultCode StopImageProcessorAsync(ServiceCtx context)
{
int irCameraHandle = context.RequestData.ReadInt32();
long appletResourceUserId = context.RequestData.ReadInt64();
Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle });
return ResultCode.Success;
}
[CommandHipc(319)] // 4.0.0+
// ActivateIrsensorWithFunctionLevel(nn::applet::AppletResourceUserId, nn::irsensor::PackedFunctionLevel, pid)
public ResultCode ActivateIrsensorWithFunctionLevel(ServiceCtx context)
@@ -225,4 +237,4 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Irs
return ResultCode.Success;
}
}
}
}

View File

@@ -26,7 +26,7 @@
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.15.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.25.0" />
</ItemGroup>
<!-- Due to Concentus. -->

View 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;
}
}
}
}

View 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);
}
}
}
}

View File

@@ -176,6 +176,26 @@ namespace Ryujinx.Memory.Tracking
}
}
/// <summary>
/// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with.
/// </summary>
/// <param name="address">CPU virtual address of the region</param>
/// <param name="size">Size of the region</param>
/// <param name="bitmap">The bitmap owning the dirty flag for this handle</param>
/// <param name="bit">The bit of this handle within the dirty flag</param>
/// <returns>The memory tracking handle</returns>
internal RegionHandle BeginTrackingBitmap(ulong address, ulong size, ConcurrentBitmap bitmap, int bit)
{
(address, size) = PageAlign(address, size);
lock (TrackingLock)
{
RegionHandle handle = new RegionHandle(this, address, size, bitmap, bit, _memoryManager.IsRangeMapped(address, size));
return handle;
}
}
/// <summary>
/// Signal that a virtual memory event happened at the given location (one byte).
/// </summary>

View File

@@ -1,11 +1,15 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading;
namespace Ryujinx.Memory.Tracking
{
/// <summary>
/// A region handle that tracks a large region using many smaller handles, to provide
/// granular tracking that can be used to track partial updates.
/// granular tracking that can be used to track partial updates. Backed by a bitmap
/// to improve performance when scanning large regions.
/// </summary>
public class MultiRegionHandle : IMultiRegionHandle
{
@@ -17,6 +21,12 @@ namespace Ryujinx.Memory.Tracking
private readonly ulong Granularity;
private readonly ulong Size;
private ConcurrentBitmap _dirtyBitmap;
private int _sequenceNumber;
private BitMap _sequenceNumberBitmap;
private int _uncheckedHandles;
public bool Dirty { get; private set; } = true;
internal MultiRegionHandle(MemoryTracking tracking, ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity)
@@ -24,6 +34,9 @@ namespace Ryujinx.Memory.Tracking
_handles = new RegionHandle[size / granularity];
Granularity = granularity;
_dirtyBitmap = new ConcurrentBitmap(_handles.Length, true);
_sequenceNumberBitmap = new BitMap(_handles.Length);
int i = 0;
if (handles != null)
@@ -40,37 +53,43 @@ namespace Ryujinx.Memory.Tracking
// Fill any gap left before this handle.
while (i < startIndex)
{
RegionHandle fillHandle = tracking.BeginTracking(address + (ulong)i * granularity, granularity);
RegionHandle fillHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i);
fillHandle.Parent = this;
_handles[i++] = fillHandle;
}
if (handle.Size == granularity)
lock (tracking.TrackingLock)
{
handle.Parent = this;
_handles[i++] = handle;
}
else
{
int endIndex = (int)((handle.EndAddress - address) / granularity);
while (i < endIndex)
if (handle is RegionHandle bitHandle && handle.Size == granularity)
{
RegionHandle splitHandle = tracking.BeginTracking(address + (ulong)i * granularity, granularity);
splitHandle.Parent = this;
handle.Parent = this;
splitHandle.Reprotect(handle.Dirty);
bitHandle.ReplaceBitmap(_dirtyBitmap, i);
RegionSignal signal = handle.PreAction;
if (signal != null)
_handles[i++] = bitHandle;
}
else
{
int endIndex = (int)((handle.EndAddress - address) / granularity);
while (i < endIndex)
{
splitHandle.RegisterAction(signal);
RegionHandle splitHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i);
splitHandle.Parent = this;
splitHandle.Reprotect(handle.Dirty);
RegionSignal signal = handle.PreAction;
if (signal != null)
{
splitHandle.RegisterAction(signal);
}
_handles[i++] = splitHandle;
}
_handles[i++] = splitHandle;
handle.Dispose();
}
handle.Dispose();
}
}
}
@@ -78,15 +97,27 @@ namespace Ryujinx.Memory.Tracking
// Fill any remaining space with new handles.
while (i < _handles.Length)
{
RegionHandle handle = tracking.BeginTracking(address + (ulong)i * granularity, granularity);
RegionHandle handle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i);
handle.Parent = this;
_handles[i++] = handle;
}
_uncheckedHandles = _handles.Length;
Address = address;
Size = size;
}
public void SignalWrite()
{
Dirty = true;
}
public IEnumerable<RegionHandle> GetHandles()
{
return _handles;
}
public void ForceDirty(ulong address, ulong size)
{
Dirty = true;
@@ -96,21 +127,15 @@ namespace Ryujinx.Memory.Tracking
for (int i = startHandle; i <= lastHandle; i++)
{
_handles[i].SequenceNumber--;
if (_sequenceNumberBitmap.Clear(i))
{
_uncheckedHandles++;
}
_handles[i].ForceDirty();
}
}
public IEnumerable<RegionHandle> GetHandles()
{
return _handles;
}
public void SignalWrite()
{
Dirty = true;
}
public void QueryModified(Action<ulong, ulong> modifiedAction)
{
if (!Dirty)
@@ -123,33 +148,95 @@ namespace Ryujinx.Memory.Tracking
QueryModified(Address, Size, modifiedAction);
}
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ParseDirtyBits(long dirtyBits, ref int baseBit, ref int prevHandle, ref ulong rgStart, ref ulong rgSize, Action<ulong, ulong> modifiedAction)
{
int startHandle = (int)((address - Address) / Granularity);
int lastHandle = (int)((address + (size - 1) - Address) / Granularity);
ulong rgStart = _handles[startHandle].Address;
ulong rgSize = 0;
for (int i = startHandle; i <= lastHandle; i++)
while (dirtyBits != 0)
{
RegionHandle handle = _handles[i];
int bit = BitOperations.TrailingZeroCount(dirtyBits);
dirtyBits &= ~(1L << bit);
int handleIndex = baseBit + bit;
RegionHandle handle = _handles[handleIndex];
if (handleIndex != prevHandle + 1)
{
// Submit handles scanned until the gap as dirty
if (rgSize != 0)
{
modifiedAction(rgStart, rgSize);
rgSize = 0;
}
rgStart = handle.Address;
}
if (handle.Dirty)
{
rgSize += handle.Size;
handle.Reprotect();
}
else
prevHandle = handleIndex;
}
baseBit += ConcurrentBitmap.IntSize;
}
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction)
{
int startHandle = (int)((address - Address) / Granularity);
int lastHandle = (int)((address + (size - 1) - Address) / Granularity);
ulong rgStart = _handles[startHandle].Address;
if (startHandle == lastHandle)
{
RegionHandle handle = _handles[startHandle];
if (handle.Dirty)
{
// Submit the region scanned so far as dirty
if (rgSize != 0)
{
modifiedAction(rgStart, rgSize);
rgSize = 0;
}
rgStart = handle.EndAddress;
handle.Reprotect();
modifiedAction(rgStart, handle.Size);
}
return;
}
ulong rgSize = 0;
long[] masks = _dirtyBitmap.Masks;
int startIndex = startHandle >> ConcurrentBitmap.IntShift;
int startBit = startHandle & ConcurrentBitmap.IntMask;
long startMask = -1L << startBit;
int endIndex = lastHandle >> ConcurrentBitmap.IntShift;
int endBit = lastHandle & ConcurrentBitmap.IntMask;
long endMask = (long)(ulong.MaxValue >> (ConcurrentBitmap.IntMask - endBit));
long startValue = Volatile.Read(ref masks[startIndex]);
int baseBit = startIndex << ConcurrentBitmap.IntShift;
int prevHandle = startHandle - 1;
if (startIndex == endIndex)
{
ParseDirtyBits(startValue & startMask & endMask, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
}
else
{
ParseDirtyBits(startValue & startMask, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
for (int i = startIndex + 1; i < endIndex; i++)
{
ParseDirtyBits(Volatile.Read(ref masks[i]), ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
}
long endValue = Volatile.Read(ref masks[endIndex]);
ParseDirtyBits(endValue & endMask, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
}
if (rgSize != 0)
@@ -158,35 +245,120 @@ namespace Ryujinx.Memory.Tracking
}
}
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction, int sequenceNumber)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ParseDirtyBits(long dirtyBits, long mask, int index, long[] seqMasks, ref int baseBit, ref int prevHandle, ref ulong rgStart, ref ulong rgSize, Action<ulong, ulong> modifiedAction)
{
int startHandle = (int)((address - Address) / Granularity);
int lastHandle = (int)((address + (size - 1) - Address) / Granularity);
long seqMask = mask & ~seqMasks[index];
dirtyBits &= seqMask;
ulong rgStart = _handles[startHandle].Address;
ulong rgSize = 0;
for (int i = startHandle; i <= lastHandle; i++)
while (dirtyBits != 0)
{
RegionHandle handle = _handles[i];
int bit = BitOperations.TrailingZeroCount(dirtyBits);
if (sequenceNumber != handle.SequenceNumber && handle.DirtyOrVolatile())
dirtyBits &= ~(1L << bit);
int handleIndex = baseBit + bit;
RegionHandle handle = _handles[handleIndex];
if (handleIndex != prevHandle + 1)
{
rgSize += handle.Size;
handle.Reprotect();
}
else
{
// Submit the region scanned so far as dirty
// Submit handles scanned until the gap as dirty
if (rgSize != 0)
{
modifiedAction(rgStart, rgSize);
rgSize = 0;
}
rgStart = handle.EndAddress;
rgStart = handle.Address;
}
handle.SequenceNumber = sequenceNumber;
rgSize += handle.Size;
handle.Reprotect();
prevHandle = handleIndex;
}
seqMasks[index] |= mask;
_uncheckedHandles -= BitOperations.PopCount((ulong)seqMask);
baseBit += ConcurrentBitmap.IntSize;
}
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction, int sequenceNumber)
{
int startHandle = (int)((address - Address) / Granularity);
int lastHandle = (int)((address + (size - 1) - Address) / Granularity);
ulong rgStart = Address + (ulong)startHandle * Granularity;
if (sequenceNumber != _sequenceNumber)
{
if (_uncheckedHandles != _handles.Length)
{
_sequenceNumberBitmap.Clear();
_uncheckedHandles = _handles.Length;
}
_sequenceNumber = sequenceNumber;
}
if (startHandle == lastHandle)
{
var handle = _handles[startHandle];
if (_sequenceNumberBitmap.Set(startHandle))
{
_uncheckedHandles--;
if (handle.DirtyOrVolatile())
{
handle.Reprotect();
modifiedAction(rgStart, handle.Size);
}
}
return;
}
if (_uncheckedHandles == 0)
{
return;
}
ulong rgSize = 0;
long[] seqMasks = _sequenceNumberBitmap.Masks;
long[] masks = _dirtyBitmap.Masks;
int startIndex = startHandle >> ConcurrentBitmap.IntShift;
int startBit = startHandle & ConcurrentBitmap.IntMask;
long startMask = -1L << startBit;
int endIndex = lastHandle >> ConcurrentBitmap.IntShift;
int endBit = lastHandle & ConcurrentBitmap.IntMask;
long endMask = (long)(ulong.MaxValue >> (ConcurrentBitmap.IntMask - endBit));
long startValue = Volatile.Read(ref masks[startIndex]);
int baseBit = startIndex << ConcurrentBitmap.IntShift;
int prevHandle = startHandle - 1;
if (startIndex == endIndex)
{
ParseDirtyBits(startValue, startMask & endMask, startIndex, seqMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
}
else
{
ParseDirtyBits(startValue, startMask, startIndex, seqMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
for (int i = startIndex + 1; i < endIndex; i++)
{
ParseDirtyBits(Volatile.Read(ref masks[i]), -1L, i, seqMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
}
long endValue = Volatile.Read(ref masks[endIndex]);
ParseDirtyBits(endValue, endMask, endIndex, seqMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
}
if (rgSize != 0)

View File

@@ -1,5 +1,4 @@
using Ryujinx.Memory.Range;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -10,7 +9,7 @@ namespace Ryujinx.Memory.Tracking
/// A tracking handle for a given region of virtual memory. The Dirty flag is updated whenever any changes are made,
/// and an action can be performed when the region is read to or written from.
/// </summary>
public class RegionHandle : IRegionHandle, IRange
public class RegionHandle : IRegionHandle
{
/// <summary>
/// If more than this number of checks have been performed on a dirty flag since its last reprotect,
@@ -23,7 +22,20 @@ namespace Ryujinx.Memory.Tracking
/// </summary>
private static int VolatileThreshold = 5;
public bool Dirty { get; private set; }
public bool Dirty
{
get
{
return Bitmap.IsSet(DirtyBit);
}
protected set
{
Bitmap.Set(DirtyBit, value);
}
}
internal int SequenceNumber { get; set; }
public bool Unmapped { get; private set; }
public ulong Address { get; }
@@ -31,7 +43,6 @@ namespace Ryujinx.Memory.Tracking
public ulong EndAddress { get; }
internal IMultiRegionHandle Parent { get; set; }
internal int SequenceNumber { get; set; }
private event Action _onDirty;
@@ -68,17 +79,26 @@ namespace Ryujinx.Memory.Tracking
internal RegionSignal PreAction => _preAction;
internal ConcurrentBitmap Bitmap;
internal int DirtyBit;
/// <summary>
/// Create a new region handle. The handle is registered with the given tracking object,
/// Create a new bitmap backed region handle. The handle is registered with the given tracking object,
/// and will be notified of any changes to the specified region.
/// </summary>
/// <param name="tracking">Tracking object for the target memory block</param>
/// <param name="address">Virtual address of the region to track</param>
/// <param name="size">Size of the region to track</param>
/// <param name="bitmap">The bitmap the dirty flag for this handle is stored in</param>
/// <param name="bit">The bit index representing the dirty flag for this handle</param>
/// <param name="mapped">True if the region handle starts mapped</param>
internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, bool mapped = true)
internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, ConcurrentBitmap bitmap, int bit, bool mapped = true)
{
Bitmap = bitmap;
DirtyBit = bit;
Dirty = mapped;
Unmapped = !mapped;
Address = address;
Size = size;
@@ -92,6 +112,54 @@ namespace Ryujinx.Memory.Tracking
}
}
/// <summary>
/// Create a new region handle. The handle is registered with the given tracking object,
/// and will be notified of any changes to the specified region.
/// </summary>
/// <param name="tracking">Tracking object for the target memory block</param>
/// <param name="address">Virtual address of the region to track</param>
/// <param name="size">Size of the region to track</param>
/// <param name="mapped">True if the region handle starts mapped</param>
internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, bool mapped = true)
{
Bitmap = new ConcurrentBitmap(1, mapped);
Unmapped = !mapped;
Address = address;
Size = size;
EndAddress = address + size;
_tracking = tracking;
_regions = tracking.GetVirtualRegionsForHandle(address, size);
foreach (var region in _regions)
{
region.Handles.Add(this);
}
}
/// <summary>
/// Replace the bitmap and bit index used to track dirty state.
/// </summary>
/// <remarks>
/// The tracking lock should be held when this is called, to ensure neither bitmap is modified.
/// </remarks>
/// <param name="bitmap">The bitmap the dirty flag for this handle is stored in</param>
/// <param name="bit">The bit index representing the dirty flag for this handle</param>
internal void ReplaceBitmap(ConcurrentBitmap bitmap, int bit)
{
// Assumes the tracking lock is held, so nothing else can signal right now.
var oldBitmap = Bitmap;
var oldBit = DirtyBit;
bitmap.Set(bit, Dirty);
Bitmap = bitmap;
DirtyBit = bit;
Dirty |= oldBitmap.IsSet(oldBit);
}
/// <summary>
/// Clear the volatile state of this handle.
/// </summary>
@@ -108,7 +176,7 @@ namespace Ryujinx.Memory.Tracking
public bool DirtyOrVolatile()
{
_checkCount++;
return Dirty || _volatile;
return _volatile || Dirty;
}
/// <summary>
@@ -195,6 +263,8 @@ namespace Ryujinx.Memory.Tracking
/// </summary>
public void ForceDirty()
{
_checkCount++;
Dirty = true;
}

View File

@@ -3,9 +3,10 @@ using System;
namespace Ryujinx.Tests.Unicorn
{
public class UnicornAArch32
public class UnicornAArch32 : IDisposable
{
internal readonly IntPtr uc;
private bool _isDisposed = false;
public IndexedProperty<int, uint> R
{
@@ -107,7 +108,22 @@ namespace Ryujinx.Tests.Unicorn
~UnicornAArch32()
{
Interface.Checked(Native.Interface.uc_close(uc));
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
Interface.Checked(Native.Interface.uc_close(uc));
_isDisposed = true;
}
}
public void RunForCount(ulong count)

View File

@@ -3,9 +3,10 @@ using System;
namespace Ryujinx.Tests.Unicorn
{
public class UnicornAArch64
public class UnicornAArch64 : IDisposable
{
internal readonly IntPtr uc;
private bool _isDisposed = false;
public IndexedProperty<int, ulong> X
{
@@ -96,7 +97,22 @@ namespace Ryujinx.Tests.Unicorn
~UnicornAArch64()
{
Interface.Checked(Native.Interface.uc_close(uc));
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
Interface.Checked(Native.Interface.uc_close(uc));
_isDisposed = true;
}
}
public void RunForCount(ulong count)

View File

@@ -80,6 +80,12 @@ namespace Ryujinx.Tests.Cpu
[TearDown]
public void Teardown()
{
if (_unicornAvailable)
{
_unicornEmu.Dispose();
_unicornEmu = null;
}
_memory.DecrementReferenceCount();
_context.Dispose();
_ram.Dispose();

View File

@@ -76,6 +76,12 @@ namespace Ryujinx.Tests.Cpu
[TearDown]
public void Teardown()
{
if (_unicornAvailable)
{
_unicornEmu.Dispose();
_unicornEmu = null;
}
_memory.DecrementReferenceCount();
_context.Dispose();
_ram.Dispose();

View File

@@ -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.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="SPB" Version="0.0.4-build24" />
<PackageReference Include="SPB" Version="0.0.4-build27" />
<PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
</ItemGroup>