Compare commits

...

7 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
19 changed files with 853 additions and 142 deletions

View File

@@ -57,6 +57,7 @@ jobs:
- name: Get git short hash - name: Get git short hash
id: git_short_hash id: git_short_hash
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT 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

View File

@@ -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";

View File

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

View File

@@ -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
} }
} }

View File

@@ -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
} }
} }

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
} }
@@ -308,12 +325,29 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if ((type & (AggregateType.Array | AggregateType.Vector)) == 0) 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()); 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);
} }

View File

@@ -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);

View File

@@ -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)

View File

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

View File

@@ -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)

View File

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

View File

@@ -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. -->

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> /// <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>

View File

@@ -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,37 +53,43 @@ 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)
{ {
handle.Parent = this; if (handle is RegionHandle bitHandle && handle.Size == granularity)
_handles[i++] = handle;
}
else
{
int endIndex = (int)((handle.EndAddress - address) / granularity);
while (i < endIndex)
{ {
RegionHandle splitHandle = tracking.BeginTracking(address + (ulong)i * granularity, granularity); handle.Parent = this;
splitHandle.Parent = this;
splitHandle.Reprotect(handle.Dirty); bitHandle.ReplaceBitmap(_dirtyBitmap, i);
RegionSignal signal = handle.PreAction; _handles[i++] = bitHandle;
if (signal != null) }
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. // 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();
} }
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 handle.Reprotect();
if (rgSize != 0) modifiedAction(rgStart, handle.Size);
{
modifiedAction(rgStart, rgSize);
rgSize = 0;
}
rgStart = handle.EndAddress;
} }
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) 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); long seqMask = mask & ~seqMasks[index];
int lastHandle = (int)((address + (size - 1) - Address) / Granularity); dirtyBits &= seqMask;
ulong rgStart = _handles[startHandle].Address; while (dirtyBits != 0)
ulong rgSize = 0;
for (int i = startHandle; i <= lastHandle; i++)
{ {
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; // Submit handles scanned until the gap as dirty
handle.Reprotect();
}
else
{
// Submit the region scanned so far as dirty
if (rgSize != 0) if (rgSize != 0)
{ {
modifiedAction(rgStart, rgSize); modifiedAction(rgStart, rgSize);
rgSize = 0; 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) if (rgSize != 0)

View File

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