From 46a11460d4701f66926f70b5dc394d209e877854 Mon Sep 17 00:00:00 2001 From: Thomas Guillemard Date: Sat, 15 Sep 2018 15:29:18 +0200 Subject: [PATCH 01/14] Rewrite the C++ Demangler (#416) * Rewrite the C++ Demangler This new Demangler provides support to almost every possible mangled symbols and should behaves like GNU c++filt. It works on 98.9% of the sdk's symbols and 99.5% of Puyo Puyo Tetris's symbols. * Fix code style * Fix noexcept enclosed expression parsing issues * fix code style issues --- Ryujinx.HLE/HOS/Diagnostics/Demangler.cs | 416 -- .../Ast/ArraySubscriptingExpression.cs | 25 + .../Diagnostics/Demangler/Ast/ArrayType.cs | 59 + .../HOS/Diagnostics/Demangler/Ast/BaseNode.cs | 113 + .../Demangler/Ast/BinaryExpression.cs | 41 + .../Demangler/Ast/BracedExpression.cs | 40 + .../Demangler/Ast/BracedRangeExpression.cs | 34 + .../Demangler/Ast/CallExpression.cs | 25 + .../Demangler/Ast/CasExpression.cs | 30 + .../Demangler/Ast/ConditionalExpression.cs | 29 + .../Demangler/Ast/ConversionExpression.cs | 24 + .../Demangler/Ast/ConversionOperatorType.cs | 15 + .../Demangler/Ast/CtorDtorNameType.cs | 24 + .../Demangler/Ast/CtorVtableSpecialName.cs | 24 + .../Demangler/Ast/DeleteExpression.cs | 33 + .../HOS/Diagnostics/Demangler/Ast/DtorName.cs | 15 + .../Demangler/Ast/DynamicExceptionSpec.cs | 16 + .../Demangler/Ast/ElaboratedType.cs | 21 + .../Demangler/Ast/EnclosedExpression.cs | 25 + .../Demangler/Ast/EncodedFunction.cs | 77 + .../Demangler/Ast/FoldExpression.cs | 48 + .../Demangler/Ast/ForwardTemplateReference.cs | 36 + .../Demangler/Ast/FunctionParameter.cs | 24 + .../Diagnostics/Demangler/Ast/FunctionType.cs | 61 + .../Demangler/Ast/GlobalQualifiedName.cs | 15 + .../Demangler/Ast/InitListExpression.cs | 30 + .../Demangler/Ast/IntegerCastExpression.cs | 22 + .../Demangler/Ast/IntegerLiteral.cs | 41 + .../Demangler/Ast/LiteralOperator.cs | 16 + .../Diagnostics/Demangler/Ast/LocalName.cs | 23 + .../Demangler/Ast/MemberExpression.cs | 25 + .../HOS/Diagnostics/Demangler/Ast/NameType.cs | 29 + .../Ast/NameTypeWithTemplateArguments.cs | 27 + .../Diagnostics/Demangler/Ast/NestedName.cs | 26 + .../Demangler/Ast/NewExpression.cs | 55 + .../Diagnostics/Demangler/Ast/NodeArray.cs | 31 + .../Diagnostics/Demangler/Ast/NoexceptSpec.cs | 16 + .../Demangler/Ast/PackedTemplateParameter.cs | 39 + .../Ast/PackedTemplateParameterExpansion.cs | 24 + .../Diagnostics/Demangler/Ast/ParentNode.cs | 17 + .../Diagnostics/Demangler/Ast/PointerType.cs | 45 + .../Demangler/Ast/PostfixExpression.cs | 22 + .../Demangler/Ast/PostfixQualifiedType.cs | 20 + .../Demangler/Ast/PrefixExpression.cs | 22 + .../Demangler/Ast/QualifiedName.cs | 23 + .../Diagnostics/Demangler/Ast/Qualifier.cs | 120 + .../Demangler/Ast/ReferenceType.cs | 47 + .../Diagnostics/Demangler/Ast/SpecialName.cs | 20 + .../Demangler/Ast/SpecialSubstitution.cs | 89 + .../Demangler/Ast/StdQualifiedName.cs | 15 + .../Demangler/Ast/TemplateArguments.cs | 27 + .../Demangler/Ast/ThrowExpression.cs | 20 + .../HOS/Diagnostics/Demangler/Demangler.cs | 3367 +++++++++++++++++ Ryujinx.HLE/HOS/Process.cs | 2 +- 54 files changed, 5113 insertions(+), 417 deletions(-) delete mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArraySubscriptingExpression.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArrayType.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BaseNode.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BinaryExpression.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedExpression.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedRangeExpression.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CallExpression.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CasExpression.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConditionalExpression.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionExpression.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionOperatorType.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorDtorNameType.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorVtableSpecialName.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DeleteExpression.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DtorName.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DynamicExceptionSpec.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ElaboratedType.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EnclosedExpression.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EncodedFunction.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FoldExpression.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ForwardTemplateReference.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionParameter.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionType.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/GlobalQualifiedName.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/InitListExpression.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerCastExpression.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerLiteral.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LiteralOperator.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LocalName.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/MemberExpression.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameType.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameTypeWithTemplateArguments.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NestedName.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NewExpression.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NodeArray.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NoexceptSpec.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameter.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameterExpansion.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ParentNode.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PointerType.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixExpression.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixQualifiedType.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PrefixExpression.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/QualifiedName.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/Qualifier.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ReferenceType.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialName.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialSubstitution.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/StdQualifiedName.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/TemplateArguments.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ThrowExpression.cs create mode 100644 Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler.cs deleted file mode 100644 index aea979c26c..0000000000 --- a/Ryujinx.HLE/HOS/Diagnostics/Demangler.cs +++ /dev/null @@ -1,416 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Ryujinx.HLE.HOS.Diagnostics -{ - static class Demangler - { - private static readonly Dictionary BuiltinTypes = new Dictionary - { - { "v", "void" }, - { "w", "wchar_t" }, - { "b", "bool" }, - { "c", "char" }, - { "a", "signed char" }, - { "h", "unsigned char" }, - { "s", "short" }, - { "t", "unsigned short" }, - { "i", "int" }, - { "j", "unsigned int" }, - { "l", "long" }, - { "m", "unsigned long" }, - { "x", "long long" }, - { "y", "unsigned long long" }, - { "n", "__int128" }, - { "o", "unsigned __int128" }, - { "f", "float" }, - { "d", "double" }, - { "e", "long double" }, - { "g", "__float128" }, - { "z", "..." }, - { "Dd", "__iec559_double" }, - { "De", "__iec559_float128" }, - { "Df", "__iec559_float" }, - { "Dh", "__iec559_float16" }, - { "Di", "char32_t" }, - { "Ds", "char16_t" }, - { "Da", "decltype(auto)" }, - { "Dn", "std::nullptr_t" }, - }; - - private static readonly Dictionary SubstitutionExtra = new Dictionary - { - {"Sa", "std::allocator"}, - {"Sb", "std::basic_string"}, - {"Ss", "std::basic_string, ::std::allocator>"}, - {"Si", "std::basic_istream>"}, - {"So", "std::basic_ostream>"}, - {"Sd", "std::basic_iostream>"} - }; - - private static int FromBase36(string encoded) - { - string base36 = "0123456789abcdefghijklmnopqrstuvwxyz"; - char[] reversedEncoded = encoded.ToLower().ToCharArray().Reverse().ToArray(); - int result = 0; - for (int i = 0; i < reversedEncoded.Length; i++) - { - char c = reversedEncoded[i]; - int value = base36.IndexOf(c); - if (value == -1) - return -1; - result += value * (int)Math.Pow(36, i); - } - return result; - } - - private static string GetCompressedValue(string compression, List compressionData, out int pos) - { - string res = null; - bool canHaveUnqualifiedName = false; - pos = -1; - if (compressionData.Count == 0 || !compression.StartsWith("S")) - return null; - - if (compression.Length >= 2 && SubstitutionExtra.TryGetValue(compression.Substring(0, 2), out string substitutionValue)) - { - pos = 1; - res = substitutionValue; - compression = compression.Substring(2); - } - else if (compression.StartsWith("St")) - { - pos = 1; - canHaveUnqualifiedName = true; - res = "std"; - compression = compression.Substring(2); - } - else if (compression.StartsWith("S_")) - { - pos = 1; - res = compressionData[0]; - canHaveUnqualifiedName = true; - compression = compression.Substring(2); - } - else - { - int id = -1; - int underscorePos = compression.IndexOf('_'); - if (underscorePos == -1) - return null; - string partialId = compression.Substring(1, underscorePos - 1); - - id = FromBase36(partialId); - if (id == -1 || compressionData.Count <= (id + 1)) - { - return null; - } - res = compressionData[id + 1]; - pos = partialId.Length + 1; - canHaveUnqualifiedName= true; - compression = compression.Substring(pos); - } - if (res != null) - { - if (canHaveUnqualifiedName) - { - List type = ReadName(compression, compressionData, out int endOfNameType); - if (endOfNameType != -1 && type != null) - { - pos += endOfNameType; - res = res + "::" + type[type.Count - 1]; - } - } - } - return res; - } - - private static List ReadName(string mangled, List compressionData, out int pos, bool isNested = true) - { - List res = new List(); - string charCountString = null; - int charCount = 0; - int i; - - pos = -1; - for (i = 0; i < mangled.Length; i++) - { - char chr = mangled[i]; - if (charCountString == null) - { - if (ReadCVQualifiers(chr) != null) - { - continue; - } - if (chr == 'S') - { - string data = GetCompressedValue(mangled.Substring(i), compressionData, out pos); - if (pos == -1) - { - return null; - } - if (res.Count == 0) - res.Add(data); - else - res.Add(res[res.Count - 1] + "::" + data); - i += pos; - if (i < mangled.Length && mangled[i] == 'E') - { - break; - } - continue; - } - else if (chr == 'E') - { - break; - } - } - if (Char.IsDigit(chr)) - { - charCountString += chr; - } - else - { - if (!int.TryParse(charCountString, out charCount)) - { - return null; - } - string demangledPart = mangled.Substring(i, charCount); - if (res.Count == 0) - res.Add(demangledPart); - else - res.Add(res[res.Count - 1] + "::" + demangledPart); - i = i + charCount - 1; - charCount = 0; - charCountString = null; - if (!isNested) - break; - } - } - if (res.Count == 0) - { - return null; - } - pos = i; - return res; - } - - private static string ReadBuiltinType(string mangledType, out int pos) - { - string res = null; - string possibleBuiltinType; - pos = -1; - possibleBuiltinType = mangledType[0].ToString(); - if (!BuiltinTypes.TryGetValue(possibleBuiltinType, out res)) - { - if (mangledType.Length >= 2) - { - // Try to match the first 2 chars if the first call failed - possibleBuiltinType = mangledType.Substring(0, 2); - BuiltinTypes.TryGetValue(possibleBuiltinType, out res); - } - } - if (res != null) - pos = possibleBuiltinType.Length; - return res; - } - - private static string ReadCVQualifiers(char qualifier) - { - if (qualifier == 'r') - return "restricted"; - else if (qualifier == 'V') - return "volatile"; - else if (qualifier == 'K') - return "const"; - return null; - } - - private static string ReadRefQualifiers(char qualifier) - { - if (qualifier == 'R') - return "&"; - else if (qualifier == 'O') - return "&&"; - return null; - } - - private static string ReadSpecialQualifiers(char qualifier) - { - if (qualifier == 'P') - return "*"; - else if (qualifier == 'C') - return "complex"; - else if (qualifier == 'G') - return "imaginary"; - return null; - } - - private static List ReadParameters(string mangledParams, List compressionData, out int pos) - { - List res = new List(); - List refQualifiers = new List(); - string parsedTypePart = null; - string currentRefQualifiers = null; - string currentBuiltinType = null; - string currentSpecialQualifiers = null; - string currentCompressedValue = null; - int i = 0; - pos = -1; - - for (i = 0; i < mangledParams.Length; i++) - { - if (currentBuiltinType != null) - { - string currentCVQualifier = String.Join(" ", refQualifiers); - // Try to mimic the compression indexing - if (currentRefQualifiers != null) - { - compressionData.Add(currentBuiltinType + currentRefQualifiers); - } - if (refQualifiers.Count != 0) - { - compressionData.Add(currentBuiltinType + " " + currentCVQualifier + currentRefQualifiers); - } - if (currentSpecialQualifiers != null) - { - compressionData.Add(currentBuiltinType + " " + currentCVQualifier + currentRefQualifiers + currentSpecialQualifiers); - } - if (currentRefQualifiers == null && currentCVQualifier == null && currentSpecialQualifiers == null) - { - compressionData.Add(currentBuiltinType); - } - currentBuiltinType = null; - currentCompressedValue = null; - currentCVQualifier = null; - currentRefQualifiers = null; - refQualifiers.Clear(); - currentSpecialQualifiers = null; - } - char chr = mangledParams[i]; - string part = mangledParams.Substring(i); - - // Try to read qualifiers - parsedTypePart = ReadCVQualifiers(chr); - if (parsedTypePart != null) - { - refQualifiers.Add(parsedTypePart); - - // need more data - continue; - } - - parsedTypePart = ReadRefQualifiers(chr); - if (parsedTypePart != null) - { - currentRefQualifiers = parsedTypePart; - - // need more data - continue; - } - - parsedTypePart = ReadSpecialQualifiers(chr); - if (parsedTypePart != null) - { - currentSpecialQualifiers = parsedTypePart; - - // need more data - continue; - } - - // TODO: extended-qualifier? - - if (part.StartsWith("S")) - { - parsedTypePart = GetCompressedValue(part, compressionData, out pos); - if (pos != -1 && parsedTypePart != null) - { - currentCompressedValue = parsedTypePart; - i += pos; - res.Add(currentCompressedValue + " " + String.Join(" ", refQualifiers) + currentRefQualifiers + currentSpecialQualifiers); - currentBuiltinType = null; - currentCompressedValue = null; - currentRefQualifiers = null; - refQualifiers.Clear(); - currentSpecialQualifiers = null; - continue; - } - pos = -1; - return null; - } - else if (part.StartsWith("N")) - { - part = part.Substring(1); - List name = ReadName(part, compressionData, out pos); - if (pos != -1 && name != null) - { - i += pos + 1; - res.Add(name[name.Count - 1] + " " + String.Join(" ", refQualifiers) + currentRefQualifiers + currentSpecialQualifiers); - currentBuiltinType = null; - currentCompressedValue = null; - currentRefQualifiers = null; - refQualifiers.Clear(); - currentSpecialQualifiers = null; - continue; - } - } - - // Try builting - parsedTypePart = ReadBuiltinType(part, out pos); - if (pos == -1) - { - return null; - } - currentBuiltinType = parsedTypePart; - res.Add(currentBuiltinType + " " + String.Join(" ", refQualifiers) + currentRefQualifiers + currentSpecialQualifiers); - i = i + pos -1; - } - pos = i; - return res; - } - - private static string ParseFunctionName(string mangled) - { - List compressionData = new List(); - int pos = 0; - string res; - bool isNested = mangled.StartsWith("N"); - - // If it's start with "N" it must be a nested function name - if (isNested) - mangled = mangled.Substring(1); - compressionData = ReadName(mangled, compressionData, out pos, isNested); - if (pos == -1) - return null; - res = compressionData[compressionData.Count - 1]; - compressionData.Remove(res); - mangled = mangled.Substring(pos + 1); - - // more data? maybe not a data name so... - if (mangled != String.Empty) - { - List parameters = ReadParameters(mangled, compressionData, out pos); - // parameters parsing error, we return the original data to avoid information loss. - if (pos == -1) - return null; - parameters = parameters.Select(outer => outer.Trim()).ToList(); - res += "(" + String.Join(", ", parameters) + ")"; - } - return res; - } - - public static string Parse(string originalMangled) - { - if (originalMangled.StartsWith("_Z")) - { - // We assume that we have a name (TOOD: support special names) - string res = ParseFunctionName(originalMangled.Substring(2)); - if (res == null) - return originalMangled; - return res; - } - return originalMangled; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArraySubscriptingExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArraySubscriptingExpression.cs new file mode 100644 index 0000000000..435789e0f2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArraySubscriptingExpression.cs @@ -0,0 +1,25 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ArraySubscriptingExpression : BaseNode + { + private BaseNode LeftNode; + private BaseNode Subscript; + + public ArraySubscriptingExpression(BaseNode LeftNode, BaseNode Subscript) : base(NodeType.ArraySubscriptingExpression) + { + this.LeftNode = LeftNode; + this.Subscript = Subscript; + } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write("("); + LeftNode.Print(Writer); + Writer.Write(")["); + Subscript.Print(Writer); + Writer.Write("]"); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArrayType.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArrayType.cs new file mode 100644 index 0000000000..1679736072 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArrayType.cs @@ -0,0 +1,59 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ArrayType : BaseNode + { + private BaseNode Base; + private BaseNode DimensionExpression; + private string DimensionString; + + public ArrayType(BaseNode Base, BaseNode DimensionExpression = null) : base(NodeType.ArrayType) + { + this.Base = Base; + this.DimensionExpression = DimensionExpression; + } + + public ArrayType(BaseNode Base, string DimensionString) : base(NodeType.ArrayType) + { + this.Base = Base; + this.DimensionString = DimensionString; + } + + public override bool HasRightPart() + { + return true; + } + + public override bool IsArray() + { + return true; + } + + public override void PrintLeft(TextWriter Writer) + { + Base.PrintLeft(Writer); + } + + public override void PrintRight(TextWriter Writer) + { + // FIXME: detect if previous char was a ]. + Writer.Write(" "); + + Writer.Write("["); + + if (DimensionString != null) + { + Writer.Write(DimensionString); + } + else if (DimensionExpression != null) + { + DimensionExpression.Print(Writer); + } + + Writer.Write("]"); + + Base.PrintRight(Writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BaseNode.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BaseNode.cs new file mode 100644 index 0000000000..870758462e --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BaseNode.cs @@ -0,0 +1,113 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public enum NodeType + { + CVQualifierType, + SimpleReferenceType, + NameType, + EncodedFunction, + NestedName, + SpecialName, + LiteralOperator, + NodeArray, + ElaboratedType, + PostfixQualifiedType, + SpecialSubstitution, + ExpandedSpecialSubstitution, + CtorDtorNameType, + EnclosedExpression, + ForwardTemplateReference, + NameTypeWithTemplateArguments, + PackedTemplateArgument, + TemplateArguments, + BooleanExpression, + CastExpression, + CallExpression, + IntegerCastExpression, + PackedTemplateParameter, + PackedTemplateParameterExpansion, + IntegerLiteral, + DeleteExpression, + MemberExpression, + ArraySubscriptingExpression, + InitListExpression, + PostfixExpression, + ConditionalExpression, + ThrowExpression, + FunctionParameter, + ConversionExpression, + BinaryExpression, + PrefixExpression, + BracedExpression, + BracedRangeExpression, + NewExpression, + QualifiedName, + StdQualifiedName, + DtOrName, + GlobalQualifiedName, + NoexceptSpec, + DynamicExceptionSpec, + FunctionType, + PointerType, + ReferenceType, + ConversionOperatorType, + LocalName, + CtorVtableSpecialName, + ArrayType + } + + public abstract class BaseNode + { + public NodeType Type { get; protected set; } + + public BaseNode(NodeType Type) + { + this.Type = Type; + } + + public virtual void Print(TextWriter Writer) + { + PrintLeft(Writer); + + if (HasRightPart()) + { + PrintRight(Writer); + } + } + + public abstract void PrintLeft(TextWriter Writer); + + public virtual bool HasRightPart() + { + return false; + } + + public virtual bool IsArray() + { + return false; + } + + public virtual bool HasFunctions() + { + return false; + } + + public virtual string GetName() + { + return null; + } + + public virtual void PrintRight(TextWriter Writer) {} + + public override string ToString() + { + StringWriter Writer = new StringWriter(); + + Print(Writer); + + return Writer.ToString(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BinaryExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BinaryExpression.cs new file mode 100644 index 0000000000..9cd1dd779a --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BinaryExpression.cs @@ -0,0 +1,41 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class BinaryExpression : BaseNode + { + private BaseNode LeftPart; + private string Name; + private BaseNode RightPart; + + public BinaryExpression(BaseNode LeftPart, string Name, BaseNode RightPart) : base(NodeType.BinaryExpression) + { + this.LeftPart = LeftPart; + this.Name = Name; + this.RightPart = RightPart; + } + + public override void PrintLeft(TextWriter Writer) + { + if (Name.Equals(">")) + { + Writer.Write("("); + } + + Writer.Write("("); + LeftPart.Print(Writer); + Writer.Write(") "); + + Writer.Write(Name); + + Writer.Write(" ("); + RightPart.Print(Writer); + Writer.Write(")"); + + if (Name.Equals(">")) + { + Writer.Write(")"); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedExpression.cs new file mode 100644 index 0000000000..59222ea3f9 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedExpression.cs @@ -0,0 +1,40 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class BracedExpression : BaseNode + { + private BaseNode Element; + private BaseNode Expression; + private bool IsArrayExpression; + + public BracedExpression(BaseNode Element, BaseNode Expression, bool IsArrayExpression) : base(NodeType.BracedExpression) + { + this.Element = Element; + this.Expression = Expression; + this.IsArrayExpression = IsArrayExpression; + } + + public override void PrintLeft(TextWriter Writer) + { + if (IsArrayExpression) + { + Writer.Write("["); + Element.Print(Writer); + Writer.Write("]"); + } + else + { + Writer.Write("."); + Element.Print(Writer); + } + + if (!Expression.GetType().Equals(NodeType.BracedExpression) || !Expression.GetType().Equals(NodeType.BracedRangeExpression)) + { + Writer.Write(" = "); + } + + Expression.Print(Writer); + } + } +} diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedRangeExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedRangeExpression.cs new file mode 100644 index 0000000000..e459f1a393 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedRangeExpression.cs @@ -0,0 +1,34 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class BracedRangeExpression : BaseNode + { + private BaseNode FirstNode; + private BaseNode LastNode; + private BaseNode Expression; + + public BracedRangeExpression(BaseNode FirstNode, BaseNode LastNode, BaseNode Expression) : base(NodeType.BracedRangeExpression) + { + this.FirstNode = FirstNode; + this.LastNode = LastNode; + this.Expression = Expression; + } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write("["); + FirstNode.Print(Writer); + Writer.Write(" ... "); + LastNode.Print(Writer); + Writer.Write("]"); + + if (!Expression.GetType().Equals(NodeType.BracedExpression) || !Expression.GetType().Equals(NodeType.BracedRangeExpression)) + { + Writer.Write(" = "); + } + + Expression.Print(Writer); + } + } +} diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CallExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CallExpression.cs new file mode 100644 index 0000000000..7e59ab09df --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CallExpression.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class CallExpression : NodeArray + { + private BaseNode Callee; + + public CallExpression(BaseNode Callee, List Nodes) : base(Nodes, NodeType.CallExpression) + { + this.Callee = Callee; + } + + public override void PrintLeft(TextWriter Writer) + { + Callee.Print(Writer); + + Writer.Write("("); + Writer.Write(string.Join(", ", Nodes.ToArray())); + Writer.Write(")"); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CasExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CasExpression.cs new file mode 100644 index 0000000000..2415c6c000 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CasExpression.cs @@ -0,0 +1,30 @@ +using System; +using System.IO; + + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class CastExpression : BaseNode + { + private string Kind; + private BaseNode To; + private BaseNode From; + + public CastExpression(string Kind, BaseNode To, BaseNode From) : base(NodeType.CastExpression) + { + this.Kind = Kind; + this.To = To; + this.From = From; + } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write(Kind); + Writer.Write("<"); + To.PrintLeft(Writer); + Writer.Write(">("); + From.PrintLeft(Writer); + Writer.Write(")"); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConditionalExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConditionalExpression.cs new file mode 100644 index 0000000000..17ac7c1a20 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConditionalExpression.cs @@ -0,0 +1,29 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ConditionalExpression : BaseNode + { + private BaseNode ThenNode; + private BaseNode ElseNode; + private BaseNode ConditionNode; + + public ConditionalExpression(BaseNode ConditionNode, BaseNode ThenNode, BaseNode ElseNode) : base(NodeType.ConditionalExpression) + { + this.ThenNode = ThenNode; + this.ConditionNode = ConditionNode; + this.ElseNode = ElseNode; + } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write("("); + ConditionNode.Print(Writer); + Writer.Write(") ? ("); + ThenNode.Print(Writer); + Writer.Write(") : ("); + ElseNode.Print(Writer); + Writer.Write(")"); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionExpression.cs new file mode 100644 index 0000000000..7c5d35d810 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionExpression.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ConversionExpression : BaseNode + { + private BaseNode TypeNode; + private BaseNode Expressions; + + public ConversionExpression(BaseNode TypeNode, BaseNode Expressions) : base(NodeType.ConversionExpression) + { + this.TypeNode = TypeNode; + this.Expressions = Expressions; + } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write("("); + TypeNode.Print(Writer); + Writer.Write(")("); + Expressions.Print(Writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionOperatorType.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionOperatorType.cs new file mode 100644 index 0000000000..55d4eecab9 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionOperatorType.cs @@ -0,0 +1,15 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ConversionOperatorType : ParentNode + { + public ConversionOperatorType(BaseNode Child) : base(NodeType.ConversionOperatorType, Child) { } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write("operator "); + Child.Print(Writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorDtorNameType.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorDtorNameType.cs new file mode 100644 index 0000000000..49ed386d08 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorDtorNameType.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class CtorDtorNameType : ParentNode + { + private bool IsDestructor; + + public CtorDtorNameType(BaseNode Name, bool IsDestructor) : base(NodeType.CtorDtorNameType, Name) + { + this.IsDestructor = IsDestructor; + } + + public override void PrintLeft(TextWriter Writer) + { + if (IsDestructor) + { + Writer.Write("~"); + } + + Writer.Write(Child.GetName()); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorVtableSpecialName.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorVtableSpecialName.cs new file mode 100644 index 0000000000..7630dbe570 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorVtableSpecialName.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class CtorVtableSpecialName : BaseNode + { + private BaseNode FirstType; + private BaseNode SecondType; + + public CtorVtableSpecialName(BaseNode FirstType, BaseNode SecondType) : base(NodeType.CtorVtableSpecialName) + { + this.FirstType = FirstType; + this.SecondType = SecondType; + } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write("construction vtable for "); + FirstType.Print(Writer); + Writer.Write("-in-"); + SecondType.Print(Writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DeleteExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DeleteExpression.cs new file mode 100644 index 0000000000..22c34c4219 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DeleteExpression.cs @@ -0,0 +1,33 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class DeleteExpression : ParentNode + { + private bool IsGlobal; + private bool IsArrayExpression; + + public DeleteExpression(BaseNode Child, bool IsGlobal, bool IsArrayExpression) : base(NodeType.DeleteExpression, Child) + { + this.IsGlobal = IsGlobal; + this.IsArrayExpression = IsArrayExpression; + } + + public override void PrintLeft(TextWriter Writer) + { + if (IsGlobal) + { + Writer.Write("::"); + } + + Writer.Write("delete"); + + if (IsArrayExpression) + { + Writer.Write("[] "); + } + + Child.Print(Writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DtorName.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DtorName.cs new file mode 100644 index 0000000000..c65c4cfb3a --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DtorName.cs @@ -0,0 +1,15 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class DtorName : ParentNode + { + public DtorName(BaseNode Name) : base(NodeType.DtOrName, Name) { } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write("~"); + Child.PrintLeft(Writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DynamicExceptionSpec.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DynamicExceptionSpec.cs new file mode 100644 index 0000000000..dca5f0dfd0 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DynamicExceptionSpec.cs @@ -0,0 +1,16 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class DynamicExceptionSpec : ParentNode + { + public DynamicExceptionSpec(BaseNode Child) : base(NodeType.DynamicExceptionSpec, Child) { } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write("throw("); + Child.Print(Writer); + Writer.Write(")"); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ElaboratedType.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ElaboratedType.cs new file mode 100644 index 0000000000..11f89c8d33 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ElaboratedType.cs @@ -0,0 +1,21 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ElaboratedType : ParentNode + { + private string Elaborated; + + public ElaboratedType(string Elaborated, BaseNode Type) : base(NodeType.ElaboratedType, Type) + { + this.Elaborated = Elaborated; + } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write(Elaborated); + Writer.Write(" "); + Child.Print(Writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EnclosedExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EnclosedExpression.cs new file mode 100644 index 0000000000..dc991aa09c --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EnclosedExpression.cs @@ -0,0 +1,25 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class EnclosedExpression : BaseNode + { + private string Prefix; + private BaseNode Expression; + private string Postfix; + + public EnclosedExpression(string Prefix, BaseNode Expression, string Postfix) : base(NodeType.EnclosedExpression) + { + this.Prefix = Prefix; + this.Expression = Expression; + this.Postfix = Postfix; + } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write(Prefix); + Expression.Print(Writer); + Writer.Write(Postfix); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EncodedFunction.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EncodedFunction.cs new file mode 100644 index 0000000000..37a9a7afe0 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EncodedFunction.cs @@ -0,0 +1,77 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class EncodedFunction : BaseNode + { + private BaseNode Name; + private BaseNode Params; + private BaseNode CV; + private BaseNode Ref; + private BaseNode Attrs; + private BaseNode Ret; + + public EncodedFunction(BaseNode Name, BaseNode Params, BaseNode CV, BaseNode Ref, BaseNode Attrs, BaseNode Ret) : base(NodeType.NameType) + { + this.Name = Name; + this.Params = Params; + this.CV = CV; + this.Ref = Ref; + this.Attrs = Attrs; + this.Ret = Ret; + } + + public override void PrintLeft(TextWriter Writer) + { + if (Ret != null) + { + Ret.PrintLeft(Writer); + + if (!Ret.HasRightPart()) + { + Writer.Write(" "); + } + } + + Name.Print(Writer); + + } + + public override bool HasRightPart() + { + return true; + } + + public override void PrintRight(TextWriter Writer) + { + Writer.Write("("); + + if (Params != null) + { + Params.Print(Writer); + } + + Writer.Write(")"); + + if (Ret != null) + { + Ret.PrintRight(Writer); + } + + if (CV != null) + { + CV.Print(Writer); + } + + if (Ref != null) + { + Ref.Print(Writer); + } + + if (Attrs != null) + { + Attrs.Print(Writer); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FoldExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FoldExpression.cs new file mode 100644 index 0000000000..e015299846 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FoldExpression.cs @@ -0,0 +1,48 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class FoldExpression : BaseNode + { + private bool IsLeftFold; + private string OperatorName; + private BaseNode Expression; + private BaseNode Initializer; + + public FoldExpression(bool IsLeftFold, string OperatorName, BaseNode Expression, BaseNode Initializer) : base(NodeType.FunctionParameter) + { + this.IsLeftFold = IsLeftFold; + this.OperatorName = OperatorName; + this.Expression = Expression; + this.Initializer = Initializer; + } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write("("); + + if (IsLeftFold && Initializer != null) + { + Initializer.Print(Writer); + Writer.Write(" "); + Writer.Write(OperatorName); + Writer.Write(" "); + } + + Writer.Write(IsLeftFold ? "... " : " "); + Writer.Write(OperatorName); + Writer.Write(!IsLeftFold ? " ..." : " "); + Expression.Print(Writer); + + if (!IsLeftFold && Initializer != null) + { + Initializer.Print(Writer); + Writer.Write(" "); + Writer.Write(OperatorName); + Writer.Write(" "); + } + + Writer.Write(")"); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ForwardTemplateReference.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ForwardTemplateReference.cs new file mode 100644 index 0000000000..6456e47bfa --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ForwardTemplateReference.cs @@ -0,0 +1,36 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ForwardTemplateReference : BaseNode + { + // TODO: Compute inside the Demangler + public BaseNode Reference; + private int Index; + + public ForwardTemplateReference(int Index) : base(NodeType.ForwardTemplateReference) + { + this.Index = Index; + } + + public override string GetName() + { + return Reference.GetName(); + } + + public override void PrintLeft(TextWriter Writer) + { + Reference.PrintLeft(Writer); + } + + public override void PrintRight(TextWriter Writer) + { + Reference.PrintRight(Writer); + } + + public override bool HasRightPart() + { + return Reference.HasRightPart(); + } + } +} diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionParameter.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionParameter.cs new file mode 100644 index 0000000000..5a1ca61d55 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionParameter.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class FunctionParameter : BaseNode + { + private string Number; + + public FunctionParameter(string Number) : base(NodeType.FunctionParameter) + { + this.Number = Number; + } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write("fp "); + + if (Number != null) + { + Writer.Write(Number); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionType.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionType.cs new file mode 100644 index 0000000000..c727eab9c5 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionType.cs @@ -0,0 +1,61 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class FunctionType : BaseNode + { + private BaseNode ReturnType; + private BaseNode Params; + private BaseNode CVQualifier; + private SimpleReferenceType ReferenceQualifier; + private BaseNode ExceptionSpec; + + public FunctionType(BaseNode ReturnType, BaseNode Params, BaseNode CVQualifier, SimpleReferenceType ReferenceQualifier, BaseNode ExceptionSpec) : base(NodeType.FunctionType) + { + this.ReturnType = ReturnType; + this.Params = Params; + this.CVQualifier = CVQualifier; + this.ReferenceQualifier = ReferenceQualifier; + this.ExceptionSpec = ExceptionSpec; + } + + public override void PrintLeft(TextWriter Writer) + { + ReturnType.PrintLeft(Writer); + Writer.Write(" "); + } + + public override void PrintRight(TextWriter Writer) + { + Writer.Write("("); + Params.Print(Writer); + Writer.Write(")"); + + ReturnType.PrintRight(Writer); + + CVQualifier.Print(Writer); + + if (ReferenceQualifier.Qualifier != Reference.None) + { + Writer.Write(" "); + ReferenceQualifier.PrintQualifier(Writer); + } + + if (ExceptionSpec != null) + { + Writer.Write(" "); + ExceptionSpec.Print(Writer); + } + } + + public override bool HasRightPart() + { + return true; + } + + public override bool HasFunctions() + { + return true; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/GlobalQualifiedName.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/GlobalQualifiedName.cs new file mode 100644 index 0000000000..2346c1bf70 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/GlobalQualifiedName.cs @@ -0,0 +1,15 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class GlobalQualifiedName : ParentNode + { + public GlobalQualifiedName(BaseNode Child) : base(NodeType.GlobalQualifiedName, Child) { } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write("::"); + Child.Print(Writer); + } + } +} diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/InitListExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/InitListExpression.cs new file mode 100644 index 0000000000..cd534590b9 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/InitListExpression.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class InitListExpression : BaseNode + { + private BaseNode TypeNode; + private List Nodes; + + public InitListExpression(BaseNode TypeNode, List Nodes) : base(NodeType.InitListExpression) + { + this.TypeNode = TypeNode; + this.Nodes = Nodes; + } + + public override void PrintLeft(TextWriter Writer) + { + if (TypeNode != null) + { + TypeNode.Print(Writer); + } + + Writer.Write("{"); + Writer.Write(string.Join(", ", Nodes.ToArray())); + Writer.Write("}"); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerCastExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerCastExpression.cs new file mode 100644 index 0000000000..984c9aefb0 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerCastExpression.cs @@ -0,0 +1,22 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class IntegerCastExpression : ParentNode + { + private string Number; + + public IntegerCastExpression(BaseNode Type, string Number) : base(NodeType.IntegerCastExpression, Type) + { + this.Number = Number; + } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write("("); + Child.Print(Writer); + Writer.Write(")"); + Writer.Write(Number); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerLiteral.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerLiteral.cs new file mode 100644 index 0000000000..215cf6dce5 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerLiteral.cs @@ -0,0 +1,41 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class IntegerLiteral : BaseNode + { + private string LitteralName; + private string LitteralValue; + + public IntegerLiteral(string LitteralName, string LitteralValue) : base(NodeType.IntegerLiteral) + { + this.LitteralValue = LitteralValue; + this.LitteralName = LitteralName; + } + + public override void PrintLeft(TextWriter Writer) + { + if (LitteralName.Length > 3) + { + Writer.Write("("); + Writer.Write(LitteralName); + Writer.Write(")"); + } + + if (LitteralValue[0] == 'n') + { + Writer.Write("-"); + Writer.Write(LitteralValue.Substring(1)); + } + else + { + Writer.Write(LitteralValue); + } + + if (LitteralName.Length <= 3) + { + Writer.Write(LitteralName); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LiteralOperator.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LiteralOperator.cs new file mode 100644 index 0000000000..f9bd4a6ef5 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LiteralOperator.cs @@ -0,0 +1,16 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class LiteralOperator : ParentNode + { + public LiteralOperator(BaseNode Child) : base(NodeType.LiteralOperator, Child) { } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write("operator \""); + Child.PrintLeft(Writer); + Writer.Write("\""); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LocalName.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LocalName.cs new file mode 100644 index 0000000000..44c216289c --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LocalName.cs @@ -0,0 +1,23 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class LocalName : BaseNode + { + private BaseNode Encoding; + private BaseNode Entity; + + public LocalName(BaseNode Encoding, BaseNode Entity) : base(NodeType.LocalName) + { + this.Encoding = Encoding; + this.Entity = Entity; + } + + public override void PrintLeft(TextWriter Writer) + { + Encoding.Print(Writer); + Writer.Write("::"); + Entity.Print(Writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/MemberExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/MemberExpression.cs new file mode 100644 index 0000000000..dd3d02dbd1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/MemberExpression.cs @@ -0,0 +1,25 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class MemberExpression : BaseNode + { + private BaseNode LeftNode; + private string Kind; + private BaseNode RightNode; + + public MemberExpression(BaseNode LeftNode, string Kind, BaseNode RightNode) : base(NodeType.MemberExpression) + { + this.LeftNode = LeftNode; + this.Kind = Kind; + this.RightNode = RightNode; + } + + public override void PrintLeft(TextWriter Writer) + { + LeftNode.Print(Writer); + Writer.Write(Kind); + RightNode.Print(Writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameType.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameType.cs new file mode 100644 index 0000000000..029440cb77 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameType.cs @@ -0,0 +1,29 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class NameType : BaseNode + { + private string NameValue; + + public NameType(string NameValue, NodeType Type) : base(Type) + { + this.NameValue = NameValue; + } + + public NameType(string NameValue) : base(NodeType.NameType) + { + this.NameValue = NameValue; + } + + public override string GetName() + { + return NameValue; + } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write(NameValue); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameTypeWithTemplateArguments.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameTypeWithTemplateArguments.cs new file mode 100644 index 0000000000..e16bd1508b --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameTypeWithTemplateArguments.cs @@ -0,0 +1,27 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class NameTypeWithTemplateArguments : BaseNode + { + private BaseNode Prev; + private BaseNode TemplateArgument; + + public NameTypeWithTemplateArguments(BaseNode Prev, BaseNode TemplateArgument) : base(NodeType.NameTypeWithTemplateArguments) + { + this.Prev = Prev; + this.TemplateArgument = TemplateArgument; + } + + public override string GetName() + { + return Prev.GetName(); + } + + public override void PrintLeft(TextWriter Writer) + { + Prev.Print(Writer); + TemplateArgument.Print(Writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NestedName.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NestedName.cs new file mode 100644 index 0000000000..0ec6d98299 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NestedName.cs @@ -0,0 +1,26 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class NestedName : ParentNode + { + private BaseNode Name; + + public NestedName(BaseNode Name, BaseNode Type) : base(NodeType.NestedName, Type) + { + this.Name = Name; + } + + public override string GetName() + { + return Name.GetName(); + } + + public override void PrintLeft(TextWriter Writer) + { + Child.Print(Writer); + Writer.Write("::"); + Name.Print(Writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NewExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NewExpression.cs new file mode 100644 index 0000000000..5cc14ad9f8 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NewExpression.cs @@ -0,0 +1,55 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class NewExpression : BaseNode + { + private NodeArray Expressions; + private BaseNode TypeNode; + private NodeArray Initializers; + + private bool IsGlobal; + private bool IsArrayExpression; + + public NewExpression(NodeArray Expressions, BaseNode TypeNode, NodeArray Initializers, bool IsGlobal, bool IsArrayExpression) : base(NodeType.NewExpression) + { + this.Expressions = Expressions; + this.TypeNode = TypeNode; + this.Initializers = Initializers; + + this.IsGlobal = IsGlobal; + this.IsArrayExpression = IsArrayExpression; + } + + public override void PrintLeft(TextWriter Writer) + { + if (IsGlobal) + { + Writer.Write("::operator "); + } + + Writer.Write("new "); + + if (IsArrayExpression) + { + Writer.Write("[] "); + } + + if (Expressions.Nodes.Count != 0) + { + Writer.Write("("); + Expressions.Print(Writer); + Writer.Write(")"); + } + + TypeNode.Print(Writer); + + if (Initializers.Nodes.Count != 0) + { + Writer.Write("("); + Initializers.Print(Writer); + Writer.Write(")"); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NodeArray.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NodeArray.cs new file mode 100644 index 0000000000..9720a8e49d --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NodeArray.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class NodeArray : BaseNode + { + public List Nodes { get; protected set; } + + public NodeArray(List Nodes) : base(NodeType.NodeArray) + { + this.Nodes = Nodes; + } + + public NodeArray(List Nodes, NodeType Type) : base(Type) + { + this.Nodes = Nodes; + } + + public override bool IsArray() + { + return true; + } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write(string.Join(", ", Nodes.ToArray())); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NoexceptSpec.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NoexceptSpec.cs new file mode 100644 index 0000000000..5bee9cfacb --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NoexceptSpec.cs @@ -0,0 +1,16 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class NoexceptSpec : ParentNode + { + public NoexceptSpec(BaseNode Child) : base(NodeType.NoexceptSpec, Child) { } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write("noexcept("); + Child.Print(Writer); + Writer.Write(")"); + } + } +} diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameter.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameter.cs new file mode 100644 index 0000000000..66ad112277 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameter.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class PackedTemplateParameter : NodeArray + { + public PackedTemplateParameter(List Nodes) : base(Nodes, NodeType.PackedTemplateParameter) { } + + public override void PrintLeft(TextWriter Writer) + { + foreach (BaseNode Node in Nodes) + { + Node.PrintLeft(Writer); + } + } + + public override void PrintRight(TextWriter Writer) + { + foreach (BaseNode Node in Nodes) + { + Node.PrintLeft(Writer); + } + } + + public override bool HasRightPart() + { + foreach (BaseNode Node in Nodes) + { + if (Node.HasRightPart()) + { + return true; + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameterExpansion.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameterExpansion.cs new file mode 100644 index 0000000000..ce9fa4a3ba --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameterExpansion.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class PackedTemplateParameterExpansion : ParentNode + { + public PackedTemplateParameterExpansion(BaseNode Child) : base(NodeType.PackedTemplateParameterExpansion, Child) {} + + public override void PrintLeft(TextWriter Writer) + { + if (Child is PackedTemplateParameter) + { + if (((PackedTemplateParameter)Child).Nodes.Count != 0) + { + Child.Print(Writer); + } + } + else + { + Writer.Write("..."); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ParentNode.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ParentNode.cs new file mode 100644 index 0000000000..f1c2834739 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ParentNode.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public abstract class ParentNode : BaseNode + { + public BaseNode Child { get; private set; } + + public ParentNode(NodeType Type, BaseNode Child) : base(Type) + { + this.Child = Child; + } + + public override string GetName() + { + return Child.GetName(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PointerType.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PointerType.cs new file mode 100644 index 0000000000..a60776a272 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PointerType.cs @@ -0,0 +1,45 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class PointerType : BaseNode + { + private BaseNode Child; + + public PointerType(BaseNode Child) : base(NodeType.PointerType) + { + this.Child = Child; + } + + public override bool HasRightPart() + { + return Child.HasRightPart(); + } + + public override void PrintLeft(TextWriter Writer) + { + Child.PrintLeft(Writer); + if (Child.IsArray()) + { + Writer.Write(" "); + } + + if (Child.IsArray() || Child.HasFunctions()) + { + Writer.Write("("); + } + + Writer.Write("*"); + } + + public override void PrintRight(TextWriter Writer) + { + if (Child.IsArray() || Child.HasFunctions()) + { + Writer.Write(")"); + } + + Child.PrintRight(Writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixExpression.cs new file mode 100644 index 0000000000..021f2de820 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixExpression.cs @@ -0,0 +1,22 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class PostfixExpression : ParentNode + { + private string Operator; + + public PostfixExpression(BaseNode Type, string Operator) : base(NodeType.PostfixExpression, Type) + { + this.Operator = Operator; + } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write("("); + Child.Print(Writer); + Writer.Write(")"); + Writer.Write(Operator); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixQualifiedType.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixQualifiedType.cs new file mode 100644 index 0000000000..465450d3f2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixQualifiedType.cs @@ -0,0 +1,20 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class PostfixQualifiedType : ParentNode + { + private string PostfixQualifier; + + public PostfixQualifiedType(string PostfixQualifier, BaseNode Type) : base(NodeType.PostfixQualifiedType, Type) + { + this.PostfixQualifier = PostfixQualifier; + } + + public override void PrintLeft(TextWriter Writer) + { + Child.Print(Writer); + Writer.Write(PostfixQualifier); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PrefixExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PrefixExpression.cs new file mode 100644 index 0000000000..619d05387b --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PrefixExpression.cs @@ -0,0 +1,22 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class PrefixExpression : ParentNode + { + private string Prefix; + + public PrefixExpression(string Prefix, BaseNode Child) : base(NodeType.PrefixExpression, Child) + { + this.Prefix = Prefix; + } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write(Prefix); + Writer.Write("("); + Child.Print(Writer); + Writer.Write(")"); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/QualifiedName.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/QualifiedName.cs new file mode 100644 index 0000000000..ce356e1628 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/QualifiedName.cs @@ -0,0 +1,23 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class QualifiedName : BaseNode + { + private BaseNode Qualifier; + private BaseNode Name; + + public QualifiedName(BaseNode Qualifier, BaseNode Name) : base(NodeType.QualifiedName) + { + this.Qualifier = Qualifier; + this.Name = Name; + } + + public override void PrintLeft(TextWriter Writer) + { + Qualifier.Print(Writer); + Writer.Write("::"); + Name.Print(Writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/Qualifier.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/Qualifier.cs new file mode 100644 index 0000000000..3721b8dee3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/Qualifier.cs @@ -0,0 +1,120 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public enum CV + { + None, + Const, + Volatile, + Restricted = 4 + } + + public enum Reference + { + None, + RValue, + LValue + } + + public class CVType : ParentNode + { + public CV Qualifier; + + public CVType(CV Qualifier, BaseNode Child) : base(NodeType.CVQualifierType, Child) + { + this.Qualifier = Qualifier; + } + + public void PrintQualifier(TextWriter Writer) + { + if ((Qualifier & CV.Const) != 0) + { + Writer.Write(" const"); + } + + if ((Qualifier & CV.Volatile) != 0) + { + Writer.Write(" volatile"); + } + + if ((Qualifier & CV.Restricted) != 0) + { + Writer.Write(" restrict"); + } + } + + public override void PrintLeft(TextWriter Writer) + { + if (Child != null) + { + Child.PrintLeft(Writer); + } + + PrintQualifier(Writer); + } + + public override bool HasRightPart() + { + return Child != null && Child.HasRightPart(); + } + + public override void PrintRight(TextWriter Writer) + { + if (Child != null) + { + Child.PrintRight(Writer); + } + } + } + + public class SimpleReferenceType : ParentNode + { + public Reference Qualifier; + + public SimpleReferenceType(Reference Qualifier, BaseNode Child) : base(NodeType.SimpleReferenceType, Child) + { + this.Qualifier = Qualifier; + } + + public void PrintQualifier(TextWriter Writer) + { + if ((Qualifier & Reference.LValue) != 0) + { + Writer.Write("&"); + } + + if ((Qualifier & Reference.RValue) != 0) + { + Writer.Write("&&"); + } + } + + public override void PrintLeft(TextWriter Writer) + { + if (Child != null) + { + Child.PrintLeft(Writer); + } + else if (Qualifier != Reference.None) + { + Writer.Write(" "); + } + + PrintQualifier(Writer); + } + + public override bool HasRightPart() + { + return Child != null && Child.HasRightPart(); + } + + public override void PrintRight(TextWriter Writer) + { + if (Child != null) + { + Child.PrintRight(Writer); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ReferenceType.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ReferenceType.cs new file mode 100644 index 0000000000..602814afb3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ReferenceType.cs @@ -0,0 +1,47 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ReferenceType : BaseNode + { + private string Reference; + private BaseNode Child; + + public ReferenceType(string Reference, BaseNode Child) : base(NodeType.ReferenceType) + { + this.Reference = Reference; + this.Child = Child; + } + + public override bool HasRightPart() + { + return Child.HasRightPart(); + } + + public override void PrintLeft(TextWriter Writer) + { + Child.PrintLeft(Writer); + + if (Child.IsArray()) + { + Writer.Write(" "); + } + + if (Child.IsArray() || Child.HasFunctions()) + { + Writer.Write("("); + } + + Writer.Write(Reference); + } + public override void PrintRight(TextWriter Writer) + { + if (Child.IsArray() || Child.HasFunctions()) + { + Writer.Write(")"); + } + + Child.PrintRight(Writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialName.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialName.cs new file mode 100644 index 0000000000..1a299af4d2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialName.cs @@ -0,0 +1,20 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class SpecialName : ParentNode + { + private string SpecialValue; + + public SpecialName(string SpecialValue, BaseNode Type) : base(NodeType.SpecialName, Type) + { + this.SpecialValue = SpecialValue; + } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write(SpecialValue); + Child.Print(Writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialSubstitution.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialSubstitution.cs new file mode 100644 index 0000000000..f4e9a14a12 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialSubstitution.cs @@ -0,0 +1,89 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class SpecialSubstitution : BaseNode + { + public enum SpecialType + { + Allocator, + BasicString, + String, + IStream, + OStream, + IOStream, + } + + private SpecialType SpecialSubstitutionKey; + + public SpecialSubstitution(SpecialType SpecialSubstitutionKey) : base(NodeType.SpecialSubstitution) + { + this.SpecialSubstitutionKey = SpecialSubstitutionKey; + } + + public void SetExtended() + { + Type = NodeType.ExpandedSpecialSubstitution; + } + + public override string GetName() + { + switch (SpecialSubstitutionKey) + { + case SpecialType.Allocator: + return "allocator"; + case SpecialType.BasicString: + return "basic_string"; + case SpecialType.String: + if (Type == NodeType.ExpandedSpecialSubstitution) + { + return "basic_string"; + } + + return "string"; + case SpecialType.IStream: + return "istream"; + case SpecialType.OStream: + return "ostream"; + case SpecialType.IOStream: + return "iostream"; + } + + return null; + } + + private string GetExtendedName() + { + switch (SpecialSubstitutionKey) + { + case SpecialType.Allocator: + return "std::allocator"; + case SpecialType.BasicString: + return "std::basic_string"; + case SpecialType.String: + return "std::basic_string, std::allocator >"; + case SpecialType.IStream: + return "std::basic_istream >"; + case SpecialType.OStream: + return "std::basic_ostream >"; + case SpecialType.IOStream: + return "std::basic_iostream >"; + } + + return null; + } + + public override void PrintLeft(TextWriter Writer) + { + if (Type == NodeType.ExpandedSpecialSubstitution) + { + Writer.Write(GetExtendedName()); + } + else + { + Writer.Write("std::"); + Writer.Write(GetName()); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/StdQualifiedName.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/StdQualifiedName.cs new file mode 100644 index 0000000000..ed1b599429 --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/StdQualifiedName.cs @@ -0,0 +1,15 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class StdQualifiedName : ParentNode + { + public StdQualifiedName(BaseNode Child) : base(NodeType.StdQualifiedName, Child) { } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write("std::"); + Child.Print(Writer); + } + } +} diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/TemplateArguments.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/TemplateArguments.cs new file mode 100644 index 0000000000..4de66e000b --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/TemplateArguments.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class TemplateArguments : NodeArray + { + public TemplateArguments(List Nodes) : base(Nodes, NodeType.TemplateArguments) { } + + public override void PrintLeft(TextWriter Writer) + { + string Params = string.Join(", ", Nodes.ToArray()); + + Writer.Write("<"); + + Writer.Write(Params); + + if (Params.EndsWith(">")) + { + Writer.Write(" "); + } + + Writer.Write(">"); + } + } +} diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ThrowExpression.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ThrowExpression.cs new file mode 100644 index 0000000000..bb1466174e --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ThrowExpression.cs @@ -0,0 +1,20 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ThrowExpression : BaseNode + { + private BaseNode Expression; + + public ThrowExpression(BaseNode Expression) : base(NodeType.ThrowExpression) + { + this.Expression = Expression; + } + + public override void PrintLeft(TextWriter Writer) + { + Writer.Write("throw "); + Expression.Print(Writer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs new file mode 100644 index 0000000000..164d5618ca --- /dev/null +++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs @@ -0,0 +1,3367 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler +{ + class Demangler + { + private static readonly string BASE_36 = "0123456789abcdefghijklmnopqrstuvwxyz"; + private List SubstitutionList = new List(); + private List TemplateParamList = new List(); + + private List ForwardTemplateReferenceList = new List(); + + public string Mangled { get; private set; } + + private int Position; + private int Length; + + private bool CanForwardTemplateReference; + private bool CanParseTemplateArgs; + + public Demangler(string Mangled) + { + this.Mangled = Mangled; + Position = 0; + Length = Mangled.Length; + CanParseTemplateArgs = true; + } + + private bool ConsumeIf(string ToConsume) + { + string MangledPart = Mangled.Substring(Position); + + if (MangledPart.StartsWith(ToConsume)) + { + Position += ToConsume.Length; + + return true; + } + + return false; + } + + private string PeekString(int Offset = 0, int Length = 1) + { + if (Position + Offset >= Length) + { + return null; + } + + return Mangled.Substring(Position + Offset, Length); + } + + private char Peek(int Offset = 0) + { + if (Position + Offset >= Length) + { + return '\0'; + } + + return Mangled[Position + Offset]; + } + + private char Consume() + { + if (Position < Length) + { + return Mangled[Position++]; + } + + return '\0'; + } + + private int Count() + { + return Length - Position; + } + + private static int FromBase36(string Encoded) + { + char[] ReversedEncoded = Encoded.ToLower().ToCharArray().Reverse().ToArray(); + + int Result = 0; + + for (int i = 0; i < ReversedEncoded.Length; i++) + { + int Value = BASE_36.IndexOf(ReversedEncoded[i]); + if (Value == -1) + { + return -1; + } + + Result += Value * (int)Math.Pow(36, i); + } + + return Result; + } + + private int ParseSeqId() + { + string Part = Mangled.Substring(Position); + int SeqIdLen = 0; + + for (; SeqIdLen < Part.Length; SeqIdLen++) + { + if (!char.IsLetterOrDigit(Part[SeqIdLen])) + { + break; + } + } + + Position += SeqIdLen; + + return FromBase36(Part.Substring(0, SeqIdLen)); + } + + // ::= S _ + // ::= S_ + // ::= St # std:: + // ::= Sa # std::allocator + // ::= Sb # std::basic_string + // ::= Ss # std::basic_string, std::allocator > + // ::= Si # std::basic_istream > + // ::= So # std::basic_ostream > + // ::= Sd # std::basic_iostream > + private BaseNode ParseSubstitution() + { + if (!ConsumeIf("S")) + { + return null; + } + + char SubstitutionSecondChar = Peek(); + if (char.IsLower(SubstitutionSecondChar)) + { + switch (SubstitutionSecondChar) + { + case 'a': + Position++; + return new SpecialSubstitution(SpecialSubstitution.SpecialType.Allocator); + case 'b': + Position++; + return new SpecialSubstitution(SpecialSubstitution.SpecialType.BasicString); + case 's': + Position++; + return new SpecialSubstitution(SpecialSubstitution.SpecialType.String); + case 'i': + Position++; + return new SpecialSubstitution(SpecialSubstitution.SpecialType.IStream); + case 'o': + Position++; + return new SpecialSubstitution(SpecialSubstitution.SpecialType.OStream); + case 'd': + Position++; + return new SpecialSubstitution(SpecialSubstitution.SpecialType.IOStream); + default: + return null; + } + } + + // ::= S_ + if (ConsumeIf("_")) + { + if (SubstitutionList.Count != 0) + { + return SubstitutionList[0]; + } + + return null; + } + + // ::= S _ + int SeqId = ParseSeqId(); + if (SeqId < 0) + { + return null; + } + + SeqId++; + + if (!ConsumeIf("_") || SeqId >= SubstitutionList.Count) + { + return null; + } + + return SubstitutionList[SeqId]; + } + + // NOTE: thoses data aren't used in the output + // ::= h _ + // ::= v _ + // ::= + // # non-virtual base override + // ::= _ + // # virtual base override, with vcall offset + private bool ParseCallOffset() + { + if (ConsumeIf("h")) + { + return ParseNumber(true).Length == 0 || !ConsumeIf("_"); + } + else if (ConsumeIf("v")) + { + return ParseNumber(true).Length == 0 || !ConsumeIf("_") || ParseNumber(true).Length == 0 || !ConsumeIf("_"); + } + + return true; + } + + + // ::= # non-dependent type name, dependent type name, or dependent typename-specifier + // ::= Ts # dependent elaborated type specifier using 'struct' or 'class' + // ::= Tu # dependent elaborated type specifier using 'union' + // ::= Te # dependent elaborated type specifier using 'enum' + private BaseNode ParseClassEnumType() + { + string ElaboratedType = null; + + if (ConsumeIf("Ts")) + { + ElaboratedType = "struct"; + } + else if (ConsumeIf("Tu")) + { + ElaboratedType = "union"; + } + else if (ConsumeIf("Te")) + { + ElaboratedType = "enum"; + } + + BaseNode Name = ParseName(); + if (Name == null) + { + return null; + } + + if (ElaboratedType == null) + { + return Name; + } + + return new ElaboratedType(ElaboratedType, Name); + } + + // ::= [] [] [Dx] F [Y] [] E + // ::= + + // # types are possible return type, then parameter types + // ::= Do # non-throwing exception-specification (e.g., noexcept, throw()) + // ::= DO E # computed (instantiation-dependent) noexcept + // ::= Dw + E # dynamic exception specification with instantiation-dependent types + private BaseNode ParseFunctionType() + { + CV CVQualifiers = ParseCVQualifiers(); + + BaseNode ExceptionSpec = null; + + if (ConsumeIf("Do")) + { + ExceptionSpec = new NameType("noexcept"); + } + else if (ConsumeIf("DO")) + { + BaseNode Expression = ParseExpression(); + if (Expression == null || !ConsumeIf("E")) + { + return null; + } + + ExceptionSpec = new NoexceptSpec(Expression); + } + else if (ConsumeIf("Dw")) + { + List Types = new List(); + + while (!ConsumeIf("E")) + { + BaseNode Type = ParseType(); + if (Type == null) + { + return null; + } + + Types.Add(Type); + } + + ExceptionSpec = new DynamicExceptionSpec(new NodeArray(Types)); + } + + // We don't need the transaction + ConsumeIf("Dx"); + + if (!ConsumeIf("F")) + { + return null; + } + + // extern "C" + ConsumeIf("Y"); + + BaseNode ReturnType = ParseType(); + if (ReturnType == null) + { + return null; + } + + Reference ReferenceQualifier = Reference.None; + List Params = new List(); + + while (true) + { + if (ConsumeIf("E")) + { + break; + } + + if (ConsumeIf("v")) + { + continue; + } + + if (ConsumeIf("RE")) + { + ReferenceQualifier = Reference.LValue; + break; + } + else if (ConsumeIf("OE")) + { + ReferenceQualifier = Reference.RValue; + break; + } + + BaseNode Type = ParseType(); + if (Type == null) + { + return null; + } + + Params.Add(Type); + } + + return new FunctionType(ReturnType, new NodeArray(Params), new CVType(CVQualifiers, null), new SimpleReferenceType(ReferenceQualifier, null), ExceptionSpec); + } + + // ::= A _ + // ::= A [] _ + private BaseNode ParseArrayType() + { + if (!ConsumeIf("A")) + { + return null; + } + + BaseNode ElementType; + if (char.IsDigit(Peek())) + { + string Dimension = ParseNumber(); + if (Dimension.Length == 0 || !ConsumeIf("_")) + { + return null; + } + + ElementType = ParseType(); + if (ElementType == null) + { + return null; + } + + return new ArrayType(ElementType, Dimension); + } + + if (!ConsumeIf("_")) + { + BaseNode DimensionExpression = ParseExpression(); + if (DimensionExpression == null || !ConsumeIf("_")) + { + return null; + } + + ElementType = ParseType(); + if (ElementType == null) + { + return null; + } + + return new ArrayType(ElementType, DimensionExpression); + } + + ElementType = ParseType(); + if (ElementType == null) + { + return null; + } + + return new ArrayType(ElementType); + } + + // ::= + // ::= (PARTIAL) + // ::= + // ::= + // ::= (TODO) + // ::= (TODO) + // ::= + // ::= + // ::= + // ::= P # pointer + // ::= R # l-value reference + // ::= O # r-value reference (C++11) + // ::= C # complex pair (C99) + // ::= G # imaginary (C99) + // ::= # See Compression below + private BaseNode ParseType(NameParserContext Context = null) + { + // Temporary context + if (Context == null) + { + Context = new NameParserContext(); + } + + BaseNode Result = null; + switch (Peek()) + { + case 'r': + case 'V': + case 'K': + int TypePos = 0; + + if (Peek(TypePos) == 'r') + { + TypePos++; + } + + if (Peek(TypePos) == 'V') + { + TypePos++; + } + + if (Peek(TypePos) == 'K') + { + TypePos++; + } + + if (Peek(TypePos) == 'F' || (Peek(TypePos) == 'D' && (Peek(TypePos + 1) == 'o' || Peek(TypePos + 1) == 'O' || Peek(TypePos + 1) == 'w' || Peek(TypePos + 1) == 'x'))) + { + Result = ParseFunctionType(); + break; + } + + CV CV = ParseCVQualifiers(); + + Result = ParseType(Context); + + if (Result == null) + { + return null; + } + + Result = new CVType(CV, Result); + break; + case 'U': + // TODO: + return null; + case 'v': + Position++; + return new NameType("void"); + case 'w': + Position++; + return new NameType("wchar_t"); + case 'b': + Position++; + return new NameType("bool"); + case 'c': + Position++; + return new NameType("char"); + case 'a': + Position++; + return new NameType("signed char"); + case 'h': + Position++; + return new NameType("unsigned char"); + case 's': + Position++; + return new NameType("short"); + case 't': + Position++; + return new NameType("unsigned short"); + case 'i': + Position++; + return new NameType("int"); + case 'j': + Position++; + return new NameType("unsigned int"); + case 'l': + Position++; + return new NameType("long"); + case 'm': + Position++; + return new NameType("unsigned long"); + case 'x': + Position++; + return new NameType("long long"); + case 'y': + Position++; + return new NameType("unsigned long long"); + case 'n': + Position++; + return new NameType("__int128"); + case 'o': + Position++; + return new NameType("unsigned __int128"); + case 'f': + Position++; + return new NameType("float"); + case 'd': + Position++; + return new NameType("double"); + case 'e': + Position++; + return new NameType("long double"); + case 'g': + Position++; + return new NameType("__float128"); + case 'z': + Position++; + return new NameType("..."); + case 'u': + Position++; + return ParseSourceName(); + case 'D': + switch (Peek(1)) + { + case 'd': + Position += 2; + return new NameType("decimal64"); + case 'e': + Position += 2; + return new NameType("decimal128"); + case 'f': + Position += 2; + return new NameType("decimal32"); + case 'h': + Position += 2; + // FIXME: GNU c++flit returns this but that is not what is supposed to be returned. + return new NameType("half"); + //return new NameType("decimal16"); + case 'i': + Position += 2; + return new NameType("char32_t"); + case 's': + Position += 2; + return new NameType("char16_t"); + case 'a': + Position += 2; + return new NameType("decltype(auto)"); + case 'n': + Position += 2; + // FIXME: GNU c++flit returns this but that is not what is supposed to be returned. + return new NameType("decltype(nullptr)"); + //return new NameType("std::nullptr_t"); + case 't': + case 'T': + Position += 2; + Result = ParseDecltype(); + break; + case 'o': + case 'O': + case 'w': + case 'x': + Result = ParseFunctionType(); + break; + default: + return null; + } + break; + case 'F': + Result = ParseFunctionType(); + break; + case 'A': + return ParseArrayType(); + case 'M': + // TODO: + Position++; + return null; + case 'T': + // might just be a class enum type + if (Peek(1) == 's' || Peek(1) == 'u' || Peek(1) == 'e') + { + Result = ParseClassEnumType(); + break; + } + + Result = ParseTemplateParam(); + if (Result == null) + { + return null; + } + + if (CanParseTemplateArgs && Peek() == 'I') + { + BaseNode TemplateArguments = ParseTemplateArguments(); + if (TemplateArguments == null) + { + return null; + } + + Result = new NameTypeWithTemplateArguments(Result, TemplateArguments); + } + break; + case 'P': + Position++; + Result = ParseType(Context); + + if (Result == null) + { + return null; + } + + Result = new PointerType(Result); + break; + case 'R': + Position++; + Result = ParseType(Context); + + if (Result == null) + { + return null; + } + + Result = new ReferenceType("&", Result); + break; + case 'O': + Position++; + Result = ParseType(Context); + + if (Result == null) + { + return null; + } + + Result = new ReferenceType("&&", Result); + break; + case 'C': + Position++; + Result = ParseType(Context); + + if (Result == null) + { + return null; + } + + Result = new PostfixQualifiedType(" complex", Result); + break; + case 'G': + Position++; + Result = ParseType(Context); + + if (Result == null) + { + return null; + } + + Result = new PostfixQualifiedType(" imaginary", Result); + break; + case 'S': + if (Peek(1) != 't') + { + BaseNode Substitution = ParseSubstitution(); + if (Substitution == null) + { + return null; + } + + if (CanParseTemplateArgs && Peek() == 'I') + { + BaseNode TemplateArgument = ParseTemplateArgument(); + if (TemplateArgument == null) + { + return null; + } + + Result = new NameTypeWithTemplateArguments(Substitution, TemplateArgument); + break; + } + return Substitution; + } + else + { + Result = ParseClassEnumType(); + break; + } + default: + Result = ParseClassEnumType(); + break; + } + if (Result != null) + { + SubstitutionList.Add(Result); + } + + return Result; + } + + // ::= TV # virtual table + // ::= TT # VTT structure (construction vtable index) + // ::= TI # typeinfo structure + // ::= TS # typeinfo name (null-terminated byte string) + // ::= Tc + // ::= TW # Thread-local wrapper + // ::= TH # Thread-local initialization + // ::= T + // # base is the nominal target function of thunk + // ::= GV # Guard variable for one-time initialization + private BaseNode ParseSpecialName(NameParserContext Context = null) + { + if (Peek() != 'T') + { + if (ConsumeIf("GV")) + { + BaseNode Name = ParseName(); + if (Name == null) + { + return null; + } + + return new SpecialName("guard variable for ", Name); + } + return null; + } + + BaseNode Node; + switch (Peek(1)) + { + // ::= TV # virtual table + case 'V': + Position += 2; + Node = ParseType(Context); + if (Node == null) + { + return null; + } + + return new SpecialName("vtable for ", Node); + // ::= TT # VTT structure (construction vtable index) + case 'T': + Position += 2; + Node = ParseType(Context); + if (Node == null) + { + return null; + } + + return new SpecialName("VTT for ", Node); + // ::= TI # typeinfo structure + case 'I': + Position += 2; + Node = ParseType(Context); + if (Node == null) + { + return null; + } + + return new SpecialName("typeinfo for ", Node); + // ::= TS # typeinfo name (null-terminated byte string) + case 'S': + Position += 2; + Node = ParseType(Context); + if (Node == null) + { + return null; + } + + return new SpecialName("typeinfo name for ", Node); + // ::= Tc + case 'c': + Position += 2; + if (ParseCallOffset() || ParseCallOffset()) + { + return null; + } + + Node = ParseEncoding(); + if (Node == null) + { + return null; + } + + return new SpecialName("covariant return thunk to ", Node); + // extension ::= TC _ + case 'C': + Position += 2; + BaseNode FirstType = ParseType(); + if (FirstType == null || ParseNumber(true).Length == 0 || !ConsumeIf("_")) + { + return null; + } + + BaseNode SecondType = ParseType(); + + return new CtorVtableSpecialName(SecondType, FirstType); + // ::= TH # Thread-local initialization + case 'H': + Position += 2; + Node = ParseName(); + if (Node == null) + { + return null; + } + + return new SpecialName("thread-local initialization routine for ", Node); + // ::= TW # Thread-local wrapper + case 'W': + Position += 2; + Node = ParseName(); + if (Node == null) + { + return null; + } + + return new SpecialName("thread-local wrapper routine for ", Node); + default: + Position++; + bool IsVirtual = Peek() == 'v'; + if (ParseCallOffset()) + { + return null; + } + + Node = ParseEncoding(); + if (Node == null) + { + return null; + } + + if (IsVirtual) + { + return new SpecialName("virtual thunk to ", Node); + } + + return new SpecialName("non-virtual thunk to ", Node); + } + } + + // ::= [r] [V] [K] # restrict (C99), volatile, const + private CV ParseCVQualifiers() + { + CV Qualifiers = CV.None; + + if (ConsumeIf("r")) + { + Qualifiers |= CV.Restricted; + } + if (ConsumeIf("V")) + { + Qualifiers |= CV.Volatile; + } + if (ConsumeIf("K")) + { + Qualifiers |= CV.Const; + } + + return Qualifiers; + } + + + // ::= R # & ref-qualifier + // ::= O # && ref-qualifier + private SimpleReferenceType ParseRefQualifiers() + { + Reference Result = Reference.None; + if (ConsumeIf("O")) + { + Result = Reference.RValue; + } + else if (ConsumeIf("R")) + { + Result = Reference.LValue; + } + return new SimpleReferenceType(Result, null); + } + + private BaseNode CreateNameNode(BaseNode Prev, BaseNode Name, NameParserContext Context) + { + BaseNode Result = Name; + if (Prev != null) + { + Result = new NestedName(Name, Prev); + } + + if (Context != null) + { + Context.FinishWithTemplateArguments = false; + } + + return Result; + } + + private int ParsePositiveNumber() + { + string Part = Mangled.Substring(Position); + int NumberLength = 0; + + for (; NumberLength < Part.Length; NumberLength++) + { + if (!char.IsDigit(Part[NumberLength])) + { + break; + } + } + + Position += NumberLength; + + if (NumberLength == 0) + { + return -1; + } + + return int.Parse(Part.Substring(0, NumberLength)); + } + + private string ParseNumber(bool IsSigned = false) + { + if (IsSigned) + { + ConsumeIf("n"); + } + + if (Count() == 0 || !char.IsDigit(Mangled[Position])) + { + return null; + } + + string Part = Mangled.Substring(Position); + int NumberLength = 0; + + for (; NumberLength < Part.Length; NumberLength++) + { + if (!char.IsDigit(Part[NumberLength])) + { + break; + } + } + + Position += NumberLength; + + return Part.Substring(0, NumberLength); + } + + // ::= + private BaseNode ParseSourceName() + { + int Length = ParsePositiveNumber(); + if (Count() < Length || Length <= 0) + { + return null; + } + + string Name = Mangled.Substring(Position, Length); + Position += Length; + if (Name.StartsWith("_GLOBAL__N")) + { + return new NameType("(anonymous namespace)"); + } + + return new NameType(Name); + } + + // ::= nw # new + // ::= na # new[] + // ::= dl # delete + // ::= da # delete[] + // ::= ps # + (unary) + // ::= ng # - (unary) + // ::= ad # & (unary) + // ::= de # * (unary) + // ::= co # ~ + // ::= pl # + + // ::= mi # - + // ::= ml # * + // ::= dv # / + // ::= rm # % + // ::= an # & + // ::= or # | + // ::= eo # ^ + // ::= aS # = + // ::= pL # += + // ::= mI # -= + // ::= mL # *= + // ::= dV # /= + // ::= rM # %= + // ::= aN # &= + // ::= oR # |= + // ::= eO # ^= + // ::= ls # << + // ::= rs # >> + // ::= lS # <<= + // ::= rS # >>= + // ::= eq # == + // ::= ne # != + // ::= lt # < + // ::= gt # > + // ::= le # <= + // ::= ge # >= + // ::= ss # <=> + // ::= nt # ! + // ::= aa # && + // ::= oo # || + // ::= pp # ++ (postfix in context) + // ::= mm # -- (postfix in context) + // ::= cm # , + // ::= pm # ->* + // ::= pt # -> + // ::= cl # () + // ::= ix # [] + // ::= qu # ? + // ::= cv # (cast) (TODO) + // ::= li # operator "" + // ::= v # vendor extended operator (TODO) + private BaseNode ParseOperatorName(NameParserContext Context) + { + switch (Peek()) + { + case 'a': + switch (Peek(1)) + { + case 'a': + Position += 2; + return new NameType("operator&&"); + case 'd': + case 'n': + Position += 2; + return new NameType("operator&"); + case 'N': + Position += 2; + return new NameType("operator&="); + case 'S': + Position += 2; + return new NameType("operator="); + default: + return null; + } + case 'c': + switch (Peek(1)) + { + case 'l': + Position += 2; + return new NameType("operator()"); + case 'm': + Position += 2; + return new NameType("operator,"); + case 'o': + Position += 2; + return new NameType("operator~"); + case 'v': + Position += 2; + + bool CanParseTemplateArgsBackup = CanParseTemplateArgs; + bool CanForwardTemplateReferenceBackup = CanForwardTemplateReference; + + CanParseTemplateArgs = false; + CanForwardTemplateReference = CanForwardTemplateReferenceBackup || Context != null; + + BaseNode Type = ParseType(); + + CanParseTemplateArgs = CanParseTemplateArgsBackup; + CanForwardTemplateReference = CanForwardTemplateReferenceBackup; + + if (Type == null) + { + return null; + } + + if (Context != null) + { + Context.CtorDtorConversion = true; + } + + return new ConversionOperatorType(Type); + default: + return null; + } + case 'd': + switch (Peek(1)) + { + case 'a': + Position += 2; + return new NameType("operator delete[]"); + case 'e': + Position += 2; + return new NameType("operator*"); + case 'l': + Position += 2; + return new NameType("operator delete"); + case 'v': + Position += 2; + return new NameType("operator/"); + case 'V': + Position += 2; + return new NameType("operator/="); + default: + return null; + } + case 'e': + switch (Peek(1)) + { + case 'o': + Position += 2; + return new NameType("operator^"); + case 'O': + Position += 2; + return new NameType("operator^="); + case 'q': + Position += 2; + return new NameType("operator=="); + default: + return null; + } + case 'g': + switch (Peek(1)) + { + case 'e': + Position += 2; + return new NameType("operator>="); + case 't': + Position += 2; + return new NameType("operator>"); + default: + return null; + } + case 'i': + if (Peek(1) == 'x') + { + Position += 2; + return new NameType("operator[]"); + } + return null; + case 'l': + switch (Peek(1)) + { + case 'e': + Position += 2; + return new NameType("operator<="); + case 'i': + Position += 2; + BaseNode SourceName = ParseSourceName(); + if (SourceName == null) + { + return null; + } + + return new LiteralOperator(SourceName); + case 's': + Position += 2; + return new NameType("operator<<"); + case 'S': + Position += 2; + return new NameType("operator<<="); + case 't': + Position += 2; + return new NameType("operator<"); + default: + return null; + } + case 'm': + switch (Peek(1)) + { + case 'i': + Position += 2; + return new NameType("operator-"); + case 'I': + Position += 2; + return new NameType("operator-="); + case 'l': + Position += 2; + return new NameType("operator*"); + case 'L': + Position += 2; + return new NameType("operator*="); + case 'm': + Position += 2; + return new NameType("operator--"); + default: + return null; + } + case 'n': + switch (Peek(1)) + { + case 'a': + Position += 2; + return new NameType("operator new[]"); + case 'e': + Position += 2; + return new NameType("operator!="); + case 'g': + Position += 2; + return new NameType("operator-"); + case 't': + Position += 2; + return new NameType("operator!"); + case 'w': + Position += 2; + return new NameType("operator new"); + default: + return null; + } + case 'o': + switch (Peek(1)) + { + case 'o': + Position += 2; + return new NameType("operator||"); + case 'r': + Position += 2; + return new NameType("operator|"); + case 'R': + Position += 2; + return new NameType("operator|="); + default: + return null; + } + case 'p': + switch (Peek(1)) + { + case 'm': + Position += 2; + return new NameType("operator->*"); + case 's': + case 'l': + Position += 2; + return new NameType("operator+"); + case 'L': + Position += 2; + return new NameType("operator+="); + case 'p': + Position += 2; + return new NameType("operator++"); + case 't': + Position += 2; + return new NameType("operator->"); + default: + return null; + } + case 'q': + if (Peek(1) == 'u') + { + Position += 2; + return new NameType("operator?"); + } + return null; + case 'r': + switch (Peek(1)) + { + case 'm': + Position += 2; + return new NameType("operator%"); + case 'M': + Position += 2; + return new NameType("operator%="); + case 's': + Position += 2; + return new NameType("operator>>"); + case 'S': + Position += 2; + return new NameType("operator>>="); + default: + return null; + } + case 's': + if (Peek(1) == 's') + { + Position += 2; + return new NameType("operator<=>"); + } + return null; + case 'v': + // TODO: ::= v # vendor extended operator + return null; + default: + return null; + } + } + + // ::= [ (TODO)] + // ::= (TODO) + // ::= + // ::= (TODO) + // ::= DC + E # structured binding declaration (TODO) + private BaseNode ParseUnqualifiedName(NameParserContext Context) + { + BaseNode Result = null; + char C = Peek(); + if (C == 'U') + { + // TODO: Unnamed Type Name + // throw new Exception("Unnamed Type Name not implemented"); + } + else if (char.IsDigit(C)) + { + Result = ParseSourceName(); + } + else if (ConsumeIf("DC")) + { + // TODO: Structured Binding Declaration + // throw new Exception("Structured Binding Declaration not implemented"); + } + else + { + Result = ParseOperatorName(Context); + } + + if (Result != null) + { + // TODO: ABI Tags + //throw new Exception("ABI Tags not implemented"); + } + return Result; + } + + // ::= C1 # complete object constructor + // ::= C2 # base object constructor + // ::= C3 # complete object allocating constructor + // ::= D0 # deleting destructor + // ::= D1 # complete object destructor + // ::= D2 # base object destructor + private BaseNode ParseCtorDtorName(NameParserContext Context, BaseNode Prev) + { + if (Prev.Type == NodeType.SpecialSubstitution && Prev is SpecialSubstitution) + { + ((SpecialSubstitution)Prev).SetExtended(); + } + + if (ConsumeIf("C")) + { + bool IsInherited = ConsumeIf("I"); + + char CtorDtorType = Peek(); + if (CtorDtorType != '1' && CtorDtorType != '2' && CtorDtorType != '3') + { + return null; + } + + Position++; + + if (Context != null) + { + Context.CtorDtorConversion = true; + } + + if (IsInherited && ParseName(Context) == null) + { + return null; + } + + return new CtorDtorNameType(Prev, false); + } + + if (ConsumeIf("D")) + { + char C = Peek(); + if (C != '0' && C != '1' && C != '2') + { + return null; + } + + Position++; + + if (Context != null) + { + Context.CtorDtorConversion = true; + } + + return new CtorDtorNameType(Prev, true); + } + + return null; + } + + // ::= fp _ # L == 0, first parameter + // ::= fp _ # L == 0, second and later parameters + // ::= fL p _ # L > 0, first parameter + // ::= fL p _ # L > 0, second and later parameters + private BaseNode ParseFunctionParameter() + { + if (ConsumeIf("fp")) + { + // ignored + ParseCVQualifiers(); + + if (!ConsumeIf("_")) + { + return null; + } + + return new FunctionParameter(ParseNumber()); + } + else if (ConsumeIf("fL")) + { + string L1Number = ParseNumber(); + if (L1Number == null || L1Number.Length == 0) + { + return null; + } + + if (!ConsumeIf("p")) + { + return null; + } + + // ignored + ParseCVQualifiers(); + + if (!ConsumeIf("_")) + { + return null; + } + + return new FunctionParameter(ParseNumber()); + } + + return null; + } + + // ::= fL + // ::= fR + // ::= fl + // ::= fr + private BaseNode ParseFoldExpression() + { + if (!ConsumeIf("f")) + { + return null; + } + + char FoldKind = Peek(); + bool HasInitializer = FoldKind == 'L' || FoldKind == 'R'; + bool IsLeftFold = FoldKind == 'l' || FoldKind == 'L'; + + if (!IsLeftFold && !(FoldKind == 'r' || FoldKind == 'R')) + { + return null; + } + + Position++; + + string OperatorName = null; + + switch (PeekString(0, 2)) + { + case "aa": + OperatorName = "&&"; + break; + case "an": + OperatorName = "&"; + break; + case "aN": + OperatorName = "&="; + break; + case "aS": + OperatorName = "="; + break; + case "cm": + OperatorName = ","; + break; + case "ds": + OperatorName = ".*"; + break; + case "dv": + OperatorName = "/"; + break; + case "dV": + OperatorName = "/="; + break; + case "eo": + OperatorName = "^"; + break; + case "eO": + OperatorName = "^="; + break; + case "eq": + OperatorName = "=="; + break; + case "ge": + OperatorName = ">="; + break; + case "gt": + OperatorName = ">"; + break; + case "le": + OperatorName = "<="; + break; + case "ls": + OperatorName = "<<"; + break; + case "lS": + OperatorName = "<<="; + break; + case "lt": + OperatorName = "<"; + break; + case "mi": + OperatorName = "-"; + break; + case "mI": + OperatorName = "-="; + break; + case "ml": + OperatorName = "*"; + break; + case "mL": + OperatorName = "*="; + break; + case "ne": + OperatorName = "!="; + break; + case "oo": + OperatorName = "||"; + break; + case "or": + OperatorName = "|"; + break; + case "oR": + OperatorName = "|="; + break; + case "pl": + OperatorName = "+"; + break; + case "pL": + OperatorName = "+="; + break; + case "rm": + OperatorName = "%"; + break; + case "rM": + OperatorName = "%="; + break; + case "rs": + OperatorName = ">>"; + break; + case "rS": + OperatorName = ">>="; + break; + default: + return null; + } + + Position += 2; + + BaseNode Expression = ParseExpression(); + if (Expression == null) + { + return null; + } + + BaseNode Initializer = null; + + if (HasInitializer) + { + Initializer = ParseExpression(); + if (Initializer == null) + { + return null; + } + } + + if (IsLeftFold && Initializer != null) + { + BaseNode Temp = Expression; + Expression = Initializer; + Initializer = Temp; + } + + return new FoldExpression(IsLeftFold, OperatorName, new PackedTemplateParameterExpansion(Expression), Initializer); + } + + + // ::= cv # type (expression), conversion with one argument + // ::= cv _ * E # type (expr-list), conversion with other than one argument + private BaseNode ParseConversionExpression() + { + if (!ConsumeIf("cv")) + { + return null; + } + + bool CanParseTemplateArgsBackup = CanParseTemplateArgs; + CanParseTemplateArgs = false; + BaseNode Type = ParseType(); + CanParseTemplateArgs = CanParseTemplateArgsBackup; + + if (Type == null) + { + return null; + } + + List Expressions = new List(); + if (ConsumeIf("_")) + { + while (!ConsumeIf("E")) + { + BaseNode Expression = ParseExpression(); + if (Expression == null) + { + return null; + } + + Expressions.Add(Expression); + } + } + else + { + BaseNode Expression = ParseExpression(); + if (Expression == null) + { + return null; + } + + Expressions.Add(Expression); + } + + return new ConversionExpression(Type, new NodeArray(Expressions)); + } + + private BaseNode ParseBinaryExpression(string Name) + { + BaseNode LeftPart = ParseExpression(); + if (LeftPart == null) + { + return null; + } + + BaseNode RightPart = ParseExpression(); + if (RightPart == null) + { + return null; + } + + return new BinaryExpression(LeftPart, Name, RightPart); + } + + private BaseNode ParsePrefixExpression(string Name) + { + BaseNode Expression = ParseExpression(); + if (Expression == null) + { + return null; + } + + return new PrefixExpression(Name, Expression); + } + + + // ::= + // ::= di # .name = expr + // ::= dx # [expr] = expr + // ::= dX + // # [expr ... expr] = expr + private BaseNode ParseBracedExpression() + { + if (Peek() == 'd') + { + BaseNode BracedExpressionNode; + switch (Peek(1)) + { + case 'i': + Position += 2; + BaseNode Field = ParseSourceName(); + if (Field == null) + { + return null; + } + + BracedExpressionNode = ParseBracedExpression(); + if (BracedExpressionNode == null) + { + return null; + } + + return new BracedExpression(Field, BracedExpressionNode, false); + case 'x': + Position += 2; + BaseNode Index = ParseExpression(); + if (Index == null) + { + return null; + } + + BracedExpressionNode = ParseBracedExpression(); + if (BracedExpressionNode == null) + { + return null; + } + + return new BracedExpression(Index, BracedExpressionNode, true); + case 'X': + Position += 2; + BaseNode RangeBeginExpression = ParseExpression(); + if (RangeBeginExpression == null) + { + return null; + } + + BaseNode RangeEndExpression = ParseExpression(); + if (RangeEndExpression == null) + { + return null; + } + + BracedExpressionNode = ParseBracedExpression(); + if (BracedExpressionNode == null) + { + return null; + } + + return new BracedRangeExpression(RangeBeginExpression, RangeEndExpression, BracedExpressionNode); + } + } + + return ParseExpression(); + } + + // ::= [gs] nw * _ E # new (expr-list) type + // ::= [gs] nw * _ # new (expr-list) type (init) + // ::= [gs] na * _ E # new[] (expr-list) type + // ::= [gs] na * _ # new[] (expr-list) type (init) + // + // ::= pi * E # parenthesized initialization + private BaseNode ParseNewExpression() + { + bool IsGlobal = ConsumeIf("gs"); + bool IsArray = Peek(1) == 'a'; + + if (!ConsumeIf("nw") || !ConsumeIf("na")) + { + return null; + } + + List Expressions = new List(); + List Initializers = new List(); + + while (!ConsumeIf("_")) + { + BaseNode Expression = ParseExpression(); + if (Expression == null) + { + return null; + } + + Expressions.Add(Expression); + } + + BaseNode TypeNode = ParseType(); + if (TypeNode == null) + { + return null; + } + + if (ConsumeIf("pi")) + { + while (!ConsumeIf("E")) + { + BaseNode Initializer = ParseExpression(); + if (Initializer == null) + { + return null; + } + + Initializers.Add(Initializer); + } + } + else if (!ConsumeIf("E")) + { + return null; + } + + return new NewExpression(new NodeArray(Expressions), TypeNode, new NodeArray(Initializers), IsGlobal, IsArray); + } + + + // ::= + // ::= + // ::= + // ::= pp_ # prefix ++ + // ::= mm_ # prefix -- + // ::= cl + E # expression (expr-list), call + // ::= cv # type (expression), conversion with one argument + // ::= cv _ * E # type (expr-list), conversion with other than one argument + // ::= tl * E # type {expr-list}, conversion with braced-init-list argument + // ::= il * E # {expr-list}, braced-init-list in any other context + // ::= [gs] nw * _ E # new (expr-list) type + // ::= [gs] nw * _ # new (expr-list) type (init) + // ::= [gs] na * _ E # new[] (expr-list) type + // ::= [gs] na * _ # new[] (expr-list) type (init) + // ::= [gs] dl # delete expression + // ::= [gs] da # delete[] expression + // ::= dc # dynamic_cast (expression) + // ::= sc # static_cast (expression) + // ::= cc # const_cast (expression) + // ::= rc # reinterpret_cast (expression) + // ::= ti # typeid (type) + // ::= te # typeid (expression) + // ::= st # sizeof (type) + // ::= sz # sizeof (expression) + // ::= at # alignof (type) + // ::= az # alignof (expression) + // ::= nx # noexcept (expression) + // ::= + // ::= + // ::= dt # expr.name + // ::= pt # expr->name + // ::= ds # expr.*expr + // ::= sZ # sizeof...(T), size of a template parameter pack + // ::= sZ # sizeof...(parameter), size of a function parameter pack + // ::= sP * E # sizeof...(T), size of a captured template parameter pack from an alias template + // ::= sp # expression..., pack expansion + // ::= tw # throw expression + // ::= tr # throw with no operand (rethrow) + // ::= # f(p), N::f(p), ::f(p), + // # freestanding dependent name (e.g., T::x), + // # objectless nonstatic member reference + // ::= + private BaseNode ParseExpression() + { + bool IsGlobal = ConsumeIf("gs"); + BaseNode Expression = null; + if (Count() < 2) + { + return null; + } + + switch (Peek()) + { + case 'L': + return ParseExpressionPrimary(); + case 'T': + return ParseTemplateParam(); + case 'f': + char C = Peek(1); + if (C == 'p' || (C == 'L' && char.IsDigit(Peek(2)))) + { + return ParseFunctionParameter(); + } + + return ParseFoldExpression(); + case 'a': + switch (Peek(1)) + { + case 'a': + Position += 2; + return ParseBinaryExpression("&&"); + case 'd': + case 'n': + Position += 2; + return ParseBinaryExpression("&"); + case 'N': + Position += 2; + return ParseBinaryExpression("&="); + case 'S': + Position += 2; + return ParseBinaryExpression("="); + case 't': + Position += 2; + BaseNode Type = ParseType(); + if (Type == null) + { + return null; + } + + return new EnclosedExpression("alignof (", Type, ")"); + case 'z': + Position += 2; + Expression = ParseExpression(); + if (Expression == null) + { + return null; + } + + return new EnclosedExpression("alignof (", Expression, ")"); + } + return null; + case 'c': + switch (Peek(1)) + { + case 'c': + Position += 2; + BaseNode To = ParseType(); + if (To == null) + { + return null; + } + + BaseNode From = ParseExpression(); + if (From == null) + { + return null; + } + + return new CastExpression("const_cast", To, From); + case 'l': + Position += 2; + BaseNode Callee = ParseExpression(); + if (Callee == null) + { + return null; + } + + List Names = new List(); + while (!ConsumeIf("E")) + { + Expression = ParseExpression(); + if (Expression == null) + { + return null; + } + + Names.Add(Expression); + } + return new CallExpression(Callee, Names); + case 'm': + Position += 2; + return ParseBinaryExpression(","); + case 'o': + Position += 2; + return ParsePrefixExpression("~"); + case 'v': + return ParseConversionExpression(); + } + return null; + case 'd': + BaseNode LeftNode = null; + BaseNode RightNode = null; + switch (Peek(1)) + { + case 'a': + Position += 2; + Expression = ParseExpression(); + if (Expression == null) + { + return Expression; + } + + return new DeleteExpression(Expression, IsGlobal, true); + case 'c': + Position += 2; + BaseNode Type = ParseType(); + if (Type == null) + { + return null; + } + + Expression = ParseExpression(); + if (Expression == null) + { + return Expression; + } + + return new CastExpression("dynamic_cast", Type, Expression); + case 'e': + Position += 2; + return ParsePrefixExpression("*"); + case 'l': + Position += 2; + Expression = ParseExpression(); + if (Expression == null) + { + return null; + } + + return new DeleteExpression(Expression, IsGlobal, false); + case 'n': + return ParseUnresolvedName(); + case 's': + Position += 2; + LeftNode = ParseExpression(); + if (LeftNode == null) + { + return null; + } + + RightNode = ParseExpression(); + if (RightNode == null) + { + return null; + } + + return new MemberExpression(LeftNode, ".*", RightNode); + case 't': + Position += 2; + LeftNode = ParseExpression(); + if (LeftNode == null) + { + return null; + } + + RightNode = ParseExpression(); + if (RightNode == null) + { + return null; + } + + return new MemberExpression(LeftNode, ".", RightNode); + case 'v': + Position += 2; + return ParseBinaryExpression("/"); + case 'V': + Position += 2; + return ParseBinaryExpression("/="); + } + return null; + case 'e': + switch (Peek(1)) + { + case 'o': + Position += 2; + return ParseBinaryExpression("^"); + case 'O': + Position += 2; + return ParseBinaryExpression("^="); + case 'q': + Position += 2; + return ParseBinaryExpression("=="); + } + return null; + case 'g': + switch (Peek(1)) + { + case 'e': + Position += 2; + return ParseBinaryExpression(">="); + case 't': + Position += 2; + return ParseBinaryExpression(">"); + } + return null; + case 'i': + switch (Peek(1)) + { + case 'x': + Position += 2; + BaseNode Base = ParseExpression(); + if (Base == null) + { + return null; + } + + BaseNode Subscript = ParseExpression(); + if (Base == null) + { + return null; + } + + return new ArraySubscriptingExpression(Base, Subscript); + case 'l': + Position += 2; + + List BracedExpressions = new List(); + while (!ConsumeIf("E")) + { + Expression = ParseBracedExpression(); + if (Expression == null) + { + return null; + } + + BracedExpressions.Add(Expression); + } + return new InitListExpression(null, BracedExpressions); + } + return null; + case 'l': + switch (Peek(1)) + { + case 'e': + Position += 2; + return ParseBinaryExpression("<="); + case 's': + Position += 2; + return ParseBinaryExpression("<<"); + case 'S': + Position += 2; + return ParseBinaryExpression("<<="); + case 't': + Position += 2; + return ParseBinaryExpression("<"); + } + return null; + case 'm': + switch (Peek(1)) + { + case 'i': + Position += 2; + return ParseBinaryExpression("-"); + case 'I': + Position += 2; + return ParseBinaryExpression("-="); + case 'l': + Position += 2; + return ParseBinaryExpression("*"); + case 'L': + Position += 2; + return ParseBinaryExpression("*="); + case 'm': + Position += 2; + if (ConsumeIf("_")) + { + return ParsePrefixExpression("--"); + } + + Expression = ParseExpression(); + if (Expression == null) + { + return null; + } + + return new PostfixExpression(Expression, "--"); + } + return null; + case 'n': + switch (Peek(1)) + { + case 'a': + case 'w': + Position += 2; + return ParseNewExpression(); + case 'e': + Position += 2; + return ParseBinaryExpression("!="); + case 'g': + Position += 2; + return ParsePrefixExpression("-"); + case 't': + Position += 2; + return ParsePrefixExpression("!"); + case 'x': + Position += 2; + Expression = ParseExpression(); + if (Expression == null) + { + return null; + } + + return new EnclosedExpression("noexcept (", Expression, ")"); + } + return null; + case 'o': + switch (Peek(1)) + { + case 'n': + return ParseUnresolvedName(); + case 'o': + Position += 2; + return ParseBinaryExpression("||"); + case 'r': + Position += 2; + return ParseBinaryExpression("|"); + case 'R': + Position += 2; + return ParseBinaryExpression("|="); + } + return null; + case 'p': + switch (Peek(1)) + { + case 'm': + Position += 2; + return ParseBinaryExpression("->*"); + case 'l': + case 's': + Position += 2; + return ParseBinaryExpression("+"); + case 'L': + Position += 2; + return ParseBinaryExpression("+="); + case 'p': + Position += 2; + if (ConsumeIf("_")) + { + return ParsePrefixExpression("++"); + } + + Expression = ParseExpression(); + if (Expression == null) + { + return null; + } + + return new PostfixExpression(Expression, "++"); + case 't': + Position += 2; + LeftNode = ParseExpression(); + if (LeftNode == null) + { + return null; + } + + RightNode = ParseExpression(); + if (RightNode == null) + { + return null; + } + + return new MemberExpression(LeftNode, "->", RightNode); + } + return null; + case 'q': + if (Peek(1) == 'u') + { + Position += 2; + BaseNode Condition = ParseExpression(); + if (Condition == null) + { + return null; + } + + LeftNode = ParseExpression(); + if (LeftNode == null) + { + return null; + } + + RightNode = ParseExpression(); + if (RightNode == null) + { + return null; + } + + return new ConditionalExpression(Condition, LeftNode, RightNode); + } + return null; + case 'r': + switch (Peek(1)) + { + case 'c': + Position += 2; + BaseNode To = ParseType(); + if (To == null) + { + return null; + } + + BaseNode From = ParseExpression(); + if (From == null) + { + return null; + } + + return new CastExpression("reinterpret_cast", To, From); + case 'm': + Position += 2; + return ParseBinaryExpression("%"); + case 'M': + Position += 2; + return ParseBinaryExpression("%"); + case 's': + Position += 2; + return ParseBinaryExpression(">>"); + case 'S': + Position += 2; + return ParseBinaryExpression(">>="); + } + return null; + case 's': + switch (Peek(1)) + { + case 'c': + Position += 2; + BaseNode To = ParseType(); + if (To == null) + { + return null; + } + + BaseNode From = ParseExpression(); + if (From == null) + { + return null; + } + + return new CastExpression("static_cast", To, From); + case 'p': + Position += 2; + Expression = ParseExpression(); + if (Expression == null) + { + return null; + } + + return new PackedTemplateParameterExpansion(Expression); + case 'r': + return ParseUnresolvedName(); + case 't': + Position += 2; + BaseNode EnclosedType = ParseType(); + if (EnclosedType == null) + { + return null; + } + + return new EnclosedExpression("sizeof (", EnclosedType, ")"); + case 'z': + Position += 2; + Expression = ParseExpression(); + if (Expression == null) + { + return null; + } + + return new EnclosedExpression("sizeof (", Expression, ")"); + case 'Z': + Position += 2; + BaseNode SizeofParamNode = null; + switch (Peek()) + { + case 'T': + // FIXME: ??? Not entire sure if it's right + SizeofParamNode = ParseFunctionParameter(); + if (SizeofParamNode == null) + { + return null; + } + + return new EnclosedExpression("sizeof...(", new PackedTemplateParameterExpansion(SizeofParamNode), ")"); + case 'f': + SizeofParamNode = ParseFunctionParameter(); + if (SizeofParamNode == null) + { + return null; + } + + return new EnclosedExpression("sizeof...(", SizeofParamNode, ")"); + } + return null; + case 'P': + Position += 2; + List Arguments = new List(); + while (!ConsumeIf("E")) + { + BaseNode Argument = ParseTemplateArgument(); + if (Argument == null) + { + return null; + } + + Arguments.Add(Argument); + } + return new EnclosedExpression("sizeof...(", new NodeArray(Arguments), ")"); + } + return null; + case 't': + switch (Peek(1)) + { + case 'e': + Expression = ParseExpression(); + if (Expression == null) + { + return null; + } + + return new EnclosedExpression("typeid (", Expression, ")"); + case 't': + BaseNode EnclosedType = ParseExpression(); + if (EnclosedType == null) + { + return null; + } + + return new EnclosedExpression("typeid (", EnclosedType, ")"); + case 'l': + Position += 2; + BaseNode TypeNode = ParseType(); + if (TypeNode == null) + { + return null; + } + + List BracedExpressions = new List(); + while (!ConsumeIf("E")) + { + Expression = ParseBracedExpression(); + if (Expression == null) + { + return null; + } + + BracedExpressions.Add(Expression); + } + return new InitListExpression(TypeNode, BracedExpressions); + case 'r': + Position += 2; + return new NameType("throw"); + case 'w': + Position += 2; + Expression = ParseExpression(); + if (Expression == null) + { + return null; + } + + return new ThrowExpression(Expression); + } + return null; + } + + if (char.IsDigit(Peek())) + { + return ParseUnresolvedName(); + } + + return null; + } + + private BaseNode ParseIntegerLiteral(string LiteralName) + { + string Number = ParseNumber(true); + if (Number == null || Number.Length == 0 || !ConsumeIf("E")) + { + return null; + } + + return new IntegerLiteral(LiteralName, Number); + } + + // ::= L E # integer literal + // ::= L E # floating literal (TODO) + // ::= L E # string literal + // ::= L E # nullptr literal (i.e., "LDnE") + // ::= L 0 E # null pointer template argument + // ::= L _ E # complex floating point literal (C 2000) + // ::= L _Z E # external name + private BaseNode ParseExpressionPrimary() + { + if (!ConsumeIf("L")) + { + return null; + } + + switch (Peek()) + { + case 'w': + Position++; + return ParseIntegerLiteral("wchar_t"); + case 'b': + if (ConsumeIf("b0E")) + { + return new NameType("false", NodeType.BooleanExpression); + } + + if (ConsumeIf("b1E")) + { + return new NameType("true", NodeType.BooleanExpression); + } + + return null; + case 'c': + Position++; + return ParseIntegerLiteral("char"); + case 'a': + Position++; + return ParseIntegerLiteral("signed char"); + case 'h': + Position++; + return ParseIntegerLiteral("unsigned char"); + case 's': + Position++; + return ParseIntegerLiteral("short"); + case 't': + Position++; + return ParseIntegerLiteral("unsigned short"); + case 'i': + Position++; + return ParseIntegerLiteral(""); + case 'j': + Position++; + return ParseIntegerLiteral("u"); + case 'l': + Position++; + return ParseIntegerLiteral("l"); + case 'm': + Position++; + return ParseIntegerLiteral("ul"); + case 'x': + Position++; + return ParseIntegerLiteral("ll"); + case 'y': + Position++; + return ParseIntegerLiteral("ull"); + case 'n': + Position++; + return ParseIntegerLiteral("__int128"); + case 'o': + Position++; + return ParseIntegerLiteral("unsigned __int128"); + case 'd': + case 'e': + case 'f': + // TODO: floating literal + return null; + case '_': + if (ConsumeIf("_Z")) + { + BaseNode Encoding = ParseEncoding(); + if (Encoding != null && ConsumeIf("E")) + { + return Encoding; + } + } + return null; + case 'T': + return null; + default: + BaseNode Type = ParseType(); + if (Type == null) + { + return null; + } + + string Number = ParseNumber(); + if (Number == null || Number.Length == 0 || !ConsumeIf("E")) + { + return null; + } + + return new IntegerCastExpression(Type, Number); + } + } + + // ::= Dt E # decltype of an id-expression or class member access (C++0x) + // ::= DT E # decltype of an expression (C++0x) + private BaseNode ParseDecltype() + { + if (!ConsumeIf("D") || (!ConsumeIf("t") && !ConsumeIf("T"))) + { + return null; + } + + BaseNode Expression = ParseExpression(); + if (Expression == null) + { + return null; + } + + if (!ConsumeIf("E")) + { + return null; + } + + return new EnclosedExpression("decltype(", Expression, ")"); + } + + // ::= T_ # first template parameter + // ::= T _ + // ::= + // ::= + private BaseNode ParseTemplateParam() + { + if (!ConsumeIf("T")) + { + return null; + } + + int Index = 0; + if (!ConsumeIf("_")) + { + Index = ParsePositiveNumber(); + if (Index < 0) + { + return null; + } + + Index++; + if (!ConsumeIf("_")) + { + return null; + } + } + + // 5.1.8: TODO: lambda? + // if (IsParsingLambdaParameters) + // return new NameType("auto"); + + if (CanForwardTemplateReference) + { + ForwardTemplateReference ForwardTemplateReference = new ForwardTemplateReference(Index); + ForwardTemplateReferenceList.Add(ForwardTemplateReference); + return ForwardTemplateReference; + } + if (Index >= TemplateParamList.Count) + { + return null; + } + + return TemplateParamList[Index]; + } + + // ::= I + E + private BaseNode ParseTemplateArguments(bool HasContext = false) + { + if (!ConsumeIf("I")) + { + return null; + } + + if (HasContext) + { + TemplateParamList.Clear(); + } + + List Args = new List(); + while (!ConsumeIf("E")) + { + if (HasContext) + { + List TemplateParamListTemp = new List(TemplateParamList); + BaseNode TemplateArgument = ParseTemplateArgument(); + TemplateParamList = TemplateParamListTemp; + if (TemplateArgument == null) + { + return null; + } + + Args.Add(TemplateArgument); + if (TemplateArgument.GetType().Equals(NodeType.PackedTemplateArgument)) + { + TemplateArgument = new PackedTemplateParameter(((NodeArray)TemplateArgument).Nodes); + } + TemplateParamList.Add(TemplateArgument); + } + else + { + BaseNode TemplateArgument = ParseTemplateArgument(); + if (TemplateArgument == null) + { + return null; + } + + Args.Add(TemplateArgument); + } + } + return new TemplateArguments(Args); + } + + + // ::= # type or template + // ::= X E # expression + // ::= # simple expressions + // ::= J * E # argument pack + private BaseNode ParseTemplateArgument() + { + switch (Peek()) + { + // X E + case 'X': + Position++; + BaseNode Expression = ParseExpression(); + if (Expression == null || !ConsumeIf("E")) + { + return null; + } + + return Expression; + // + case 'L': + return ParseExpressionPrimary(); + // J * E + case 'J': + Position++; + List TemplateArguments = new List(); + while (!ConsumeIf("E")) + { + BaseNode TemplateArgument = ParseTemplateArgument(); + if (TemplateArgument == null) + { + return null; + } + + TemplateArguments.Add(TemplateArgument); + } + return new NodeArray(TemplateArguments, NodeType.PackedTemplateArgument); + // + default: + return ParseType(); + } + } + + class NameParserContext + { + public CVType CV; + public SimpleReferenceType Ref; + public bool FinishWithTemplateArguments; + public bool CtorDtorConversion; + } + + + // ::= [ ] # T:: or T:: + // ::= # decltype(p):: + // ::= + private BaseNode ParseUnresolvedType() + { + if (Peek() == 'T') + { + BaseNode TemplateParam = ParseTemplateParam(); + if (TemplateParam == null) + { + return null; + } + + SubstitutionList.Add(TemplateParam); + return TemplateParam; + } + else if (Peek() == 'D') + { + BaseNode DeclType = ParseDecltype(); + if (DeclType == null) + { + return null; + } + + SubstitutionList.Add(DeclType); + return DeclType; + } + return ParseSubstitution(); + } + + // ::= [ ] + private BaseNode ParseSimpleId() + { + BaseNode SourceName = ParseSourceName(); + if (SourceName == null) + { + return null; + } + + if (Peek() == 'I') + { + BaseNode TemplateArguments = ParseTemplateArguments(); + if (TemplateArguments == null) + { + return null; + } + + return new NameTypeWithTemplateArguments(SourceName, TemplateArguments); + } + return SourceName; + } + + // ::= # e.g., ~T or ~decltype(f()) + // ::= # e.g., ~A<2*N> + private BaseNode ParseDestructorName() + { + BaseNode Node; + if (char.IsDigit(Peek())) + { + Node = ParseSimpleId(); + } + else + { + Node = ParseUnresolvedType(); + } + if (Node == null) + { + return null; + } + + return new DtorName(Node); + } + + // ::= # unresolved name + // extension ::= # unresolved operator-function-id + // extension ::= # unresolved operator template-id + // ::= on # unresolved operator-function-id + // ::= on # unresolved operator template-id + // ::= dn # destructor or pseudo-destructor; + // # e.g. ~X or ~X + private BaseNode ParseBaseUnresolvedName() + { + if (char.IsDigit(Peek())) + { + return ParseSimpleId(); + } + else if (ConsumeIf("dn")) + { + return ParseDestructorName(); + } + + ConsumeIf("on"); + BaseNode OperatorName = ParseOperatorName(null); + if (OperatorName == null) + { + return null; + } + + if (Peek() == 'I') + { + BaseNode TemplateArguments = ParseTemplateArguments(); + if (TemplateArguments == null) + { + return null; + } + + return new NameTypeWithTemplateArguments(OperatorName, TemplateArguments); + } + return OperatorName; + } + + // ::= [gs] # x or (with "gs") ::x + // ::= sr # T::x / decltype(p)::x + // ::= srN + E + // # T::N::x /decltype(p)::N::x + // ::= [gs] sr + E + // # A::x, N::y, A::z; "gs" means leading "::" + private BaseNode ParseUnresolvedName(NameParserContext Context = null) + { + BaseNode Result = null; + if (ConsumeIf("srN")) + { + Result = ParseUnresolvedType(); + if (Result == null) + { + return null; + } + + if (Peek() == 'I') + { + BaseNode TemplateArguments = ParseTemplateArguments(); + if (TemplateArguments == null) + { + return null; + } + + Result = new NameTypeWithTemplateArguments(Result, TemplateArguments); + if (Result == null) + { + return null; + } + } + + while (!ConsumeIf("E")) + { + BaseNode SimpleId = ParseSimpleId(); + if (SimpleId == null) + { + return null; + } + + Result = new QualifiedName(Result, SimpleId); + if (Result == null) + { + return null; + } + } + + BaseNode BaseName = ParseBaseUnresolvedName(); + if (BaseName == null) + { + return null; + } + + return new QualifiedName(Result, BaseName); + } + + bool IsGlobal = ConsumeIf("gs"); + + // ::= [gs] # x or (with "gs") ::x + if (!ConsumeIf("sr")) + { + Result = ParseBaseUnresolvedName(); + if (Result == null) + { + return null; + } + + if (IsGlobal) + { + Result = new GlobalQualifiedName(Result); + } + + return Result; + } + + // ::= [gs] sr + E + if (char.IsDigit(Peek())) + { + do + { + BaseNode Qualifier = ParseSimpleId(); + if (Qualifier == null) + { + return null; + } + + if (Result != null) + { + Result = new QualifiedName(Result, Qualifier); + } + else if (IsGlobal) + { + Result = new GlobalQualifiedName(Qualifier); + } + else + { + Result = Qualifier; + } + + if (Result == null) + { + return null; + } + } while (!ConsumeIf("E")); + } + // ::= sr [tempate-args] # T::x / decltype(p)::x + else + { + Result = ParseUnresolvedType(); + if (Result == null) + { + return null; + } + + if (Peek() == 'I') + { + BaseNode TemplateArguments = ParseTemplateArguments(); + if (TemplateArguments == null) + { + return null; + } + + Result = new NameTypeWithTemplateArguments(Result, TemplateArguments); + if (Result == null) + { + return null; + } + } + } + + if (Result == null) + { + return null; + } + + BaseNode BaseUnresolvedName = ParseBaseUnresolvedName(); + if (BaseUnresolvedName == null) + { + return null; + } + + return new QualifiedName(Result, BaseUnresolvedName); + } + + // ::= + // ::= St # ::std:: + private BaseNode ParseUnscopedName(NameParserContext Context) + { + if (ConsumeIf("St")) + { + BaseNode UnresolvedName = ParseUnresolvedName(Context); + if (UnresolvedName == null) + { + return null; + } + + return new StdQualifiedName(UnresolvedName); + } + return ParseUnresolvedName(Context); + } + + // ::= N [] [] E + // ::= N [] [] E + private BaseNode ParseNestedName(NameParserContext Context) + { + // Impossible in theory + if (Consume() != 'N') + { + return null; + } + + BaseNode Result = null; + CVType CV = new CVType(ParseCVQualifiers(), null); + if (Context != null) + { + Context.CV = CV; + } + + SimpleReferenceType Ref = ParseRefQualifiers(); + if (Context != null) + { + Context.Ref = Ref; + } + + if (ConsumeIf("St")) + { + Result = new NameType("std"); + } + + while (!ConsumeIf("E")) + { + // end + if (ConsumeIf("M")) + { + if (Result == null) + { + return null; + } + + continue; + } + char C = Peek(); + + // TODO: template args + if (C == 'T') + { + BaseNode TemplateParam = ParseTemplateParam(); + if (TemplateParam == null) + { + return null; + } + + Result = CreateNameNode(Result, TemplateParam, Context); + SubstitutionList.Add(Result); + continue; + } + + // + if (C == 'I') + { + BaseNode TemplateArgument = ParseTemplateArguments(Context != null); + if (TemplateArgument == null || Result == null) + { + return null; + } + + Result = new NameTypeWithTemplateArguments(Result, TemplateArgument); + if (Context != null) + { + Context.FinishWithTemplateArguments = true; + } + + SubstitutionList.Add(Result); + continue; + } + + // + if (C == 'D' && (Peek(1) == 't' || Peek(1) == 'T')) + { + BaseNode Decltype = ParseDecltype(); + if (Decltype == null) + { + return null; + } + + Result = CreateNameNode(Result, Decltype, Context); + SubstitutionList.Add(Result); + continue; + } + + // + if (C == 'S' && Peek(1) != 't') + { + BaseNode Substitution = ParseSubstitution(); + if (Substitution == null) + { + return null; + } + + Result = CreateNameNode(Result, Substitution, Context); + if (Result != Substitution) + { + SubstitutionList.Add(Substitution); + } + + continue; + } + + // of ParseUnqualifiedName + if (C == 'C' || (C == 'D' && Peek(1) != 'C')) + { + // We cannot have nothing before this + if (Result == null) + { + return null; + } + + BaseNode CtOrDtorName = ParseCtorDtorName(Context, Result); + + if (CtOrDtorName == null) + { + return null; + } + + Result = CreateNameNode(Result, CtOrDtorName, Context); + + // TODO: ABI Tags (before) + if (Result == null) + { + return null; + } + + SubstitutionList.Add(Result); + continue; + } + + BaseNode UnqualifiedName = ParseUnqualifiedName(Context); + if (UnqualifiedName == null) + { + return null; + } + Result = CreateNameNode(Result, UnqualifiedName, Context); + + SubstitutionList.Add(Result); + } + if (Result == null || SubstitutionList.Count == 0) + { + return null; + } + + SubstitutionList.RemoveAt(SubstitutionList.Count - 1); + return Result; + } + + // ::= _ # when number < 10 + // ::= __ _ # when number >= 10 + private void ParseDiscriminator() + { + if (Count() == 0) + { + return; + } + // We ignore the discriminator, we don't need it. + if (ConsumeIf("_")) + { + ConsumeIf("_"); + while (char.IsDigit(Peek()) && Count() != 0) + { + Consume(); + } + ConsumeIf("_"); + } + } + + // ::= Z E [] + // ::= Z E s [] + // ::= Z Ed [ ] _ + private BaseNode ParseLocalName(NameParserContext Context) + { + if (!ConsumeIf("Z")) + { + return null; + } + + BaseNode Encoding = ParseEncoding(); + if (Encoding == null || !ConsumeIf("E")) + { + return null; + } + + BaseNode EntityName; + if (ConsumeIf("s")) + { + ParseDiscriminator(); + return new LocalName(Encoding, new NameType("string literal")); + } + else if (ConsumeIf("d")) + { + ParseNumber(true); + if (!ConsumeIf("_")) + { + return null; + } + + EntityName = ParseName(Context); + if (EntityName == null) + { + return null; + } + + return new LocalName(Encoding, EntityName); + } + + EntityName = ParseName(Context); + if (EntityName == null) + { + return null; + } + + ParseDiscriminator(); + return new LocalName(Encoding, EntityName); + } + + // ::= + // ::= + // ::= + // ::= # See Scope Encoding below (TODO) + private BaseNode ParseName(NameParserContext Context = null) + { + ConsumeIf("L"); + + if (Peek() == 'N') + { + return ParseNestedName(Context); + } + + if (Peek() == 'Z') + { + return ParseLocalName(Context); + } + + if (Peek() == 'S' && Peek(1) != 't') + { + BaseNode Substitution = ParseSubstitution(); + if (Substitution == null) + { + return null; + } + + if (Peek() != 'I') + { + return null; + } + + BaseNode TemplateArguments = ParseTemplateArguments(Context != null); + if (TemplateArguments == null) + { + return null; + } + + if (Context != null) + { + Context.FinishWithTemplateArguments = true; + } + + return new NameTypeWithTemplateArguments(Substitution, TemplateArguments); + } + + BaseNode Result = ParseUnscopedName(Context); + if (Result == null) + { + return null; + } + + if (Peek() == 'I') + { + SubstitutionList.Add(Result); + BaseNode TemplateArguments = ParseTemplateArguments(Context != null); + if (TemplateArguments == null) + { + return null; + } + + if (Context != null) + { + Context.FinishWithTemplateArguments = true; + } + + return new NameTypeWithTemplateArguments(Result, TemplateArguments); + } + + return Result; + } + + private bool IsEncodingEnd() + { + char C = Peek(); + return Count() == 0 || C == 'E' || C == '.' || C == '_'; + } + + // ::= + // ::= + // ::= + private BaseNode ParseEncoding() + { + NameParserContext Context = new NameParserContext(); + if (Peek() == 'T' || (Peek() == 'G' && Peek(1) == 'V')) + { + return ParseSpecialName(Context); + } + + BaseNode Name = ParseName(Context); + if (Name == null) + { + return null; + } + + // TODO: compute template refs here + + if (IsEncodingEnd()) + { + return Name; + } + + // TODO: Ua9enable_ifI + + BaseNode ReturnType = null; + if (!Context.CtorDtorConversion && Context.FinishWithTemplateArguments) + { + ReturnType = ParseType(); + if (ReturnType == null) + { + return null; + } + } + + if (ConsumeIf("v")) + { + return new EncodedFunction(Name, null, Context.CV, Context.Ref, null, ReturnType); + } + + List Params = new List(); + + // backup because that can be destroyed by parseType + CVType CV = Context.CV; + SimpleReferenceType Ref = Context.Ref; + + while (!IsEncodingEnd()) + { + BaseNode Param = ParseType(); + if (Param == null) + { + return null; + } + + Params.Add(Param); + } + + return new EncodedFunction(Name, new NodeArray(Params), CV, Ref, null, ReturnType); + } + + // ::= _Z + // ::= + private BaseNode Parse() + { + if (ConsumeIf("_Z")) + { + BaseNode Encoding = ParseEncoding(); + if (Encoding != null && Count() == 0) + { + return Encoding; + } + return null; + } + else + { + BaseNode Type = ParseType(); + if (Type != null && Count() == 0) + { + return Type; + } + return null; + } + } + + public static string Parse(string OriginalMangled) + { + Demangler Instance = new Demangler(OriginalMangled); + BaseNode ResNode = Instance.Parse(); + + if (ResNode != null) + { + StringWriter Writer = new StringWriter(); + ResNode.Print(Writer); + return Writer.ToString(); + } + + return OriginalMangled; + } + } +} diff --git a/Ryujinx.HLE/HOS/Process.cs b/Ryujinx.HLE/HOS/Process.cs index bfda93f494..72ed6b0d8e 100644 --- a/Ryujinx.HLE/HOS/Process.cs +++ b/Ryujinx.HLE/HOS/Process.cs @@ -3,7 +3,7 @@ using ChocolArm64.Events; using ChocolArm64.Memory; using ChocolArm64.State; using Ryujinx.HLE.Exceptions; -using Ryujinx.HLE.HOS.Diagnostics; +using Ryujinx.HLE.HOS.Diagnostics.Demangler; using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Services.Nv; using Ryujinx.HLE.HOS.SystemState; From 8a78a703f2e8e374286efaae575d87fb38242427 Mon Sep 17 00:00:00 2001 From: HorrorTroll Date: Mon, 17 Sep 2018 07:24:56 +0700 Subject: [PATCH 02/14] Implement B5G6R5Unorm, BGR5A1Unorm, RGBA32Uint, R16Unorm and Z16 texture format. Fix BC6H_UF16 from Unorm to Sfloat (#417) * Implement B5G6R5Unorm & BGR5A1Unorm * Implement RGBA32Uint * Implement R16Unorm & Z16 texture format * Fix BC6H_UF16 from Unorm to Sfloat --- Ryujinx.Graphics/Gal/GalTextureFormat.cs | 1 + .../Gal/OpenGL/OGLEnumConverter.cs | 20 ++++---- Ryujinx.Graphics/Texture/ImageUtils.cs | 49 ++++++++++--------- 3 files changed, 38 insertions(+), 32 deletions(-) diff --git a/Ryujinx.Graphics/Gal/GalTextureFormat.cs b/Ryujinx.Graphics/Gal/GalTextureFormat.cs index 009d2b826e..e865859530 100644 --- a/Ryujinx.Graphics/Gal/GalTextureFormat.cs +++ b/Ryujinx.Graphics/Gal/GalTextureFormat.cs @@ -26,6 +26,7 @@ namespace Ryujinx.Graphics.Gal Z24S8 = 0x29, ZF32 = 0x2f, ZF32_X24S8 = 0x30, + Z16 = 0x3a, Astc2D4x4 = 0x40, Astc2D5x5 = 0x41, Astc2D6x6 = 0x42, diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs index 69c133a335..876c7b992a 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs @@ -183,16 +183,16 @@ namespace Ryujinx.Graphics.Gal.OpenGL { switch (Format) { - case GalImageFormat.BC6H_UF16 | GalImageFormat.Unorm: return InternalFormat.CompressedRgbBptcUnsignedFloat; - case GalImageFormat.BC6H_SF16 | GalImageFormat.Unorm: return InternalFormat.CompressedRgbBptcSignedFloat; - case GalImageFormat.BC7 | GalImageFormat.Unorm: return InternalFormat.CompressedRgbaBptcUnorm; - case GalImageFormat.BC1_RGBA | GalImageFormat.Unorm: return InternalFormat.CompressedRgbaS3tcDxt1Ext; - case GalImageFormat.BC2 | GalImageFormat.Unorm: return InternalFormat.CompressedRgbaS3tcDxt3Ext; - case GalImageFormat.BC3 | GalImageFormat.Unorm: return InternalFormat.CompressedRgbaS3tcDxt5Ext; - case GalImageFormat.BC4 | GalImageFormat.Snorm: return InternalFormat.CompressedSignedRedRgtc1; - case GalImageFormat.BC4 | GalImageFormat.Unorm: return InternalFormat.CompressedRedRgtc1; - case GalImageFormat.BC5 | GalImageFormat.Snorm: return InternalFormat.CompressedSignedRgRgtc2; - case GalImageFormat.BC5 | GalImageFormat.Unorm: return InternalFormat.CompressedRgRgtc2; + case GalImageFormat.BC6H_UF16 | GalImageFormat.Sfloat: return InternalFormat.CompressedRgbBptcUnsignedFloat; + case GalImageFormat.BC6H_SF16 | GalImageFormat.Unorm: return InternalFormat.CompressedRgbBptcSignedFloat; + case GalImageFormat.BC7 | GalImageFormat.Unorm: return InternalFormat.CompressedRgbaBptcUnorm; + case GalImageFormat.BC1_RGBA | GalImageFormat.Unorm: return InternalFormat.CompressedRgbaS3tcDxt1Ext; + case GalImageFormat.BC2 | GalImageFormat.Unorm: return InternalFormat.CompressedRgbaS3tcDxt3Ext; + case GalImageFormat.BC3 | GalImageFormat.Unorm: return InternalFormat.CompressedRgbaS3tcDxt5Ext; + case GalImageFormat.BC4 | GalImageFormat.Snorm: return InternalFormat.CompressedSignedRedRgtc1; + case GalImageFormat.BC4 | GalImageFormat.Unorm: return InternalFormat.CompressedRedRgtc1; + case GalImageFormat.BC5 | GalImageFormat.Snorm: return InternalFormat.CompressedSignedRgRgtc2; + case GalImageFormat.BC5 | GalImageFormat.Unorm: return InternalFormat.CompressedRgRgtc2; } throw new NotImplementedException($"{Format & GalImageFormat.FormatMask} {Format & GalImageFormat.TypeMask}"); diff --git a/Ryujinx.Graphics/Texture/ImageUtils.cs b/Ryujinx.Graphics/Texture/ImageUtils.cs index c09eaf8693..f6db08944e 100644 --- a/Ryujinx.Graphics/Texture/ImageUtils.cs +++ b/Ryujinx.Graphics/Texture/ImageUtils.cs @@ -56,30 +56,31 @@ namespace Ryujinx.Graphics.Texture { GalTextureFormat.Z24S8, GalImageFormat.D24_S8 | Unorm }, { GalTextureFormat.ZF32, GalImageFormat.D32 | Sfloat }, { GalTextureFormat.ZF32_X24S8, GalImageFormat.D32_S8 | Unorm }, + { GalTextureFormat.Z16, GalImageFormat.D16 | Unorm }, //Compressed formats - { GalTextureFormat.BC6H_SF16, GalImageFormat.BC6H_SF16 | Unorm }, - { GalTextureFormat.BC6H_UF16, GalImageFormat.BC6H_UF16 | Unorm }, - { GalTextureFormat.BC7U, GalImageFormat.BC7 | Unorm }, - { GalTextureFormat.BC1, GalImageFormat.BC1_RGBA | Unorm }, - { GalTextureFormat.BC2, GalImageFormat.BC2 | Unorm }, - { GalTextureFormat.BC3, GalImageFormat.BC3 | Unorm }, - { GalTextureFormat.BC4, GalImageFormat.BC4 | Unorm | Snorm }, - { GalTextureFormat.BC5, GalImageFormat.BC5 | Unorm | Snorm }, - { GalTextureFormat.Astc2D4x4, GalImageFormat.ASTC_4x4 | Unorm }, - { GalTextureFormat.Astc2D5x5, GalImageFormat.ASTC_5x5 | Unorm }, - { GalTextureFormat.Astc2D6x6, GalImageFormat.ASTC_6x6 | Unorm }, - { GalTextureFormat.Astc2D8x8, GalImageFormat.ASTC_8x8 | Unorm }, - { GalTextureFormat.Astc2D10x10, GalImageFormat.ASTC_10x10 | Unorm }, - { GalTextureFormat.Astc2D12x12, GalImageFormat.ASTC_12x12 | Unorm }, - { GalTextureFormat.Astc2D5x4, GalImageFormat.ASTC_5x4 | Unorm }, - { GalTextureFormat.Astc2D6x5, GalImageFormat.ASTC_6x5 | Unorm }, - { GalTextureFormat.Astc2D8x6, GalImageFormat.ASTC_8x6 | Unorm }, - { GalTextureFormat.Astc2D10x8, GalImageFormat.ASTC_10x8 | Unorm }, - { GalTextureFormat.Astc2D12x10, GalImageFormat.ASTC_12x10 | Unorm }, - { GalTextureFormat.Astc2D8x5, GalImageFormat.ASTC_8x5 | Unorm }, - { GalTextureFormat.Astc2D10x5, GalImageFormat.ASTC_10x5 | Unorm }, - { GalTextureFormat.Astc2D10x6, GalImageFormat.ASTC_10x6 | Unorm } + { GalTextureFormat.BC6H_SF16, GalImageFormat.BC6H_SF16 | Unorm }, + { GalTextureFormat.BC6H_UF16, GalImageFormat.BC6H_UF16 | Sfloat }, + { GalTextureFormat.BC7U, GalImageFormat.BC7 | Unorm }, + { GalTextureFormat.BC1, GalImageFormat.BC1_RGBA | Unorm }, + { GalTextureFormat.BC2, GalImageFormat.BC2 | Unorm }, + { GalTextureFormat.BC3, GalImageFormat.BC3 | Unorm }, + { GalTextureFormat.BC4, GalImageFormat.BC4 | Unorm | Snorm }, + { GalTextureFormat.BC5, GalImageFormat.BC5 | Unorm | Snorm }, + { GalTextureFormat.Astc2D4x4, GalImageFormat.ASTC_4x4 | Unorm }, + { GalTextureFormat.Astc2D5x5, GalImageFormat.ASTC_5x5 | Unorm }, + { GalTextureFormat.Astc2D6x6, GalImageFormat.ASTC_6x6 | Unorm }, + { GalTextureFormat.Astc2D8x8, GalImageFormat.ASTC_8x8 | Unorm }, + { GalTextureFormat.Astc2D10x10, GalImageFormat.ASTC_10x10 | Unorm }, + { GalTextureFormat.Astc2D12x12, GalImageFormat.ASTC_12x12 | Unorm }, + { GalTextureFormat.Astc2D5x4, GalImageFormat.ASTC_5x4 | Unorm }, + { GalTextureFormat.Astc2D6x5, GalImageFormat.ASTC_6x5 | Unorm }, + { GalTextureFormat.Astc2D8x6, GalImageFormat.ASTC_8x6 | Unorm }, + { GalTextureFormat.Astc2D10x8, GalImageFormat.ASTC_10x8 | Unorm }, + { GalTextureFormat.Astc2D12x10, GalImageFormat.ASTC_12x10 | Unorm }, + { GalTextureFormat.Astc2D8x5, GalImageFormat.ASTC_8x5 | Unorm }, + { GalTextureFormat.Astc2D10x5, GalImageFormat.ASTC_10x5 | Unorm }, + { GalTextureFormat.Astc2D10x6, GalImageFormat.ASTC_10x6 | Unorm } }; private static readonly Dictionary s_ImageTable = @@ -167,6 +168,7 @@ namespace Ryujinx.Graphics.Texture switch (Format) { case GalSurfaceFormat.RGBA32Float: return GalImageFormat.R32G32B32A32 | Sfloat; + case GalSurfaceFormat.RGBA32Uint: return GalImageFormat.R32G32B32A32 | Uint; case GalSurfaceFormat.RGBA16Float: return GalImageFormat.R16G16B16A16 | Sfloat; case GalSurfaceFormat.RG32Float: return GalImageFormat.R32G32 | Sfloat; case GalSurfaceFormat.RG32Sint: return GalImageFormat.R32G32 | Sint; @@ -184,7 +186,10 @@ namespace Ryujinx.Graphics.Texture case GalSurfaceFormat.RG8Unorm: return GalImageFormat.R8G8 | Unorm; case GalSurfaceFormat.RG8Snorm: return GalImageFormat.R8 | Snorm; case GalSurfaceFormat.R16Float: return GalImageFormat.R16 | Sfloat; + case GalSurfaceFormat.R16Unorm: return GalImageFormat.R16 | Unorm; case GalSurfaceFormat.R8Unorm: return GalImageFormat.R8 | Unorm; + case GalSurfaceFormat.B5G6R5Unorm: return GalImageFormat.B5G6R5 | Unorm; + case GalSurfaceFormat.BGR5A1Unorm: return GalImageFormat.A1R5G5B5 | Unorm; } throw new NotImplementedException(Format.ToString()); From c7387be0d296f54a6ad5678d25e2c0d4910b7da4 Mon Sep 17 00:00:00 2001 From: LDj3SNuD <35856442+LDj3SNuD@users.noreply.github.com> Date: Mon, 17 Sep 2018 06:54:05 +0200 Subject: [PATCH 03/14] Fix/Add 1+12 [Saturating] [Rounded] Shift Right Narrow (imm.) Instructions; add 14 Tests. Add 6 Tests for PR#405. Add 2 Tests for PR#412. (#409) * Update AOpCodeTable.cs * Update AInstEmitSimdShift.cs * Update CpuTestSimdShImm.cs * Update AInstEmitSimdArithmetic.cs * Update AInstEmitSimdHelper.cs * Create CpuTestSimdIns.cs * Update CpuTest.cs * Update CpuTestSimd.cs * Update CpuTestSimdReg.cs * Update CpuTest.cs * Update CpuTestSimd.cs * Update CpuTestSimdReg.cs * Update CpuTestSimd.cs * Update CpuTestSimdReg.cs * Update CpuTest.cs * Update CpuTestSimdReg.cs * Update CpuTestSimd.cs --- ChocolArm64/AOpCodeTable.cs | 12 + .../Instruction/AInstEmitSimdArithmetic.cs | 12 +- .../Instruction/AInstEmitSimdHelper.cs | 46 +- ChocolArm64/Instruction/AInstEmitSimdShift.cs | 243 +++++++--- Ryujinx.Tests/Cpu/CpuTest.cs | 125 +++++- Ryujinx.Tests/Cpu/CpuTestSimd.cs | 420 ++++++++++-------- Ryujinx.Tests/Cpu/CpuTestSimdIns.cs | 74 +++ Ryujinx.Tests/Cpu/CpuTestSimdReg.cs | 307 ++++++++++++- Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs | 318 +++++++++++++ 9 files changed, 1254 insertions(+), 303 deletions(-) create mode 100644 Ryujinx.Tests/Cpu/CpuTestSimdIns.cs diff --git a/ChocolArm64/AOpCodeTable.cs b/ChocolArm64/AOpCodeTable.cs index b053334f3c..fe3dce41df 100644 --- a/ChocolArm64/AOpCodeTable.cs +++ b/ChocolArm64/AOpCodeTable.cs @@ -364,6 +364,7 @@ namespace ChocolArm64 SetA64("0x00111000100000000110xxxxxxxxxx", AInstEmit.Rev16_V, typeof(AOpCodeSimd)); SetA64("0x1011100x100000000010xxxxxxxxxx", AInstEmit.Rev32_V, typeof(AOpCodeSimd)); SetA64("0x001110<<100000000010xxxxxxxxxx", AInstEmit.Rev64_V, typeof(AOpCodeSimd)); + SetA64("0x00111100>>>xxx100011xxxxxxxxxx", AInstEmit.Rshrn_V, typeof(AOpCodeSimdShImm)); SetA64("0x101110<<1xxxxx011000xxxxxxxxxx", AInstEmit.Rsubhn_V, typeof(AOpCodeSimdReg)); SetA64("0x001110<<1xxxxx011111xxxxxxxxxx", AInstEmit.Saba_V, typeof(AOpCodeSimdReg)); SetA64("0x001110<<1xxxxx010100xxxxxxxxxx", AInstEmit.Sabal_V, typeof(AOpCodeSimdReg)); @@ -409,7 +410,14 @@ namespace ChocolArm64 SetA64("01111110101xxxxx101101xxxxxxxxxx", AInstEmit.Sqrdmulh_S, typeof(AOpCodeSimdReg)); SetA64("0x101110011xxxxx101101xxxxxxxxxx", AInstEmit.Sqrdmulh_V, typeof(AOpCodeSimdReg)); SetA64("0x101110101xxxxx101101xxxxxxxxxx", AInstEmit.Sqrdmulh_V, typeof(AOpCodeSimdReg)); + SetA64("0101111100>>>xxx100111xxxxxxxxxx", AInstEmit.Sqrshrn_S, typeof(AOpCodeSimdShImm)); SetA64("0x00111100>>>xxx100111xxxxxxxxxx", AInstEmit.Sqrshrn_V, typeof(AOpCodeSimdShImm)); + SetA64("0111111100>>>xxx100011xxxxxxxxxx", AInstEmit.Sqrshrun_S, typeof(AOpCodeSimdShImm)); + SetA64("0x10111100>>>xxx100011xxxxxxxxxx", AInstEmit.Sqrshrun_V, typeof(AOpCodeSimdShImm)); + SetA64("0101111100>>>xxx100101xxxxxxxxxx", AInstEmit.Sqshrn_S, typeof(AOpCodeSimdShImm)); + SetA64("0x00111100>>>xxx100101xxxxxxxxxx", AInstEmit.Sqshrn_V, typeof(AOpCodeSimdShImm)); + SetA64("0111111100>>>xxx100001xxxxxxxxxx", AInstEmit.Sqshrun_S, typeof(AOpCodeSimdShImm)); + SetA64("0x10111100>>>xxx100001xxxxxxxxxx", AInstEmit.Sqshrun_V, typeof(AOpCodeSimdShImm)); SetA64("01011110xx1xxxxx001011xxxxxxxxxx", AInstEmit.Sqsub_S, typeof(AOpCodeSimdReg)); SetA64("0>001110<<1xxxxx001011xxxxxxxxxx", AInstEmit.Sqsub_V, typeof(AOpCodeSimdReg)); SetA64("01011110<<100001010010xxxxxxxxxx", AInstEmit.Sqxtn_S, typeof(AOpCodeSimd)); @@ -476,6 +484,10 @@ namespace ChocolArm64 SetA64("0x101110<<1xxxxx110000xxxxxxxxxx", AInstEmit.Umull_V, typeof(AOpCodeSimdReg)); SetA64("01111110xx1xxxxx000011xxxxxxxxxx", AInstEmit.Uqadd_S, typeof(AOpCodeSimdReg)); SetA64("0>101110<<1xxxxx000011xxxxxxxxxx", AInstEmit.Uqadd_V, typeof(AOpCodeSimdReg)); + SetA64("0111111100>>>xxx100111xxxxxxxxxx", AInstEmit.Uqrshrn_S, typeof(AOpCodeSimdShImm)); + SetA64("0x10111100>>>xxx100111xxxxxxxxxx", AInstEmit.Uqrshrn_V, typeof(AOpCodeSimdShImm)); + SetA64("0111111100>>>xxx100101xxxxxxxxxx", AInstEmit.Uqshrn_S, typeof(AOpCodeSimdShImm)); + SetA64("0x10111100>>>xxx100101xxxxxxxxxx", AInstEmit.Uqshrn_V, typeof(AOpCodeSimdShImm)); SetA64("01111110xx1xxxxx001011xxxxxxxxxx", AInstEmit.Uqsub_S, typeof(AOpCodeSimdReg)); SetA64("0>101110<<1xxxxx001011xxxxxxxxxx", AInstEmit.Uqsub_V, typeof(AOpCodeSimdReg)); SetA64("01111110<<100001010010xxxxxxxxxx", AInstEmit.Uqxtn_S, typeof(AOpCodeSimd)); diff --git a/ChocolArm64/Instruction/AInstEmitSimdArithmetic.cs b/ChocolArm64/Instruction/AInstEmitSimdArithmetic.cs index b9aedd07b3..27a86d84c2 100644 --- a/ChocolArm64/Instruction/AInstEmitSimdArithmetic.cs +++ b/ChocolArm64/Instruction/AInstEmitSimdArithmetic.cs @@ -1199,22 +1199,22 @@ namespace ChocolArm64.Instruction public static void Sqxtn_S(AILEmitterCtx Context) { - EmitScalarSaturatingNarrowOpSxSx(Context, () => { }); + EmitSaturatingNarrowOp(Context, SaturatingNarrowFlags.ScalarSxSx); } public static void Sqxtn_V(AILEmitterCtx Context) { - EmitVectorSaturatingNarrowOpSxSx(Context, () => { }); + EmitSaturatingNarrowOp(Context, SaturatingNarrowFlags.VectorSxSx); } public static void Sqxtun_S(AILEmitterCtx Context) { - EmitScalarSaturatingNarrowOpSxZx(Context, () => { }); + EmitSaturatingNarrowOp(Context, SaturatingNarrowFlags.ScalarSxZx); } public static void Sqxtun_V(AILEmitterCtx Context) { - EmitVectorSaturatingNarrowOpSxZx(Context, () => { }); + EmitSaturatingNarrowOp(Context, SaturatingNarrowFlags.VectorSxZx); } public static void Srhadd_V(AILEmitterCtx Context) @@ -1455,12 +1455,12 @@ namespace ChocolArm64.Instruction public static void Uqxtn_S(AILEmitterCtx Context) { - EmitScalarSaturatingNarrowOpZxZx(Context, () => { }); + EmitSaturatingNarrowOp(Context, SaturatingNarrowFlags.ScalarZxZx); } public static void Uqxtn_V(AILEmitterCtx Context) { - EmitVectorSaturatingNarrowOpZxZx(Context, () => { }); + EmitSaturatingNarrowOp(Context, SaturatingNarrowFlags.VectorZxZx); } public static void Urhadd_V(AILEmitterCtx Context) diff --git a/ChocolArm64/Instruction/AInstEmitSimdHelper.cs b/ChocolArm64/Instruction/AInstEmitSimdHelper.cs index cb884c1ac8..75a5a0d092 100644 --- a/ChocolArm64/Instruction/AInstEmitSimdHelper.cs +++ b/ChocolArm64/Instruction/AInstEmitSimdHelper.cs @@ -1004,56 +1004,14 @@ namespace ChocolArm64.Instruction ScalarSxSx = Scalar | SignedSrc | SignedDst, ScalarSxZx = Scalar | SignedSrc, - ScalarZxSx = Scalar | SignedDst, ScalarZxZx = Scalar, VectorSxSx = SignedSrc | SignedDst, VectorSxZx = SignedSrc, - VectorZxSx = SignedDst, VectorZxZx = 0 } - public static void EmitScalarSaturatingNarrowOpSxSx(AILEmitterCtx Context, Action Emit) - { - EmitSaturatingNarrowOp(Context, Emit, SaturatingNarrowFlags.ScalarSxSx); - } - - public static void EmitScalarSaturatingNarrowOpSxZx(AILEmitterCtx Context, Action Emit) - { - EmitSaturatingNarrowOp(Context, Emit, SaturatingNarrowFlags.ScalarSxZx); - } - - public static void EmitScalarSaturatingNarrowOpZxSx(AILEmitterCtx Context, Action Emit) - { - EmitSaturatingNarrowOp(Context, Emit, SaturatingNarrowFlags.ScalarZxSx); - } - - public static void EmitScalarSaturatingNarrowOpZxZx(AILEmitterCtx Context, Action Emit) - { - EmitSaturatingNarrowOp(Context, Emit, SaturatingNarrowFlags.ScalarZxZx); - } - - public static void EmitVectorSaturatingNarrowOpSxSx(AILEmitterCtx Context, Action Emit) - { - EmitSaturatingNarrowOp(Context, Emit, SaturatingNarrowFlags.VectorSxSx); - } - - public static void EmitVectorSaturatingNarrowOpSxZx(AILEmitterCtx Context, Action Emit) - { - EmitSaturatingNarrowOp(Context, Emit, SaturatingNarrowFlags.VectorSxZx); - } - - public static void EmitVectorSaturatingNarrowOpZxSx(AILEmitterCtx Context, Action Emit) - { - EmitSaturatingNarrowOp(Context, Emit, SaturatingNarrowFlags.VectorZxSx); - } - - public static void EmitVectorSaturatingNarrowOpZxZx(AILEmitterCtx Context, Action Emit) - { - EmitSaturatingNarrowOp(Context, Emit, SaturatingNarrowFlags.VectorZxZx); - } - - public static void EmitSaturatingNarrowOp(AILEmitterCtx Context, Action Emit, SaturatingNarrowFlags Flags) + public static void EmitSaturatingNarrowOp(AILEmitterCtx Context, SaturatingNarrowFlags Flags) { AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; @@ -1080,8 +1038,6 @@ namespace ChocolArm64.Instruction { EmitVectorExtract(Context, Op.Rn, Index, Op.Size + 1, SignedSrc); - Emit(); - EmitSatQ(Context, Op.Size, SignedSrc, SignedDst); EmitVectorInsertTmp(Context, Part + Index, Op.Size); diff --git a/ChocolArm64/Instruction/AInstEmitSimdShift.cs b/ChocolArm64/Instruction/AInstEmitSimdShift.cs index 4dee53b9b1..127abf1df8 100644 --- a/ChocolArm64/Instruction/AInstEmitSimdShift.cs +++ b/ChocolArm64/Instruction/AInstEmitSimdShift.cs @@ -10,6 +10,11 @@ namespace ChocolArm64.Instruction { static partial class AInstEmit { + public static void Rshrn_V(AILEmitterCtx Context) + { + EmitVectorShrImmNarrowOpZx(Context, Round: true); + } + public static void Shl_S(AILEmitterCtx Context) { AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; @@ -45,9 +50,7 @@ namespace ChocolArm64.Instruction public static void Shrn_V(AILEmitterCtx Context) { - AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; - - EmitVectorShImmNarrowBinaryZx(Context, () => Context.Emit(OpCodes.Shr_Un), GetImmShr(Op)); + EmitVectorShrImmNarrowOpZx(Context, Round: false); } public static void Sli_V(AILEmitterCtx Context) @@ -85,26 +88,44 @@ namespace ChocolArm64.Instruction } } + public static void Sqrshrn_S(AILEmitterCtx Context) + { + EmitRoundShrImmSaturatingNarrowOp(Context, ShrImmSaturatingNarrowFlags.ScalarSxSx); + } + public static void Sqrshrn_V(AILEmitterCtx Context) { - AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; + EmitRoundShrImmSaturatingNarrowOp(Context, ShrImmSaturatingNarrowFlags.VectorSxSx); + } - int Shift = GetImmShr(Op); + public static void Sqrshrun_S(AILEmitterCtx Context) + { + EmitRoundShrImmSaturatingNarrowOp(Context, ShrImmSaturatingNarrowFlags.ScalarSxZx); + } - long RoundConst = 1L << (Shift - 1); + public static void Sqrshrun_V(AILEmitterCtx Context) + { + EmitRoundShrImmSaturatingNarrowOp(Context, ShrImmSaturatingNarrowFlags.VectorSxZx); + } - Action Emit = () => - { - Context.EmitLdc_I8(RoundConst); + public static void Sqshrn_S(AILEmitterCtx Context) + { + EmitShrImmSaturatingNarrowOp(Context, ShrImmSaturatingNarrowFlags.ScalarSxSx); + } - Context.Emit(OpCodes.Add); + public static void Sqshrn_V(AILEmitterCtx Context) + { + EmitShrImmSaturatingNarrowOp(Context, ShrImmSaturatingNarrowFlags.VectorSxSx); + } - Context.EmitLdc_I4(Shift); + public static void Sqshrun_S(AILEmitterCtx Context) + { + EmitShrImmSaturatingNarrowOp(Context, ShrImmSaturatingNarrowFlags.ScalarSxZx); + } - Context.Emit(OpCodes.Shr); - }; - - EmitVectorSaturatingNarrowOpSxSx(Context, Emit); + public static void Sqshrun_V(AILEmitterCtx Context) + { + EmitShrImmSaturatingNarrowOp(Context, ShrImmSaturatingNarrowFlags.VectorSxZx); } public static void Srshr_S(AILEmitterCtx Context) @@ -159,6 +180,26 @@ namespace ChocolArm64.Instruction EmitVectorShrImmOpSx(Context, ShrImmFlags.Accumulate); } + public static void Uqrshrn_S(AILEmitterCtx Context) + { + EmitRoundShrImmSaturatingNarrowOp(Context, ShrImmSaturatingNarrowFlags.ScalarZxZx); + } + + public static void Uqrshrn_V(AILEmitterCtx Context) + { + EmitRoundShrImmSaturatingNarrowOp(Context, ShrImmSaturatingNarrowFlags.VectorZxZx); + } + + public static void Uqshrn_S(AILEmitterCtx Context) + { + EmitShrImmSaturatingNarrowOp(Context, ShrImmSaturatingNarrowFlags.ScalarZxZx); + } + + public static void Uqshrn_V(AILEmitterCtx Context) + { + EmitShrImmSaturatingNarrowOp(Context, ShrImmSaturatingNarrowFlags.VectorZxZx); + } + public static void Urshr_S(AILEmitterCtx Context) { EmitScalarShrImmOpZx(Context, ShrImmFlags.Round); @@ -367,6 +408,138 @@ namespace ChocolArm64.Instruction } } + private static void EmitVectorShrImmNarrowOpZx(AILEmitterCtx Context, bool Round) + { + AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; + + int Shift = GetImmShr(Op); + + long RoundConst = 1L << (Shift - 1); + + int Elems = 8 >> Op.Size; + + int Part = Op.RegisterSize == ARegisterSize.SIMD128 ? Elems : 0; + + if (Part != 0) + { + Context.EmitLdvec(Op.Rd); + Context.EmitStvectmp(); + } + + for (int Index = 0; Index < Elems; Index++) + { + EmitVectorExtractZx(Context, Op.Rn, Index, Op.Size + 1); + + if (Round) + { + Context.EmitLdc_I8(RoundConst); + + Context.Emit(OpCodes.Add); + } + + Context.EmitLdc_I4(Shift); + + Context.Emit(OpCodes.Shr_Un); + + EmitVectorInsertTmp(Context, Part + Index, Op.Size); + } + + Context.EmitLdvectmp(); + Context.EmitStvec(Op.Rd); + + if (Part == 0) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + + [Flags] + private enum ShrImmSaturatingNarrowFlags + { + Scalar = 1 << 0, + SignedSrc = 1 << 1, + SignedDst = 1 << 2, + + Round = 1 << 3, + + ScalarSxSx = Scalar | SignedSrc | SignedDst, + ScalarSxZx = Scalar | SignedSrc, + ScalarZxZx = Scalar, + + VectorSxSx = SignedSrc | SignedDst, + VectorSxZx = SignedSrc, + VectorZxZx = 0 + } + + private static void EmitRoundShrImmSaturatingNarrowOp(AILEmitterCtx Context, ShrImmSaturatingNarrowFlags Flags) + { + EmitShrImmSaturatingNarrowOp(Context, ShrImmSaturatingNarrowFlags.Round | Flags); + } + + private static void EmitShrImmSaturatingNarrowOp(AILEmitterCtx Context, ShrImmSaturatingNarrowFlags Flags) + { + AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp; + + bool Scalar = (Flags & ShrImmSaturatingNarrowFlags.Scalar) != 0; + bool SignedSrc = (Flags & ShrImmSaturatingNarrowFlags.SignedSrc) != 0; + bool SignedDst = (Flags & ShrImmSaturatingNarrowFlags.SignedDst) != 0; + bool Round = (Flags & ShrImmSaturatingNarrowFlags.Round) != 0; + + int Shift = GetImmShr(Op); + + long RoundConst = 1L << (Shift - 1); + + int Elems = !Scalar ? 8 >> Op.Size : 1; + + int Part = !Scalar && (Op.RegisterSize == ARegisterSize.SIMD128) ? Elems : 0; + + if (Scalar) + { + EmitVectorZeroLowerTmp(Context); + } + + if (Part != 0) + { + Context.EmitLdvec(Op.Rd); + Context.EmitStvectmp(); + } + + for (int Index = 0; Index < Elems; Index++) + { + EmitVectorExtract(Context, Op.Rn, Index, Op.Size + 1, SignedSrc); + + if (Op.Size <= 1 || !Round) + { + if (Round) + { + Context.EmitLdc_I8(RoundConst); + + Context.Emit(OpCodes.Add); + } + + Context.EmitLdc_I4(Shift); + + Context.Emit(SignedSrc ? OpCodes.Shr : OpCodes.Shr_Un); + } + else /* if (Op.Size == 2 && Round) */ + { + EmitShrImm_64(Context, SignedSrc, RoundConst, Shift); // Shift <= 32 + } + + EmitSatQ(Context, Op.Size, SignedSrc, SignedDst); + + EmitVectorInsertTmp(Context, Part + Index, Op.Size); + } + + Context.EmitLdvectmp(); + Context.EmitStvec(Op.Rd); + + if (Part == 0) + { + EmitVectorZeroUpper(Context, Op.Rd); + } + } + // Dst_64 = (Int(Src_64, Signed) + RoundConst) >> Shift; private static void EmitShrImm_64( AILEmitterCtx Context, @@ -374,11 +547,6 @@ namespace ChocolArm64.Instruction long RoundConst, int Shift) { - if (((AOpCodeSimd)Context.CurrOp).Size < 3) - { - throw new InvalidOperationException(); - } - Context.EmitLdc_I8(RoundConst); Context.EmitLdc_I4(Shift); @@ -387,41 +555,6 @@ namespace ChocolArm64.Instruction : nameof(ASoftFallback.UnsignedShrImm_64)); } - private static void EmitVectorShImmNarrowBinarySx(AILEmitterCtx Context, Action Emit, int Imm) - { - EmitVectorShImmNarrowBinaryOp(Context, Emit, Imm, true); - } - - private static void EmitVectorShImmNarrowBinaryZx(AILEmitterCtx Context, Action Emit, int Imm) - { - EmitVectorShImmNarrowBinaryOp(Context, Emit, Imm, false); - } - - private static void EmitVectorShImmNarrowBinaryOp(AILEmitterCtx Context, Action Emit, int Imm, bool Signed) - { - AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp; - - int Elems = 8 >> Op.Size; - - int Part = Op.RegisterSize == ARegisterSize.SIMD128 ? Elems : 0; - - for (int Index = 0; Index < Elems; Index++) - { - EmitVectorExtract(Context, Op.Rn, Index, Op.Size + 1, Signed); - - Context.EmitLdc_I4(Imm); - - Emit(); - - EmitVectorInsert(Context, Op.Rd, Part + Index, Op.Size); - } - - if (Part == 0) - { - EmitVectorZeroUpper(Context, Op.Rd); - } - } - private static void EmitVectorShImmWidenBinarySx(AILEmitterCtx Context, Action Emit, int Imm) { EmitVectorShImmWidenBinaryOp(Context, Emit, Imm, true); diff --git a/Ryujinx.Tests/Cpu/CpuTest.cs b/Ryujinx.Tests/Cpu/CpuTest.cs index 1f7151ffe7..c69c7a02f9 100644 --- a/Ryujinx.Tests/Cpu/CpuTest.cs +++ b/Ryujinx.Tests/Cpu/CpuTest.cs @@ -93,6 +93,7 @@ namespace Ryujinx.Tests.Cpu Vector128 V0 = default(Vector128), Vector128 V1 = default(Vector128), Vector128 V2 = default(Vector128), + Vector128 V3 = default(Vector128), bool Overflow = false, bool Carry = false, bool Zero = false, bool Negative = false, int Fpcr = 0x0, int Fpsr = 0x0) { @@ -106,6 +107,7 @@ namespace Ryujinx.Tests.Cpu Thread.ThreadState.V0 = V0; Thread.ThreadState.V1 = V1; Thread.ThreadState.V2 = V2; + Thread.ThreadState.V3 = V3; Thread.ThreadState.Overflow = Overflow; Thread.ThreadState.Carry = Carry; @@ -127,6 +129,7 @@ namespace Ryujinx.Tests.Cpu UnicornEmu.Q[0] = V0; UnicornEmu.Q[1] = V1; UnicornEmu.Q[2] = V2; + UnicornEmu.Q[3] = V3; UnicornEmu.OverflowFlag = Overflow; UnicornEmu.CarryFlag = Carry; @@ -162,13 +165,14 @@ namespace Ryujinx.Tests.Cpu Vector128 V0 = default(Vector128), Vector128 V1 = default(Vector128), Vector128 V2 = default(Vector128), + Vector128 V3 = default(Vector128), bool Overflow = false, bool Carry = false, bool Zero = false, bool Negative = false, int Fpcr = 0x0, int Fpsr = 0x0) { this.Opcode(Opcode); this.Opcode(0xD4200000); // BRK #0 this.Opcode(0xD65F03C0); // RET - SetThreadState(X0, X1, X2, X3, X31, V0, V1, V2, Overflow, Carry, Zero, Negative, Fpcr, Fpsr); + SetThreadState(X0, X1, X2, X3, X31, V0, V1, V2, V3, Overflow, Carry, Zero, Negative, Fpcr, Fpsr); ExecuteOpcodes(); return GetThreadState(); @@ -195,13 +199,30 @@ namespace Ryujinx.Tests.Cpu QC = 1 << 27 } - protected void CompareAgainstUnicorn(FPSR FpsrMask = FPSR.None) + protected enum FpSkips { None, IfNaN_S, IfNaN_D }; + + protected enum FpUseTolerance { None, OneUlps_S, OneUlps_D }; + + protected void CompareAgainstUnicorn( + FPSR FpsrMask = FPSR.None, + FpSkips FpSkips = FpSkips.None, + FpUseTolerance FpUseTolerance = FpUseTolerance.None) { if (!UnicornAvailable) { return; } + if (FpSkips == FpSkips.IfNaN_S && float.IsNaN(VectorExtractSingle(UnicornEmu.Q[0], (byte)0))) + { + Assert.Ignore("NaN test."); + } + + if (FpSkips == FpSkips.IfNaN_D && double.IsNaN(VectorExtractDouble(UnicornEmu.Q[0], (byte)0))) + { + Assert.Ignore("NaN test."); + } + Assert.That(Thread.ThreadState.X0, Is.EqualTo(UnicornEmu.X[0])); Assert.That(Thread.ThreadState.X1, Is.EqualTo(UnicornEmu.X[1])); Assert.That(Thread.ThreadState.X2, Is.EqualTo(UnicornEmu.X[2])); @@ -236,7 +257,51 @@ namespace Ryujinx.Tests.Cpu Assert.That(Thread.ThreadState.X31, Is.EqualTo(UnicornEmu.SP)); - Assert.That(Thread.ThreadState.V0, Is.EqualTo(UnicornEmu.Q[0])); + if (FpUseTolerance == FpUseTolerance.None) + { + Assert.That(Thread.ThreadState.V0, Is.EqualTo(UnicornEmu.Q[0])); + } + else + { + if (!Is.EqualTo(UnicornEmu.Q[0]).ApplyTo(Thread.ThreadState.V0).IsSuccess) + { + if (FpUseTolerance == FpUseTolerance.OneUlps_S) + { + if (float.IsNormal (VectorExtractSingle(UnicornEmu.Q[0], (byte)0)) || + float.IsSubnormal(VectorExtractSingle(UnicornEmu.Q[0], (byte)0))) + { + Assert.That (VectorExtractSingle(Thread.ThreadState.V0, (byte)0), + Is.EqualTo(VectorExtractSingle(UnicornEmu.Q[0], (byte)0)).Within(1).Ulps); + Assert.That (VectorExtractSingle(Thread.ThreadState.V0, (byte)1), + Is.EqualTo(VectorExtractSingle(UnicornEmu.Q[0], (byte)1)).Within(1).Ulps); + Assert.That (VectorExtractSingle(Thread.ThreadState.V0, (byte)2), + Is.EqualTo(VectorExtractSingle(UnicornEmu.Q[0], (byte)2)).Within(1).Ulps); + Assert.That (VectorExtractSingle(Thread.ThreadState.V0, (byte)3), + Is.EqualTo(VectorExtractSingle(UnicornEmu.Q[0], (byte)3)).Within(1).Ulps); + } + else + { + Assert.That(Thread.ThreadState.V0, Is.EqualTo(UnicornEmu.Q[0])); + } + } + + if (FpUseTolerance == FpUseTolerance.OneUlps_D) + { + if (double.IsNormal (VectorExtractDouble(UnicornEmu.Q[0], (byte)0)) || + double.IsSubnormal(VectorExtractDouble(UnicornEmu.Q[0], (byte)0))) + { + Assert.That (VectorExtractDouble(Thread.ThreadState.V0, (byte)0), + Is.EqualTo(VectorExtractDouble(UnicornEmu.Q[0], (byte)0)).Within(1).Ulps); + Assert.That (VectorExtractDouble(Thread.ThreadState.V0, (byte)1), + Is.EqualTo(VectorExtractDouble(UnicornEmu.Q[0], (byte)1)).Within(1).Ulps); + } + else + { + Assert.That(Thread.ThreadState.V0, Is.EqualTo(UnicornEmu.Q[0])); + } + } + } + } Assert.That(Thread.ThreadState.V1, Is.EqualTo(UnicornEmu.Q[1])); Assert.That(Thread.ThreadState.V2, Is.EqualTo(UnicornEmu.Q[2])); Assert.That(Thread.ThreadState.V3, Is.EqualTo(UnicornEmu.Q[3])); @@ -310,6 +375,18 @@ namespace Ryujinx.Tests.Cpu return Sse.StaticCast(Sse2.SetVector128(BitConverter.DoubleToInt64Bits(E1), 0)); } + protected static float VectorExtractSingle(Vector128 Vector, byte Index) + { + if (!Sse41.IsSupported) + { + throw new PlatformNotSupportedException(); + } + + int Value = Sse41.Extract(Sse.StaticCast(Vector), Index); + + return BitConverter.Int32BitsToSingle(Value); + } + protected static double VectorExtractDouble(Vector128 Vector, byte Index) { if (!Sse41.IsSupported) @@ -371,5 +448,47 @@ namespace Ryujinx.Tests.Cpu return Sse41.Extract(Sse.StaticCast(Vector), (byte)1); } + + protected static uint GenNormal_S() + { + uint Rnd; + + do Rnd = TestContext.CurrentContext.Random.NextUInt(); + while ((Rnd & 0x7F800000u) == 0u || + (Rnd & 0x7F800000u) == 0x7F800000u); + + return Rnd; + } + + protected static uint GenSubNormal_S() + { + uint Rnd; + + do Rnd = TestContext.CurrentContext.Random.NextUInt(); + while ((Rnd & 0x007FFFFFu) == 0u); + + return Rnd & 0x807FFFFFu; + } + + protected static ulong GenNormal_D() + { + ulong Rnd; + + do Rnd = TestContext.CurrentContext.Random.NextULong(); + while ((Rnd & 0x7FF0000000000000ul) == 0ul || + (Rnd & 0x7FF0000000000000ul) == 0x7FF0000000000000ul); + + return Rnd; + } + + protected static ulong GenSubNormal_D() + { + ulong Rnd; + + do Rnd = TestContext.CurrentContext.Random.NextULong(); + while ((Rnd & 0x000FFFFFFFFFFFFFul) == 0ul); + + return Rnd & 0x800FFFFFFFFFFFFFul; + } } } diff --git a/Ryujinx.Tests/Cpu/CpuTestSimd.cs b/Ryujinx.Tests/Cpu/CpuTestSimd.cs index ec0cd104fd..b423b4de73 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimd.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimd.cs @@ -4,6 +4,7 @@ using ChocolArm64.State; using NUnit.Framework; +using System.Collections.Generic; using System.Runtime.Intrinsics; namespace Ryujinx.Tests.Cpu @@ -13,7 +14,7 @@ namespace Ryujinx.Tests.Cpu { #if Simd -#region "ValueSource" +#region "ValueSource (Types)" private static ulong[] _1B1H1S1D_() { return new ulong[] { 0x0000000000000000ul, 0x000000000000007Ful, @@ -78,78 +79,183 @@ namespace Ryujinx.Tests.Cpu 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul }; } - private static ulong[] _1S_F_() + private static IEnumerable _1S_F_() { - return new ulong[] + yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x0000000080800000ul; // -Min Normal + yield return 0x00000000807FFFFFul; // -Max SubNormal + yield return 0x0000000080000001ul; // -Min SubNormal + yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0000000000800000ul; // +Min Normal + yield return 0x00000000007FFFFFul; // +Max SubNormal + yield return 0x0000000000000001ul; // +Min SubNormal + + if (!NoZeros) { - 0x00000000FFFFFFFFul, // -QNaN (all ones payload) - 0x00000000FFBFFFFFul, // -SNaN (all ones payload) - 0x00000000FF800000ul, // -INF - 0x00000000FF7FFFFFul, // -Max Normal, float.MinValue - 0x0000000080800000ul, // -Min Normal - 0x00000000807FFFFFul, // -Max SubNormal - 0x0000000080000001ul, // -Min SubNormal - 0x0000000080000000ul, // -0 - 0x0000000000000000ul, // +0 - 0x0000000000000001ul, // +Min SubNormal - 0x00000000007FFFFFul, // +Max SubNormal - 0x0000000000800000ul, // +Min Normal - 0x000000007F7FFFFFul, // +Max Normal, float.MaxValue - 0x000000007F800000ul, // +INF - 0x000000007FBFFFFFul, // +SNaN (all ones payload) - 0x000000007FFFFFFFul // +QNaN (all ones payload) + yield return 0x0000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0x00000000FF800000ul; // -Infinity + yield return 0x000000007F800000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0x00000000FFFFFFFFul; // -QNaN (all ones payload) + yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) + yield return 0x000000007FFFFFFFul; // +QNaN (all ones payload) + yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) + } + + for (int Cnt = 1; Cnt <= RndCnt; Cnt++) + { + ulong Grbg = TestContext.CurrentContext.Random.NextUInt(); + ulong Rnd1 = GenNormal_S(); + ulong Rnd2 = GenSubNormal_S(); + + yield return (Grbg << 32) | Rnd1; + yield return (Grbg << 32) | Rnd2; + } + } + + private static IEnumerable _2S_F_() + { + yield return 0xFF7FFFFFFF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x8080000080800000ul; // -Min Normal + yield return 0x807FFFFF807FFFFFul; // -Max SubNormal + yield return 0x8000000180000001ul; // -Min SubNormal + yield return 0x7F7FFFFF7F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0080000000800000ul; // +Min Normal + yield return 0x007FFFFF007FFFFFul; // +Max SubNormal + yield return 0x0000000100000001ul; // +Min SubNormal + + if (!NoZeros) + { + yield return 0x8000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0xFF800000FF800000ul; // -Infinity + yield return 0x7F8000007F800000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0xFFFFFFFFFFFFFFFFul; // -QNaN (all ones payload) + yield return 0xFFBFFFFFFFBFFFFFul; // -SNaN (all ones payload) + yield return 0x7FFFFFFF7FFFFFFFul; // +QNaN (all ones payload) + yield return 0x7FBFFFFF7FBFFFFFul; // +SNaN (all ones payload) + } + + for (int Cnt = 1; Cnt <= RndCnt; Cnt++) + { + ulong Rnd1 = GenNormal_S(); + ulong Rnd2 = GenSubNormal_S(); + + yield return (Rnd1 << 32) | Rnd1; + yield return (Rnd2 << 32) | Rnd2; + } + } + + private static IEnumerable _1D_F_() + { + yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) + yield return 0x8010000000000000ul; // -Min Normal + yield return 0x800FFFFFFFFFFFFFul; // -Max SubNormal + yield return 0x8000000000000001ul; // -Min SubNormal + yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) + yield return 0x0010000000000000ul; // +Min Normal + yield return 0x000FFFFFFFFFFFFFul; // +Max SubNormal + yield return 0x0000000000000001ul; // +Min SubNormal + + if (!NoZeros) + { + yield return 0x8000000000000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0xFFF0000000000000ul; // -Infinity + yield return 0x7FF0000000000000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0xFFFFFFFFFFFFFFFFul; // -QNaN (all ones payload) + yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) + yield return 0x7FFFFFFFFFFFFFFFul; // +QNaN (all ones payload) + yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) + } + + for (int Cnt = 1; Cnt <= RndCnt; Cnt++) + { + ulong Rnd1 = GenNormal_D(); + ulong Rnd2 = GenSubNormal_D(); + + yield return Rnd1; + yield return Rnd2; + } + } +#endregion + +#region "ValueSource (Opcodes)" + private static uint[] _F_Cvt_NZ_SU_S_S_() + { + return new uint[] + { + 0x5E21A820u, // FCVTNS S0, S1 + 0x7E21A820u, // FCVTNU S0, S1 + 0x5EA1B820u, // FCVTZS S0, S1 + 0x7EA1B820u // FCVTZU S0, S1 }; } - private static ulong[] _2S_F_() + private static uint[] _F_Cvt_NZ_SU_S_D_() { - return new ulong[] + return new uint[] { - 0xFFFFFFFFFFFFFFFFul, // -QNaN (all ones payload) - 0xFFBFFFFFFFBFFFFFul, // -SNaN (all ones payload) - 0xFF800000FF800000ul, // -INF - 0xFF7FFFFFFF7FFFFFul, // -Max Normal, float.MinValue - 0x8080000080800000ul, // -Min Normal - 0x807FFFFF807FFFFFul, // -Max SubNormal - 0x8000000180000001ul, // -Min SubNormal - 0x8000000080000000ul, // -0 - 0x0000000000000000ul, // +0 - 0x0000000100000001ul, // +Min SubNormal - 0x007FFFFF007FFFFFul, // +Max SubNormal - 0x0080000000800000ul, // +Min Normal - 0x7F7FFFFF7F7FFFFFul, // +Max Normal, float.MaxValue - 0x7F8000007F800000ul, // +INF - 0x7FBFFFFF7FBFFFFFul, // +SNaN (all ones payload) - 0x7FFFFFFF7FFFFFFFul // +QNaN (all ones payload) + 0x5E61A820u, // FCVTNS D0, D1 + 0x7E61A820u, // FCVTNU D0, D1 + 0x5EE1B820u, // FCVTZS D0, D1 + 0x7EE1B820u // FCVTZU D0, D1 }; } - private static ulong[] _1D_F_() + private static uint[] _F_Cvt_NZ_SU_V_2S_4S_() { - return new ulong[] + return new uint[] { - 0xFFFFFFFFFFFFFFFFul, // -QNaN (all ones payload) - 0xFFF7FFFFFFFFFFFFul, // -SNaN (all ones payload) - 0xFFF0000000000000ul, // -INF - 0xFFEFFFFFFFFFFFFFul, // -Max Normal, double.MinValue - 0x8010000000000000ul, // -Min Normal - 0x800FFFFFFFFFFFFFul, // -Max SubNormal - 0x8000000000000001ul, // -Min SubNormal - 0x8000000000000000ul, // -0 - 0x0000000000000000ul, // +0 - 0x0000000000000001ul, // +Min SubNormal - 0x000FFFFFFFFFFFFFul, // +Max SubNormal - 0x0010000000000000ul, // +Min Normal - 0x7FEFFFFFFFFFFFFFul, // +Max Normal, double.MaxValue - 0x7FF0000000000000ul, // +INF - 0x7FF7FFFFFFFFFFFFul, // +SNaN (all ones payload) - 0x7FFFFFFFFFFFFFFFul // +QNaN (all ones payload) + 0x0E21A800u, // FCVTNS V0.2S, V0.2S + 0x2E21A800u, // FCVTNU V0.2S, V0.2S + 0x0EA1B800u, // FCVTZS V0.2S, V0.2S + 0x2EA1B800u // FCVTZU V0.2S, V0.2S + }; + } + + private static uint[] _F_Cvt_NZ_SU_V_2D_() + { + return new uint[] + { + 0x4E61A800u, // FCVTNS V0.2D, V0.2D + 0x6E61A800u, // FCVTNU V0.2D, V0.2D + 0x4EE1B800u, // FCVTZS V0.2D, V0.2D + 0x6EE1B800u // FCVTZU V0.2D, V0.2D }; } #endregion private const int RndCnt = 2; + private static readonly bool NoZeros = false; + private static readonly bool NoInfs = false; + private static readonly bool NoNaNs = false; + [Test, Pairwise, Description("ABS , ")] public void Abs_S_D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, @@ -645,176 +751,104 @@ namespace Ryujinx.Tests.Cpu CompareAgainstUnicorn(); } - [Test, Pairwise, Description("FCVTNS , ")] - public void Fcvtns_S_S([Values(0u)] uint Rd, - [Values(1u, 0u)] uint Rn, - [ValueSource("_1S_F_")] [Random(RndCnt)] ulong Z, - [ValueSource("_1S_F_")] [Random(RndCnt)] ulong A) + [Test, Pairwise, Description("FCVT
, ")] + public void Fcvt_S_SD([ValueSource("_1S_F_")] ulong A) { + //const int DNFlagBit = 25; // Default NaN mode control bit. //const int FZFlagBit = 24; // Flush-to-zero mode control bit. - uint Opcode = 0x5E21A800; // FCVTNS S0, S0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + uint Opcode = 0x1E22C020; // FCVT D0, S1 - //int Fpcr = 1 << FZFlagBit; // Flush-to-zero mode enabled. + ulong Z = TestContext.CurrentContext.Random.NextULong(); + Vector128 V0 = MakeVectorE1(Z); + Vector128 V1 = MakeVectorE0(A); + //int Fpcr = 1 << DNFlagBit; // Any operation involving one or more NaNs returns the Default NaN. + //Fpcr |= 1 << FZFlagBit; // Flush-to-zero mode enabled. + + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1/*, Fpcr: Fpcr*/); + + CompareAgainstUnicorn(/*FpsrMask: FPSR.IDC | FPSR.IOC*/); + } + + [Test, Pairwise, Description("FCVT , ")] + public void Fcvt_S_DS([ValueSource("_1D_F_")] ulong A) + { + uint Opcode = 0x1E624020; // FCVT S0, D1 + + ulong Z = TestContext.CurrentContext.Random.NextULong(); Vector128 V0 = MakeVectorE0E1(Z, Z); Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1/*, Fpcr: Fpcr*/); + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void F_Cvt_NZ_SU_S_S([ValueSource("_F_Cvt_NZ_SU_S_S_")] uint Opcodes, + [ValueSource("_1S_F_")] ulong A) + { + //const int FZFlagBit = 24; // Flush-to-zero mode control bit. + + ulong Z = TestContext.CurrentContext.Random.NextULong(); + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + + //int Fpcr = 1 << FZFlagBit; // Flush-to-zero mode enabled. + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1/*, Fpcr: Fpcr*/); CompareAgainstUnicorn(/*FpsrMask: FPSR.IDC | FPSR.IXC | FPSR.IOC*/); } - [Test, Pairwise, Description("FCVTNS , ")] - public void Fcvtns_S_D([Values(0u)] uint Rd, - [Values(1u, 0u)] uint Rn, - [ValueSource("_1D_F_")] [Random(RndCnt)] ulong Z, - [ValueSource("_1D_F_")] [Random(RndCnt)] ulong A) + [Test, Pairwise] + public void F_Cvt_NZ_SU_S_D([ValueSource("_F_Cvt_NZ_SU_S_D_")] uint Opcodes, + [ValueSource("_1D_F_")] ulong A) { - //const int FZFlagBit = 24; // Flush-to-zero mode control bit. - - uint Opcode = 0x5E61A800; // FCVTNS D0, D0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - - //int Fpcr = 1 << FZFlagBit; // Flush-to-zero mode enabled. - - Vector128 V0 = MakeVectorE0E1(Z, Z); + ulong Z = TestContext.CurrentContext.Random.NextULong(); + Vector128 V0 = MakeVectorE1(Z); Vector128 V1 = MakeVectorE0(A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1/*, Fpcr: Fpcr*/); + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1); - CompareAgainstUnicorn(/*FpsrMask: FPSR.IDC | FPSR.IXC | FPSR.IOC*/); + CompareAgainstUnicorn(); } - [Test, Pairwise, Description("FCVTNS ., .")] - public void Fcvtns_V_2S_4S([Values(0u)] uint Rd, - [Values(1u, 0u)] uint Rn, - [ValueSource("_2S_F_")] [Random(RndCnt)] ulong Z, - [ValueSource("_2S_F_")] [Random(RndCnt)] ulong A, - [Values(0b0u, 0b1u)] uint Q) // <2S, 4S> + [Test, Pairwise] + public void F_Cvt_NZ_SU_V_2S_4S([ValueSource("_F_Cvt_NZ_SU_V_2S_4S_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_2S_F_")] ulong Z, + [ValueSource("_2S_F_")] ulong A, + [Values(0b0u, 0b1u)] uint Q) // <2S, 4S> { - //const int FZFlagBit = 24; // Flush-to-zero mode control bit. - - uint Opcode = 0x0E21A800; // FCVTNS V0.2S, V0.2S - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((Q & 1) << 30); - - //int Fpcr = 1 << FZFlagBit; // Flush-to-zero mode enabled. + Opcodes |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcodes |= ((Q & 1) << 30); Vector128 V0 = MakeVectorE0E1(Z, Z); Vector128 V1 = MakeVectorE0E1(A, A * Q); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1/*, Fpcr: Fpcr*/); + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1); - CompareAgainstUnicorn(/*FpsrMask: FPSR.IDC | FPSR.IXC | FPSR.IOC*/); + CompareAgainstUnicorn(); } - [Test, Pairwise, Description("FCVTNS ., .")] - public void Fcvtns_V_2D([Values(0u)] uint Rd, - [Values(1u, 0u)] uint Rn, - [ValueSource("_1D_F_")] [Random(RndCnt)] ulong Z, - [ValueSource("_1D_F_")] [Random(RndCnt)] ulong A) + [Test, Pairwise] + public void F_Cvt_NZ_SU_V_2D([ValueSource("_F_Cvt_NZ_SU_V_2D_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_1D_F_")] ulong Z, + [ValueSource("_1D_F_")] ulong A) { - //const int FZFlagBit = 24; // Flush-to-zero mode control bit. - - uint Opcode = 0x4E61A800; // FCVTNS V0.2D, V0.2D - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - - //int Fpcr = 1 << FZFlagBit; // Flush-to-zero mode enabled. + Opcodes |= ((Rn & 31) << 5) | ((Rd & 31) << 0); Vector128 V0 = MakeVectorE0E1(Z, Z); Vector128 V1 = MakeVectorE0E1(A, A); - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1/*, Fpcr: Fpcr*/); + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1); - CompareAgainstUnicorn(/*FpsrMask: FPSR.IDC | FPSR.IXC | FPSR.IOC*/); - } - - [Test, Pairwise, Description("FCVTNU , ")] - public void Fcvtnu_S_S([Values(0u)] uint Rd, - [Values(1u, 0u)] uint Rn, - [ValueSource("_1S_F_")] [Random(RndCnt)] ulong Z, - [ValueSource("_1S_F_")] [Random(RndCnt)] ulong A) - { - //const int FZFlagBit = 24; // Flush-to-zero mode control bit. - - uint Opcode = 0x7E21A800; // FCVTNU S0, S0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - - //int Fpcr = 1 << FZFlagBit; // Flush-to-zero mode enabled. - - Vector128 V0 = MakeVectorE0E1(Z, Z); - Vector128 V1 = MakeVectorE0(A); - - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1/*, Fpcr: Fpcr*/); - - CompareAgainstUnicorn(/*FpsrMask: FPSR.IDC | FPSR.IXC | FPSR.IOC*/); - } - - [Test, Pairwise, Description("FCVTNU , ")] - public void Fcvtnu_S_D([Values(0u)] uint Rd, - [Values(1u, 0u)] uint Rn, - [ValueSource("_1D_F_")] [Random(RndCnt)] ulong Z, - [ValueSource("_1D_F_")] [Random(RndCnt)] ulong A) - { - //const int FZFlagBit = 24; // Flush-to-zero mode control bit. - - uint Opcode = 0x7E61A800; // FCVTNU D0, D0 - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - - //int Fpcr = 1 << FZFlagBit; // Flush-to-zero mode enabled. - - Vector128 V0 = MakeVectorE0E1(Z, Z); - Vector128 V1 = MakeVectorE0(A); - - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1/*, Fpcr: Fpcr*/); - - CompareAgainstUnicorn(/*FpsrMask: FPSR.IDC | FPSR.IXC | FPSR.IOC*/); - } - - [Test, Pairwise, Description("FCVTNU ., .")] - public void Fcvtnu_V_2S_4S([Values(0u)] uint Rd, - [Values(1u, 0u)] uint Rn, - [ValueSource("_2S_F_")] [Random(RndCnt)] ulong Z, - [ValueSource("_2S_F_")] [Random(RndCnt)] ulong A, - [Values(0b0u, 0b1u)] uint Q) // <2S, 4S> - { - //const int FZFlagBit = 24; // Flush-to-zero mode control bit. - - uint Opcode = 0x2E21A800; // FCVTNU V0.2S, V0.2S - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - Opcode |= ((Q & 1) << 30); - - //int Fpcr = 1 << FZFlagBit; // Flush-to-zero mode enabled. - - Vector128 V0 = MakeVectorE0E1(Z, Z); - Vector128 V1 = MakeVectorE0E1(A, A * Q); - - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1/*, Fpcr: Fpcr*/); - - CompareAgainstUnicorn(/*FpsrMask: FPSR.IDC | FPSR.IXC | FPSR.IOC*/); - } - - [Test, Pairwise, Description("FCVTNU ., .")] - public void Fcvtnu_V_2D([Values(0u)] uint Rd, - [Values(1u, 0u)] uint Rn, - [ValueSource("_1D_F_")] [Random(RndCnt)] ulong Z, - [ValueSource("_1D_F_")] [Random(RndCnt)] ulong A) - { - //const int FZFlagBit = 24; // Flush-to-zero mode control bit. - - uint Opcode = 0x6E61A800; // FCVTNU V0.2D, V0.2D - Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); - - //int Fpcr = 1 << FZFlagBit; // Flush-to-zero mode enabled. - - Vector128 V0 = MakeVectorE0E1(Z, Z); - Vector128 V1 = MakeVectorE0E1(A, A); - - AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1/*, Fpcr: Fpcr*/); - - CompareAgainstUnicorn(/*FpsrMask: FPSR.IDC | FPSR.IXC | FPSR.IOC*/); + CompareAgainstUnicorn(); } [Test, Pairwise, Description("NEG , ")] diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdIns.cs b/Ryujinx.Tests/Cpu/CpuTestSimdIns.cs new file mode 100644 index 0000000000..387cdf5dd9 --- /dev/null +++ b/Ryujinx.Tests/Cpu/CpuTestSimdIns.cs @@ -0,0 +1,74 @@ +#define SimdIns + +using ChocolArm64.State; + +using NUnit.Framework; + +using System.Runtime.Intrinsics; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdIns")] // Tested: second half of 2018. + public sealed class CpuTestSimdIns : CpuTest + { +#if SimdIns + +#region "ValueSource" + private static uint[] _W_() + { + return new uint[] { 0x00000000u, 0x0000007Fu, + 0x00000080u, 0x000000FFu, + 0x00007FFFu, 0x00008000u, + 0x0000FFFFu, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu }; + } + + private static ulong[] _X_() + { + return new ulong[] { 0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul }; + } +#endregion + + private const int RndCnt = 2; + + [Test, Pairwise, Description("DUP ., ")] + public void Dup_Gp_W([Values(0u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [ValueSource("_W_")] [Random(RndCnt)] uint Wn, + [Values(0, 1, 2)] int Size, // Q0: <8B, 4H, 2S> + [Values(0b0u, 0b1u)] uint Q) // Q1: <16B, 8H, 4S> + { + uint Imm5 = (1U << Size) & 0x1F; + + uint Opcode = 0x0E000C00; // RESERVED + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcode |= (Imm5 << 16); + Opcode |= ((Q & 1) << 30); + + ulong Z = TestContext.CurrentContext.Random.NextULong(); + Vector128 V0 = MakeVectorE0E1(Z, Z); + + AThreadState ThreadState = SingleOpcode(Opcode, X1: Wn, V0: V0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("DUP ., ")] + public void Dup_Gp_X([Values(0u)] uint Rd, + [Values(1u, 31u)] uint Rn, + [ValueSource("_X_")] [Random(RndCnt)] ulong Xn) + { + uint Opcode = 0x4E080C00; // DUP V0.2D, X0 + Opcode |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + + ulong Z = TestContext.CurrentContext.Random.NextULong(); + Vector128 V0 = MakeVectorE0E1(Z, Z); + + AThreadState ThreadState = SingleOpcode(Opcode, X1: Xn, V0: V0); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs b/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs index b7150db30a..ae409a6d85 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs @@ -4,6 +4,7 @@ using ChocolArm64.State; using NUnit.Framework; +using System.Collections.Generic; using System.Runtime.Intrinsics; namespace Ryujinx.Tests.Cpu @@ -13,7 +14,7 @@ namespace Ryujinx.Tests.Cpu { #if SimdReg -#region "ValueSource" +#region "ValueSource (Types)" private static ulong[] _1B1H1S1D_() { return new ulong[] { 0x0000000000000000ul, 0x000000000000007Ful, @@ -76,10 +77,188 @@ namespace Ryujinx.Tests.Cpu 0x8000000080000000ul, 0x7FFFFFFFFFFFFFFFul, 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul }; } + + private static IEnumerable _1S_F_() + { + yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x0000000080800000ul; // -Min Normal + yield return 0x00000000807FFFFFul; // -Max SubNormal + yield return 0x0000000080000001ul; // -Min SubNormal + yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0000000000800000ul; // +Min Normal + yield return 0x00000000007FFFFFul; // +Max SubNormal + yield return 0x0000000000000001ul; // +Min SubNormal + + if (!NoZeros) + { + yield return 0x0000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0x00000000FF800000ul; // -Infinity + yield return 0x000000007F800000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0x00000000FFFFFFFFul; // -QNaN (all ones payload) + yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) + yield return 0x000000007FFFFFFFul; // +QNaN (all ones payload) + yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) + } + + for (int Cnt = 1; Cnt <= RndCnt; Cnt++) + { + ulong Grbg = TestContext.CurrentContext.Random.NextUInt(); + ulong Rnd1 = GenNormal_S(); + ulong Rnd2 = GenSubNormal_S(); + + yield return (Grbg << 32) | Rnd1; + yield return (Grbg << 32) | Rnd2; + } + } + + private static IEnumerable _2S_F_() + { + yield return 0xFF7FFFFFFF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x8080000080800000ul; // -Min Normal + yield return 0x807FFFFF807FFFFFul; // -Max SubNormal + yield return 0x8000000180000001ul; // -Min SubNormal + yield return 0x7F7FFFFF7F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0080000000800000ul; // +Min Normal + yield return 0x007FFFFF007FFFFFul; // +Max SubNormal + yield return 0x0000000100000001ul; // +Min SubNormal + + if (!NoZeros) + { + yield return 0x8000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0xFF800000FF800000ul; // -Infinity + yield return 0x7F8000007F800000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0xFFFFFFFFFFFFFFFFul; // -QNaN (all ones payload) + yield return 0xFFBFFFFFFFBFFFFFul; // -SNaN (all ones payload) + yield return 0x7FFFFFFF7FFFFFFFul; // +QNaN (all ones payload) + yield return 0x7FBFFFFF7FBFFFFFul; // +SNaN (all ones payload) + } + + for (int Cnt = 1; Cnt <= RndCnt; Cnt++) + { + ulong Rnd1 = GenNormal_S(); + ulong Rnd2 = GenSubNormal_S(); + + yield return (Rnd1 << 32) | Rnd1; + yield return (Rnd2 << 32) | Rnd2; + } + } + + private static IEnumerable _1D_F_() + { + yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) + yield return 0x8010000000000000ul; // -Min Normal + yield return 0x800FFFFFFFFFFFFFul; // -Max SubNormal + yield return 0x8000000000000001ul; // -Min SubNormal + yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) + yield return 0x0010000000000000ul; // +Min Normal + yield return 0x000FFFFFFFFFFFFFul; // +Max SubNormal + yield return 0x0000000000000001ul; // +Min SubNormal + + if (!NoZeros) + { + yield return 0x8000000000000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!NoInfs) + { + yield return 0xFFF0000000000000ul; // -Infinity + yield return 0x7FF0000000000000ul; // +Infinity + } + + if (!NoNaNs) + { + yield return 0xFFFFFFFFFFFFFFFFul; // -QNaN (all ones payload) + yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) + yield return 0x7FFFFFFFFFFFFFFFul; // +QNaN (all ones payload) + yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) + } + + for (int Cnt = 1; Cnt <= RndCnt; Cnt++) + { + ulong Rnd1 = GenNormal_D(); + ulong Rnd2 = GenSubNormal_D(); + + yield return Rnd1; + yield return Rnd2; + } + } +#endregion + +#region "ValueSource (Opcodes)" + private static uint[] _F_Max_Min_Nm_S_S_() + { + return new uint[] + { + 0x1E224820u, // FMAX S0, S1, S2 + 0x1E226820u, // FMAXNM S0, S1, S2 + 0x1E225820u, // FMIN S0, S1, S2 + 0x1E227820u // FMINNM S0, S1, S2 + }; + } + + private static uint[] _F_Max_Min_Nm_S_D_() + { + return new uint[] + { + 0x1E624820u, // FMAX D0, D1, D2 + 0x1E626820u, // FMAXNM D0, D1, D2 + 0x1E625820u, // FMIN D0, D1, D2 + 0x1E627820u // FMINNM D0, D1, D2 + }; + } + + private static uint[] _F_Max_Min_Nm_P_V_2S_4S_() + { + return new uint[] + { + 0x0E20F400u, // FMAX V0.2S, V0.2S, V0.2S + 0x0E20C400u, // FMAXNM V0.2S, V0.2S, V0.2S + 0x2E20F400u, // FMAXP V0.2S, V0.2S, V0.2S + 0x0EA0F400u, // FMIN V0.2S, V0.2S, V0.2S + 0x0EA0C400u, // FMINNM V0.2S, V0.2S, V0.2S + 0x2EA0F400u // FMINP V0.2S, V0.2S, V0.2S + }; + } + + private static uint[] _F_Max_Min_Nm_P_V_2D_() + { + return new uint[] + { + 0x4E60F400u, // FMAX V0.2D, V0.2D, V0.2D + 0x4E60C400u, // FMAXNM V0.2D, V0.2D, V0.2D + 0x6E60F400u, // FMAXP V0.2D, V0.2D, V0.2D + 0x4EE0F400u, // FMIN V0.2D, V0.2D, V0.2D + 0x4EE0C400u, // FMINNM V0.2D, V0.2D, V0.2D + 0x6EE0F400u // FMINP V0.2D, V0.2D, V0.2D + }; + } #endregion private const int RndCnt = 2; + private static readonly bool NoZeros = false; + private static readonly bool NoInfs = false; + private static readonly bool NoNaNs = false; + [Test, Pairwise, Description("ADD , , ")] public void Add_S_D([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, @@ -856,6 +1035,132 @@ namespace Ryujinx.Tests.Cpu CompareAgainstUnicorn(); } + [Test, Pairwise, Description("FMADD , , , ")] + public void Fmadd_S_S([ValueSource("_1S_F_")] ulong A, + [ValueSource("_1S_F_")] ulong B, + [ValueSource("_1S_F_")] ulong C) + { + //const int DNFlagBit = 25; // Default NaN mode control bit. + //const int FZFlagBit = 24; // Flush-to-zero mode control bit. + + uint Opcode = 0x1F020C20; // FMADD S0, S1, S2, S3 + + ulong Z = TestContext.CurrentContext.Random.NextULong(); + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + Vector128 V2 = MakeVectorE0(B); + Vector128 V3 = MakeVectorE0(C); + + //int Fpcr = 1 << DNFlagBit; // Any operation involving one or more NaNs returns the Default NaN. + //Fpcr |= 1 << FZFlagBit; // Flush-to-zero mode enabled. + + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2, V3: V3/*, Fpcr: Fpcr*/); + + CompareAgainstUnicorn(/*FpsrMask: FPSR.IDC | FPSR.IOC, */FpSkips: FpSkips.IfNaN_S/*, FpUseTolerance: FpUseTolerance.OneUlps_S*/); + } + + [Test, Pairwise, Description("FMADD
, , , ")] + public void Fmadd_S_D([ValueSource("_1D_F_")] ulong A, + [ValueSource("_1D_F_")] ulong B, + [ValueSource("_1D_F_")] ulong C) + { + uint Opcode = 0x1F420C20; // FMADD D0, D1, D2, D3 + + ulong Z = TestContext.CurrentContext.Random.NextULong(); + Vector128 V0 = MakeVectorE1(Z); + Vector128 V1 = MakeVectorE0(A); + Vector128 V2 = MakeVectorE0(B); + Vector128 V3 = MakeVectorE0(C); + + AThreadState ThreadState = SingleOpcode(Opcode, V0: V0, V1: V1, V2: V2, V3: V3); + + CompareAgainstUnicorn(FpSkips: FpSkips.IfNaN_D/*, FpUseTolerance: FpUseTolerance.OneUlps_D*/); + } + + [Test, Pairwise] + public void F_Max_Min_Nm_S_S([ValueSource("_F_Max_Min_Nm_S_S_")] uint Opcodes, + [ValueSource("_1S_F_")] ulong A, + [ValueSource("_1S_F_")] ulong B) + { + //const int DNFlagBit = 25; // Default NaN mode control bit. + //const int FZFlagBit = 24; // Flush-to-zero mode control bit. + + ulong Z = TestContext.CurrentContext.Random.NextULong(); + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + Vector128 V2 = MakeVectorE0(B); + + //int Fpcr = 1 << DNFlagBit; // Any operation involving one or more NaNs returns the Default NaN. + //Fpcr |= 1 << FZFlagBit; // Flush-to-zero mode enabled. + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2/*, Fpcr: Fpcr*/); + + CompareAgainstUnicorn(/*FpsrMask: FPSR.IDC | FPSR.IOC*/); + } + + [Test, Pairwise] + public void F_Max_Min_Nm_S_D([ValueSource("_F_Max_Min_Nm_S_D_")] uint Opcodes, + [ValueSource("_1D_F_")] ulong A, + [ValueSource("_1D_F_")] ulong B) + { + ulong Z = TestContext.CurrentContext.Random.NextULong(); + Vector128 V0 = MakeVectorE1(Z); + Vector128 V1 = MakeVectorE0(A); + Vector128 V2 = MakeVectorE0(B); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void F_Max_Min_Nm_P_V_2S_4S([ValueSource("_F_Max_Min_Nm_P_V_2S_4S_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_2S_F_")] ulong Z, + [ValueSource("_2S_F_")] ulong A, + [ValueSource("_2S_F_")] ulong B, + [Values(0b0u, 0b1u)] uint Q) // <2S, 4S> + { + //const int DNFlagBit = 25; // Default NaN mode control bit. + //const int FZFlagBit = 24; // Flush-to-zero mode control bit. + + Opcodes |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcodes |= ((Q & 1) << 30); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A * Q); + Vector128 V2 = MakeVectorE0E1(B, B * Q); + + //int Fpcr = 1 << DNFlagBit; // Any operation involving one or more NaNs returns the Default NaN. + //Fpcr |= 1 << FZFlagBit; // Flush-to-zero mode enabled. + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2/*, Fpcr: Fpcr*/); + + CompareAgainstUnicorn(/*FpsrMask: FPSR.IDC | FPSR.IOC*/); + } + + [Test, Pairwise] + public void F_Max_Min_Nm_P_V_2D([ValueSource("_F_Max_Min_Nm_P_V_2D_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [Values(2u, 0u)] uint Rm, + [ValueSource("_1D_F_")] ulong Z, + [ValueSource("_1D_F_")] ulong A, + [ValueSource("_1D_F_")] ulong B) + { + Opcodes |= ((Rm & 31) << 16) | ((Rn & 31) << 5) | ((Rd & 31) << 0); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + Vector128 V2 = MakeVectorE0E1(B, B); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1, V2: V2); + + CompareAgainstUnicorn(); + } + [Test, Pairwise, Description("ORN ., ., .")] public void Orn_V_8B([Values(0u)] uint Rd, [Values(1u, 0u)] uint Rn, diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs b/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs index 7728522265..edc50d4d0b 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs @@ -20,6 +20,18 @@ namespace Ryujinx.Tests.Cpu 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul }; } + private static ulong[] _1H_() + { + return new ulong[] { 0x0000000000000000ul, 0x0000000000007FFFul, + 0x0000000000008000ul, 0x000000000000FFFFul }; + } + + private static ulong[] _1S_() + { + return new ulong[] { 0x0000000000000000ul, 0x000000007FFFFFFFul, + 0x0000000080000000ul, 0x00000000FFFFFFFFul }; + } + private static ulong[] _2S_() { return new ulong[] { 0x0000000000000000ul, 0x7FFFFFFF7FFFFFFFul, @@ -114,6 +126,111 @@ namespace Ryujinx.Tests.Cpu 0x6F401400u // USRA V0.2D, V0.2D, #64 }; } + + private static uint[] _ShrImmNarrow_V_8H8B_8H16B_() + { + return new uint[] + { + 0x0F088C00u, // RSHRN V0.8B, V0.8H, #8 + 0x0F088400u // SHRN V0.8B, V0.8H, #8 + }; + } + + private static uint[] _ShrImmNarrow_V_4S4H_4S8H_() + { + return new uint[] + { + 0x0F108C00u, // RSHRN V0.4H, V0.4S, #16 + 0x0F108400u // SHRN V0.4H, V0.4S, #16 + }; + } + + private static uint[] _ShrImmNarrow_V_2D2S_2D4S_() + { + return new uint[] + { + 0x0F208C00u, // RSHRN V0.2S, V0.2D, #32 + 0x0F208400u // SHRN V0.2S, V0.2D, #32 + }; + } + + private static uint[] _ShrImmSaturatingNarrow_S_HB_() + { + return new uint[] + { + 0x5F089C00u, // SQRSHRN B0, H0, #8 + 0x7F089C00u, // UQRSHRN B0, H0, #8 + 0x7F088C00u, // SQRSHRUN B0, H0, #8 + 0x5F089400u, // SQSHRN B0, H0, #8 + 0x7F089400u, // UQSHRN B0, H0, #8 + 0x7F088400u // SQSHRUN B0, H0, #8 + }; + } + + private static uint[] _ShrImmSaturatingNarrow_S_SH_() + { + return new uint[] + { + 0x5F109C00u, // SQRSHRN H0, S0, #16 + 0x7F109C00u, // UQRSHRN H0, S0, #16 + 0x7F108C00u, // SQRSHRUN H0, S0, #16 + 0x5F109400u, // SQSHRN H0, S0, #16 + 0x7F109400u, // UQSHRN H0, S0, #16 + 0x7F108400u // SQSHRUN H0, S0, #16 + }; + } + + private static uint[] _ShrImmSaturatingNarrow_S_DS_() + { + return new uint[] + { + 0x5F209C00u, // SQRSHRN S0, D0, #32 + 0x7F209C00u, // UQRSHRN S0, D0, #32 + 0x7F208C00u, // SQRSHRUN S0, D0, #32 + 0x5F209400u, // SQSHRN S0, D0, #32 + 0x7F209400u, // UQSHRN S0, D0, #32 + 0x7F208400u // SQSHRUN S0, D0, #32 + }; + } + + private static uint[] _ShrImmSaturatingNarrow_V_8H8B_8H16B_() + { + return new uint[] + { + 0x0F089C00u, // SQRSHRN V0.8B, V0.8H, #8 + 0x2F089C00u, // UQRSHRN V0.8B, V0.8H, #8 + 0x2F088C00u, // SQRSHRUN V0.8B, V0.8H, #8 + 0x0F089400u, // SQSHRN V0.8B, V0.8H, #8 + 0x2F089400u, // UQSHRN V0.8B, V0.8H, #8 + 0x2F088400u // SQSHRUN V0.8B, V0.8H, #8 + }; + } + + private static uint[] _ShrImmSaturatingNarrow_V_4S4H_4S8H_() + { + return new uint[] + { + 0x0F109C00u, // SQRSHRN V0.4H, V0.4S, #16 + 0x2F109C00u, // UQRSHRN V0.4H, V0.4S, #16 + 0x2F108C00u, // SQRSHRUN V0.4H, V0.4S, #16 + 0x0F109400u, // SQSHRN V0.4H, V0.4S, #16 + 0x2F109400u, // UQSHRN V0.4H, V0.4S, #16 + 0x2F108400u // SQSHRUN V0.4H, V0.4S, #16 + }; + } + + private static uint[] _ShrImmSaturatingNarrow_V_2D2S_2D4S_() + { + return new uint[] + { + 0x0F209C00u, // SQRSHRN V0.2S, V0.2D, #32 + 0x2F209C00u, // UQRSHRN V0.2S, V0.2D, #32 + 0x2F208C00u, // SQRSHRUN V0.2S, V0.2D, #32 + 0x0F209400u, // SQSHRN V0.2S, V0.2D, #32 + 0x2F209400u, // UQSHRN V0.2S, V0.2D, #32 + 0x2F208400u // SQSHRUN V0.2S, V0.2D, #32 + }; + } #endregion private const int RndCnt = 2; @@ -339,6 +456,207 @@ namespace Ryujinx.Tests.Cpu CompareAgainstUnicorn(); } + + [Test, Pairwise] + public void ShrImmNarrow_V_8H8B_8H16B([ValueSource("_ShrImmNarrow_V_8H8B_8H16B_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_4H_")] [Random(RndCnt)] ulong Z, + [ValueSource("_4H_")] [Random(RndCnt)] ulong A, + [Range(1u, 8u)] uint Shift, + [Values(0b0u, 0b1u)] uint Q) // <8H8B, 8H16B> + { + uint ImmHB = (16 - Shift) & 0x7F; + + Opcodes |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcodes |= (ImmHB << 16); + Opcodes |= ((Q & 1) << 30); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShrImmNarrow_V_4S4H_4S8H([ValueSource("_ShrImmNarrow_V_4S4H_4S8H_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_2S_")] [Random(RndCnt)] ulong Z, + [ValueSource("_2S_")] [Random(RndCnt)] ulong A, + [Range(1u, 16u)] uint Shift, + [Values(0b0u, 0b1u)] uint Q) // <4S4H, 4S8H> + { + uint ImmHB = (32 - Shift) & 0x7F; + + Opcodes |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcodes |= (ImmHB << 16); + Opcodes |= ((Q & 1) << 30); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShrImmNarrow_V_2D2S_2D4S([ValueSource("_ShrImmNarrow_V_2D2S_2D4S_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong A, + [Range(1u, 32u)] uint Shift, + [Values(0b0u, 0b1u)] uint Q) // <2D2S, 2D4S> + { + uint ImmHB = (64 - Shift) & 0x7F; + + Opcodes |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcodes |= (ImmHB << 16); + Opcodes |= ((Q & 1) << 30); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0E1(A, A); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShrImmSaturatingNarrow_S_HB([ValueSource("_ShrImmSaturatingNarrow_S_HB_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_1H_")] [Random(RndCnt)] ulong Z, + [ValueSource("_1H_")] [Random(RndCnt)] ulong A, + [Range(1u, 8u)] uint Shift) + { + uint ImmHB = (16 - Shift) & 0x7F; + + Opcodes |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcodes |= (ImmHB << 16); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1); + + CompareAgainstUnicorn(FpsrMask: FPSR.QC); + } + + [Test, Pairwise] + public void ShrImmSaturatingNarrow_S_SH([ValueSource("_ShrImmSaturatingNarrow_S_SH_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_1S_")] [Random(RndCnt)] ulong Z, + [ValueSource("_1S_")] [Random(RndCnt)] ulong A, + [Range(1u, 16u)] uint Shift) + { + uint ImmHB = (32 - Shift) & 0x7F; + + Opcodes |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcodes |= (ImmHB << 16); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1); + + CompareAgainstUnicorn(FpsrMask: FPSR.QC); + } + + [Test, Pairwise] + public void ShrImmSaturatingNarrow_S_DS([ValueSource("_ShrImmSaturatingNarrow_S_DS_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong A, + [Range(1u, 32u)] uint Shift) + { + uint ImmHB = (64 - Shift) & 0x7F; + + Opcodes |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcodes |= (ImmHB << 16); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1); + + CompareAgainstUnicorn(FpsrMask: FPSR.QC); + } + + [Test, Pairwise] + public void ShrImmSaturatingNarrow_V_8H8B_8H16B([ValueSource("_ShrImmSaturatingNarrow_V_8H8B_8H16B_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_4H_")] [Random(RndCnt)] ulong Z, + [ValueSource("_4H_")] [Random(RndCnt)] ulong A, + [Range(1u, 8u)] uint Shift, + [Values(0b0u, 0b1u)] uint Q) // <8H8B, 8H16B> + { + uint ImmHB = (16 - Shift) & 0x7F; + + Opcodes |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcodes |= (ImmHB << 16); + Opcodes |= ((Q & 1) << 30); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1); + + CompareAgainstUnicorn(FpsrMask: FPSR.QC); + } + + [Test, Pairwise] + public void ShrImmSaturatingNarrow_V_4S4H_4S8H([ValueSource("_ShrImmSaturatingNarrow_V_4S4H_4S8H_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_2S_")] [Random(RndCnt)] ulong Z, + [ValueSource("_2S_")] [Random(RndCnt)] ulong A, + [Range(1u, 16u)] uint Shift, + [Values(0b0u, 0b1u)] uint Q) // <4S4H, 4S8H> + { + uint ImmHB = (32 - Shift) & 0x7F; + + Opcodes |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcodes |= (ImmHB << 16); + Opcodes |= ((Q & 1) << 30); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1); + + CompareAgainstUnicorn(FpsrMask: FPSR.QC); + } + + [Test, Pairwise] + public void ShrImmSaturatingNarrow_V_2D2S_2D4S([ValueSource("_ShrImmSaturatingNarrow_V_2D2S_2D4S_")] uint Opcodes, + [Values(0u)] uint Rd, + [Values(1u, 0u)] uint Rn, + [ValueSource("_1D_")] [Random(RndCnt)] ulong Z, + [ValueSource("_1D_")] [Random(RndCnt)] ulong A, + [Range(1u, 32u)] uint Shift, + [Values(0b0u, 0b1u)] uint Q) // <2D2S, 2D4S> + { + uint ImmHB = (64 - Shift) & 0x7F; + + Opcodes |= ((Rn & 31) << 5) | ((Rd & 31) << 0); + Opcodes |= (ImmHB << 16); + Opcodes |= ((Q & 1) << 30); + + Vector128 V0 = MakeVectorE0E1(Z, Z); + Vector128 V1 = MakeVectorE0(A); + + AThreadState ThreadState = SingleOpcode(Opcodes, V0: V0, V1: V1); + + CompareAgainstUnicorn(FpsrMask: FPSR.QC); + } #endif } } From bec95cacc1061f91373a1e3a1411981af7fe2e4e Mon Sep 17 00:00:00 2001 From: gdkchan Date: Tue, 18 Sep 2018 00:12:47 -0300 Subject: [PATCH 04/14] Ensure that buffers are available after a signal when GetReleasedBuffers is called (#369) --- Ryujinx.Audio/OpenAL/OpenALAudioOut.cs | 137 +++++++++++-------------- 1 file changed, 58 insertions(+), 79 deletions(-) diff --git a/Ryujinx.Audio/OpenAL/OpenALAudioOut.cs b/Ryujinx.Audio/OpenAL/OpenALAudioOut.cs index 80a070c9f3..d7a2a77751 100644 --- a/Ryujinx.Audio/OpenAL/OpenALAudioOut.cs +++ b/Ryujinx.Audio/OpenAL/OpenALAudioOut.cs @@ -28,16 +28,12 @@ namespace Ryujinx.Audio.OpenAL public PlaybackState State { get; set; } - private bool ShouldCallReleaseCallback; - private ConcurrentDictionary Buffers; private Queue QueuedTagsQueue; private Queue ReleasedTagsQueue; - private int LastReleasedCount; - private bool Disposed; public Track(int SampleRate, ALFormat Format, ReleaseCallback Callback) @@ -59,8 +55,6 @@ namespace Ryujinx.Audio.OpenAL public bool ContainsBuffer(long Tag) { - SyncQueuedTags(); - foreach (long QueuedTag in QueuedTagsQueue) { if (QueuedTag == Tag) @@ -72,20 +66,29 @@ namespace Ryujinx.Audio.OpenAL return false; } - public long[] GetReleasedBuffers(int MaxCount) + public long[] GetReleasedBuffers(int Count) { - ClearReleased(); + AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int ReleasedCount); + + ReleasedCount += ReleasedTagsQueue.Count; + + if (Count > ReleasedCount) + { + Count = ReleasedCount; + } List Tags = new List(); - HashSet Unique = new HashSet(); - - while (MaxCount-- > 0 && ReleasedTagsQueue.TryDequeue(out long Tag)) + while (Count-- > 0 && ReleasedTagsQueue.TryDequeue(out long Tag)) { - if (Unique.Add(Tag)) - { - Tags.Add(Tag); - } + Tags.Add(Tag); + } + + while (Count-- > 0 && QueuedTagsQueue.TryDequeue(out long Tag)) + { + AL.SourceUnqueueBuffers(SourceId, 1); + + Tags.Add(Tag); } return Tags.ToArray(); @@ -112,67 +115,27 @@ namespace Ryujinx.Audio.OpenAL return Id; } - public void ClearReleased() + public void CallReleaseCallbackIfNeeded() { - SyncQueuedTags(); - AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int ReleasedCount); - CheckReleaseChanges(ReleasedCount); - if (ReleasedCount > 0) { - AL.SourceUnqueueBuffers(SourceId, ReleasedCount); - } - } + //If we signal, then we also need to have released buffers available + //to return when GetReleasedBuffers is called. + //If playback needs to be re-started due to all buffers being processed, + //then OpenAL zeros the counts (ReleasedCount), so we keep it on the queue. + while (ReleasedCount-- > 0 && QueuedTagsQueue.TryDequeue(out long Tag)) + { + AL.SourceUnqueueBuffers(SourceId, 1); - public void CallReleaseCallbackIfNeeded() - { - CheckReleaseChanges(); - - if (ShouldCallReleaseCallback) - { - ShouldCallReleaseCallback = false; + ReleasedTagsQueue.Enqueue(Tag); + } Callback(); } } - private void CheckReleaseChanges() - { - AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int ReleasedCount); - - CheckReleaseChanges(ReleasedCount); - } - - private void CheckReleaseChanges(int NewReleasedCount) - { - if (LastReleasedCount != NewReleasedCount) - { - LastReleasedCount = NewReleasedCount; - - ShouldCallReleaseCallback = true; - } - } - - private void SyncQueuedTags() - { - AL.GetSource(SourceId, ALGetSourcei.BuffersQueued, out int QueuedCount); - AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int ReleasedCount); - - QueuedCount -= ReleasedCount; - - while (QueuedTagsQueue.Count > QueuedCount) - { - ReleasedTagsQueue.Enqueue(QueuedTagsQueue.Dequeue()); - } - - while (ReleasedTagsQueue.Count > MaxReleased) - { - ReleasedTagsQueue.Dequeue(); - } - } - public void Dispose() { Dispose(true); @@ -266,7 +229,10 @@ namespace Ryujinx.Audio.OpenAL { if (Tracks.TryRemove(Track, out Track Td)) { - Td.Dispose(); + lock (Td) + { + Td.Dispose(); + } } } @@ -274,7 +240,10 @@ namespace Ryujinx.Audio.OpenAL { if (Tracks.TryGetValue(Track, out Track Td)) { - return Td.ContainsBuffer(Tag); + lock (Td) + { + return Td.ContainsBuffer(Tag); + } } return false; @@ -284,7 +253,10 @@ namespace Ryujinx.Audio.OpenAL { if (Tracks.TryGetValue(Track, out Track Td)) { - return Td.GetReleasedBuffers(MaxCount); + lock (Td) + { + return Td.GetReleasedBuffers(MaxCount); + } } return null; @@ -294,15 +266,18 @@ namespace Ryujinx.Audio.OpenAL { if (Tracks.TryGetValue(Track, out Track Td)) { - int BufferId = Td.AppendBuffer(Tag); + lock (Td) + { + int BufferId = Td.AppendBuffer(Tag); - int Size = Buffer.Length * Marshal.SizeOf(); + int Size = Buffer.Length * Marshal.SizeOf(); - AL.BufferData(BufferId, Td.Format, Buffer, Size, Td.SampleRate); + AL.BufferData(BufferId, Td.Format, Buffer, Size, Td.SampleRate); - AL.SourceQueueBuffer(Td.SourceId, BufferId); + AL.SourceQueueBuffer(Td.SourceId, BufferId); - StartPlaybackIfNeeded(Td); + StartPlaybackIfNeeded(Td); + } } } @@ -310,9 +285,12 @@ namespace Ryujinx.Audio.OpenAL { if (Tracks.TryGetValue(Track, out Track Td)) { - Td.State = PlaybackState.Playing; + lock (Td) + { + Td.State = PlaybackState.Playing; - StartPlaybackIfNeeded(Td); + StartPlaybackIfNeeded(Td); + } } } @@ -324,8 +302,6 @@ namespace Ryujinx.Audio.OpenAL if (State != ALSourceState.Playing && Td.State == PlaybackState.Playing) { - Td.ClearReleased(); - AL.SourcePlay(Td.SourceId); } } @@ -334,9 +310,12 @@ namespace Ryujinx.Audio.OpenAL { if (Tracks.TryGetValue(Track, out Track Td)) { - Td.State = PlaybackState.Stopped; + lock (Td) + { + Td.State = PlaybackState.Stopped; - AL.SourceStop(Td.SourceId); + AL.SourceStop(Td.SourceId); + } } } From d4187aaa9d7194aa26d04aee838edbc3a38f1862 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Tue, 18 Sep 2018 01:30:35 -0300 Subject: [PATCH 05/14] Allow "reinterpretation" of framebuffer/zeta formats (#418) * (Re)Implement format reinterpretation, other changes * Implement writeback to guest memory, some refactoring * More refactoring, implement reinterpretation the old way again * Clean up * Some fixes on M2MF (old Dma engine), added partial support for P2MF, fix conditional ssy, add Z24S8 zeta format, other fixes * nit: Formatting * Address PR feedback --- ChocolArm64/Memory/AMemory.cs | 29 ++ Ryujinx.Graphics/Gal/GalImage.cs | 48 ++- Ryujinx.Graphics/Gal/GalMemoryLayout.cs | 8 + Ryujinx.Graphics/Gal/GalPipelineState.cs | 2 + ...ameBufferFormat.cs => GalSurfaceFormat.cs} | 0 Ryujinx.Graphics/Gal/GalTextureFormat.cs | 1 + Ryujinx.Graphics/Gal/IGalRasterizer.cs | 2 +- Ryujinx.Graphics/Gal/IGalRenderTarget.cs | 18 +- Ryujinx.Graphics/Gal/IGalTexture.cs | 8 +- Ryujinx.Graphics/Gal/OpenGL/ImageHandler.cs | 147 +------ .../Gal/OpenGL/OGLCachedResource.cs | 6 +- .../Gal/OpenGL/OGLEnumConverter.cs | 4 +- Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs | 11 + Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs | 6 +- .../Gal/OpenGL/OGLRenderTarget.cs | 358 +++++++--------- Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs | 163 ++++--- Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs | 1 + .../Gal/Shader/ShaderDecodeAlu.cs | 128 +++--- .../Gal/Shader/ShaderDecodeFlow.cs | 24 +- .../{ShaderDecode.cs => ShaderDecodeFunc.cs} | 2 +- .../Gal/Shader/ShaderDecodeHelper.cs | 2 - .../Gal/Shader/ShaderDecodeMem.cs | 16 +- .../Gal/Shader/ShaderDecodeMove.cs | 42 +- .../Gal/Shader/ShaderDecodeSpecial.cs | 6 +- Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs | 44 +- Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs | 6 +- Ryujinx.Graphics/GpuResourceManager.cs | 122 ++++++ Ryujinx.Graphics/Memory/NvGpuVmmCache.cs | 317 ++------------ Ryujinx.Graphics/NvGpu.cs | 19 +- .../{Memory => }/NvGpuBufferType.cs | 2 +- Ryujinx.Graphics/NvGpuEngine.cs | 4 +- Ryujinx.Graphics/NvGpuEngine2d.cs | 125 ++---- Ryujinx.Graphics/NvGpuEngine3d.cs | 125 +++--- Ryujinx.Graphics/NvGpuEngine3dReg.cs | 2 + Ryujinx.Graphics/NvGpuEngineDma.cs | 143 ------- Ryujinx.Graphics/NvGpuEngineM2mf.cs | 192 +++++++++ ...uEngineDmaReg.cs => NvGpuEngineM2mfReg.cs} | 5 +- Ryujinx.Graphics/NvGpuEngineP2mf.cs | 102 +++++ Ryujinx.Graphics/NvGpuEngineP2mfReg.cs | 17 + Ryujinx.Graphics/NvGpuFifo.cs | 65 +-- Ryujinx.Graphics/Texture/ImageUtils.cs | 362 ++++++++-------- Ryujinx.Graphics/Texture/TextureFactory.cs | 52 +-- Ryujinx.Graphics/Texture/TextureHelper.cs | 29 +- Ryujinx.Graphics/Texture/TextureInfo.cs | 60 --- Ryujinx.Graphics/Texture/TextureReader.cs | 398 ------------------ Ryujinx.Graphics/Texture/TextureWriter.cs | 35 -- Ryujinx.Graphics/ValueRange.cs | 17 + Ryujinx.Graphics/ValueRangeSet.cs | 234 ++++++++++ Ryujinx.HLE/HOS/Kernel/SvcSystem.cs | 3 +- Ryujinx.HLE/HOS/Services/Acc/IProfile.cs | 1 - .../HOS/Services/Vi/IHOSBinderDriver.cs | 12 +- Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs | 48 +-- 52 files changed, 1594 insertions(+), 1979 deletions(-) create mode 100644 Ryujinx.Graphics/Gal/GalMemoryLayout.cs rename Ryujinx.Graphics/Gal/{GalFrameBufferFormat.cs => GalSurfaceFormat.cs} (100%) rename Ryujinx.Graphics/Gal/Shader/{ShaderDecode.cs => ShaderDecodeFunc.cs} (83%) create mode 100644 Ryujinx.Graphics/GpuResourceManager.cs rename Ryujinx.Graphics/{Memory => }/NvGpuBufferType.cs (79%) delete mode 100644 Ryujinx.Graphics/NvGpuEngineDma.cs create mode 100644 Ryujinx.Graphics/NvGpuEngineM2mf.cs rename Ryujinx.Graphics/{NvGpuEngineDmaReg.cs => NvGpuEngineM2mfReg.cs} (81%) create mode 100644 Ryujinx.Graphics/NvGpuEngineP2mf.cs create mode 100644 Ryujinx.Graphics/NvGpuEngineP2mfReg.cs delete mode 100644 Ryujinx.Graphics/Texture/TextureInfo.cs delete mode 100644 Ryujinx.Graphics/Texture/TextureReader.cs delete mode 100644 Ryujinx.Graphics/Texture/TextureWriter.cs create mode 100644 Ryujinx.Graphics/ValueRange.cs create mode 100644 Ryujinx.Graphics/ValueRangeSet.cs diff --git a/ChocolArm64/Memory/AMemory.cs b/ChocolArm64/Memory/AMemory.cs index 566e6b54ce..806a0b8608 100644 --- a/ChocolArm64/Memory/AMemory.cs +++ b/ChocolArm64/Memory/AMemory.cs @@ -287,6 +287,14 @@ namespace ChocolArm64.Memory return Data; } + public void ReadBytes(long Position, byte[] Data, int StartIndex, int Size) + { + //Note: This will be moved later. + EnsureRangeIsValid(Position, (uint)Size); + + Marshal.Copy((IntPtr)Translate(Position), Data, StartIndex, Size); + } + public void WriteSByte(long Position, sbyte Value) { WriteByte(Position, (byte)Value); @@ -403,6 +411,27 @@ namespace ChocolArm64.Memory Marshal.Copy(Data, 0, (IntPtr)TranslateWrite(Position), Data.Length); } + public void WriteBytes(long Position, byte[] Data, int StartIndex, int Size) + { + //Note: This will be moved later. + //Using Translate instead of TranslateWrite is on purpose. + EnsureRangeIsValid(Position, (uint)Size); + + Marshal.Copy(Data, StartIndex, (IntPtr)Translate(Position), Size); + } + + public void CopyBytes(long Src, long Dst, long Size) + { + //Note: This will be moved later. + EnsureRangeIsValid(Src, Size); + EnsureRangeIsValid(Dst, Size); + + byte* SrcPtr = Translate(Src); + byte* DstPtr = TranslateWrite(Dst); + + Buffer.MemoryCopy(SrcPtr, DstPtr, Size, Size); + } + public void Map(long VA, long PA, long Size) { SetPTEntries(VA, RamPtr + PA, Size); diff --git a/Ryujinx.Graphics/Gal/GalImage.cs b/Ryujinx.Graphics/Gal/GalImage.cs index dc6f02e044..92f43cc9d4 100644 --- a/Ryujinx.Graphics/Gal/GalImage.cs +++ b/Ryujinx.Graphics/Gal/GalImage.cs @@ -1,12 +1,17 @@ +using Ryujinx.Graphics.Texture; + namespace Ryujinx.Graphics.Gal { public struct GalImage { public int Width; public int Height; + public int TileWidth; + public int GobBlockHeight; + public int Pitch; - public GalImageFormat Format; - + public GalImageFormat Format; + public GalMemoryLayout Layout; public GalTextureSource XSource; public GalTextureSource YSource; public GalTextureSource ZSource; @@ -15,19 +20,44 @@ namespace Ryujinx.Graphics.Gal public GalImage( int Width, int Height, + int TileWidth, + int GobBlockHeight, + GalMemoryLayout Layout, GalImageFormat Format, GalTextureSource XSource = GalTextureSource.Red, GalTextureSource YSource = GalTextureSource.Green, GalTextureSource ZSource = GalTextureSource.Blue, GalTextureSource WSource = GalTextureSource.Alpha) { - this.Width = Width; - this.Height = Height; - this.Format = Format; - this.XSource = XSource; - this.YSource = YSource; - this.ZSource = ZSource; - this.WSource = WSource; + this.Width = Width; + this.Height = Height; + this.TileWidth = TileWidth; + this.GobBlockHeight = GobBlockHeight; + this.Layout = Layout; + this.Format = Format; + this.XSource = XSource; + this.YSource = YSource; + this.ZSource = ZSource; + this.WSource = WSource; + + Pitch = ImageUtils.GetPitch(Format, Width); + } + + public bool SizeMatches(GalImage Image) + { + if (ImageUtils.GetBytesPerPixel(Format) != + ImageUtils.GetBytesPerPixel(Image.Format)) + { + return false; + } + + if (ImageUtils.GetAlignedWidth(this) != + ImageUtils.GetAlignedWidth(Image)) + { + return false; + } + + return Height == Image.Height; } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalMemoryLayout.cs b/Ryujinx.Graphics/Gal/GalMemoryLayout.cs new file mode 100644 index 0000000000..73fabf8c2a --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalMemoryLayout.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Gal +{ + public enum GalMemoryLayout + { + BlockLinear = 0, + Pitch = 1 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalPipelineState.cs b/Ryujinx.Graphics/Gal/GalPipelineState.cs index 7c66951415..6ee6c93e0c 100644 --- a/Ryujinx.Graphics/Gal/GalPipelineState.cs +++ b/Ryujinx.Graphics/Gal/GalPipelineState.cs @@ -21,6 +21,8 @@ public GalVertexBinding[] VertexBindings; + public bool FramebufferSrgb; + public float FlipX; public float FlipY; diff --git a/Ryujinx.Graphics/Gal/GalFrameBufferFormat.cs b/Ryujinx.Graphics/Gal/GalSurfaceFormat.cs similarity index 100% rename from Ryujinx.Graphics/Gal/GalFrameBufferFormat.cs rename to Ryujinx.Graphics/Gal/GalSurfaceFormat.cs diff --git a/Ryujinx.Graphics/Gal/GalTextureFormat.cs b/Ryujinx.Graphics/Gal/GalTextureFormat.cs index e865859530..5ad769437c 100644 --- a/Ryujinx.Graphics/Gal/GalTextureFormat.cs +++ b/Ryujinx.Graphics/Gal/GalTextureFormat.cs @@ -7,6 +7,7 @@ namespace Ryujinx.Graphics.Gal R32G32 = 0x4, A8B8G8R8 = 0x8, A2B10G10R10 = 0x9, + R16G16 = 0xc, R32 = 0xf, BC6H_SF16 = 0x10, BC6H_UF16 = 0x11, diff --git a/Ryujinx.Graphics/Gal/IGalRasterizer.cs b/Ryujinx.Graphics/Gal/IGalRasterizer.cs index a20b6f5322..1ee630e23b 100644 --- a/Ryujinx.Graphics/Gal/IGalRasterizer.cs +++ b/Ryujinx.Graphics/Gal/IGalRasterizer.cs @@ -24,7 +24,7 @@ namespace Ryujinx.Graphics.Gal void SetIndexArray(int Size, GalIndexFormat Format); - void DrawArrays(int First, int PrimCount, GalPrimitiveType PrimType); + void DrawArrays(int First, int Count, GalPrimitiveType PrimType); void DrawElements(long IboKey, int First, int VertexBase, GalPrimitiveType PrimType); } diff --git a/Ryujinx.Graphics/Gal/IGalRenderTarget.cs b/Ryujinx.Graphics/Gal/IGalRenderTarget.cs index c44434ef69..6c9166f24d 100644 --- a/Ryujinx.Graphics/Gal/IGalRenderTarget.cs +++ b/Ryujinx.Graphics/Gal/IGalRenderTarget.cs @@ -1,23 +1,17 @@ -using System; - namespace Ryujinx.Graphics.Gal { public interface IGalRenderTarget { - void BindColor(long Key, int Attachment); + void BindColor(long Key, int Attachment, GalImage Image); void UnbindColor(int Attachment); - void BindZeta(long Key); + void BindZeta(long Key, GalImage Image); void UnbindZeta(); - void BindTexture(long Key, int Index); - void Set(long Key); - void Set(byte[] Data, int Width, int Height); - void SetMap(int[] Map); void SetTransform(bool FlipX, bool FlipY, int Top, int Left, int Right, int Bottom); @@ -40,12 +34,8 @@ namespace Ryujinx.Graphics.Gal int DstX1, int DstY1); - void GetBufferData(long Key, Action Callback); + void Reinterpret(long Key, GalImage NewImage); - void SetBufferData( - long Key, - int Width, - int Height, - byte[] Buffer); + byte[] GetData(long Key); } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalTexture.cs b/Ryujinx.Graphics/Gal/IGalTexture.cs index 292f59efa1..aeecdf1ac5 100644 --- a/Ryujinx.Graphics/Gal/IGalTexture.cs +++ b/Ryujinx.Graphics/Gal/IGalTexture.cs @@ -5,13 +5,13 @@ namespace Ryujinx.Graphics.Gal void LockCache(); void UnlockCache(); + void Create(long Key, int Size, GalImage Image); + void Create(long Key, byte[] Data, GalImage Image); - void CreateFb(long Key, long Size, GalImage Image); + bool TryGetImage(long Key, out GalImage Image); - bool TryGetCachedTexture(long Key, long DataSize, out GalImage Image); - - void Bind(long Key, int Index); + void Bind(long Key, int Index, GalImage Image); void SetSampler(GalTextureSampler Sampler); } diff --git a/Ryujinx.Graphics/Gal/OpenGL/ImageHandler.cs b/Ryujinx.Graphics/Gal/OpenGL/ImageHandler.cs index dda825385e..8db0b8a8c9 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/ImageHandler.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/ImageHandler.cs @@ -1,14 +1,9 @@ -using OpenTK.Graphics.OpenGL; -using Ryujinx.Graphics.Texture; -using System; +using Ryujinx.Graphics.Texture; namespace Ryujinx.Graphics.Gal.OpenGL { class ImageHandler { - private static int CopyBuffer = 0; - private static int CopyBufferSize = 0; - public GalImage Image { get; private set; } public int Width => Image.Width; @@ -16,144 +11,16 @@ namespace Ryujinx.Graphics.Gal.OpenGL public GalImageFormat Format => Image.Format; - public PixelInternalFormat InternalFormat { get; private set; } - public PixelFormat PixelFormat { get; private set; } - public PixelType PixelType { get; private set; } - public int Handle { get; private set; } - private bool Initialized; - - public ImageHandler() - { - Handle = GL.GenTexture(); - } - - public ImageHandler(int Handle, GalImage Image) - { - this.Handle = Handle; - - this.Image = Image; - } - - public void EnsureSetup(GalImage NewImage) - { - if (Width == NewImage.Width && - Height == NewImage.Height && - Format == NewImage.Format && - Initialized) - { - return; - } - - PixelInternalFormat InternalFmt; - PixelFormat PixelFormat; - PixelType PixelType; - - if (ImageUtils.IsCompressed(NewImage.Format)) - { - InternalFmt = (PixelInternalFormat)OGLEnumConverter.GetCompressedImageFormat(NewImage.Format); - - PixelFormat = default(PixelFormat); - PixelType = default(PixelType); - } - else - { - (InternalFmt, PixelFormat, PixelType) = OGLEnumConverter.GetImageFormat(NewImage.Format); - } - - GL.BindTexture(TextureTarget.Texture2D, Handle); - - if (Initialized) - { - if (CopyBuffer == 0) - { - CopyBuffer = GL.GenBuffer(); - } - - int CurrentSize = Math.Max(ImageUtils.GetSize(NewImage), - ImageUtils.GetSize(Image)); - - GL.BindBuffer(BufferTarget.PixelPackBuffer, CopyBuffer); - GL.BindBuffer(BufferTarget.PixelUnpackBuffer, CopyBuffer); - - if (CopyBufferSize < CurrentSize) - { - CopyBufferSize = CurrentSize; - - GL.BufferData(BufferTarget.PixelPackBuffer, CurrentSize, IntPtr.Zero, BufferUsageHint.StreamCopy); - } - - if (ImageUtils.IsCompressed(Image.Format)) - { - GL.GetCompressedTexImage(TextureTarget.Texture2D, 0, IntPtr.Zero); - } - else - { - GL.GetTexImage(TextureTarget.Texture2D, 0, this.PixelFormat, this.PixelType, IntPtr.Zero); - } - - GL.DeleteTexture(Handle); - - Handle = GL.GenTexture(); - - GL.BindTexture(TextureTarget.Texture2D, Handle); - } - - const int MinFilter = (int)TextureMinFilter.Linear; - const int MagFilter = (int)TextureMagFilter.Linear; - - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, MinFilter); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, MagFilter); - - const int Level = 0; - const int Border = 0; - - if (ImageUtils.IsCompressed(NewImage.Format)) - { - Console.WriteLine("Hit"); - - GL.CompressedTexImage2D( - TextureTarget.Texture2D, - Level, - (InternalFormat)InternalFmt, - NewImage.Width, - NewImage.Height, - Border, - ImageUtils.GetSize(NewImage), - IntPtr.Zero); - } - else - { - GL.TexImage2D( - TextureTarget.Texture2D, - Level, - InternalFmt, - NewImage.Width, - NewImage.Height, - Border, - PixelFormat, - PixelType, - IntPtr.Zero); - } - - if (Initialized) - { - GL.BindBuffer(BufferTarget.PixelPackBuffer, 0); - GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0); - } - - Image = NewImage; - - this.InternalFormat = InternalFmt; - this.PixelFormat = PixelFormat; - this.PixelType = PixelType; - - Initialized = true; - } - public bool HasColor => ImageUtils.HasColor(Image.Format); public bool HasDepth => ImageUtils.HasDepth(Image.Format); public bool HasStencil => ImageUtils.HasStencil(Image.Format); + + public ImageHandler(int Handle, GalImage Image) + { + this.Handle = Handle; + this.Image = Image; + } } } diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLCachedResource.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLCachedResource.cs index 01ebf98202..839915eae4 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLCachedResource.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLCachedResource.cs @@ -22,9 +22,9 @@ namespace Ryujinx.Graphics.Gal.OpenGL public CacheBucket(T Value, long DataSize, LinkedListNode Node) { - this.Value = Value; - this.DataSize = DataSize; - this.Node = Node; + this.Value = Value; + this.DataSize = DataSize; + this.Node = Node; Timestamp = Environment.TickCount; } diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs index 876c7b992a..fac3875e56 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs @@ -149,8 +149,8 @@ namespace Ryujinx.Graphics.Gal.OpenGL case GalImageFormat.R32 | GalImageFormat.Sfloat: return (PixelInternalFormat.R32f, PixelFormat.Red, PixelType.Float); case GalImageFormat.R32 | GalImageFormat.Sint: return (PixelInternalFormat.R32i, PixelFormat.Red, PixelType.Int); case GalImageFormat.R32 | GalImageFormat.Uint: return (PixelInternalFormat.R32ui, PixelFormat.Red, PixelType.UnsignedInt); - case GalImageFormat.A1R5G5B5 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort5551); - case GalImageFormat.B5G6R5 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgba, PixelFormat.Rgb, PixelType.UnsignedShort565); + case GalImageFormat.A1R5G5B5 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort1555Reversed); + case GalImageFormat.B5G6R5 | GalImageFormat.Unorm: return (PixelInternalFormat.Rgba, PixelFormat.Rgb, PixelType.UnsignedShort565Reversed); case GalImageFormat.R16G16 | GalImageFormat.Sfloat: return (PixelInternalFormat.Rg16f, PixelFormat.Rg, PixelType.HalfFloat); case GalImageFormat.R16G16 | GalImageFormat.Sint: return (PixelInternalFormat.Rg16i, PixelFormat.RgInteger, PixelType.Short); case GalImageFormat.R16G16 | GalImageFormat.Snorm: return (PixelInternalFormat.Rg16Snorm, PixelFormat.Rg, PixelType.Byte); diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs index 051b105048..cf856a15a5 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs @@ -126,6 +126,11 @@ namespace Ryujinx.Graphics.Gal.OpenGL BindVertexLayout(New); + if (New.FramebufferSrgb != Old.FramebufferSrgb) + { + Enable(EnableCap.FramebufferSrgb, New.FramebufferSrgb); + } + if (New.FlipX != Old.FlipX || New.FlipY != Old.FlipY || New.Instance != Old.Instance) { Shader.SetExtraData(New.FlipX, New.FlipY, New.Instance); @@ -337,6 +342,12 @@ namespace Ryujinx.Graphics.Gal.OpenGL foreach (GalVertexAttrib Attrib in Binding.Attribs) { + //Skip uninitialized attributes. + if (Attrib.Size == 0) + { + continue; + } + GL.EnableVertexAttribArray(Attrib.Index); GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs index 4510669280..ebfba63d6c 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs @@ -113,14 +113,14 @@ namespace Ryujinx.Graphics.Gal.OpenGL IndexBuffer.ElemSizeLog2 = (int)Format; } - public void DrawArrays(int First, int PrimCount, GalPrimitiveType PrimType) + public void DrawArrays(int First, int Count, GalPrimitiveType PrimType) { - if (PrimCount == 0) + if (Count == 0) { return; } - GL.DrawArrays(OGLEnumConverter.GetPrimitiveType(PrimType), First, PrimCount); + GL.DrawArrays(OGLEnumConverter.GetPrimitiveType(PrimType), First, Count); } public void DrawElements(long IboKey, int First, int VertexBase, GalPrimitiveType PrimType) diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs index 99bfa350de..7dde32d828 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs @@ -15,9 +15,9 @@ namespace Ryujinx.Graphics.Gal.OpenGL public Rect(int X, int Y, int Width, int Height) { - this.X = X; - this.Y = Y; - this.Width = Width; + this.X = X; + this.Y = Y; + this.Width = Width; this.Height = Height; } } @@ -29,7 +29,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL private OGLTexture Texture; - private ImageHandler RawTex; private ImageHandler ReadTex; private Rect Viewport; @@ -64,13 +63,13 @@ namespace Ryujinx.Graphics.Gal.OpenGL this.Texture = Texture; } - public void BindColor(long Key, int Attachment) + public void BindColor(long Key, int Attachment, GalImage Image) { - if (Texture.TryGetImage(Key, out ImageHandler Tex)) + if (Texture.TryGetImageHandler(Key, out ImageHandler CachedImage)) { EnsureFrameBuffer(); - Attach(ref ColorAttachments[Attachment], Tex.Handle, FramebufferAttachment.ColorAttachment0 + Attachment); + Attach(ref ColorAttachments[Attachment], CachedImage.Handle, FramebufferAttachment.ColorAttachment0 + Attachment); } else { @@ -84,40 +83,39 @@ namespace Ryujinx.Graphics.Gal.OpenGL Attach(ref ColorAttachments[Attachment], 0, FramebufferAttachment.ColorAttachment0 + Attachment); } - - public void BindZeta(long Key) + + public void BindZeta(long Key, GalImage Image) { - if (Texture.TryGetImage(Key, out ImageHandler Tex)) + if (Texture.TryGetImageHandler(Key, out ImageHandler CachedImage)) { EnsureFrameBuffer(); - if (Tex.HasDepth && Tex.HasStencil) + if (CachedImage.HasDepth && CachedImage.HasStencil) { - if (DepthAttachment != Tex.Handle || - StencilAttachment != Tex.Handle) + if (DepthAttachment != CachedImage.Handle || + StencilAttachment != CachedImage.Handle) { GL.FramebufferTexture( FramebufferTarget.DrawFramebuffer, FramebufferAttachment.DepthStencilAttachment, - Tex.Handle, + CachedImage.Handle, 0); - DepthAttachment = Tex.Handle; - - StencilAttachment = Tex.Handle; + DepthAttachment = CachedImage.Handle; + StencilAttachment = CachedImage.Handle; } } - else if (Tex.HasDepth) + else if (CachedImage.HasDepth) { - Attach(ref DepthAttachment, Tex.Handle, FramebufferAttachment.DepthAttachment); + Attach(ref DepthAttachment, CachedImage.Handle, FramebufferAttachment.DepthAttachment); Attach(ref StencilAttachment, 0, FramebufferAttachment.StencilAttachment); } - else if (Tex.HasStencil) + else if (CachedImage.HasStencil) { Attach(ref DepthAttachment, 0, FramebufferAttachment.DepthAttachment); - Attach(ref StencilAttachment, Tex.Handle, FramebufferAttachment.StencilAttachment); + Attach(ref StencilAttachment, CachedImage.Handle, FramebufferAttachment.StencilAttachment); } else { @@ -130,12 +128,25 @@ namespace Ryujinx.Graphics.Gal.OpenGL } } + private void Attach(ref int OldHandle, int NewHandle, FramebufferAttachment FbAttachment) + { + if (OldHandle != NewHandle) + { + GL.FramebufferTexture( + FramebufferTarget.DrawFramebuffer, + FbAttachment, + NewHandle, + 0); + + OldHandle = NewHandle; + } + } + public void UnbindZeta() { EnsureFrameBuffer(); - if (DepthAttachment != 0 || - StencilAttachment != 0) + if (DepthAttachment != 0 || StencilAttachment != 0) { GL.FramebufferTexture( FramebufferTarget.DrawFramebuffer, @@ -143,44 +154,14 @@ namespace Ryujinx.Graphics.Gal.OpenGL 0, 0); - DepthAttachment = 0; - + DepthAttachment = 0; StencilAttachment = 0; } } - public void BindTexture(long Key, int Index) - { - if (Texture.TryGetImage(Key, out ImageHandler Tex)) - { - GL.ActiveTexture(TextureUnit.Texture0 + Index); - - GL.BindTexture(TextureTarget.Texture2D, Tex.Handle); - } - } - public void Set(long Key) { - if (Texture.TryGetImage(Key, out ImageHandler Tex)) - { - ReadTex = Tex; - } - } - - public void Set(byte[] Data, int Width, int Height) - { - if (RawTex == null) - { - RawTex = new ImageHandler(); - } - - RawTex.EnsureSetup(new GalImage(Width, Height, RawFormat)); - - GL.BindTexture(TextureTarget.Texture2D, RawTex.Handle); - - GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, Width, Height, RawTex.PixelFormat, RawTex.PixelType, Data); - - ReadTex = RawTex; + Texture.TryGetImageHandler(Key, out ReadTex); } public void SetMap(int[] Map) @@ -280,13 +261,15 @@ namespace Ryujinx.Graphics.Gal.OpenGL int DstY0 = FlipY ? DstPaddingY : Window.Height - DstPaddingY; int DstY1 = FlipY ? Window.Height - DstPaddingY : DstPaddingY; - if (SrcFb == 0) SrcFb = GL.GenFramebuffer(); - - GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0); - GL.Viewport(0, 0, Window.Width, Window.Height); + if (SrcFb == 0) + { + SrcFb = GL.GenFramebuffer(); + } + GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, SrcFb); + GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0); GL.FramebufferTexture(FramebufferTarget.ReadFramebuffer, FramebufferAttachment.ColorAttachment0, ReadTex.Handle, 0); @@ -298,7 +281,8 @@ namespace Ryujinx.Graphics.Gal.OpenGL GL.BlitFramebuffer( SrcX0, SrcY0, SrcX1, SrcY1, DstX0, DstY0, DstX1, DstY1, - ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Linear); + ClearBufferMask.ColorBufferBit, + BlitFramebufferFilter.Linear); EnsureFrameBuffer(); } @@ -315,110 +299,135 @@ namespace Ryujinx.Graphics.Gal.OpenGL int DstX1, int DstY1) { - if (Texture.TryGetImage(SrcKey, out ImageHandler SrcTex) && - Texture.TryGetImage(DstKey, out ImageHandler DstTex)) + if (Texture.TryGetImageHandler(SrcKey, out ImageHandler SrcTex) && + Texture.TryGetImageHandler(DstKey, out ImageHandler DstTex)) { - if (SrcTex.HasColor != DstTex.HasColor || - SrcTex.HasDepth != DstTex.HasDepth || + if (SrcTex.HasColor != DstTex.HasColor || + SrcTex.HasDepth != DstTex.HasDepth || SrcTex.HasStencil != DstTex.HasStencil) { throw new NotImplementedException(); } + if (SrcFb == 0) + { + SrcFb = GL.GenFramebuffer(); + } + + if (DstFb == 0) + { + DstFb = GL.GenFramebuffer(); + } + + GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, SrcFb); + GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, DstFb); + + FramebufferAttachment Attachment = GetAttachment(SrcTex); + + GL.FramebufferTexture(FramebufferTarget.ReadFramebuffer, Attachment, SrcTex.Handle, 0); + GL.FramebufferTexture(FramebufferTarget.DrawFramebuffer, Attachment, DstTex.Handle, 0); + + BlitFramebufferFilter Filter = BlitFramebufferFilter.Nearest; + if (SrcTex.HasColor) { - CopyTextures( - SrcX0, SrcY0, SrcX1, SrcY1, - DstX0, DstY0, DstX1, DstY1, - SrcTex.Handle, - DstTex.Handle, - FramebufferAttachment.ColorAttachment0, - ClearBufferMask.ColorBufferBit, - true); - } - else if (SrcTex.HasDepth && SrcTex.HasStencil) - { - CopyTextures( - SrcX0, SrcY0, SrcX1, SrcY1, - DstX0, DstY0, DstX1, DstY1, - SrcTex.Handle, - DstTex.Handle, - FramebufferAttachment.DepthStencilAttachment, - ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit, - false); - } - else if (SrcTex.HasDepth) - { - CopyTextures( - SrcX0, SrcY0, SrcX1, SrcY1, - DstX0, DstY0, DstX1, DstY1, - SrcTex.Handle, - DstTex.Handle, - FramebufferAttachment.DepthAttachment, - ClearBufferMask.DepthBufferBit, - false); - } - else if (SrcTex.HasStencil) - { - CopyTextures( - SrcX0, SrcY0, SrcX1, SrcY1, - DstX0, DstY0, DstX1, DstY1, - SrcTex.Handle, - DstTex.Handle, - FramebufferAttachment.StencilAttachment, - ClearBufferMask.StencilBufferBit, - false); - } - else - { - throw new InvalidOperationException(); + GL.DrawBuffer(DrawBufferMode.ColorAttachment0); + + Filter = BlitFramebufferFilter.Linear; } + + ClearBufferMask Mask = GetClearMask(SrcTex); + + GL.Clear(Mask); + + GL.BlitFramebuffer(SrcX0, SrcY0, SrcX1, SrcY1, DstX0, DstY0, DstX1, DstY1, Mask, Filter); + + EnsureFrameBuffer(); } } - public void GetBufferData(long Key, Action Callback) + public void Reinterpret(long Key, GalImage NewImage) { - if (Texture.TryGetImage(Key, out ImageHandler Tex)) + if (!Texture.TryGetImage(Key, out GalImage OldImage)) { - byte[] Data = new byte[ImageUtils.GetSize(Tex.Image)]; + return; + } - GL.BindTexture(TextureTarget.Texture2D, Tex.Handle); + if (NewImage.Format == OldImage.Format) + { + return; + } - GL.GetTexImage( - TextureTarget.Texture2D, - 0, - Tex.PixelFormat, - Tex.PixelType, - Data); + byte[] Data = GetData(Key); - Callback(Data); + GL.PixelStore(PixelStoreParameter.UnpackRowLength, OldImage.Width); + + Texture.Create(Key, Data, NewImage); + + GL.PixelStore(PixelStoreParameter.UnpackRowLength, 0); + } + + public byte[] GetData(long Key) + { + if (!Texture.TryGetImageHandler(Key, out ImageHandler CachedImage)) + { + return null; + } + + if (SrcFb == 0) + { + SrcFb = GL.GenFramebuffer(); + } + + GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, SrcFb); + + FramebufferAttachment Attachment = GetAttachment(CachedImage); + + GL.FramebufferTexture(FramebufferTarget.ReadFramebuffer, Attachment, CachedImage.Handle, 0); + + int Size = ImageUtils.GetSize(CachedImage.Image); + + byte[] Data = new byte[Size]; + + int Width = CachedImage.Width; + int Height = CachedImage.Height; + + (_, PixelFormat Format, PixelType Type) = OGLEnumConverter.GetImageFormat(CachedImage.Format); + + GL.ReadPixels(0, 0, Width, Height, Format, Type, Data); + + return Data; + } + + private static FramebufferAttachment GetAttachment(ImageHandler CachedImage) + { + if (CachedImage.HasColor) + { + return FramebufferAttachment.ColorAttachment0; + } + else if (CachedImage.HasDepth && CachedImage.HasStencil) + { + return FramebufferAttachment.DepthStencilAttachment; + } + else if (CachedImage.HasDepth) + { + return FramebufferAttachment.DepthAttachment; + } + else if (CachedImage.HasStencil) + { + return FramebufferAttachment.StencilAttachment; + } + else + { + throw new InvalidOperationException(); } } - public void SetBufferData( - long Key, - int Width, - int Height, - byte[] Buffer) + private static ClearBufferMask GetClearMask(ImageHandler CachedImage) { - if (Texture.TryGetImage(Key, out ImageHandler Tex)) - { - GL.BindTexture(TextureTarget.Texture2D, Tex.Handle); - - const int Level = 0; - const int Border = 0; - - GL.TexImage2D( - TextureTarget.Texture2D, - Level, - Tex.InternalFormat, - Width, - Height, - Border, - Tex.PixelFormat, - Tex.PixelType, - Buffer); - } + return (CachedImage.HasColor ? ClearBufferMask.ColorBufferBit : 0) | + (CachedImage.HasDepth ? ClearBufferMask.DepthBufferBit : 0) | + (CachedImage.HasStencil ? ClearBufferMask.StencilBufferBit : 0); } private void EnsureFrameBuffer() @@ -430,68 +439,5 @@ namespace Ryujinx.Graphics.Gal.OpenGL GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, DummyFrameBuffer); } - - private void Attach(ref int OldHandle, int NewHandle, FramebufferAttachment FbAttachment) - { - if (OldHandle != NewHandle) - { - GL.FramebufferTexture( - FramebufferTarget.DrawFramebuffer, - FbAttachment, - NewHandle, - 0); - - OldHandle = NewHandle; - } - } - - private void CopyTextures( - int SrcX0, - int SrcY0, - int SrcX1, - int SrcY1, - int DstX0, - int DstY0, - int DstX1, - int DstY1, - int SrcTexture, - int DstTexture, - FramebufferAttachment Attachment, - ClearBufferMask Mask, - bool Color) - { - if (SrcFb == 0) SrcFb = GL.GenFramebuffer(); - if (DstFb == 0) DstFb = GL.GenFramebuffer(); - - GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, SrcFb); - GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, DstFb); - - GL.FramebufferTexture( - FramebufferTarget.ReadFramebuffer, - Attachment, - SrcTexture, - 0); - - GL.FramebufferTexture( - FramebufferTarget.DrawFramebuffer, - Attachment, - DstTexture, - 0); - - if (Color) - { - GL.DrawBuffer(DrawBufferMode.ColorAttachment0); - } - - GL.Clear(Mask); - - GL.BlitFramebuffer( - SrcX0, SrcY0, SrcX1, SrcY1, - DstX0, DstY0, DstX1, DstY1, - Mask, - Color ? BlitFramebufferFilter.Linear : BlitFramebufferFilter.Nearest); - - EnsureFrameBuffer(); - } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs index 7e1c0e5354..3347afbd1c 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs @@ -28,17 +28,65 @@ namespace Ryujinx.Graphics.Gal.OpenGL GL.DeleteTexture(CachedImage.Handle); } - public void Create(long Key, byte[] Data, GalImage Image) + public void Create(long Key, int Size, GalImage Image) { int Handle = GL.GenTexture(); - TextureCache.AddOrUpdate(Key, new ImageHandler(Handle, Image), (uint)Data.Length); - GL.BindTexture(TextureTarget.Texture2D, Handle); const int Level = 0; //TODO: Support mipmap textures. const int Border = 0; + TextureCache.AddOrUpdate(Key, new ImageHandler(Handle, Image), (uint)Size); + + GalImageFormat TypeLess = Image.Format & GalImageFormat.FormatMask; + + bool IsASTC = TypeLess >= GalImageFormat.ASTC_BEGIN && TypeLess <= GalImageFormat.ASTC_END; + + if (ImageUtils.IsCompressed(Image.Format) && !IsASTC) + { + InternalFormat InternalFmt = OGLEnumConverter.GetCompressedImageFormat(Image.Format); + + GL.CompressedTexImage2D( + TextureTarget.Texture2D, + Level, + InternalFmt, + Image.Width, + Image.Height, + Border, + Size, + IntPtr.Zero); + } + else + { + (PixelInternalFormat InternalFmt, + PixelFormat Format, + PixelType Type) = OGLEnumConverter.GetImageFormat(Image.Format); + + GL.TexImage2D( + TextureTarget.Texture2D, + Level, + InternalFmt, + Image.Width, + Image.Height, + Border, + Format, + Type, + IntPtr.Zero); + } + } + + public void Create(long Key, byte[] Data, GalImage Image) + { + int Handle = GL.GenTexture(); + + GL.BindTexture(TextureTarget.Texture2D, Handle); + + const int Level = 0; //TODO: Support mipmap textures. + const int Border = 0; + + TextureCache.AddOrUpdate(Key, new ImageHandler(Handle, Image), (uint)Data.Length); + GalImageFormat TypeLess = Image.Format & GalImageFormat.FormatMask; bool IsASTC = TypeLess >= GalImageFormat.ASTC_BEGIN && TypeLess <= GalImageFormat.ASTC_END; @@ -62,8 +110,8 @@ namespace Ryujinx.Graphics.Gal.OpenGL //TODO: Use KHR_texture_compression_astc_hdr when available if (IsASTC) { - int TextureBlockWidth = GetAstcBlockWidth(Image.Format); - int TextureBlockHeight = GetAstcBlockHeight(Image.Format); + int TextureBlockWidth = ImageUtils.GetBlockWidth(Image.Format); + int TextureBlockHeight = ImageUtils.GetBlockHeight(Image.Format); Data = ASTCDecoder.DecodeToRGBA8888( Data, @@ -85,12 +133,14 @@ namespace Ryujinx.Graphics.Gal.OpenGL Image.Format = GalImageFormat.R8G8 | (Image.Format & GalImageFormat.TypeMask); } - (PixelInternalFormat InternalFormat, PixelFormat Format, PixelType Type) = OGLEnumConverter.GetImageFormat(Image.Format); + (PixelInternalFormat InternalFmt, + PixelFormat Format, + PixelType Type) = OGLEnumConverter.GetImageFormat(Image.Format); GL.TexImage2D( TextureTarget.Texture2D, Level, - InternalFormat, + InternalFmt, Image.Width, Image.Height, Border, @@ -98,31 +148,23 @@ namespace Ryujinx.Graphics.Gal.OpenGL Type, Data); } - - int SwizzleR = (int)OGLEnumConverter.GetTextureSwizzle(Image.XSource); - int SwizzleG = (int)OGLEnumConverter.GetTextureSwizzle(Image.YSource); - int SwizzleB = (int)OGLEnumConverter.GetTextureSwizzle(Image.ZSource); - int SwizzleA = (int)OGLEnumConverter.GetTextureSwizzle(Image.WSource); - - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleR, SwizzleR); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleG, SwizzleG); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleB, SwizzleB); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleA, SwizzleA); } - public void CreateFb(long Key, long Size, GalImage Image) + public bool TryGetImage(long Key, out GalImage Image) { - if (!TryGetImage(Key, out ImageHandler CachedImage)) + if (TextureCache.TryGetValue(Key, out ImageHandler CachedImage)) { - CachedImage = new ImageHandler(); + Image = CachedImage.Image; - TextureCache.AddOrUpdate(Key, CachedImage, Size); + return true; } - CachedImage.EnsureSetup(Image); + Image = default(GalImage); + + return false; } - public bool TryGetImage(long Key, out ImageHandler CachedImage) + public bool TryGetImageHandler(long Key, out ImageHandler CachedImage) { if (TextureCache.TryGetValue(Key, out CachedImage)) { @@ -134,76 +176,23 @@ namespace Ryujinx.Graphics.Gal.OpenGL return false; } - private static int GetAstcBlockWidth(GalImageFormat Format) - { - switch (Format) - { - case GalImageFormat.ASTC_4x4 | GalImageFormat.Unorm: return 4; - case GalImageFormat.ASTC_5x5 | GalImageFormat.Unorm: return 5; - case GalImageFormat.ASTC_6x6 | GalImageFormat.Unorm: return 6; - case GalImageFormat.ASTC_8x8 | GalImageFormat.Unorm: return 8; - case GalImageFormat.ASTC_10x10 | GalImageFormat.Unorm: return 10; - case GalImageFormat.ASTC_12x12 | GalImageFormat.Unorm: return 12; - case GalImageFormat.ASTC_5x4 | GalImageFormat.Unorm: return 5; - case GalImageFormat.ASTC_6x5 | GalImageFormat.Unorm: return 6; - case GalImageFormat.ASTC_8x6 | GalImageFormat.Unorm: return 8; - case GalImageFormat.ASTC_10x8 | GalImageFormat.Unorm: return 10; - case GalImageFormat.ASTC_12x10 | GalImageFormat.Unorm: return 12; - case GalImageFormat.ASTC_8x5 | GalImageFormat.Unorm: return 8; - case GalImageFormat.ASTC_10x5 | GalImageFormat.Unorm: return 10; - case GalImageFormat.ASTC_10x6 | GalImageFormat.Unorm: return 10; - } - - throw new ArgumentException(nameof(Format)); - } - - private static int GetAstcBlockHeight(GalImageFormat Format) - { - switch (Format) - { - case GalImageFormat.ASTC_4x4 | GalImageFormat.Unorm: return 4; - case GalImageFormat.ASTC_5x5 | GalImageFormat.Unorm: return 5; - case GalImageFormat.ASTC_6x6 | GalImageFormat.Unorm: return 6; - case GalImageFormat.ASTC_8x8 | GalImageFormat.Unorm: return 8; - case GalImageFormat.ASTC_10x10 | GalImageFormat.Unorm: return 10; - case GalImageFormat.ASTC_12x12 | GalImageFormat.Unorm: return 12; - case GalImageFormat.ASTC_5x4 | GalImageFormat.Unorm: return 4; - case GalImageFormat.ASTC_6x5 | GalImageFormat.Unorm: return 5; - case GalImageFormat.ASTC_8x6 | GalImageFormat.Unorm: return 6; - case GalImageFormat.ASTC_10x8 | GalImageFormat.Unorm: return 8; - case GalImageFormat.ASTC_12x10 | GalImageFormat.Unorm: return 10; - case GalImageFormat.ASTC_8x5 | GalImageFormat.Unorm: return 5; - case GalImageFormat.ASTC_10x5 | GalImageFormat.Unorm: return 5; - case GalImageFormat.ASTC_10x6 | GalImageFormat.Unorm: return 6; - } - - throw new ArgumentException(nameof(Format)); - } - - public bool TryGetCachedTexture(long Key, long DataSize, out GalImage Image) - { - if (TextureCache.TryGetSize(Key, out long Size) && Size == DataSize) - { - if (TextureCache.TryGetValue(Key, out ImageHandler CachedImage)) - { - Image = CachedImage.Image; - - return true; - } - } - - Image = default(GalImage); - - return false; - } - - public void Bind(long Key, int Index) + public void Bind(long Key, int Index, GalImage Image) { if (TextureCache.TryGetValue(Key, out ImageHandler CachedImage)) { GL.ActiveTexture(TextureUnit.Texture0 + Index); GL.BindTexture(TextureTarget.Texture2D, CachedImage.Handle); + + int[] SwizzleRgba = new int[] + { + (int)OGLEnumConverter.GetTextureSwizzle(Image.XSource), + (int)OGLEnumConverter.GetTextureSwizzle(Image.YSource), + (int)OGLEnumConverter.GetTextureSwizzle(Image.ZSource), + (int)OGLEnumConverter.GetTextureSwizzle(Image.WSource) + }; + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleRgba, SwizzleRgba); } } diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs index ac34400e38..60fe91c280 100644 --- a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs +++ b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs @@ -641,6 +641,7 @@ namespace Ryujinx.Graphics.Gal.Shader default: SB.AppendLine(Identation + GetSrcExpr(Op, true) + ";"); + break; } } diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs index c2ee474b17..0a3c0da98a 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs @@ -6,32 +6,32 @@ namespace Ryujinx.Graphics.Gal.Shader { static partial class ShaderDecode { - public static void Bfe_C(ShaderIrBlock Block, long OpCode, long Position) + public static void Bfe_C(ShaderIrBlock Block, long OpCode, int Position) { EmitBfe(Block, OpCode, ShaderOper.CR); } - public static void Bfe_I(ShaderIrBlock Block, long OpCode, long Position) + public static void Bfe_I(ShaderIrBlock Block, long OpCode, int Position) { EmitBfe(Block, OpCode, ShaderOper.Imm); } - public static void Bfe_R(ShaderIrBlock Block, long OpCode, long Position) + public static void Bfe_R(ShaderIrBlock Block, long OpCode, int Position) { EmitBfe(Block, OpCode, ShaderOper.RR); } - public static void Fadd_C(ShaderIrBlock Block, long OpCode, long Position) + public static void Fadd_C(ShaderIrBlock Block, long OpCode, int Position) { EmitFadd(Block, OpCode, ShaderOper.CR); } - public static void Fadd_I(ShaderIrBlock Block, long OpCode, long Position) + public static void Fadd_I(ShaderIrBlock Block, long OpCode, int Position) { EmitFadd(Block, OpCode, ShaderOper.Immf); } - public static void Fadd_I32(ShaderIrBlock Block, long OpCode, long Position) + public static void Fadd_I32(ShaderIrBlock Block, long OpCode, int Position) { ShaderIrNode OperA = OpCode.Gpr8(); ShaderIrNode OperB = OpCode.Immf32_20(); @@ -49,47 +49,47 @@ namespace Ryujinx.Graphics.Gal.Shader Block.AddNode(OpCode.PredNode(new ShaderIrAsg(OpCode.Gpr0(), Op))); } - public static void Fadd_R(ShaderIrBlock Block, long OpCode, long Position) + public static void Fadd_R(ShaderIrBlock Block, long OpCode, int Position) { EmitFadd(Block, OpCode, ShaderOper.RR); } - public static void Ffma_CR(ShaderIrBlock Block, long OpCode, long Position) + public static void Ffma_CR(ShaderIrBlock Block, long OpCode, int Position) { EmitFfma(Block, OpCode, ShaderOper.CR); } - public static void Ffma_I(ShaderIrBlock Block, long OpCode, long Position) + public static void Ffma_I(ShaderIrBlock Block, long OpCode, int Position) { EmitFfma(Block, OpCode, ShaderOper.Immf); } - public static void Ffma_RC(ShaderIrBlock Block, long OpCode, long Position) + public static void Ffma_RC(ShaderIrBlock Block, long OpCode, int Position) { EmitFfma(Block, OpCode, ShaderOper.RC); } - public static void Ffma_RR(ShaderIrBlock Block, long OpCode, long Position) + public static void Ffma_RR(ShaderIrBlock Block, long OpCode, int Position) { EmitFfma(Block, OpCode, ShaderOper.RR); } - public static void Fmnmx_C(ShaderIrBlock Block, long OpCode, long Position) + public static void Fmnmx_C(ShaderIrBlock Block, long OpCode, int Position) { EmitFmnmx(Block, OpCode, ShaderOper.CR); } - public static void Fmnmx_I(ShaderIrBlock Block, long OpCode, long Position) + public static void Fmnmx_I(ShaderIrBlock Block, long OpCode, int Position) { EmitFmnmx(Block, OpCode, ShaderOper.Immf); } - public static void Fmnmx_R(ShaderIrBlock Block, long OpCode, long Position) + public static void Fmnmx_R(ShaderIrBlock Block, long OpCode, int Position) { EmitFmnmx(Block, OpCode, ShaderOper.RR); } - public static void Fmul_I32(ShaderIrBlock Block, long OpCode, long Position) + public static void Fmul_I32(ShaderIrBlock Block, long OpCode, int Position) { ShaderIrNode OperA = OpCode.Gpr8(); ShaderIrNode OperB = OpCode.Immf32_20(); @@ -99,62 +99,62 @@ namespace Ryujinx.Graphics.Gal.Shader Block.AddNode(OpCode.PredNode(new ShaderIrAsg(OpCode.Gpr0(), Op))); } - public static void Fmul_C(ShaderIrBlock Block, long OpCode, long Position) + public static void Fmul_C(ShaderIrBlock Block, long OpCode, int Position) { EmitFmul(Block, OpCode, ShaderOper.CR); } - public static void Fmul_I(ShaderIrBlock Block, long OpCode, long Position) + public static void Fmul_I(ShaderIrBlock Block, long OpCode, int Position) { EmitFmul(Block, OpCode, ShaderOper.Immf); } - public static void Fmul_R(ShaderIrBlock Block, long OpCode, long Position) + public static void Fmul_R(ShaderIrBlock Block, long OpCode, int Position) { EmitFmul(Block, OpCode, ShaderOper.RR); } - public static void Fset_C(ShaderIrBlock Block, long OpCode, long Position) + public static void Fset_C(ShaderIrBlock Block, long OpCode, int Position) { EmitFset(Block, OpCode, ShaderOper.CR); } - public static void Fset_I(ShaderIrBlock Block, long OpCode, long Position) + public static void Fset_I(ShaderIrBlock Block, long OpCode, int Position) { EmitFset(Block, OpCode, ShaderOper.Immf); } - public static void Fset_R(ShaderIrBlock Block, long OpCode, long Position) + public static void Fset_R(ShaderIrBlock Block, long OpCode, int Position) { EmitFset(Block, OpCode, ShaderOper.RR); } - public static void Fsetp_C(ShaderIrBlock Block, long OpCode, long Position) + public static void Fsetp_C(ShaderIrBlock Block, long OpCode, int Position) { EmitFsetp(Block, OpCode, ShaderOper.CR); } - public static void Fsetp_I(ShaderIrBlock Block, long OpCode, long Position) + public static void Fsetp_I(ShaderIrBlock Block, long OpCode, int Position) { EmitFsetp(Block, OpCode, ShaderOper.Immf); } - public static void Fsetp_R(ShaderIrBlock Block, long OpCode, long Position) + public static void Fsetp_R(ShaderIrBlock Block, long OpCode, int Position) { EmitFsetp(Block, OpCode, ShaderOper.RR); } - public static void Iadd_C(ShaderIrBlock Block, long OpCode, long Position) + public static void Iadd_C(ShaderIrBlock Block, long OpCode, int Position) { EmitIadd(Block, OpCode, ShaderOper.CR); } - public static void Iadd_I(ShaderIrBlock Block, long OpCode, long Position) + public static void Iadd_I(ShaderIrBlock Block, long OpCode, int Position) { EmitIadd(Block, OpCode, ShaderOper.Imm); } - public static void Iadd_I32(ShaderIrBlock Block, long OpCode, long Position) + public static void Iadd_I32(ShaderIrBlock Block, long OpCode, int Position) { ShaderIrNode OperA = OpCode.Gpr8(); ShaderIrNode OperB = OpCode.Imm32_20(); @@ -168,42 +168,42 @@ namespace Ryujinx.Graphics.Gal.Shader Block.AddNode(OpCode.PredNode(new ShaderIrAsg(OpCode.Gpr0(), Op))); } - public static void Iadd_R(ShaderIrBlock Block, long OpCode, long Position) + public static void Iadd_R(ShaderIrBlock Block, long OpCode, int Position) { EmitIadd(Block, OpCode, ShaderOper.RR); } - public static void Iadd3_C(ShaderIrBlock Block, long OpCode, long Position) + public static void Iadd3_C(ShaderIrBlock Block, long OpCode, int Position) { EmitIadd3(Block, OpCode, ShaderOper.CR); } - public static void Iadd3_I(ShaderIrBlock Block, long OpCode, long Position) + public static void Iadd3_I(ShaderIrBlock Block, long OpCode, int Position) { EmitIadd3(Block, OpCode, ShaderOper.Imm); } - public static void Iadd3_R(ShaderIrBlock Block, long OpCode, long Position) + public static void Iadd3_R(ShaderIrBlock Block, long OpCode, int Position) { EmitIadd3(Block, OpCode, ShaderOper.RR); } - public static void Imnmx_C(ShaderIrBlock Block, long OpCode, long Position) + public static void Imnmx_C(ShaderIrBlock Block, long OpCode, int Position) { EmitImnmx(Block, OpCode, ShaderOper.CR); } - public static void Imnmx_I(ShaderIrBlock Block, long OpCode, long Position) + public static void Imnmx_I(ShaderIrBlock Block, long OpCode, int Position) { EmitImnmx(Block, OpCode, ShaderOper.Imm); } - public static void Imnmx_R(ShaderIrBlock Block, long OpCode, long Position) + public static void Imnmx_R(ShaderIrBlock Block, long OpCode, int Position) { EmitImnmx(Block, OpCode, ShaderOper.RR); } - public static void Ipa(ShaderIrBlock Block, long OpCode, long Position) + public static void Ipa(ShaderIrBlock Block, long OpCode, int Position) { ShaderIrNode OperA = OpCode.Abuf28(); ShaderIrNode OperB = OpCode.Gpr20(); @@ -217,52 +217,52 @@ namespace Ryujinx.Graphics.Gal.Shader Block.AddNode(OpCode.PredNode(new ShaderIrAsg(OpCode.Gpr0(), Op))); } - public static void Iscadd_C(ShaderIrBlock Block, long OpCode, long Position) + public static void Iscadd_C(ShaderIrBlock Block, long OpCode, int Position) { EmitIscadd(Block, OpCode, ShaderOper.CR); } - public static void Iscadd_I(ShaderIrBlock Block, long OpCode, long Position) + public static void Iscadd_I(ShaderIrBlock Block, long OpCode, int Position) { EmitIscadd(Block, OpCode, ShaderOper.Imm); } - public static void Iscadd_R(ShaderIrBlock Block, long OpCode, long Position) + public static void Iscadd_R(ShaderIrBlock Block, long OpCode, int Position) { EmitIscadd(Block, OpCode, ShaderOper.RR); } - public static void Iset_C(ShaderIrBlock Block, long OpCode, long Position) + public static void Iset_C(ShaderIrBlock Block, long OpCode, int Position) { EmitIset(Block, OpCode, ShaderOper.CR); } - public static void Iset_I(ShaderIrBlock Block, long OpCode, long Position) + public static void Iset_I(ShaderIrBlock Block, long OpCode, int Position) { EmitIset(Block, OpCode, ShaderOper.Imm); } - public static void Iset_R(ShaderIrBlock Block, long OpCode, long Position) + public static void Iset_R(ShaderIrBlock Block, long OpCode, int Position) { EmitIset(Block, OpCode, ShaderOper.RR); } - public static void Isetp_C(ShaderIrBlock Block, long OpCode, long Position) + public static void Isetp_C(ShaderIrBlock Block, long OpCode, int Position) { EmitIsetp(Block, OpCode, ShaderOper.CR); } - public static void Isetp_I(ShaderIrBlock Block, long OpCode, long Position) + public static void Isetp_I(ShaderIrBlock Block, long OpCode, int Position) { EmitIsetp(Block, OpCode, ShaderOper.Imm); } - public static void Isetp_R(ShaderIrBlock Block, long OpCode, long Position) + public static void Isetp_R(ShaderIrBlock Block, long OpCode, int Position) { EmitIsetp(Block, OpCode, ShaderOper.RR); } - public static void Lop_I32(ShaderIrBlock Block, long OpCode, long Position) + public static void Lop_I32(ShaderIrBlock Block, long OpCode, int Position) { int SubOp = OpCode.Read(53, 3); @@ -296,22 +296,22 @@ namespace Ryujinx.Graphics.Gal.Shader } } - public static void Lop_C(ShaderIrBlock Block, long OpCode, long Position) + public static void Lop_C(ShaderIrBlock Block, long OpCode, int Position) { EmitLop(Block, OpCode, ShaderOper.CR); } - public static void Lop_I(ShaderIrBlock Block, long OpCode, long Position) + public static void Lop_I(ShaderIrBlock Block, long OpCode, int Position) { EmitLop(Block, OpCode, ShaderOper.Imm); } - public static void Lop_R(ShaderIrBlock Block, long OpCode, long Position) + public static void Lop_R(ShaderIrBlock Block, long OpCode, int Position) { EmitLop(Block, OpCode, ShaderOper.RR); } - public static void Mufu(ShaderIrBlock Block, long OpCode, long Position) + public static void Mufu(ShaderIrBlock Block, long OpCode, int Position) { int SubOp = OpCode.Read(20, 0xf); @@ -340,7 +340,7 @@ namespace Ryujinx.Graphics.Gal.Shader Block.AddNode(OpCode.PredNode(new ShaderIrAsg(OpCode.Gpr0(), Op))); } - public static void Psetp(ShaderIrBlock Block, long OpCode, long Position) + public static void Psetp(ShaderIrBlock Block, long OpCode, int Position) { bool NegA = OpCode.Read(15); bool NegB = OpCode.Read(32); @@ -394,47 +394,47 @@ namespace Ryujinx.Graphics.Gal.Shader Block.AddNode(OpCode.PredNode(new ShaderIrAsg(P0Node, Op))); } - public static void Rro_C(ShaderIrBlock Block, long OpCode, long Position) + public static void Rro_C(ShaderIrBlock Block, long OpCode, int Position) { EmitRro(Block, OpCode, ShaderOper.CR); } - public static void Rro_I(ShaderIrBlock Block, long OpCode, long Position) + public static void Rro_I(ShaderIrBlock Block, long OpCode, int Position) { EmitRro(Block, OpCode, ShaderOper.Immf); } - public static void Rro_R(ShaderIrBlock Block, long OpCode, long Position) + public static void Rro_R(ShaderIrBlock Block, long OpCode, int Position) { EmitRro(Block, OpCode, ShaderOper.RR); } - public static void Shl_C(ShaderIrBlock Block, long OpCode, long Position) + public static void Shl_C(ShaderIrBlock Block, long OpCode, int Position) { EmitAluBinary(Block, OpCode, ShaderOper.CR, ShaderIrInst.Lsl); } - public static void Shl_I(ShaderIrBlock Block, long OpCode, long Position) + public static void Shl_I(ShaderIrBlock Block, long OpCode, int Position) { EmitAluBinary(Block, OpCode, ShaderOper.Imm, ShaderIrInst.Lsl); } - public static void Shl_R(ShaderIrBlock Block, long OpCode, long Position) + public static void Shl_R(ShaderIrBlock Block, long OpCode, int Position) { EmitAluBinary(Block, OpCode, ShaderOper.RR, ShaderIrInst.Lsl); } - public static void Shr_C(ShaderIrBlock Block, long OpCode, long Position) + public static void Shr_C(ShaderIrBlock Block, long OpCode, int Position) { EmitAluBinary(Block, OpCode, ShaderOper.CR, GetShrInst(OpCode)); } - public static void Shr_I(ShaderIrBlock Block, long OpCode, long Position) + public static void Shr_I(ShaderIrBlock Block, long OpCode, int Position) { EmitAluBinary(Block, OpCode, ShaderOper.Imm, GetShrInst(OpCode)); } - public static void Shr_R(ShaderIrBlock Block, long OpCode, long Position) + public static void Shr_R(ShaderIrBlock Block, long OpCode, int Position) { EmitAluBinary(Block, OpCode, ShaderOper.RR, GetShrInst(OpCode)); } @@ -446,7 +446,7 @@ namespace Ryujinx.Graphics.Gal.Shader return Signed ? ShaderIrInst.Asr : ShaderIrInst.Lsr; } - public static void Vmad(ShaderIrBlock Block, long OpCode, long Position) + public static void Vmad(ShaderIrBlock Block, long OpCode, int Position) { ShaderIrNode OperA = OpCode.Gpr8(); @@ -481,22 +481,22 @@ namespace Ryujinx.Graphics.Gal.Shader Block.AddNode(OpCode.PredNode(new ShaderIrAsg(OpCode.Gpr0(), Final))); } - public static void Xmad_CR(ShaderIrBlock Block, long OpCode, long Position) + public static void Xmad_CR(ShaderIrBlock Block, long OpCode, int Position) { EmitXmad(Block, OpCode, ShaderOper.CR); } - public static void Xmad_I(ShaderIrBlock Block, long OpCode, long Position) + public static void Xmad_I(ShaderIrBlock Block, long OpCode, int Position) { EmitXmad(Block, OpCode, ShaderOper.Imm); } - public static void Xmad_RC(ShaderIrBlock Block, long OpCode, long Position) + public static void Xmad_RC(ShaderIrBlock Block, long OpCode, int Position) { EmitXmad(Block, OpCode, ShaderOper.RC); } - public static void Xmad_RR(ShaderIrBlock Block, long OpCode, long Position) + public static void Xmad_RR(ShaderIrBlock Block, long OpCode, int Position) { EmitXmad(Block, OpCode, ShaderOper.RR); } diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs index dfd10e0029..bc2539bd7e 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs @@ -1,12 +1,10 @@ using System; -using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper; - namespace Ryujinx.Graphics.Gal.Shader { static partial class ShaderDecode { - public static void Bra(ShaderIrBlock Block, long OpCode, long Position) + public static void Bra(ShaderIrBlock Block, long OpCode, int Position) { if ((OpCode & 0x20) != 0) { @@ -15,14 +13,12 @@ namespace Ryujinx.Graphics.Gal.Shader throw new NotImplementedException(); } - int Target = OpCode.Branch(); - - ShaderIrOperImm Imm = new ShaderIrOperImm(Target); + ShaderIrOperImm Imm = new ShaderIrOperImm(Position + OpCode.Branch()); Block.AddNode(OpCode.PredNode(new ShaderIrOp(ShaderIrInst.Bra, Imm))); } - public static void Exit(ShaderIrBlock Block, long OpCode, long Position) + public static void Exit(ShaderIrBlock Block, long OpCode, int Position) { int CCode = (int)OpCode & 0x1f; @@ -31,15 +27,14 @@ namespace Ryujinx.Graphics.Gal.Shader { Block.AddNode(OpCode.PredNode(new ShaderIrOp(ShaderIrInst.Exit))); } - } - public static void Kil(ShaderIrBlock Block, long OpCode, long Position) + public static void Kil(ShaderIrBlock Block, long OpCode, int Position) { Block.AddNode(OpCode.PredNode(new ShaderIrOp(ShaderIrInst.Kil))); } - public static void Ssy(ShaderIrBlock Block, long OpCode, long Position) + public static void Ssy(ShaderIrBlock Block, long OpCode, int Position) { if ((OpCode & 0x20) != 0) { @@ -48,19 +43,14 @@ namespace Ryujinx.Graphics.Gal.Shader throw new NotImplementedException(); } - int Offset = OpCode.Branch(); - - int Target = (int)(Position + Offset); - - ShaderIrOperImm Imm = new ShaderIrOperImm(Target); + ShaderIrOperImm Imm = new ShaderIrOperImm(Position + OpCode.Branch()); Block.AddNode(new ShaderIrOp(ShaderIrInst.Ssy, Imm)); } - public static void Sync(ShaderIrBlock Block, long OpCode, long Position) + public static void Sync(ShaderIrBlock Block, long OpCode, int Position) { //TODO: Implement Sync condition codes - Block.AddNode(OpCode.PredNode(new ShaderIrOp(ShaderIrInst.Sync))); } } diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecode.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFunc.cs similarity index 83% rename from Ryujinx.Graphics/Gal/Shader/ShaderDecode.cs rename to Ryujinx.Graphics/Gal/Shader/ShaderDecodeFunc.cs index 73625f65fe..73248aa0c7 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecode.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFunc.cs @@ -1,4 +1,4 @@ namespace Ryujinx.Graphics.Gal.Shader { - delegate void ShaderDecodeFunc(ShaderIrBlock Block, long OpCode, long Position); + delegate void ShaderDecodeFunc(ShaderIrBlock Block, long OpCode, int Position); } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs index 010f06aa6c..ebacd53ab4 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs @@ -1,5 +1,3 @@ -using System; - namespace Ryujinx.Graphics.Gal.Shader { static class ShaderDecodeHelper diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs index 508a0205dd..cd65599503 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs @@ -31,7 +31,7 @@ namespace Ryujinx.Graphics.Gal.Shader { RGB_, RG_A, R_BA, _GBA, RGBA, ____, ____, ____ } }; - public static void Ld_A(ShaderIrBlock Block, long OpCode, long Position) + public static void Ld_A(ShaderIrBlock Block, long OpCode, int Position) { ShaderIrNode[] Opers = OpCode.Abuf20(); @@ -50,7 +50,7 @@ namespace Ryujinx.Graphics.Gal.Shader } } - public static void Ld_C(ShaderIrBlock Block, long OpCode, long Position) + public static void Ld_C(ShaderIrBlock Block, long OpCode, int Position) { int CbufPos = OpCode.Read(22, 0x3fff); int CbufIndex = OpCode.Read(36, 0x1f); @@ -97,7 +97,7 @@ namespace Ryujinx.Graphics.Gal.Shader } } - public static void St_A(ShaderIrBlock Block, long OpCode, long Position) + public static void St_A(ShaderIrBlock Block, long OpCode, int Position) { ShaderIrNode[] Opers = OpCode.Abuf20(); @@ -113,7 +113,7 @@ namespace Ryujinx.Graphics.Gal.Shader } } - public static void Texq(ShaderIrBlock Block, long OpCode, long Position) + public static void Texq(ShaderIrBlock Block, long OpCode, int Position) { ShaderIrNode OperD = OpCode.Gpr0(); ShaderIrNode OperA = OpCode.Gpr8(); @@ -132,12 +132,12 @@ namespace Ryujinx.Graphics.Gal.Shader Block.AddNode(OpCode.PredNode(new ShaderIrAsg(OperA, Op1))); //Is this right? } - public static void Tex(ShaderIrBlock Block, long OpCode, long Position) + public static void Tex(ShaderIrBlock Block, long OpCode, int Position) { EmitTex(Block, OpCode, GprHandle: false); } - public static void Tex_B(ShaderIrBlock Block, long OpCode, long Position) + public static void Tex_B(ShaderIrBlock Block, long OpCode, int Position) { EmitTex(Block, OpCode, GprHandle: true); } @@ -202,12 +202,12 @@ namespace Ryujinx.Graphics.Gal.Shader } } - public static void Texs(ShaderIrBlock Block, long OpCode, long Position) + public static void Texs(ShaderIrBlock Block, long OpCode, int Position) { EmitTexs(Block, OpCode, ShaderIrInst.Texs); } - public static void Tlds(ShaderIrBlock Block, long OpCode, long Position) + public static void Tlds(ShaderIrBlock Block, long OpCode, int Position) { EmitTexs(Block, OpCode, ShaderIrInst.Txlf); } diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs index add3940273..cd602db7c1 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs @@ -25,67 +25,67 @@ namespace Ryujinx.Graphics.Gal.Shader F64 = 3 } - public static void F2f_C(ShaderIrBlock Block, long OpCode, long Position) + public static void F2f_C(ShaderIrBlock Block, long OpCode, int Position) { EmitF2f(Block, OpCode, ShaderOper.CR); } - public static void F2f_I(ShaderIrBlock Block, long OpCode, long Position) + public static void F2f_I(ShaderIrBlock Block, long OpCode, int Position) { EmitF2f(Block, OpCode, ShaderOper.Immf); } - public static void F2f_R(ShaderIrBlock Block, long OpCode, long Position) + public static void F2f_R(ShaderIrBlock Block, long OpCode, int Position) { EmitF2f(Block, OpCode, ShaderOper.RR); } - public static void F2i_C(ShaderIrBlock Block, long OpCode, long Position) + public static void F2i_C(ShaderIrBlock Block, long OpCode, int Position) { EmitF2i(Block, OpCode, ShaderOper.CR); } - public static void F2i_I(ShaderIrBlock Block, long OpCode, long Position) + public static void F2i_I(ShaderIrBlock Block, long OpCode, int Position) { EmitF2i(Block, OpCode, ShaderOper.Immf); } - public static void F2i_R(ShaderIrBlock Block, long OpCode, long Position) + public static void F2i_R(ShaderIrBlock Block, long OpCode, int Position) { EmitF2i(Block, OpCode, ShaderOper.RR); } - public static void I2f_C(ShaderIrBlock Block, long OpCode, long Position) + public static void I2f_C(ShaderIrBlock Block, long OpCode, int Position) { EmitI2f(Block, OpCode, ShaderOper.CR); } - public static void I2f_I(ShaderIrBlock Block, long OpCode, long Position) + public static void I2f_I(ShaderIrBlock Block, long OpCode, int Position) { EmitI2f(Block, OpCode, ShaderOper.Imm); } - public static void I2f_R(ShaderIrBlock Block, long OpCode, long Position) + public static void I2f_R(ShaderIrBlock Block, long OpCode, int Position) { EmitI2f(Block, OpCode, ShaderOper.RR); } - public static void I2i_C(ShaderIrBlock Block, long OpCode, long Position) + public static void I2i_C(ShaderIrBlock Block, long OpCode, int Position) { EmitI2i(Block, OpCode, ShaderOper.CR); } - public static void I2i_I(ShaderIrBlock Block, long OpCode, long Position) + public static void I2i_I(ShaderIrBlock Block, long OpCode, int Position) { EmitI2i(Block, OpCode, ShaderOper.Imm); } - public static void I2i_R(ShaderIrBlock Block, long OpCode, long Position) + public static void I2i_R(ShaderIrBlock Block, long OpCode, int Position) { EmitI2i(Block, OpCode, ShaderOper.RR); } - public static void Isberd(ShaderIrBlock Block, long OpCode, long Position) + public static void Isberd(ShaderIrBlock Block, long OpCode, int Position) { //This instruction seems to be used to translate from an address to a vertex index in a GS //Stub it as such @@ -95,50 +95,50 @@ namespace Ryujinx.Graphics.Gal.Shader Block.AddNode(OpCode.PredNode(new ShaderIrAsg(OpCode.Gpr0(), OpCode.Gpr8()))); } - public static void Mov_C(ShaderIrBlock Block, long OpCode, long Position) + public static void Mov_C(ShaderIrBlock Block, long OpCode, int Position) { ShaderIrOperCbuf Cbuf = OpCode.Cbuf34(); Block.AddNode(OpCode.PredNode(new ShaderIrAsg(OpCode.Gpr0(), Cbuf))); } - public static void Mov_I(ShaderIrBlock Block, long OpCode, long Position) + public static void Mov_I(ShaderIrBlock Block, long OpCode, int Position) { ShaderIrOperImm Imm = OpCode.Imm19_20(); Block.AddNode(OpCode.PredNode(new ShaderIrAsg(OpCode.Gpr0(), Imm))); } - public static void Mov_I32(ShaderIrBlock Block, long OpCode, long Position) + public static void Mov_I32(ShaderIrBlock Block, long OpCode, int Position) { ShaderIrOperImm Imm = OpCode.Imm32_20(); Block.AddNode(OpCode.PredNode(new ShaderIrAsg(OpCode.Gpr0(), Imm))); } - public static void Mov_R(ShaderIrBlock Block, long OpCode, long Position) + public static void Mov_R(ShaderIrBlock Block, long OpCode, int Position) { ShaderIrOperGpr Gpr = OpCode.Gpr20(); Block.AddNode(OpCode.PredNode(new ShaderIrAsg(OpCode.Gpr0(), Gpr))); } - public static void Sel_C(ShaderIrBlock Block, long OpCode, long Position) + public static void Sel_C(ShaderIrBlock Block, long OpCode, int Position) { EmitSel(Block, OpCode, ShaderOper.CR); } - public static void Sel_I(ShaderIrBlock Block, long OpCode, long Position) + public static void Sel_I(ShaderIrBlock Block, long OpCode, int Position) { EmitSel(Block, OpCode, ShaderOper.Imm); } - public static void Sel_R(ShaderIrBlock Block, long OpCode, long Position) + public static void Sel_R(ShaderIrBlock Block, long OpCode, int Position) { EmitSel(Block, OpCode, ShaderOper.RR); } - public static void Mov_S(ShaderIrBlock Block, long OpCode, long Position) + public static void Mov_S(ShaderIrBlock Block, long OpCode, int Position) { Block.AddNode(new ShaderIrCmnt("Stubbed.")); diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeSpecial.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeSpecial.cs index c3e42654a9..35abdb7641 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeSpecial.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeSpecial.cs @@ -1,10 +1,8 @@ -using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper; - -namespace Ryujinx.Graphics.Gal.Shader +namespace Ryujinx.Graphics.Gal.Shader { static partial class ShaderDecode { - public static void Out_R(ShaderIrBlock Block, long OpCode, long Position) + public static void Out_R(ShaderIrBlock Block, long OpCode, int Position) { //TODO: Those registers have to be used for something ShaderIrOperGpr Gpr0 = OpCode.Gpr0(); diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs index 81d8f31268..ac6ae8d5d1 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs @@ -10,12 +10,14 @@ namespace Ryujinx.Graphics.Gal.Shader public static ShaderIrBlock[] Decode(IGalMemory Memory, long Start) { - Dictionary Visited = new Dictionary(); - Dictionary VisitedEnd = new Dictionary(); + Dictionary Visited = new Dictionary(); + Dictionary VisitedEnd = new Dictionary(); Queue Blocks = new Queue(); - ShaderIrBlock Enqueue(long Position, ShaderIrBlock Source = null) + long Beginning = Start + HeaderSize; + + ShaderIrBlock Enqueue(int Position, ShaderIrBlock Source = null) { if (!Visited.TryGetValue(Position, out ShaderIrBlock Output)) { @@ -34,13 +36,13 @@ namespace Ryujinx.Graphics.Gal.Shader return Output; } - ShaderIrBlock Entry = Enqueue(Start + HeaderSize); + ShaderIrBlock Entry = Enqueue(0); while (Blocks.Count > 0) { ShaderIrBlock Current = Blocks.Dequeue(); - FillBlock(Memory, Current, Start + HeaderSize); + FillBlock(Memory, Current, Beginning); //Set child blocks. "Branch" is the block the branch instruction //points to (when taken), "Next" is the block at the next address, @@ -54,20 +56,18 @@ namespace Ryujinx.Graphics.Gal.Shader if (InnerOp?.Inst == ShaderIrInst.Bra) { - int Offset = ((ShaderIrOperImm)InnerOp.OperandA).Value; - - long Target = Current.EndPosition + Offset; + int Target = ((ShaderIrOperImm)InnerOp.OperandA).Value; Current.Branch = Enqueue(Target, Current); } foreach (ShaderIrNode Node in Current.Nodes) { - if (Node is ShaderIrOp CurrOp && CurrOp.Inst == ShaderIrInst.Ssy) - { - int Offset = ((ShaderIrOperImm)CurrOp.OperandA).Value; + InnerOp = GetInnermostOp(Node); - long Target = Offset; + if (InnerOp is ShaderIrOp CurrOp && CurrOp.Inst == ShaderIrInst.Ssy) + { + int Target = ((ShaderIrOperImm)CurrOp.OperandA).Value; Current.Branch = Enqueue(Target, Current); } @@ -112,15 +112,15 @@ namespace Ryujinx.Graphics.Gal.Shader while (Visited.Count > 0) { - ulong FirstPos = ulong.MaxValue; + uint FirstPos = uint.MaxValue; foreach (ShaderIrBlock Block in Visited.Values) { - if (FirstPos > (ulong)Block.Position) - FirstPos = (ulong)Block.Position; + if (FirstPos > (uint)Block.Position) + FirstPos = (uint)Block.Position; } - ShaderIrBlock Current = Visited[(long)FirstPos]; + ShaderIrBlock Current = Visited[(int)FirstPos]; do { @@ -138,20 +138,20 @@ namespace Ryujinx.Graphics.Gal.Shader private static void FillBlock(IGalMemory Memory, ShaderIrBlock Block, long Beginning) { - long Position = Block.Position; + int Position = Block.Position; do { //Ignore scheduling instructions, which are written every 32 bytes. - if (((Position - Beginning) & 0x1f) == 0) + if ((Position & 0x1f) == 0) { Position += 8; continue; } - uint Word0 = (uint)Memory.ReadInt32(Position + 0); - uint Word1 = (uint)Memory.ReadInt32(Position + 4); + uint Word0 = (uint)Memory.ReadInt32(Position + Beginning + 0); + uint Word1 = (uint)Memory.ReadInt32(Position + Beginning + 4); Position += 8; @@ -161,7 +161,7 @@ namespace Ryujinx.Graphics.Gal.Shader if (AddDbgComments) { - string DbgOpCode = $"0x{(Position - Beginning - 8):x16}: 0x{OpCode:x16} "; + string DbgOpCode = $"0x{(Position - 8):x16}: 0x{OpCode:x16} "; DbgOpCode += (Decode?.Method.Name ?? "???"); @@ -169,7 +169,7 @@ namespace Ryujinx.Graphics.Gal.Shader { int Offset = ((int)(OpCode >> 20) << 8) >> 8; - long Target = Position + Offset - Beginning; + long Target = Position + Offset; DbgOpCode += " (0x" + Target.ToString("x16") + ")"; } diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs index 50e563b846..782f96261b 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs @@ -4,8 +4,8 @@ namespace Ryujinx.Graphics.Gal.Shader { class ShaderIrBlock { - public long Position { get; set; } - public long EndPosition { get; set; } + public int Position { get; set; } + public int EndPosition { get; set; } public ShaderIrBlock Next { get; set; } public ShaderIrBlock Branch { get; set; } @@ -14,7 +14,7 @@ namespace Ryujinx.Graphics.Gal.Shader public List Nodes { get; private set; } - public ShaderIrBlock(long Position) + public ShaderIrBlock(int Position) { this.Position = Position; diff --git a/Ryujinx.Graphics/GpuResourceManager.cs b/Ryujinx.Graphics/GpuResourceManager.cs new file mode 100644 index 0000000000..ba35910d30 --- /dev/null +++ b/Ryujinx.Graphics/GpuResourceManager.cs @@ -0,0 +1,122 @@ +using Ryujinx.Graphics.Gal; +using Ryujinx.Graphics.Memory; +using Ryujinx.Graphics.Texture; +using System.Collections.Generic; + +namespace Ryujinx.Graphics +{ + public class GpuResourceManager + { + private NvGpu Gpu; + + private HashSet[] UploadedKeys; + + public GpuResourceManager(NvGpu Gpu) + { + this.Gpu = Gpu; + + UploadedKeys = new HashSet[(int)NvGpuBufferType.Count]; + + for (int Index = 0; Index < UploadedKeys.Length; Index++) + { + UploadedKeys[Index] = new HashSet(); + } + } + + public void SendColorBuffer(NvGpuVmm Vmm, long Position, int Attachment, GalImage NewImage) + { + long Size = (uint)ImageUtils.GetSize(NewImage); + + MarkAsCached(Vmm, Position, Size, NvGpuBufferType.Texture); + + bool IsCached = Gpu.Renderer.Texture.TryGetImage(Position, out GalImage CachedImage); + + if (IsCached && CachedImage.SizeMatches(NewImage)) + { + Gpu.Renderer.RenderTarget.Reinterpret(Position, NewImage); + Gpu.Renderer.RenderTarget.BindColor(Position, Attachment, NewImage); + + return; + } + + Gpu.Renderer.Texture.Create(Position, (int)Size, NewImage); + + Gpu.Renderer.RenderTarget.BindColor(Position, Attachment, NewImage); + } + + public void SendZetaBuffer(NvGpuVmm Vmm, long Position, GalImage NewImage) + { + long Size = (uint)ImageUtils.GetSize(NewImage); + + MarkAsCached(Vmm, Position, Size, NvGpuBufferType.Texture); + + bool IsCached = Gpu.Renderer.Texture.TryGetImage(Position, out GalImage CachedImage); + + if (IsCached && CachedImage.SizeMatches(NewImage)) + { + Gpu.Renderer.RenderTarget.Reinterpret(Position, NewImage); + Gpu.Renderer.RenderTarget.BindZeta(Position, NewImage); + + return; + } + + Gpu.Renderer.Texture.Create(Position, (int)Size, NewImage); + + Gpu.Renderer.RenderTarget.BindZeta(Position, NewImage); + } + + public void SendTexture(NvGpuVmm Vmm, long Position, GalImage NewImage, int TexIndex = -1) + { + long Size = (uint)ImageUtils.GetSize(NewImage); + + if (!MemoryRegionModified(Vmm, Position, Size, NvGpuBufferType.Texture)) + { + if (Gpu.Renderer.Texture.TryGetImage(Position, out GalImage CachedImage) && CachedImage.SizeMatches(NewImage)) + { + Gpu.Renderer.RenderTarget.Reinterpret(Position, NewImage); + + if (TexIndex >= 0) + { + Gpu.Renderer.Texture.Bind(Position, TexIndex, NewImage); + } + + return; + } + } + + byte[] Data = ImageUtils.ReadTexture(Vmm, NewImage, Position); + + Gpu.Renderer.Texture.Create(Position, Data, NewImage); + + if (TexIndex >= 0) + { + Gpu.Renderer.Texture.Bind(Position, TexIndex, NewImage); + } + } + + private void MarkAsCached(NvGpuVmm Vmm, long Position, long Size, NvGpuBufferType Type) + { + Vmm.IsRegionModified(Position, Size, Type); + } + + private bool MemoryRegionModified(NvGpuVmm Vmm, long Position, long Size, NvGpuBufferType Type) + { + HashSet Uploaded = UploadedKeys[(int)Type]; + + if (!Uploaded.Add(Position)) + { + return false; + } + + return Vmm.IsRegionModified(Position, Size, Type); + } + + public void ClearPbCache() + { + for (int Index = 0; Index < UploadedKeys.Length; Index++) + { + UploadedKeys[Index].Clear(); + } + } + } +} diff --git a/Ryujinx.Graphics/Memory/NvGpuVmmCache.cs b/Ryujinx.Graphics/Memory/NvGpuVmmCache.cs index 56979e1f06..f1c16a3657 100644 --- a/Ryujinx.Graphics/Memory/NvGpuVmmCache.cs +++ b/Ryujinx.Graphics/Memory/NvGpuVmmCache.cs @@ -1,309 +1,82 @@ using ChocolArm64.Memory; using System; -using System.Collections.Generic; namespace Ryujinx.Graphics.Memory { class NvGpuVmmCache { - private const long RamSize = 4L * 1024 * 1024 * 1024; - - private const int MaxCpCount = 10000; - private const int MaxCpTimeDelta = 60000; - - private class CachedPage - { - private struct Range - { - public long Start; - public long End; - - public Range(long Start, long End) - { - this.Start = Start; - this.End = End; - } - } - - private List[] Regions; - - private HashSet ResidencyKeys; - - public LinkedListNode Node { get; set; } - - public int Timestamp { get; private set; } - - public CachedPage() - { - Regions = new List[(int)NvGpuBufferType.Count]; - - for (int Index = 0; Index < Regions.Length; Index++) - { - Regions[Index] = new List(); - } - - ResidencyKeys = new HashSet(); - } - - public void AddResidency(long Key) - { - ResidencyKeys.Add(Key); - } - - public void RemoveResidency(HashSet[] Residency, long PageSize) - { - for (int i = 0; i < (int)NvGpuBufferType.Count; i++) - { - foreach (Range Region in Regions[i]) - { - foreach (long Key in ResidencyKeys) - { - Residency[Region.Start / PageSize].Remove(Key); - } - } - } - } - - public bool AddRange(long Start, long End, NvGpuBufferType BufferType) - { - List BtRegions = Regions[(int)BufferType]; - - for (int Index = 0; Index < BtRegions.Count; Index++) - { - Range Rg = BtRegions[Index]; - - if (Start >= Rg.Start && End <= Rg.End) - { - return false; - } - - if (Start <= Rg.End && Rg.Start <= End) - { - long MinStart = Math.Min(Rg.Start, Start); - long MaxEnd = Math.Max(Rg.End, End); - - BtRegions[Index] = new Range(MinStart, MaxEnd); - - Timestamp = Environment.TickCount; - - return true; - } - } - - BtRegions.Add(new Range(Start, End)); - - Timestamp = Environment.TickCount; - - return true; - } - - public int GetTotalCount() - { - int Count = 0; - - for (int Index = 0; Index < Regions.Length; Index++) - { - Count += Regions[Index].Count; - } - - return Count; - } - } - - private Dictionary Cache; - - private LinkedList SortedCache; - - private HashSet[] Residency; - - private long ResidencyPageSize; - - private int CpCount; + private ValueRangeSet CachedRanges; public NvGpuVmmCache() { - Cache = new Dictionary(); - - SortedCache = new LinkedList(); + CachedRanges = new ValueRangeSet(); } public bool IsRegionModified(AMemory Memory, NvGpuBufferType BufferType, long PA, long Size) { (bool[] Modified, long ModifiedCount) = Memory.IsRegionModified(PA, Size); - PA = Memory.GetPhysicalAddress(PA); - - ClearCachedPagesIfNeeded(); - - long PageSize = AMemory.PageSize; - - EnsureResidencyInitialized(PageSize); - - bool HasResidents = AddResidency(PA, Size); - - if (!HasResidents && ModifiedCount == 0) - { - return false; - } - - long Mask = PageSize - 1; - - long ResidencyKey = PA; - - long PAEnd = PA + Size; - - bool RegMod = false; - + //Remove all modified ranges. int Index = 0; - while (PA < PAEnd) + long Position = PA & ~NvGpuVmm.PageMask; + + while (ModifiedCount > 0) { - long Key = PA & ~AMemory.PageMask; - - long PAPgEnd = Math.Min((PA + AMemory.PageSize) & ~AMemory.PageMask, PAEnd); - - bool IsCached = Cache.TryGetValue(Key, out CachedPage Cp); - - if (IsCached) + if (Modified[Index++]) { - CpCount -= Cp.GetTotalCount(); + CachedRanges.Remove(new ValueRange(Position, Position + NvGpuVmm.PageSize)); - SortedCache.Remove(Cp.Node); + ModifiedCount--; + } + + Position += NvGpuVmm.PageSize; + } + + //Mask has the bit set for the current resource type. + //If the region is not yet present on the list, then a new ValueRange + //is directly added with the current resource type as the only bit set. + //Otherwise, it just sets the bit for this new resource type on the current mask. + int Mask = 1 << (int)BufferType; + + ValueRange NewCached = new ValueRange(PA, PA + Size); + + ValueRange[] Ranges = CachedRanges.GetAllIntersections(NewCached); + + long LastEnd = NewCached.Start; + + long Coverage = 0; + + for (Index = 0; Index < Ranges.Length; Index++) + { + ValueRange Current = Ranges[Index]; + + long RgStart = Math.Max(Current.Start, NewCached.Start); + long RgEnd = Math.Min(Current.End, NewCached.End); + + if ((Current.Value & Mask) == 0) + { + CachedRanges.Add(new ValueRange(RgStart, RgEnd, Current.Value | Mask)); } else { - Cp = new CachedPage(); - - Cache.Add(Key, Cp); + Coverage += RgEnd - RgStart; } - if (Modified[Index++] && IsCached) + if (RgStart > LastEnd) { - Cp = new CachedPage(); - - Cache[Key] = Cp; + CachedRanges.Add(new ValueRange(LastEnd, RgStart, Mask)); } - Cp.AddResidency(ResidencyKey); - - Cp.Node = SortedCache.AddLast(Key); - - RegMod |= Cp.AddRange(PA, PAPgEnd, BufferType); - - CpCount += Cp.GetTotalCount(); - - PA = PAPgEnd; + LastEnd = RgEnd; } - return RegMod; - } - - private bool AddResidency(long PA, long Size) - { - long PageSize = ResidencyPageSize; - - long Mask = PageSize - 1; - - long Key = PA; - - bool ResidentFound = false; - - for (long Cursor = PA & ~Mask; Cursor < ((PA + Size + PageSize - 1) & ~Mask); Cursor += PageSize) + if (LastEnd < NewCached.End) { - long PageIndex = Cursor / PageSize; - - Residency[PageIndex].Add(Key); - - if (Residency[PageIndex].Count > 1) - { - ResidentFound = true; - } + CachedRanges.Add(new ValueRange(LastEnd, NewCached.End, Mask)); } - return ResidentFound; - } - - private void EnsureResidencyInitialized(long PageSize) - { - if (Residency == null) - { - Residency = new HashSet[RamSize / PageSize]; - - for (int i = 0; i < Residency.Length; i++) - { - Residency[i] = new HashSet(); - } - - ResidencyPageSize = PageSize; - } - else - { - if (ResidencyPageSize != PageSize) - { - throw new InvalidOperationException("Tried to change residency page size"); - } - } - } - - private void ClearCachedPagesIfNeeded() - { - if (CpCount <= MaxCpCount) - { - return; - } - - int Timestamp = Environment.TickCount; - - int TimeDelta; - - do - { - if (!TryPopOldestCachedPageKey(Timestamp, out long Key)) - { - break; - } - - CachedPage Cp = Cache[Key]; - - Cp.RemoveResidency(Residency, ResidencyPageSize); - - Cache.Remove(Key); - - CpCount -= Cp.GetTotalCount(); - - TimeDelta = RingDelta(Cp.Timestamp, Timestamp); - } - while (CpCount > (MaxCpCount >> 1) || (uint)TimeDelta > (uint)MaxCpTimeDelta); - } - - private bool TryPopOldestCachedPageKey(int Timestamp, out long Key) - { - LinkedListNode Node = SortedCache.First; - - if (Node == null) - { - Key = 0; - - return false; - } - - SortedCache.Remove(Node); - - Key = Node.Value; - - return true; - } - - private int RingDelta(int Old, int New) - { - if ((uint)New < (uint)Old) - { - return New + (~Old + 1); - } - else - { - return New - Old; - } + return Coverage != Size; } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/NvGpu.cs b/Ryujinx.Graphics/NvGpu.cs index 7ba700b89e..4c6abd234c 100644 --- a/Ryujinx.Graphics/NvGpu.cs +++ b/Ryujinx.Graphics/NvGpu.cs @@ -1,5 +1,4 @@ using Ryujinx.Graphics.Gal; -using Ryujinx.Graphics; namespace Ryujinx.Graphics { @@ -7,21 +6,27 @@ namespace Ryujinx.Graphics { public IGalRenderer Renderer { get; private set; } + public GpuResourceManager ResourceManager { get; private set; } + public NvGpuFifo Fifo { get; private set; } - public NvGpuEngine2d Engine2d { get; private set; } - public NvGpuEngine3d Engine3d { get; private set; } - public NvGpuEngineDma EngineDma { get; private set; } + internal NvGpuEngine2d Engine2d { get; private set; } + internal NvGpuEngine3d Engine3d { get; private set; } + internal NvGpuEngineM2mf EngineM2mf { get; private set; } + internal NvGpuEngineP2mf EngineP2mf { get; private set; } public NvGpu(IGalRenderer Renderer) { this.Renderer = Renderer; + ResourceManager = new GpuResourceManager(this); + Fifo = new NvGpuFifo(this); - Engine2d = new NvGpuEngine2d(this); - Engine3d = new NvGpuEngine3d(this); - EngineDma = new NvGpuEngineDma(this); + Engine2d = new NvGpuEngine2d(this); + Engine3d = new NvGpuEngine3d(this); + EngineM2mf = new NvGpuEngineM2mf(this); + EngineP2mf = new NvGpuEngineP2mf(this); } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Memory/NvGpuBufferType.cs b/Ryujinx.Graphics/NvGpuBufferType.cs similarity index 79% rename from Ryujinx.Graphics/Memory/NvGpuBufferType.cs rename to Ryujinx.Graphics/NvGpuBufferType.cs index 6f0d257188..f6b555970d 100644 --- a/Ryujinx.Graphics/Memory/NvGpuBufferType.cs +++ b/Ryujinx.Graphics/NvGpuBufferType.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.Graphics.Memory +namespace Ryujinx.Graphics { public enum NvGpuBufferType { diff --git a/Ryujinx.Graphics/NvGpuEngine.cs b/Ryujinx.Graphics/NvGpuEngine.cs index 3d5e118a59..d533e47849 100644 --- a/Ryujinx.Graphics/NvGpuEngine.cs +++ b/Ryujinx.Graphics/NvGpuEngine.cs @@ -5,7 +5,7 @@ namespace Ryujinx.Graphics _2d = 0x902d, _3d = 0xb197, Compute = 0xb1c0, - Kepler = 0xa140, - Dma = 0xb0b5 + P2mf = 0xa140, + M2mf = 0xb0b5 } } \ No newline at end of file diff --git a/Ryujinx.Graphics/NvGpuEngine2d.cs b/Ryujinx.Graphics/NvGpuEngine2d.cs index f26b00204b..4bf7c1e86e 100644 --- a/Ryujinx.Graphics/NvGpuEngine2d.cs +++ b/Ryujinx.Graphics/NvGpuEngine2d.cs @@ -1,7 +1,6 @@ using Ryujinx.Graphics.Gal; using Ryujinx.Graphics.Memory; using Ryujinx.Graphics.Texture; -using System; using System.Collections.Generic; namespace Ryujinx.Graphics @@ -62,25 +61,25 @@ namespace Ryujinx.Graphics { CopyOperation Operation = (CopyOperation)ReadRegister(NvGpuEngine2dReg.CopyOperation); + int SrcFormat = ReadRegister(NvGpuEngine2dReg.SrcFormat); bool SrcLinear = ReadRegister(NvGpuEngine2dReg.SrcLinear) != 0; int SrcWidth = ReadRegister(NvGpuEngine2dReg.SrcWidth); int SrcHeight = ReadRegister(NvGpuEngine2dReg.SrcHeight); int SrcPitch = ReadRegister(NvGpuEngine2dReg.SrcPitch); int SrcBlkDim = ReadRegister(NvGpuEngine2dReg.SrcBlockDimensions); + int DstFormat = ReadRegister(NvGpuEngine2dReg.DstFormat); bool DstLinear = ReadRegister(NvGpuEngine2dReg.DstLinear) != 0; int DstWidth = ReadRegister(NvGpuEngine2dReg.DstWidth); int DstHeight = ReadRegister(NvGpuEngine2dReg.DstHeight); int DstPitch = ReadRegister(NvGpuEngine2dReg.DstPitch); int DstBlkDim = ReadRegister(NvGpuEngine2dReg.DstBlockDimensions); - TextureSwizzle SrcSwizzle = SrcLinear - ? TextureSwizzle.Pitch - : TextureSwizzle.BlockLinear; + GalImageFormat SrcImgFormat = ImageUtils.ConvertSurface((GalSurfaceFormat)SrcFormat); + GalImageFormat DstImgFormat = ImageUtils.ConvertSurface((GalSurfaceFormat)DstFormat); - TextureSwizzle DstSwizzle = DstLinear - ? TextureSwizzle.Pitch - : TextureSwizzle.BlockLinear; + GalMemoryLayout SrcLayout = GetLayout(SrcLinear); + GalMemoryLayout DstLayout = GetLayout(DstLinear); int SrcBlockHeight = 1 << ((SrcBlkDim >> 4) & 0xf); int DstBlockHeight = 1 << ((DstBlkDim >> 4) & 0xf); @@ -91,91 +90,41 @@ namespace Ryujinx.Graphics long SrcKey = Vmm.GetPhysicalAddress(SrcAddress); long DstKey = Vmm.GetPhysicalAddress(DstAddress); - bool IsSrcFb = Gpu.Engine3d.IsFrameBufferPosition(SrcKey); - bool IsDstFb = Gpu.Engine3d.IsFrameBufferPosition(DstKey); + GalImage SrcTexture = new GalImage( + SrcWidth, + SrcHeight, 1, + SrcBlockHeight, + SrcLayout, + SrcImgFormat); - TextureInfo SrcTexture() - { - return new TextureInfo( - SrcAddress, - SrcWidth, - SrcHeight, - SrcPitch, - SrcBlockHeight, 1, - SrcSwizzle, - GalImageFormat.A8B8G8R8 | GalImageFormat.Unorm); - } + GalImage DstTexture = new GalImage( + DstWidth, + DstHeight, 1, + DstBlockHeight, + DstLayout, + DstImgFormat); - TextureInfo DstTexture() - { - return new TextureInfo( - DstAddress, - DstWidth, - DstHeight, - DstPitch, - DstBlockHeight, 1, - DstSwizzle, - GalImageFormat.A8B8G8R8 | GalImageFormat.Unorm); - } + Gpu.ResourceManager.SendTexture(Vmm, SrcKey, SrcTexture); + Gpu.ResourceManager.SendTexture(Vmm, DstKey, DstTexture); - //TODO: fb -> fb copies, tex -> fb copies, formats other than RGBA8, - //make it throw for unimpl stuff (like the copy mode)... - if (IsSrcFb && IsDstFb) - { - //Frame Buffer -> Frame Buffer copy. - Gpu.Renderer.RenderTarget.Copy( - SrcKey, - DstKey, - 0, - 0, - SrcWidth, - SrcHeight, - 0, - 0, - DstWidth, - DstHeight); - } - if (IsSrcFb) - { - //Frame Buffer -> Texture copy. - Gpu.Renderer.RenderTarget.GetBufferData(SrcKey, (byte[] Buffer) => - { - TextureInfo Src = SrcTexture(); - TextureInfo Dst = DstTexture(); + Gpu.Renderer.RenderTarget.Copy( + SrcKey, + DstKey, + 0, + 0, + SrcWidth, + SrcHeight, + 0, + 0, + DstWidth, + DstHeight); + } - if (Src.Width != Dst.Width || - Src.Height != Dst.Height) - { - throw new NotImplementedException("Texture resizing is not supported"); - } - - TextureWriter.Write(Vmm, Dst, Buffer); - }); - } - else if (IsDstFb) - { - byte[] Buffer = TextureReader.Read(Vmm, SrcTexture()); - - Gpu.Renderer.RenderTarget.SetBufferData( - DstKey, - DstWidth, - DstHeight, - Buffer); - } - else - { - //Texture -> Texture copy. - TextureInfo Src = SrcTexture(); - TextureInfo Dst = DstTexture(); - - if (Src.Width != Dst.Width || - Src.Height != Dst.Height) - { - throw new NotImplementedException("Texture resizing is not supported"); - } - - TextureWriter.Write(Vmm, Dst, TextureReader.Read(Vmm, Src)); - } + private static GalMemoryLayout GetLayout(bool Linear) + { + return Linear + ? GalMemoryLayout.Pitch + : GalMemoryLayout.BlockLinear; } private long MakeInt64From2xInt32(NvGpuEngine2dReg Reg) diff --git a/Ryujinx.Graphics/NvGpuEngine3d.cs b/Ryujinx.Graphics/NvGpuEngine3d.cs index 624eddae0a..a1d0ec803a 100644 --- a/Ryujinx.Graphics/NvGpuEngine3d.cs +++ b/Ryujinx.Graphics/NvGpuEngine3d.cs @@ -23,8 +23,6 @@ namespace Ryujinx.Graphics private ConstBuffer[][] ConstBuffers; - private HashSet FrameBuffers; - private List[] UploadedKeys; private int CurrentInstance = 0; @@ -60,8 +58,6 @@ namespace Ryujinx.Graphics ConstBuffers[Index] = new ConstBuffer[18]; } - FrameBuffers = new HashSet(); - UploadedKeys = new List[(int)NvGpuBufferType.Count]; for (int i = 0; i < UploadedKeys.Length; i++) @@ -96,7 +92,7 @@ namespace Ryujinx.Graphics GalPipelineState State = new GalPipelineState(); - SetFlip(State); + SetFrameBuffer(State); SetFrontFace(State); SetCullFace(State); SetDepth(State); @@ -104,10 +100,7 @@ namespace Ryujinx.Graphics SetAlphaBlending(State); SetPrimitiveRestart(State); - for (int FbIndex = 0; FbIndex < 8; FbIndex++) - { - SetFrameBuffer(Vmm, 0); - } + SetFrameBuffer(Vmm, 0); SetZeta(Vmm); @@ -173,9 +166,9 @@ namespace Ryujinx.Graphics { long VA = MakeInt64From2xInt32(NvGpuEngine3dReg.FrameBufferNAddress + FbIndex * 0x10); - int Format = ReadRegister(NvGpuEngine3dReg.FrameBufferNFormat + FbIndex * 0x10); + int SurfFormat = ReadRegister(NvGpuEngine3dReg.FrameBufferNFormat + FbIndex * 0x10); - if (VA == 0 || Format == 0) + if (VA == 0 || SurfFormat == 0) { Gpu.Renderer.RenderTarget.UnbindColor(FbIndex); @@ -184,11 +177,15 @@ namespace Ryujinx.Graphics long Key = Vmm.GetPhysicalAddress(VA); - FrameBuffers.Add(Key); - int Width = ReadRegister(NvGpuEngine3dReg.FrameBufferNWidth + FbIndex * 0x10); int Height = ReadRegister(NvGpuEngine3dReg.FrameBufferNHeight + FbIndex * 0x10); + int BlockDim = ReadRegister(NvGpuEngine3dReg.FrameBufferNBlockDim + FbIndex * 0x10); + + int GobBlockHeight = 1 << ((BlockDim >> 4) & 7); + + GalMemoryLayout Layout = (GalMemoryLayout)((BlockDim >> 12) & 1); + float TX = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNTranslateX + FbIndex * 8); float TY = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNTranslateY + FbIndex * 8); @@ -201,48 +198,54 @@ namespace Ryujinx.Graphics int VpW = (int)(TX + MathF.Abs(SX)) - VpX; int VpH = (int)(TY + MathF.Abs(SY)) - VpY; - GalImageFormat ImageFormat = ImageUtils.ConvertSurface((GalSurfaceFormat)Format); + GalImageFormat Format = ImageUtils.ConvertSurface((GalSurfaceFormat)SurfFormat); - GalImage Image = new GalImage(Width, Height, ImageFormat); + GalImage Image = new GalImage(Width, Height, 1, GobBlockHeight, Layout, Format); - long Size = ImageUtils.GetSize(Image); - - Gpu.Renderer.Texture.CreateFb(Key, Size, Image); - - Gpu.Renderer.RenderTarget.BindColor(Key, FbIndex); + Gpu.ResourceManager.SendColorBuffer(Vmm, Key, FbIndex, Image); Gpu.Renderer.RenderTarget.SetViewport(VpX, VpY, VpW, VpH); } + private void SetFrameBuffer(GalPipelineState State) + { + State.FramebufferSrgb = (ReadRegister(NvGpuEngine3dReg.FrameBufferSrgb) & 1) != 0; + + State.FlipX = GetFlipSign(NvGpuEngine3dReg.ViewportNScaleX); + State.FlipY = GetFlipSign(NvGpuEngine3dReg.ViewportNScaleY); + } + private void SetZeta(NvGpuVmm Vmm) { - long ZA = MakeInt64From2xInt32(NvGpuEngine3dReg.ZetaAddress); + long VA = MakeInt64From2xInt32(NvGpuEngine3dReg.ZetaAddress); - int Format = ReadRegister(NvGpuEngine3dReg.ZetaFormat); + int ZetaFormat = ReadRegister(NvGpuEngine3dReg.ZetaFormat); + + int BlockDim = ReadRegister(NvGpuEngine3dReg.ZetaBlockDimensions); + + int GobBlockHeight = 1 << ((BlockDim >> 4) & 7); + + GalMemoryLayout Layout = (GalMemoryLayout)((BlockDim >> 12) & 1); //? bool ZetaEnable = (ReadRegister(NvGpuEngine3dReg.ZetaEnable) & 1) != 0; - if (ZA == 0 || Format == 0 || !ZetaEnable) + if (VA == 0 || ZetaFormat == 0 || !ZetaEnable) { Gpu.Renderer.RenderTarget.UnbindZeta(); return; } - long Key = Vmm.GetPhysicalAddress(ZA); + long Key = Vmm.GetPhysicalAddress(VA); int Width = ReadRegister(NvGpuEngine3dReg.ZetaHoriz); int Height = ReadRegister(NvGpuEngine3dReg.ZetaVert); - GalImageFormat ImageFormat = ImageUtils.ConvertZeta((GalZetaFormat)Format); + GalImageFormat Format = ImageUtils.ConvertZeta((GalZetaFormat)ZetaFormat); - GalImage Image = new GalImage(Width, Height, ImageFormat); + GalImage Image = new GalImage(Width, Height, 1, GobBlockHeight, Layout, Format); - long Size = ImageUtils.GetSize(Image); - - Gpu.Renderer.Texture.CreateFb(Key, Size, Image); - - Gpu.Renderer.RenderTarget.BindZeta(Key); + Gpu.ResourceManager.SendZetaBuffer(Vmm, Key, Image); } private long[] UploadShaders(NvGpuVmm Vmm) @@ -322,12 +325,6 @@ namespace Ryujinx.Graphics throw new ArgumentOutOfRangeException(nameof(Program)); } - private void SetFlip(GalPipelineState State) - { - State.FlipX = GetFlipSign(NvGpuEngine3dReg.ViewportNScaleX); - State.FlipY = GetFlipSign(NvGpuEngine3dReg.ViewportNScaleY); - } - private void SetFrontFace(GalPipelineState State) { float SignX = GetFlipSign(NvGpuEngine3dReg.ViewportNScaleX); @@ -504,53 +501,30 @@ namespace Ryujinx.Graphics TicPosition += TicIndex * 0x20; TscPosition += TscIndex * 0x20; + GalImage Image = TextureFactory.MakeTexture(Vmm, TicPosition); + GalTextureSampler Sampler = TextureFactory.MakeSampler(Gpu, Vmm, TscPosition); long Key = Vmm.ReadInt64(TicPosition + 4) & 0xffffffffffff; + if (Image.Layout == GalMemoryLayout.BlockLinear) + { + Key &= ~0x1ffL; + } + else if (Image.Layout == GalMemoryLayout.Pitch) + { + Key &= ~0x1fL; + } + Key = Vmm.GetPhysicalAddress(Key); if (Key == -1) { - //FIXME: Should'nt ignore invalid addresses. + //FIXME: Shouldn't ignore invalid addresses. return; } - if (IsFrameBufferPosition(Key)) - { - //This texture is a frame buffer texture, - //we shouldn't read anything from memory and bind - //the frame buffer texture instead, since we're not - //really writing anything to memory. - Gpu.Renderer.RenderTarget.BindTexture(Key, TexIndex); - } - else - { - GalImage NewImage = TextureFactory.MakeTexture(Vmm, TicPosition); - - long Size = (uint)ImageUtils.GetSize(NewImage); - - bool HasCachedTexture = false; - - if (Gpu.Renderer.Texture.TryGetCachedTexture(Key, Size, out GalImage Image)) - { - if (NewImage.Equals(Image) && !QueryKeyUpload(Vmm, Key, Size, NvGpuBufferType.Texture)) - { - Gpu.Renderer.Texture.Bind(Key, TexIndex); - - HasCachedTexture = true; - } - } - - if (!HasCachedTexture) - { - byte[] Data = TextureFactory.GetTextureData(Vmm, TicPosition); - - Gpu.Renderer.Texture.Create(Key, Data, NewImage); - } - - Gpu.Renderer.Texture.Bind(Key, TexIndex); - } + Gpu.ResourceManager.SendTexture(Vmm, Key, Image, TexIndex); Gpu.Renderer.Texture.SetSampler(Sampler); } @@ -876,11 +850,6 @@ namespace Ryujinx.Graphics Registers[(int)Reg] = Value; } - public bool IsFrameBufferPosition(long Position) - { - return FrameBuffers.Contains(Position); - } - private bool QueryKeyUpload(NvGpuVmm Vmm, long Key, long Size, NvGpuBufferType Type) { List Uploaded = UploadedKeys[(int)Type]; diff --git a/Ryujinx.Graphics/NvGpuEngine3dReg.cs b/Ryujinx.Graphics/NvGpuEngine3dReg.cs index f96e711500..3abcf6f5ca 100644 --- a/Ryujinx.Graphics/NvGpuEngine3dReg.cs +++ b/Ryujinx.Graphics/NvGpuEngine3dReg.cs @@ -6,6 +6,7 @@ namespace Ryujinx.Graphics FrameBufferNWidth = 0x202, FrameBufferNHeight = 0x203, FrameBufferNFormat = 0x204, + FrameBufferNBlockDim = 0x205, ViewportNScaleX = 0x280, ViewportNScaleY = 0x281, ViewportNScaleZ = 0x282, @@ -62,6 +63,7 @@ namespace Ryujinx.Graphics StencilBackOpZFail = 0x567, StencilBackOpZPass = 0x568, StencilBackFuncFunc = 0x569, + FrameBufferSrgb = 0x56e, ShaderAddress = 0x582, VertexBeginGl = 0x586, PrimRestartEnable = 0x591, diff --git a/Ryujinx.Graphics/NvGpuEngineDma.cs b/Ryujinx.Graphics/NvGpuEngineDma.cs deleted file mode 100644 index 04be742f85..0000000000 --- a/Ryujinx.Graphics/NvGpuEngineDma.cs +++ /dev/null @@ -1,143 +0,0 @@ -using Ryujinx.Graphics.Memory; -using Ryujinx.Graphics.Texture; -using System.Collections.Generic; - -namespace Ryujinx.Graphics -{ - public class NvGpuEngineDma : INvGpuEngine - { - public int[] Registers { get; private set; } - - private NvGpu Gpu; - - private Dictionary Methods; - - public NvGpuEngineDma(NvGpu Gpu) - { - this.Gpu = Gpu; - - Registers = new int[0x1d6]; - - Methods = new Dictionary(); - - void AddMethod(int Meth, int Count, int Stride, NvGpuMethod Method) - { - while (Count-- > 0) - { - Methods.Add(Meth, Method); - - Meth += Stride; - } - } - - AddMethod(0xc0, 1, 1, Execute); - } - - public void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) - { - if (Methods.TryGetValue(PBEntry.Method, out NvGpuMethod Method)) - { - Method(Vmm, PBEntry); - } - else - { - WriteRegister(PBEntry); - } - } - - private void Execute(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) - { - int Control = PBEntry.Arguments[0]; - - bool SrcLinear = ((Control >> 7) & 1) != 0; - bool DstLinear = ((Control >> 8) & 1) != 0; - - long SrcAddress = MakeInt64From2xInt32(NvGpuEngineDmaReg.SrcAddress); - long DstAddress = MakeInt64From2xInt32(NvGpuEngineDmaReg.DstAddress); - - int SrcPitch = ReadRegister(NvGpuEngineDmaReg.SrcPitch); - int DstPitch = ReadRegister(NvGpuEngineDmaReg.DstPitch); - - int DstBlkDim = ReadRegister(NvGpuEngineDmaReg.DstBlkDim); - int DstSizeX = ReadRegister(NvGpuEngineDmaReg.DstSizeX); - int DstSizeY = ReadRegister(NvGpuEngineDmaReg.DstSizeY); - int DstSizeZ = ReadRegister(NvGpuEngineDmaReg.DstSizeZ); - int DstPosXY = ReadRegister(NvGpuEngineDmaReg.DstPosXY); - int DstPosZ = ReadRegister(NvGpuEngineDmaReg.DstPosZ); - - int SrcBlkDim = ReadRegister(NvGpuEngineDmaReg.SrcBlkDim); - int SrcSizeX = ReadRegister(NvGpuEngineDmaReg.SrcSizeX); - int SrcSizeY = ReadRegister(NvGpuEngineDmaReg.SrcSizeY); - int SrcSizeZ = ReadRegister(NvGpuEngineDmaReg.SrcSizeZ); - int SrcPosXY = ReadRegister(NvGpuEngineDmaReg.SrcPosXY); - int SrcPosZ = ReadRegister(NvGpuEngineDmaReg.SrcPosZ); - - int DstPosX = (DstPosXY >> 0) & 0xffff; - int DstPosY = (DstPosXY >> 16) & 0xffff; - - int SrcPosX = (SrcPosXY >> 0) & 0xffff; - int SrcPosY = (SrcPosXY >> 16) & 0xffff; - - int SrcBlockHeight = 1 << ((SrcBlkDim >> 4) & 0xf); - int DstBlockHeight = 1 << ((DstBlkDim >> 4) & 0xf); - - ISwizzle SrcSwizzle; - - if (SrcLinear) - { - SrcSwizzle = new LinearSwizzle(SrcPitch, 1); - } - else - { - SrcSwizzle = new BlockLinearSwizzle(SrcSizeX, 1, SrcBlockHeight); - } - - ISwizzle DstSwizzle; - - if (DstLinear) - { - DstSwizzle = new LinearSwizzle(DstPitch, 1); - } - else - { - DstSwizzle = new BlockLinearSwizzle(DstSizeX, 1, DstBlockHeight); - } - - for (int Y = 0; Y < DstSizeY; Y++) - for (int X = 0; X < DstSizeX; X++) - { - long SrcOffset = SrcAddress + (uint)SrcSwizzle.GetSwizzleOffset(X, Y); - long DstOffset = DstAddress + (uint)DstSwizzle.GetSwizzleOffset(X, Y); - - Vmm.WriteByte(DstOffset, Vmm.ReadByte(SrcOffset)); - } - } - - private long MakeInt64From2xInt32(NvGpuEngineDmaReg Reg) - { - return - (long)Registers[(int)Reg + 0] << 32 | - (uint)Registers[(int)Reg + 1]; - } - - private void WriteRegister(NvGpuPBEntry PBEntry) - { - int ArgsCount = PBEntry.Arguments.Count; - - if (ArgsCount > 0) - { - Registers[PBEntry.Method] = PBEntry.Arguments[ArgsCount - 1]; - } - } - - private int ReadRegister(NvGpuEngineDmaReg Reg) - { - return Registers[(int)Reg]; - } - - private void WriteRegister(NvGpuEngineDmaReg Reg, int Value) - { - Registers[(int)Reg] = Value; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/NvGpuEngineM2mf.cs b/Ryujinx.Graphics/NvGpuEngineM2mf.cs new file mode 100644 index 0000000000..d612128066 --- /dev/null +++ b/Ryujinx.Graphics/NvGpuEngineM2mf.cs @@ -0,0 +1,192 @@ +using Ryujinx.Graphics.Memory; +using Ryujinx.Graphics.Texture; +using System.Collections.Generic; + +namespace Ryujinx.Graphics +{ + public class NvGpuEngineM2mf : INvGpuEngine + { + public int[] Registers { get; private set; } + + private NvGpu Gpu; + + private Dictionary Methods; + + public NvGpuEngineM2mf(NvGpu Gpu) + { + this.Gpu = Gpu; + + Registers = new int[0x1d6]; + + Methods = new Dictionary(); + + void AddMethod(int Meth, int Count, int Stride, NvGpuMethod Method) + { + while (Count-- > 0) + { + Methods.Add(Meth, Method); + + Meth += Stride; + } + } + + AddMethod(0xc0, 1, 1, Execute); + } + + public void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) + { + if (Methods.TryGetValue(PBEntry.Method, out NvGpuMethod Method)) + { + Method(Vmm, PBEntry); + } + else + { + WriteRegister(PBEntry); + } + } + + private void Execute(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) + { + //TODO: Some registers and copy modes are still not implemented. + int Control = PBEntry.Arguments[0]; + + bool SrcLinear = ((Control >> 7) & 1) != 0; + bool DstLinear = ((Control >> 8) & 1) != 0; + bool Copy2d = ((Control >> 9) & 1) != 0; + + long SrcAddress = MakeInt64From2xInt32(NvGpuEngineM2mfReg.SrcAddress); + long DstAddress = MakeInt64From2xInt32(NvGpuEngineM2mfReg.DstAddress); + + int SrcPitch = ReadRegister(NvGpuEngineM2mfReg.SrcPitch); + int DstPitch = ReadRegister(NvGpuEngineM2mfReg.DstPitch); + + int XCount = ReadRegister(NvGpuEngineM2mfReg.XCount); + int YCount = ReadRegister(NvGpuEngineM2mfReg.YCount); + + int Swizzle = ReadRegister(NvGpuEngineM2mfReg.Swizzle); + + int DstBlkDim = ReadRegister(NvGpuEngineM2mfReg.DstBlkDim); + int DstSizeX = ReadRegister(NvGpuEngineM2mfReg.DstSizeX); + int DstSizeY = ReadRegister(NvGpuEngineM2mfReg.DstSizeY); + int DstSizeZ = ReadRegister(NvGpuEngineM2mfReg.DstSizeZ); + int DstPosXY = ReadRegister(NvGpuEngineM2mfReg.DstPosXY); + int DstPosZ = ReadRegister(NvGpuEngineM2mfReg.DstPosZ); + + int SrcBlkDim = ReadRegister(NvGpuEngineM2mfReg.SrcBlkDim); + int SrcSizeX = ReadRegister(NvGpuEngineM2mfReg.SrcSizeX); + int SrcSizeY = ReadRegister(NvGpuEngineM2mfReg.SrcSizeY); + int SrcSizeZ = ReadRegister(NvGpuEngineM2mfReg.SrcSizeZ); + int SrcPosXY = ReadRegister(NvGpuEngineM2mfReg.SrcPosXY); + int SrcPosZ = ReadRegister(NvGpuEngineM2mfReg.SrcPosZ); + + int SrcCpp = ((Swizzle >> 20) & 7) + 1; + int DstCpp = ((Swizzle >> 24) & 7) + 1; + + int DstPosX = (DstPosXY >> 0) & 0xffff; + int DstPosY = (DstPosXY >> 16) & 0xffff; + + int SrcPosX = (SrcPosXY >> 0) & 0xffff; + int SrcPosY = (SrcPosXY >> 16) & 0xffff; + + int SrcBlockHeight = 1 << ((SrcBlkDim >> 4) & 0xf); + int DstBlockHeight = 1 << ((DstBlkDim >> 4) & 0xf); + + long SrcPA = Vmm.GetPhysicalAddress(SrcAddress); + long DstPA = Vmm.GetPhysicalAddress(DstAddress); + + if (Copy2d) + { + if (SrcLinear) + { + SrcPosX = SrcPosY = SrcPosZ = 0; + } + + if (DstLinear) + { + DstPosX = DstPosY = DstPosZ = 0; + } + + if (SrcLinear && DstLinear) + { + for (int Y = 0; Y < YCount; Y++) + { + int SrcOffset = (SrcPosY + Y) * SrcPitch + SrcPosX * SrcCpp; + int DstOffset = (DstPosY + Y) * DstPitch + DstPosX * DstCpp; + + long Src = SrcPA + (uint)SrcOffset; + long Dst = DstPA + (uint)DstOffset; + + Vmm.Memory.CopyBytes(Src, Dst, XCount * SrcCpp); + } + } + else + { + ISwizzle SrcSwizzle; + + if (SrcLinear) + { + SrcSwizzle = new LinearSwizzle(SrcPitch, SrcCpp); + } + else + { + SrcSwizzle = new BlockLinearSwizzle(SrcSizeX, SrcCpp, SrcBlockHeight); + } + + ISwizzle DstSwizzle; + + if (DstLinear) + { + DstSwizzle = new LinearSwizzle(DstPitch, DstCpp); + } + else + { + DstSwizzle = new BlockLinearSwizzle(DstSizeX, DstCpp, DstBlockHeight); + } + + for (int Y = 0; Y < YCount; Y++) + for (int X = 0; X < XCount; X++) + { + int SrcOffset = SrcSwizzle.GetSwizzleOffset(SrcPosX + X, SrcPosY + Y); + int DstOffset = DstSwizzle.GetSwizzleOffset(DstPosX + X, DstPosY + Y); + + long Src = SrcPA + (uint)SrcOffset; + long Dst = DstPA + (uint)DstOffset; + + Vmm.Memory.CopyBytes(Src, Dst, SrcCpp); + } + } + } + else + { + Vmm.Memory.CopyBytes(SrcPA, DstPA, XCount); + } + } + + private long MakeInt64From2xInt32(NvGpuEngineM2mfReg Reg) + { + return + (long)Registers[(int)Reg + 0] << 32 | + (uint)Registers[(int)Reg + 1]; + } + + private void WriteRegister(NvGpuPBEntry PBEntry) + { + int ArgsCount = PBEntry.Arguments.Count; + + if (ArgsCount > 0) + { + Registers[PBEntry.Method] = PBEntry.Arguments[ArgsCount - 1]; + } + } + + private int ReadRegister(NvGpuEngineM2mfReg Reg) + { + return Registers[(int)Reg]; + } + + private void WriteRegister(NvGpuEngineM2mfReg Reg, int Value) + { + Registers[(int)Reg] = Value; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/NvGpuEngineDmaReg.cs b/Ryujinx.Graphics/NvGpuEngineM2mfReg.cs similarity index 81% rename from Ryujinx.Graphics/NvGpuEngineDmaReg.cs rename to Ryujinx.Graphics/NvGpuEngineM2mfReg.cs index b0fa1fbf37..170e0b7b64 100644 --- a/Ryujinx.Graphics/NvGpuEngineDmaReg.cs +++ b/Ryujinx.Graphics/NvGpuEngineM2mfReg.cs @@ -1,11 +1,14 @@ namespace Ryujinx.Graphics { - enum NvGpuEngineDmaReg + enum NvGpuEngineM2mfReg { SrcAddress = 0x100, DstAddress = 0x102, SrcPitch = 0x104, DstPitch = 0x105, + XCount = 0x106, + YCount = 0x107, + Swizzle = 0x1c2, DstBlkDim = 0x1c3, DstSizeX = 0x1c4, DstSizeY = 0x1c5, diff --git a/Ryujinx.Graphics/NvGpuEngineP2mf.cs b/Ryujinx.Graphics/NvGpuEngineP2mf.cs new file mode 100644 index 0000000000..842dfc5221 --- /dev/null +++ b/Ryujinx.Graphics/NvGpuEngineP2mf.cs @@ -0,0 +1,102 @@ +using Ryujinx.Graphics.Memory; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Ryujinx.Graphics +{ + public class NvGpuEngineP2mf : INvGpuEngine + { + public int[] Registers { get; private set; } + + private NvGpu Gpu; + + private Dictionary Methods; + + private ReadOnlyCollection DataBuffer; + + public NvGpuEngineP2mf(NvGpu Gpu) + { + this.Gpu = Gpu; + + Registers = new int[0x80]; + + Methods = new Dictionary(); + + void AddMethod(int Meth, int Count, int Stride, NvGpuMethod Method) + { + while (Count-- > 0) + { + Methods.Add(Meth, Method); + + Meth += Stride; + } + } + + AddMethod(0x6c, 1, 1, Execute); + AddMethod(0x6d, 1, 1, PushData); + } + + public void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) + { + if (Methods.TryGetValue(PBEntry.Method, out NvGpuMethod Method)) + { + Method(Vmm, PBEntry); + } + else + { + WriteRegister(PBEntry); + } + } + + private void Execute(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) + { + //TODO: Some registers and copy modes are still not implemented. + int Control = PBEntry.Arguments[0]; + + long DstAddress = MakeInt64From2xInt32(NvGpuEngineP2mfReg.DstAddress); + + int LineLengthIn = ReadRegister(NvGpuEngineP2mfReg.LineLengthIn); + + DataBuffer = null; + + Gpu.Fifo.Step(); + + for (int Offset = 0; Offset < LineLengthIn; Offset += 4) + { + Vmm.WriteInt32(DstAddress + Offset, DataBuffer[Offset >> 2]); + } + } + + private void PushData(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) + { + DataBuffer = PBEntry.Arguments; + } + + private long MakeInt64From2xInt32(NvGpuEngineP2mfReg Reg) + { + return + (long)Registers[(int)Reg + 0] << 32 | + (uint)Registers[(int)Reg + 1]; + } + + private void WriteRegister(NvGpuPBEntry PBEntry) + { + int ArgsCount = PBEntry.Arguments.Count; + + if (ArgsCount > 0) + { + Registers[PBEntry.Method] = PBEntry.Arguments[ArgsCount - 1]; + } + } + + private int ReadRegister(NvGpuEngineP2mfReg Reg) + { + return Registers[(int)Reg]; + } + + private void WriteRegister(NvGpuEngineP2mfReg Reg, int Value) + { + Registers[(int)Reg] = Value; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/NvGpuEngineP2mfReg.cs b/Ryujinx.Graphics/NvGpuEngineP2mfReg.cs new file mode 100644 index 0000000000..803ed299b5 --- /dev/null +++ b/Ryujinx.Graphics/NvGpuEngineP2mfReg.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Graphics +{ + enum NvGpuEngineP2mfReg + { + LineLengthIn = 0x60, + LineCount = 0x61, + DstAddress = 0x62, + DstPitch = 0x64, + DstBlockDim = 0x65, + DstWidth = 0x66, + DstHeight = 0x67, + DstDepth = 0x68, + DstZ = 0x69, + DstX = 0x6a, + DstY = 0x6b + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/NvGpuFifo.cs b/Ryujinx.Graphics/NvGpuFifo.cs index 3b79a055e4..16d16f5edf 100644 --- a/Ryujinx.Graphics/NvGpuFifo.cs +++ b/Ryujinx.Graphics/NvGpuFifo.cs @@ -94,6 +94,8 @@ namespace Ryujinx.Graphics Gpu.Engine3d.ResetCache(); + Gpu.ResourceManager.ClearPbCache(); + CurrPbEntryIndex = 0; } @@ -103,20 +105,36 @@ namespace Ryujinx.Graphics } private void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) + { + if ((NvGpuFifoMeth)PBEntry.Method == NvGpuFifoMeth.BindChannel) + { + NvGpuEngine Engine = (NvGpuEngine)PBEntry.Arguments[0]; + + SubChannels[PBEntry.SubChannel] = Engine; + } + else + { + switch (SubChannels[PBEntry.SubChannel]) + { + case NvGpuEngine._2d: Call2dMethod (Vmm, PBEntry); break; + case NvGpuEngine._3d: Call3dMethod (Vmm, PBEntry); break; + case NvGpuEngine.P2mf: CallP2mfMethod(Vmm, PBEntry); break; + case NvGpuEngine.M2mf: CallM2mfMethod(Vmm, PBEntry); break; + } + } + } + + private void Call2dMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) + { + Gpu.Engine2d.CallMethod(Vmm, PBEntry); + } + + private void Call3dMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) { if (PBEntry.Method < 0x80) { switch ((NvGpuFifoMeth)PBEntry.Method) { - case NvGpuFifoMeth.BindChannel: - { - NvGpuEngine Engine = (NvGpuEngine)PBEntry.Arguments[0]; - - SubChannels[PBEntry.SubChannel] = Engine; - - break; - } - case NvGpuFifoMeth.SetMacroUploadAddress: { CurrMacroPosition = PBEntry.Arguments[0]; @@ -150,25 +168,7 @@ namespace Ryujinx.Graphics } } } - else - { - switch (SubChannels[PBEntry.SubChannel]) - { - case NvGpuEngine._2d: Call2dMethod (Vmm, PBEntry); break; - case NvGpuEngine._3d: Call3dMethod (Vmm, PBEntry); break; - case NvGpuEngine.Dma: CallDmaMethod(Vmm, PBEntry); break; - } - } - } - - private void Call2dMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) - { - Gpu.Engine2d.CallMethod(Vmm, PBEntry); - } - - private void Call3dMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) - { - if (PBEntry.Method < 0xe00) + else if (PBEntry.Method < 0xe00) { Gpu.Engine3d.CallMethod(Vmm, PBEntry); } @@ -190,9 +190,14 @@ namespace Ryujinx.Graphics } } - private void CallDmaMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) + private void CallP2mfMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) { - Gpu.EngineDma.CallMethod(Vmm, PBEntry); + Gpu.EngineP2mf.CallMethod(Vmm, PBEntry); + } + + private void CallM2mfMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) + { + Gpu.EngineM2mf.CallMethod(Vmm, PBEntry); } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Texture/ImageUtils.cs b/Ryujinx.Graphics/Texture/ImageUtils.cs index f6db08944e..7d3dde49e7 100644 --- a/Ryujinx.Graphics/Texture/ImageUtils.cs +++ b/Ryujinx.Graphics/Texture/ImageUtils.cs @@ -1,33 +1,37 @@ -using Ryujinx.Graphics.Gal; +using ChocolArm64.Memory; +using Ryujinx.Graphics.Gal; +using Ryujinx.Graphics.Memory; using System; using System.Collections.Generic; namespace Ryujinx.Graphics.Texture { - static class ImageUtils + public static class ImageUtils { - struct ImageDescriptor + [Flags] + private enum TargetBuffer { - public TextureReaderDelegate Reader; + Color = 1 << 0, + Depth = 1 << 1, + Stencil = 1 << 2, - public bool HasColor; - public bool HasDepth; - public bool HasStencil; + DepthStencil = Depth | Stencil + } - public bool Compressed; + private struct ImageDescriptor + { + public int BytesPerPixel { get; private set; } + public int BlockWidth { get; private set; } + public int BlockHeight { get; private set; } - public ImageDescriptor( - TextureReaderDelegate Reader, - bool HasColor, - bool HasDepth, - bool HasStencil, - bool Compressed) + public TargetBuffer Target { get; private set; } + + public ImageDescriptor(int BytesPerPixel, int BlockWidth, int BlockHeight, TargetBuffer Target) { - this.Reader = Reader; - this.HasColor = HasColor; - this.HasDepth = HasDepth; - this.HasStencil = HasStencil; - this.Compressed = Compressed; + this.BytesPerPixel = BytesPerPixel; + this.BlockWidth = BlockWidth; + this.BlockHeight = BlockHeight; + this.Target = Target; } } @@ -48,6 +52,7 @@ namespace Ryujinx.Graphics.Texture { GalTextureFormat.G8R8, GalImageFormat.G8R8 | Snorm | Unorm | Sint | Uint }, { GalTextureFormat.R16, GalImageFormat.R16 | Snorm | Unorm | Sint | Uint | Sfloat }, { GalTextureFormat.R8, GalImageFormat.R8 | Snorm | Unorm | Sint | Uint }, + { GalTextureFormat.R16G16, GalImageFormat.R16G16 | Snorm }, { GalTextureFormat.R32, GalImageFormat.R32 | Sint | Uint | Sfloat }, { GalTextureFormat.A4B4G4R4, GalImageFormat.A4B4G4R4 | Unorm }, { GalTextureFormat.A1B5G5R5, GalImageFormat.A1R5G5B5 | Unorm }, @@ -85,58 +90,58 @@ namespace Ryujinx.Graphics.Texture private static readonly Dictionary s_ImageTable = new Dictionary() - { - { GalImageFormat.R32G32B32A32, new ImageDescriptor(TextureReader.Read16Bpp, true, false, false, false) }, - { GalImageFormat.R16G16B16A16, new ImageDescriptor(TextureReader.Read8Bpp, true, false, false, false) }, - { GalImageFormat.R32G32, new ImageDescriptor(TextureReader.Read8Bpp, true, false, false, false) }, - { GalImageFormat.A8B8G8R8, new ImageDescriptor(TextureReader.Read4Bpp, true, false, false, false) }, - { GalImageFormat.A2B10G10R10, new ImageDescriptor(TextureReader.Read4Bpp, true, false, false, false) }, - { GalImageFormat.R32, new ImageDescriptor(TextureReader.Read4Bpp, true, false, false, false) }, - { GalImageFormat.A4B4G4R4, new ImageDescriptor(TextureReader.Read2Bpp, true, false, false, false) }, - { GalImageFormat.BC6H_SF16, new ImageDescriptor(TextureReader.Read16BptCompressedTexture4x4, true, false, false, true) }, - { GalImageFormat.BC6H_UF16, new ImageDescriptor(TextureReader.Read16BptCompressedTexture4x4, true, false, false, true) }, - { GalImageFormat.A1R5G5B5, new ImageDescriptor(TextureReader.Read5551, true, false, false, false) }, - { GalImageFormat.B5G6R5, new ImageDescriptor(TextureReader.Read565, true, false, false, false) }, - { GalImageFormat.BC7, new ImageDescriptor(TextureReader.Read16BptCompressedTexture4x4, true, false, false, true) }, - { GalImageFormat.R16G16, new ImageDescriptor(TextureReader.Read4Bpp, true, false, false, false) }, - { GalImageFormat.R8G8, new ImageDescriptor(TextureReader.Read2Bpp, true, false, false, false) }, - { GalImageFormat.G8R8, new ImageDescriptor(TextureReader.Read2Bpp, true, false, false, false) }, - { GalImageFormat.R16, new ImageDescriptor(TextureReader.Read2Bpp, true, false, false, false) }, - { GalImageFormat.R8, new ImageDescriptor(TextureReader.Read1Bpp, true, false, false, false) }, - { GalImageFormat.B10G11R11, new ImageDescriptor(TextureReader.Read4Bpp, true, false, false, false) }, - { GalImageFormat.A8B8G8R8_SRGB, new ImageDescriptor(TextureReader.Read4Bpp, true, false, false, false) }, - { GalImageFormat.BC1_RGBA, new ImageDescriptor(TextureReader.Read8Bpt4x4, true, false, false, true) }, - { GalImageFormat.BC2, new ImageDescriptor(TextureReader.Read16BptCompressedTexture4x4, true, false, false, true) }, - { GalImageFormat.BC3, new ImageDescriptor(TextureReader.Read16BptCompressedTexture4x4, true, false, false, true) }, - { GalImageFormat.BC4, new ImageDescriptor(TextureReader.Read8Bpt4x4, true, false, false, true) }, - { GalImageFormat.BC5, new ImageDescriptor(TextureReader.Read16BptCompressedTexture4x4, true, false, false, true) }, - { GalImageFormat.ASTC_4x4, new ImageDescriptor(TextureReader.Read16BptCompressedTexture4x4, true, false, false, true) }, - { GalImageFormat.ASTC_5x5, new ImageDescriptor(TextureReader.Read16BptCompressedTexture5x5, true, false, false, true) }, - { GalImageFormat.ASTC_6x6, new ImageDescriptor(TextureReader.Read16BptCompressedTexture6x6, true, false, false, true) }, - { GalImageFormat.ASTC_8x8, new ImageDescriptor(TextureReader.Read16BptCompressedTexture8x8, true, false, false, true) }, - { GalImageFormat.ASTC_10x10, new ImageDescriptor(TextureReader.Read16BptCompressedTexture10x10, true, false, false, true) }, - { GalImageFormat.ASTC_12x12, new ImageDescriptor(TextureReader.Read16BptCompressedTexture12x12, true, false, false, true) }, - { GalImageFormat.ASTC_5x4, new ImageDescriptor(TextureReader.Read16BptCompressedTexture5x4, true, false, false, true) }, - { GalImageFormat.ASTC_6x5, new ImageDescriptor(TextureReader.Read16BptCompressedTexture6x5, true, false, false, true) }, - { GalImageFormat.ASTC_8x6, new ImageDescriptor(TextureReader.Read16BptCompressedTexture8x6, true, false, false, true) }, - { GalImageFormat.ASTC_10x8, new ImageDescriptor(TextureReader.Read16BptCompressedTexture10x8, true, false, false, true) }, - { GalImageFormat.ASTC_12x10, new ImageDescriptor(TextureReader.Read16BptCompressedTexture12x10, true, false, false, true) }, - { GalImageFormat.ASTC_8x5, new ImageDescriptor(TextureReader.Read16BptCompressedTexture8x5, true, false, false, true) }, - { GalImageFormat.ASTC_10x5, new ImageDescriptor(TextureReader.Read16BptCompressedTexture10x5, true, false, false, true) }, - { GalImageFormat.ASTC_10x6, new ImageDescriptor(TextureReader.Read16BptCompressedTexture10x6, true, false, false, true) }, + { + { GalImageFormat.R32G32B32A32, new ImageDescriptor(16, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.R16G16B16A16, new ImageDescriptor(8, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.R32G32, new ImageDescriptor(8, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.A8B8G8R8, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.A2B10G10R10, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.R32, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.A4B4G4R4, new ImageDescriptor(2, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.BC6H_SF16, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) }, + { GalImageFormat.BC6H_UF16, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) }, + { GalImageFormat.A1R5G5B5, new ImageDescriptor(2, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.B5G6R5, new ImageDescriptor(2, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.BC7, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) }, + { GalImageFormat.R16G16, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.R8G8, new ImageDescriptor(2, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.G8R8, new ImageDescriptor(2, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.R16, new ImageDescriptor(2, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.R8, new ImageDescriptor(1, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.B10G11R11, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.A8B8G8R8_SRGB, new ImageDescriptor(4, 1, 1, TargetBuffer.Color) }, + { GalImageFormat.BC1_RGBA, new ImageDescriptor(8, 4, 4, TargetBuffer.Color) }, + { GalImageFormat.BC2, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) }, + { GalImageFormat.BC3, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) }, + { GalImageFormat.BC4, new ImageDescriptor(8, 4, 4, TargetBuffer.Color) }, + { GalImageFormat.BC5, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) }, + { GalImageFormat.ASTC_4x4, new ImageDescriptor(16, 4, 4, TargetBuffer.Color) }, + { GalImageFormat.ASTC_5x5, new ImageDescriptor(16, 5, 5, TargetBuffer.Color) }, + { GalImageFormat.ASTC_6x6, new ImageDescriptor(16, 6, 6, TargetBuffer.Color) }, + { GalImageFormat.ASTC_8x8, new ImageDescriptor(16, 8, 8, TargetBuffer.Color) }, + { GalImageFormat.ASTC_10x10, new ImageDescriptor(16, 10, 10, TargetBuffer.Color) }, + { GalImageFormat.ASTC_12x12, new ImageDescriptor(16, 12, 12, TargetBuffer.Color) }, + { GalImageFormat.ASTC_5x4, new ImageDescriptor(16, 5, 4, TargetBuffer.Color) }, + { GalImageFormat.ASTC_6x5, new ImageDescriptor(16, 6, 5, TargetBuffer.Color) }, + { GalImageFormat.ASTC_8x6, new ImageDescriptor(16, 8, 6, TargetBuffer.Color) }, + { GalImageFormat.ASTC_10x8, new ImageDescriptor(16, 10, 8, TargetBuffer.Color) }, + { GalImageFormat.ASTC_12x10, new ImageDescriptor(16, 12, 10, TargetBuffer.Color) }, + { GalImageFormat.ASTC_8x5, new ImageDescriptor(16, 8, 5, TargetBuffer.Color) }, + { GalImageFormat.ASTC_10x5, new ImageDescriptor(16, 10, 5, TargetBuffer.Color) }, + { GalImageFormat.ASTC_10x6, new ImageDescriptor(16, 10, 6, TargetBuffer.Color) }, - { GalImageFormat.D24_S8, new ImageDescriptor(TextureReader.Read4Bpp, false, true, true, false) }, - { GalImageFormat.D32, new ImageDescriptor(TextureReader.Read4Bpp, false, true, false, false) }, - { GalImageFormat.D16, new ImageDescriptor(TextureReader.Read2Bpp, false, true, false, false) }, - { GalImageFormat.D32_S8, new ImageDescriptor(TextureReader.Read8Bpp, false, true, true, false) }, - }; + { GalImageFormat.D24_S8, new ImageDescriptor(4, 1, 1, TargetBuffer.DepthStencil) }, + { GalImageFormat.D32, new ImageDescriptor(4, 1, 1, TargetBuffer.Depth) }, + { GalImageFormat.D16, new ImageDescriptor(2, 1, 1, TargetBuffer.Depth) }, + { GalImageFormat.D32_S8, new ImageDescriptor(8, 1, 1, TargetBuffer.DepthStencil) }, + }; public static GalImageFormat ConvertTexture( GalTextureFormat Format, - GalTextureType RType, - GalTextureType GType, - GalTextureType BType, - GalTextureType AType) + GalTextureType RType, + GalTextureType GType, + GalTextureType BType, + GalTextureType AType) { if (RType != GType || RType != BType || RType != AType) { @@ -202,128 +207,157 @@ namespace Ryujinx.Graphics.Texture case GalZetaFormat.Z32Float: return GalImageFormat.D32 | Sfloat; case GalZetaFormat.S8Z24Unorm: return GalImageFormat.D24_S8 | Unorm; case GalZetaFormat.Z16Unorm: return GalImageFormat.D16 | Unorm; - //This one might not be Uint, change when a texture uses this format - case GalZetaFormat.Z32S8X24Float: return GalImageFormat.D32_S8 | Uint; + case GalZetaFormat.Z24S8Unorm: return GalImageFormat.D24_S8 | Unorm; + case GalZetaFormat.Z32S8X24Float: return GalImageFormat.D32_S8 | Sfloat; } throw new NotImplementedException(Format.ToString()); } - public static TextureReaderDelegate GetReader(GalImageFormat Format) + public static byte[] ReadTexture(IAMemory Memory, GalImage Image, long Position) { - return GetImageDescriptor(Format).Reader; + AMemory CpuMemory; + + if (Memory is NvGpuVmm Vmm) + { + CpuMemory = Vmm.Memory; + } + else + { + CpuMemory = (AMemory)Memory; + } + + ISwizzle Swizzle = TextureHelper.GetSwizzle(Image); + + ImageDescriptor Desc = GetImageDescriptor(Image.Format); + + (int Width, int Height) = GetImageSizeInBlocks(Image); + + int BytesPerPixel = Desc.BytesPerPixel; + + int OutOffs = 0; + + byte[] Data = new byte[Width * Height * BytesPerPixel]; + + for (int Y = 0; Y < Height; Y++) + for (int X = 0; X < Width; X++) + { + long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); + + CpuMemory.ReadBytes(Position + Offset, Data, OutOffs, BytesPerPixel); + + OutOffs += BytesPerPixel; + } + + return Data; + } + + public static void WriteTexture(NvGpuVmm Vmm, GalImage Image, long Position, byte[] Data) + { + ISwizzle Swizzle = TextureHelper.GetSwizzle(Image); + + ImageDescriptor Desc = GetImageDescriptor(Image.Format); + + (int Width, int Height) = ImageUtils.GetImageSizeInBlocks(Image); + + int BytesPerPixel = Desc.BytesPerPixel; + + int InOffs = 0; + + for (int Y = 0; Y < Height; Y++) + for (int X = 0; X < Width; X++) + { + long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); + + Vmm.Memory.WriteBytes(Position + Offset, Data, InOffs, BytesPerPixel); + + InOffs += BytesPerPixel; + } } public static int GetSize(GalImage Image) { - switch (Image.Format & GalImageFormat.FormatMask) + ImageDescriptor Desc = GetImageDescriptor(Image.Format); + + int Width = DivRoundUp(Image.Width, Desc.BlockWidth); + int Height = DivRoundUp(Image.Height, Desc.BlockHeight); + + return Desc.BytesPerPixel * Width * Height; + } + + public static int GetPitch(GalImageFormat Format, int Width) + { + ImageDescriptor Desc = GetImageDescriptor(Format); + + return Desc.BytesPerPixel * DivRoundUp(Width, Desc.BlockWidth); + } + + public static int GetBlockWidth(GalImageFormat Format) + { + return GetImageDescriptor(Format).BlockWidth; + } + + public static int GetBlockHeight(GalImageFormat Format) + { + return GetImageDescriptor(Format).BlockHeight; + } + + public static int GetAlignedWidth(GalImage Image) + { + ImageDescriptor Desc = GetImageDescriptor(Image.Format); + + int AlignMask; + + if (Image.Layout == GalMemoryLayout.BlockLinear) { - case GalImageFormat.R32G32B32A32: - return Image.Width * Image.Height * 16; - - case GalImageFormat.R16G16B16A16: - case GalImageFormat.D32_S8: - case GalImageFormat.R32G32: - return Image.Width * Image.Height * 8; - - case GalImageFormat.A8B8G8R8: - case GalImageFormat.A8B8G8R8_SRGB: - case GalImageFormat.A2B10G10R10: - case GalImageFormat.R16G16: - case GalImageFormat.R32: - case GalImageFormat.D32: - case GalImageFormat.B10G11R11: - case GalImageFormat.D24_S8: - return Image.Width * Image.Height * 4; - - case GalImageFormat.B4G4R4A4: - case GalImageFormat.A1R5G5B5: - case GalImageFormat.B5G6R5: - case GalImageFormat.R8G8: - case GalImageFormat.G8R8: - case GalImageFormat.R16: - case GalImageFormat.D16: - return Image.Width * Image.Height * 2; - - case GalImageFormat.R8: - return Image.Width * Image.Height; - - case GalImageFormat.BC1_RGBA: - case GalImageFormat.BC4: - { - return CompressedTextureSize(Image.Width, Image.Height, 4, 4, 8); - } - - case GalImageFormat.BC6H_SF16: - case GalImageFormat.BC6H_UF16: - case GalImageFormat.BC7: - case GalImageFormat.BC2: - case GalImageFormat.BC3: - case GalImageFormat.BC5: - case GalImageFormat.ASTC_4x4: - return CompressedTextureSize(Image.Width, Image.Height, 4, 4, 16); - - case GalImageFormat.ASTC_5x5: - return CompressedTextureSize(Image.Width, Image.Height, 5, 5, 16); - - case GalImageFormat.ASTC_6x6: - return CompressedTextureSize(Image.Width, Image.Height, 6, 6, 16); - - case GalImageFormat.ASTC_8x8: - return CompressedTextureSize(Image.Width, Image.Height, 8, 8, 16); - - case GalImageFormat.ASTC_10x10: - return CompressedTextureSize(Image.Width, Image.Height, 10, 10, 16); - - case GalImageFormat.ASTC_12x12: - return CompressedTextureSize(Image.Width, Image.Height, 12, 12, 16); - - case GalImageFormat.ASTC_5x4: - return CompressedTextureSize(Image.Width, Image.Height, 5, 4, 16); - - case GalImageFormat.ASTC_6x5: - return CompressedTextureSize(Image.Width, Image.Height, 6, 5, 16); - - case GalImageFormat.ASTC_8x6: - return CompressedTextureSize(Image.Width, Image.Height, 8, 6, 16); - - case GalImageFormat.ASTC_10x8: - return CompressedTextureSize(Image.Width, Image.Height, 10, 8, 16); - - case GalImageFormat.ASTC_12x10: - return CompressedTextureSize(Image.Width, Image.Height, 12, 10, 16); - - case GalImageFormat.ASTC_8x5: - return CompressedTextureSize(Image.Width, Image.Height, 8, 5, 16); - - case GalImageFormat.ASTC_10x5: - return CompressedTextureSize(Image.Width, Image.Height, 10, 5, 16); - - case GalImageFormat.ASTC_10x6: - return CompressedTextureSize(Image.Width, Image.Height, 10, 6, 16); + AlignMask = Image.TileWidth * (64 / Desc.BytesPerPixel) - 1; + } + else + { + AlignMask = (32 / Desc.BytesPerPixel) - 1; } - throw new NotImplementedException((Image.Format & GalImageFormat.FormatMask).ToString()); + return (Image.Width + AlignMask) & ~AlignMask; + } + + public static (int Width, int Height) GetImageSizeInBlocks(GalImage Image) + { + ImageDescriptor Desc = GetImageDescriptor(Image.Format); + + return (DivRoundUp(Image.Width, Desc.BlockWidth), + DivRoundUp(Image.Height, Desc.BlockHeight)); + } + + public static int GetBytesPerPixel(GalImageFormat Format) + { + return GetImageDescriptor(Format).BytesPerPixel; + } + + private static int DivRoundUp(int LHS, int RHS) + { + return (LHS + (RHS - 1)) / RHS; } public static bool HasColor(GalImageFormat Format) { - return GetImageDescriptor(Format).HasColor; + return (GetImageDescriptor(Format).Target & TargetBuffer.Color) != 0; } public static bool HasDepth(GalImageFormat Format) { - return GetImageDescriptor(Format).HasDepth; + return (GetImageDescriptor(Format).Target & TargetBuffer.Depth) != 0; } public static bool HasStencil(GalImageFormat Format) { - return GetImageDescriptor(Format).HasStencil; + return (GetImageDescriptor(Format).Target & TargetBuffer.Stencil) != 0; } public static bool IsCompressed(GalImageFormat Format) { - return GetImageDescriptor(Format).Compressed; + ImageDescriptor Desc = GetImageDescriptor(Format); + + return (Desc.BlockWidth | Desc.BlockHeight) != 1; } private static ImageDescriptor GetImageDescriptor(GalImageFormat Format) @@ -351,13 +385,5 @@ namespace Ryujinx.Graphics.Texture default: throw new NotImplementedException(((int)Type).ToString()); } } - - private static int CompressedTextureSize(int TextureWidth, int TextureHeight, int BlockWidth, int BlockHeight, int Bpb) - { - int W = (TextureWidth + (BlockWidth - 1)) / BlockWidth; - int H = (TextureHeight + (BlockHeight - 1)) / BlockHeight; - - return W * H * Bpb; - } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Texture/TextureFactory.cs b/Ryujinx.Graphics/Texture/TextureFactory.cs index fa7a0f80e0..766c53da46 100644 --- a/Ryujinx.Graphics/Texture/TextureFactory.cs +++ b/Ryujinx.Graphics/Texture/TextureFactory.cs @@ -17,44 +17,20 @@ namespace Ryujinx.Graphics.Texture GalTextureSource ZSource = (GalTextureSource)((Tic[0] >> 25) & 7); GalTextureSource WSource = (GalTextureSource)((Tic[0] >> 28) & 7); - int Width = (Tic[4] & 0xffff) + 1; - int Height = (Tic[5] & 0xffff) + 1; - - return new GalImage( - Width, - Height, - Format, - XSource, - YSource, - ZSource, - WSource); - } - - public static byte[] GetTextureData(NvGpuVmm Vmm, long TicPosition) - { - int[] Tic = ReadWords(Vmm, TicPosition, 8); - - GalImageFormat Format = GetImageFormat(Tic); - - long TextureAddress = (uint)Tic[1]; - - TextureAddress |= (long)((ushort)Tic[2]) << 32; - TextureSwizzle Swizzle = (TextureSwizzle)((Tic[2] >> 21) & 7); + GalMemoryLayout Layout; + if (Swizzle == TextureSwizzle.BlockLinear || Swizzle == TextureSwizzle.BlockLinearColorKey) { - TextureAddress &= ~0x1ffL; + Layout = GalMemoryLayout.BlockLinear; } - else if (Swizzle == TextureSwizzle.Pitch || - Swizzle == TextureSwizzle.PitchColorKey) + else { - TextureAddress &= ~0x1fL; + Layout = GalMemoryLayout.Pitch; } - int Pitch = (Tic[3] & 0xffff) << 5; - int BlockHeightLog2 = (Tic[3] >> 3) & 7; int TileWidthLog2 = (Tic[3] >> 10) & 7; @@ -64,17 +40,17 @@ namespace Ryujinx.Graphics.Texture int Width = (Tic[4] & 0xffff) + 1; int Height = (Tic[5] & 0xffff) + 1; - TextureInfo Texture = new TextureInfo( - TextureAddress, + return new GalImage( Width, Height, - Pitch, - BlockHeight, TileWidth, - Swizzle, - Format); - - return TextureReader.Read(Vmm, Texture); + BlockHeight, + Layout, + Format, + XSource, + YSource, + ZSource, + WSource); } public static GalTextureSampler MakeSampler(NvGpu Gpu, NvGpuVmm Vmm, long TscPosition) @@ -107,7 +83,7 @@ namespace Ryujinx.Graphics.Texture private static GalImageFormat GetImageFormat(int[] Tic) { - GalTextureType RType = (GalTextureType)((Tic[0] >> 7) & 7); + GalTextureType RType = (GalTextureType)((Tic[0] >> 7) & 7); GalTextureType GType = (GalTextureType)((Tic[0] >> 10) & 7); GalTextureType BType = (GalTextureType)((Tic[0] >> 13) & 7); GalTextureType AType = (GalTextureType)((Tic[0] >> 16) & 7); diff --git a/Ryujinx.Graphics/Texture/TextureHelper.cs b/Ryujinx.Graphics/Texture/TextureHelper.cs index 8130ab41ae..9e966e6bd0 100644 --- a/Ryujinx.Graphics/Texture/TextureHelper.cs +++ b/Ryujinx.Graphics/Texture/TextureHelper.cs @@ -1,33 +1,30 @@ using ChocolArm64.Memory; using Ryujinx.Graphics.Gal; using Ryujinx.Graphics.Memory; -using System; namespace Ryujinx.Graphics.Texture { static class TextureHelper { - public static ISwizzle GetSwizzle(TextureInfo Texture, int BlockWidth, int Bpp) + public static ISwizzle GetSwizzle(GalImage Image) { - int Width = (Texture.Width + (BlockWidth - 1)) / BlockWidth; + int BlockWidth = ImageUtils.GetBlockWidth (Image.Format); + int BytesPerPixel = ImageUtils.GetBytesPerPixel(Image.Format); - int AlignMask = Texture.TileWidth * (64 / Bpp) - 1; + int Width = (Image.Width + (BlockWidth - 1)) / BlockWidth; - Width = (Width + AlignMask) & ~AlignMask; - - switch (Texture.Swizzle) + if (Image.Layout == GalMemoryLayout.BlockLinear) { - case TextureSwizzle._1dBuffer: - case TextureSwizzle.Pitch: - case TextureSwizzle.PitchColorKey: - return new LinearSwizzle(Texture.Pitch, Bpp); + int AlignMask = Image.TileWidth * (64 / BytesPerPixel) - 1; - case TextureSwizzle.BlockLinear: - case TextureSwizzle.BlockLinearColorKey: - return new BlockLinearSwizzle(Width, Bpp, Texture.BlockHeight); + Width = (Width + AlignMask) & ~AlignMask; + + return new BlockLinearSwizzle(Width, BytesPerPixel, Image.GobBlockHeight); + } + else + { + return new LinearSwizzle(Image.Pitch, BytesPerPixel); } - - throw new NotImplementedException(Texture.Swizzle.ToString()); } public static (AMemory Memory, long Position) GetMemoryAndPosition( diff --git a/Ryujinx.Graphics/Texture/TextureInfo.cs b/Ryujinx.Graphics/Texture/TextureInfo.cs deleted file mode 100644 index 66445dcce7..0000000000 --- a/Ryujinx.Graphics/Texture/TextureInfo.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Ryujinx.Graphics.Gal; - -namespace Ryujinx.Graphics.Texture -{ - public struct TextureInfo - { - public long Position { get; private set; } - - public int Width { get; private set; } - public int Height { get; private set; } - public int Pitch { get; private set; } - - public int BlockHeight { get; private set; } - public int TileWidth { get; private set; } - - public TextureSwizzle Swizzle { get; private set; } - - public GalImageFormat Format { get; private set; } - - public TextureInfo( - long Position, - int Width, - int Height) - { - this.Position = Position; - this.Width = Width; - this.Height = Height; - - Pitch = 0; - - BlockHeight = 16; - - TileWidth = 1; - - Swizzle = TextureSwizzle.BlockLinear; - - Format = GalImageFormat.A8B8G8R8 | GalImageFormat.Unorm; - } - - public TextureInfo( - long Position, - int Width, - int Height, - int Pitch, - int BlockHeight, - int TileWidth, - TextureSwizzle Swizzle, - GalImageFormat Format) - { - this.Position = Position; - this.Width = Width; - this.Height = Height; - this.Pitch = Pitch; - this.BlockHeight = BlockHeight; - this.TileWidth = TileWidth; - this.Swizzle = Swizzle; - this.Format = Format; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Texture/TextureReader.cs b/Ryujinx.Graphics/Texture/TextureReader.cs deleted file mode 100644 index dbaed1a8e8..0000000000 --- a/Ryujinx.Graphics/Texture/TextureReader.cs +++ /dev/null @@ -1,398 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.Graphics.Gal; -using System; - -namespace Ryujinx.Graphics.Texture -{ - delegate byte[] TextureReaderDelegate(IAMemory Memory, TextureInfo Texture); - - public static class TextureReader - { - public static byte[] Read(IAMemory Memory, TextureInfo Texture) - { - TextureReaderDelegate Reader = ImageUtils.GetReader(Texture.Format); - - return Reader(Memory, Texture); - } - - internal unsafe static byte[] Read1Bpp(IAMemory Memory, TextureInfo Texture) - { - int Width = Texture.Width; - int Height = Texture.Height; - - byte[] Output = new byte[Width * Height]; - - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, 1, 1); - - (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( - Memory, - Texture.Position); - - fixed (byte* BuffPtr = Output) - { - long OutOffs = 0; - - for (int Y = 0; Y < Height; Y++) - for (int X = 0; X < Width; X++) - { - long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); - - byte Pixel = CpuMem.ReadByte(Position + Offset); - - *(BuffPtr + OutOffs) = Pixel; - - OutOffs++; - } - } - - return Output; - } - - internal unsafe static byte[] Read5551(IAMemory Memory, TextureInfo Texture) - { - int Width = Texture.Width; - int Height = Texture.Height; - - byte[] Output = new byte[Width * Height * 2]; - - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, 1, 2); - - (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( - Memory, - Texture.Position); - - fixed (byte* BuffPtr = Output) - { - long OutOffs = 0; - - for (int Y = 0; Y < Height; Y++) - for (int X = 0; X < Width; X++) - { - long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); - - uint Pixel = (uint)CpuMem.ReadInt16(Position + Offset); - - Pixel = (Pixel & 0x001f) << 11 | - (Pixel & 0x03e0) << 1 | - (Pixel & 0x7c00) >> 9 | - (Pixel & 0x8000) >> 15; - - *(short*)(BuffPtr + OutOffs) = (short)Pixel; - - OutOffs += 2; - } - } - - return Output; - } - - internal unsafe static byte[] Read565(IAMemory Memory, TextureInfo Texture) - { - int Width = Texture.Width; - int Height = Texture.Height; - - byte[] Output = new byte[Width * Height * 2]; - - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, 1, 2); - - (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( - Memory, - Texture.Position); - - fixed (byte* BuffPtr = Output) - { - long OutOffs = 0; - - for (int Y = 0; Y < Height; Y++) - for (int X = 0; X < Width; X++) - { - long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); - - uint Pixel = (uint)CpuMem.ReadInt16(Position + Offset); - - Pixel = (Pixel & 0x001f) << 11 | - (Pixel & 0x07e0) | - (Pixel & 0xf800) >> 11; - - *(short*)(BuffPtr + OutOffs) = (short)Pixel; - - OutOffs += 2; - } - } - - return Output; - } - - internal unsafe static byte[] Read2Bpp(IAMemory Memory, TextureInfo Texture) - { - int Width = Texture.Width; - int Height = Texture.Height; - - byte[] Output = new byte[Width * Height * 2]; - - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, 1, 2); - - (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( - Memory, - Texture.Position); - - fixed (byte* BuffPtr = Output) - { - long OutOffs = 0; - - for (int Y = 0; Y < Height; Y++) - for (int X = 0; X < Width; X++) - { - long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); - - short Pixel = CpuMem.ReadInt16(Position + Offset); - - *(short*)(BuffPtr + OutOffs) = Pixel; - - OutOffs += 2; - } - } - - return Output; - } - - internal unsafe static byte[] Read4Bpp(IAMemory Memory, TextureInfo Texture) - { - int Width = Texture.Width; - int Height = Texture.Height; - - byte[] Output = new byte[Width * Height * 4]; - - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, 1, 4); - - (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( - Memory, - Texture.Position); - - fixed (byte* BuffPtr = Output) - { - long OutOffs = 0; - - for (int Y = 0; Y < Height; Y++) - for (int X = 0; X < Width; X++) - { - long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); - - int Pixel = CpuMem.ReadInt32(Position + Offset); - - *(int*)(BuffPtr + OutOffs) = Pixel; - - OutOffs += 4; - } - } - - return Output; - } - - internal unsafe static byte[] Read8Bpp(IAMemory Memory, TextureInfo Texture) - { - int Width = Texture.Width; - int Height = Texture.Height; - - byte[] Output = new byte[Width * Height * 8]; - - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, 1, 8); - - (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( - Memory, - Texture.Position); - - fixed (byte* BuffPtr = Output) - { - long OutOffs = 0; - - for (int Y = 0; Y < Height; Y++) - for (int X = 0; X < Width; X++) - { - long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); - - long Pixel = CpuMem.ReadInt64(Position + Offset); - - *(long*)(BuffPtr + OutOffs) = Pixel; - - OutOffs += 8; - } - } - - return Output; - } - - internal unsafe static byte[] Read16Bpp(IAMemory Memory, TextureInfo Texture) - { - int Width = Texture.Width; - int Height = Texture.Height; - - byte[] Output = new byte[Width * Height * 16]; - - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, 1, 16); - - (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( - Memory, - Texture.Position); - - fixed (byte* BuffPtr = Output) - { - long OutOffs = 0; - - for (int Y = 0; Y < Height; Y++) - for (int X = 0; X < Width; X++) - { - long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); - - long PxLow = CpuMem.ReadInt64(Position + Offset + 0); - long PxHigh = CpuMem.ReadInt64(Position + Offset + 8); - - *(long*)(BuffPtr + OutOffs + 0) = PxLow; - *(long*)(BuffPtr + OutOffs + 8) = PxHigh; - - OutOffs += 16; - } - } - - return Output; - } - - internal unsafe static byte[] Read8Bpt4x4(IAMemory Memory, TextureInfo Texture) - { - int Width = (Texture.Width + 3) / 4; - int Height = (Texture.Height + 3) / 4; - - byte[] Output = new byte[Width * Height * 8]; - - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, 4, 8); - - (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( - Memory, - Texture.Position); - - fixed (byte* BuffPtr = Output) - { - long OutOffs = 0; - - for (int Y = 0; Y < Height; Y++) - for (int X = 0; X < Width; X++) - { - long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); - - long Tile = CpuMem.ReadInt64(Position + Offset); - - *(long*)(BuffPtr + OutOffs) = Tile; - - OutOffs += 8; - } - } - - return Output; - } - - internal unsafe static byte[] Read16BptCompressedTexture(IAMemory Memory, TextureInfo Texture, int BlockWidth, int BlockHeight) - { - int Width = (Texture.Width + (BlockWidth - 1)) / BlockWidth; - int Height = (Texture.Height + (BlockHeight - 1)) / BlockHeight; - - byte[] Output = new byte[Width * Height * 16]; - - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, BlockWidth, 16); - - (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( - Memory, - Texture.Position); - - fixed (byte* BuffPtr = Output) - { - long OutOffs = 0; - - for (int Y = 0; Y < Height; Y++) - for (int X = 0; X < Width; X++) - { - long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); - - long Tile0 = CpuMem.ReadInt64(Position + Offset + 0); - long Tile1 = CpuMem.ReadInt64(Position + Offset + 8); - - *(long*)(BuffPtr + OutOffs + 0) = Tile0; - *(long*)(BuffPtr + OutOffs + 8) = Tile1; - - OutOffs += 16; - } - } - - return Output; - } - - internal static byte[] Read16BptCompressedTexture4x4(IAMemory Memory, TextureInfo Texture) - { - return Read16BptCompressedTexture(Memory, Texture, 4, 4); - } - - internal static byte[] Read16BptCompressedTexture5x5(IAMemory Memory, TextureInfo Texture) - { - return Read16BptCompressedTexture(Memory, Texture, 5, 5); - } - - internal static byte[] Read16BptCompressedTexture6x6(IAMemory Memory, TextureInfo Texture) - { - return Read16BptCompressedTexture(Memory, Texture, 6, 6); - } - - internal static byte[] Read16BptCompressedTexture8x8(IAMemory Memory, TextureInfo Texture) - { - return Read16BptCompressedTexture(Memory, Texture, 8, 8); - } - - internal static byte[] Read16BptCompressedTexture10x10(IAMemory Memory, TextureInfo Texture) - { - return Read16BptCompressedTexture(Memory, Texture, 10, 10); - } - - internal static byte[] Read16BptCompressedTexture12x12(IAMemory Memory, TextureInfo Texture) - { - return Read16BptCompressedTexture(Memory, Texture, 12, 12); - } - - internal static byte[] Read16BptCompressedTexture5x4(IAMemory Memory, TextureInfo Texture) - { - return Read16BptCompressedTexture(Memory, Texture, 5, 4); - } - - internal static byte[] Read16BptCompressedTexture6x5(IAMemory Memory, TextureInfo Texture) - { - return Read16BptCompressedTexture(Memory, Texture, 6, 5); - } - - internal static byte[] Read16BptCompressedTexture8x6(IAMemory Memory, TextureInfo Texture) - { - return Read16BptCompressedTexture(Memory, Texture, 8, 6); - } - - internal static byte[] Read16BptCompressedTexture10x8(IAMemory Memory, TextureInfo Texture) - { - return Read16BptCompressedTexture(Memory, Texture, 10, 8); - } - - internal static byte[] Read16BptCompressedTexture12x10(IAMemory Memory, TextureInfo Texture) - { - return Read16BptCompressedTexture(Memory, Texture, 12, 10); - } - - internal static byte[] Read16BptCompressedTexture8x5(IAMemory Memory, TextureInfo Texture) - { - return Read16BptCompressedTexture(Memory, Texture, 5, 5); - } - - internal static byte[] Read16BptCompressedTexture10x5(IAMemory Memory, TextureInfo Texture) - { - return Read16BptCompressedTexture(Memory, Texture, 10, 5); - } - - internal static byte[] Read16BptCompressedTexture10x6(IAMemory Memory, TextureInfo Texture) - { - return Read16BptCompressedTexture(Memory, Texture, 10, 6); - } - } -} diff --git a/Ryujinx.Graphics/Texture/TextureWriter.cs b/Ryujinx.Graphics/Texture/TextureWriter.cs deleted file mode 100644 index 16e78c56f3..0000000000 --- a/Ryujinx.Graphics/Texture/TextureWriter.cs +++ /dev/null @@ -1,35 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.Graphics.Gal; -using Ryujinx.Graphics.Memory; - -namespace Ryujinx.Graphics.Texture -{ - static class TextureWriter - { - public unsafe static void Write(IAMemory Memory, TextureInfo Texture, byte[] Data) - { - ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, 1, 4); - - (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition( - Memory, - Texture.Position); - - fixed (byte* BuffPtr = Data) - { - long InOffs = 0; - - for (int Y = 0; Y < Texture.Height; Y++) - for (int X = 0; X < Texture.Width; X++) - { - long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); - - int Pixel = *(int*)(BuffPtr + InOffs); - - CpuMem.WriteInt32(Position + Offset, Pixel); - - InOffs += 4; - } - } - } - } -} diff --git a/Ryujinx.Graphics/ValueRange.cs b/Ryujinx.Graphics/ValueRange.cs new file mode 100644 index 0000000000..6298bd8ed8 --- /dev/null +++ b/Ryujinx.Graphics/ValueRange.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Graphics +{ + struct ValueRange + { + public long Start { get; private set; } + public long End { get; private set; } + + public T Value { get; set; } + + public ValueRange(long Start, long End, T Value = default(T)) + { + this.Start = Start; + this.End = End; + this.Value = Value; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/ValueRangeSet.cs b/Ryujinx.Graphics/ValueRangeSet.cs new file mode 100644 index 0000000000..479f41ed27 --- /dev/null +++ b/Ryujinx.Graphics/ValueRangeSet.cs @@ -0,0 +1,234 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics +{ + class ValueRangeSet + { + private List> Ranges; + + public ValueRangeSet() + { + Ranges = new List>(); + } + + public void Add(ValueRange Range) + { + if (Range.End <= Range.Start) + { + //Empty or invalid range, do nothing. + return; + } + + int First = BinarySearchFirstIntersection(Range); + + if (First == -1) + { + //No intersections case. + //Find first greater than range (after the current one). + //If found, add before, otherwise add to the end of the list. + int GtIndex = BinarySearchGt(Range); + + if (GtIndex != -1) + { + Ranges.Insert(GtIndex, Range); + } + else + { + Ranges.Add(Range); + } + + return; + } + + (int Start, int End) = GetAllIntersectionRanges(Range, First); + + ValueRange Prev = Ranges[Start]; + ValueRange Next = Ranges[End]; + + Ranges.RemoveRange(Start, (End - Start) + 1); + + InsertNextNeighbour(Start, Range, Next); + + int NewIndex = Start; + + Ranges.Insert(Start, Range); + + InsertPrevNeighbour(Start, Range, Prev); + + //Try merging neighbours if the value is equal. + if (NewIndex > 0) + { + Prev = Ranges[NewIndex - 1]; + + if (Prev.End == Range.Start && CompareValues(Prev, Range)) + { + Ranges.RemoveAt(--NewIndex); + + Ranges[NewIndex] = new ValueRange(Prev.Start, Range.End, Range.Value); + } + } + + if (NewIndex < Ranges.Count - 1) + { + Next = Ranges[NewIndex + 1]; + + if (Next.Start == Range.End && CompareValues(Next, Range)) + { + Ranges.RemoveAt(NewIndex + 1); + + Ranges[NewIndex] = new ValueRange(Range.Start, Next.End, Range.Value); + } + } + } + + private bool CompareValues(ValueRange LHS, ValueRange RHS) + { + return LHS.Value?.Equals(RHS.Value) ?? RHS.Value == null; + } + + public void Remove(ValueRange Range) + { + int First = BinarySearchFirstIntersection(Range); + + if (First == -1) + { + //Nothing to remove. + return; + } + + (int Start, int End) = GetAllIntersectionRanges(Range, First); + + ValueRange Prev = Ranges[Start]; + ValueRange Next = Ranges[End]; + + Ranges.RemoveRange(Start, (End - Start) + 1); + + InsertNextNeighbour(Start, Range, Next); + InsertPrevNeighbour(Start, Range, Prev); + } + + private void InsertNextNeighbour(int Index, ValueRange Range, ValueRange Next) + { + //Split last intersection (ordered by Start) if necessary. + if (Range.End < Next.End) + { + InsertNewRange(Index, Range.End, Next.End, Next.Value); + } + } + + private void InsertPrevNeighbour(int Index, ValueRange Range, ValueRange Prev) + { + //Split first intersection (ordered by Start) if necessary. + if (Range.Start > Prev.Start) + { + InsertNewRange(Index, Prev.Start, Range.Start, Prev.Value); + } + } + + private void InsertNewRange(int Index, long Start, long End, T Value) + { + Ranges.Insert(Index, new ValueRange(Start, End, Value)); + } + + public ValueRange[] GetAllIntersections(ValueRange Range) + { + int First = BinarySearchFirstIntersection(Range); + + if (First == -1) + { + return new ValueRange[0]; + } + + (int Start, int End) = GetAllIntersectionRanges(Range, First); + + return Ranges.GetRange(Start, (End - Start) + 1).ToArray(); + } + + private (int Start, int End) GetAllIntersectionRanges(ValueRange Range, int BaseIndex) + { + int Start = BaseIndex; + int End = BaseIndex; + + while (Start > 0 && Intersects(Range, Ranges[Start - 1])) + { + Start--; + } + + while (End < Ranges.Count - 1 && Intersects(Range, Ranges[End + 1])) + { + End++; + } + + return (Start, End); + } + + private int BinarySearchFirstIntersection(ValueRange Range) + { + int Left = 0; + int Right = Ranges.Count - 1; + + while (Left <= Right) + { + int Size = Right - Left; + + int Middle = Left + (Size >> 1); + + ValueRange Current = Ranges[Middle]; + + if (Intersects(Range, Current)) + { + return Middle; + } + + if (Range.Start < Current.Start) + { + Right = Middle - 1; + } + else + { + Left = Middle + 1; + } + } + + return -1; + } + + private int BinarySearchGt(ValueRange Range) + { + int GtIndex = -1; + + int Left = 0; + int Right = Ranges.Count - 1; + + while (Left <= Right) + { + int Size = Right - Left; + + int Middle = Left + (Size >> 1); + + ValueRange Current = Ranges[Middle]; + + if (Range.Start < Current.Start) + { + Right = Middle - 1; + + if (GtIndex == -1 || Current.Start < Ranges[GtIndex].Start) + { + GtIndex = Middle; + } + } + else + { + Left = Middle + 1; + } + } + + return GtIndex; + } + + private bool Intersects(ValueRange LHS, ValueRange RHS) + { + return LHS.Start < RHS.End && RHS.Start < LHS.End; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs b/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs index 7cc1c8588e..d10eb11729 100644 --- a/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs +++ b/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs @@ -288,7 +288,8 @@ namespace Ryujinx.HLE.HOS.Kernel //Fail for info not available on older Kernel versions. if (InfoType == 18 || InfoType == 19 || - InfoType == 20) + InfoType == 20 || + InfoType == 21) { ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidEnumValue); diff --git a/Ryujinx.HLE/HOS/Services/Acc/IProfile.cs b/Ryujinx.HLE/HOS/Services/Acc/IProfile.cs index 316f16d082..f68c819178 100644 --- a/Ryujinx.HLE/HOS/Services/Acc/IProfile.cs +++ b/Ryujinx.HLE/HOS/Services/Acc/IProfile.cs @@ -3,7 +3,6 @@ using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.Logging; using Ryujinx.HLE.Utilities; -using System; using System.Collections.Generic; using System.IO; using System.Reflection; diff --git a/Ryujinx.HLE/HOS/Services/Vi/IHOSBinderDriver.cs b/Ryujinx.HLE/HOS/Services/Vi/IHOSBinderDriver.cs index bf5f20a2a1..19e0d949ed 100644 --- a/Ryujinx.HLE/HOS/Services/Vi/IHOSBinderDriver.cs +++ b/Ryujinx.HLE/HOS/Services/Vi/IHOSBinderDriver.cs @@ -13,7 +13,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi public override IReadOnlyDictionary Commands => m_Commands; - private KEvent ReleaseEvent; + private KEvent BinderEvent; private NvFlinger Flinger; @@ -27,9 +27,11 @@ namespace Ryujinx.HLE.HOS.Services.Vi { 3, TransactParcelAuto } }; - ReleaseEvent = new KEvent(); + BinderEvent = new KEvent(); - Flinger = new NvFlinger(Renderer, ReleaseEvent); + BinderEvent.WaitEvent.Set(); + + Flinger = new NvFlinger(Renderer, BinderEvent); } public long TransactParcel(ServiceCtx Context) @@ -75,7 +77,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi int Id = Context.RequestData.ReadInt32(); uint Unk = Context.RequestData.ReadUInt32(); - int Handle = Context.Process.HandleTable.OpenHandle(ReleaseEvent); + int Handle = Context.Process.HandleTable.OpenHandle(BinderEvent); Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle); @@ -91,7 +93,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi { if (Disposing) { - ReleaseEvent.Dispose(); + BinderEvent.Dispose(); Flinger.Dispose(); } diff --git a/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs b/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs index 2a6918c441..a8493758d5 100644 --- a/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs +++ b/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs @@ -1,6 +1,7 @@ using Ryujinx.Graphics.Gal; -using Ryujinx.Graphics.Texture; +using Ryujinx.Graphics.Memory; using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Services.Nv.NvGpuAS; using Ryujinx.HLE.HOS.Services.Nv.NvMap; using Ryujinx.HLE.Logging; using System; @@ -19,7 +20,7 @@ namespace Ryujinx.HLE.HOS.Services.Android private Dictionary<(string, int), ServiceProcessParcel> Commands; - private KEvent ReleaseEvent; + private KEvent BinderEvent; private IGalRenderer Renderer; @@ -67,7 +68,7 @@ namespace Ryujinx.HLE.HOS.Services.Android private bool Disposed; - public NvFlinger(IGalRenderer Renderer, KEvent ReleaseEvent) + public NvFlinger(IGalRenderer Renderer, KEvent BinderEvent) { Commands = new Dictionary<(string, int), ServiceProcessParcel>() { @@ -82,8 +83,8 @@ namespace Ryujinx.HLE.HOS.Services.Android { ("android.gui.IGraphicBufferProducer", 0xe), GbpPreallocBuffer } }; - this.Renderer = Renderer; - this.ReleaseEvent = ReleaseEvent; + this.Renderer = Renderer; + this.BinderEvent = BinderEvent; BufferQueue = new BufferEntry[0x40]; @@ -301,42 +302,41 @@ namespace Ryujinx.HLE.HOS.Services.Android bool FlipX = BufferQueue[Slot].Transform.HasFlag(HalTransform.FlipX); bool FlipY = BufferQueue[Slot].Transform.HasFlag(HalTransform.FlipY); - //Rotation is being ignored + //Note: Rotation is being ignored. int Top = Crop.Top; int Left = Crop.Left; int Right = Crop.Right; int Bottom = Crop.Bottom; - Renderer.QueueAction(() => Renderer.RenderTarget.SetTransform(FlipX, FlipY, Top, Left, Right, Bottom)); + NvGpuVmm Vmm = NvGpuASIoctl.GetASCtx(Context).Vmm; - //TODO: Support double buffering here aswell, it is broken for GPU - //frame buffers because it seems to be completely out of sync. - if (Context.Device.Gpu.Engine3d.IsFrameBufferPosition(FbAddr)) + Renderer.QueueAction(() => { - //Frame buffer is rendered to by the GPU, we can just - //bind the frame buffer texture, it's not necessary to read anything. - Renderer.QueueAction(() => Renderer.RenderTarget.Set(FbAddr)); - } - else - { - //Frame buffer is not set on the GPU registers, in this case - //assume that the app is manually writing to it. - TextureInfo Texture = new TextureInfo(FbAddr, FbWidth, FbHeight); + if (!Renderer.Texture.TryGetImage(FbAddr, out GalImage Image)) + { + Image = new GalImage( + FbWidth, + FbHeight, 1, 16, + GalMemoryLayout.BlockLinear, + GalImageFormat.A8B8G8R8 | GalImageFormat.Unorm); + } - byte[] Data = TextureReader.Read(Context.Memory, Texture); + Context.Device.Gpu.ResourceManager.ClearPbCache(); + Context.Device.Gpu.ResourceManager.SendTexture(Vmm, FbAddr, Image); - Renderer.QueueAction(() => Renderer.RenderTarget.Set(Data, FbWidth, FbHeight)); - } + Renderer.RenderTarget.SetTransform(FlipX, FlipY, Top, Left, Right, Bottom); + Renderer.RenderTarget.Set(FbAddr); - Context.Device.Gpu.Renderer.QueueAction(() => ReleaseBuffer(Slot)); + ReleaseBuffer(Slot); + }); } private void ReleaseBuffer(int Slot) { BufferQueue[Slot].State = BufferState.Free; - ReleaseEvent.WaitEvent.Set(); + BinderEvent.WaitEvent.Set(); lock (WaitBufferFree) { From d6fba62f8ac89cff76832d6e2cfe5a7a82e1ff1d Mon Sep 17 00:00:00 2001 From: gdkchan Date: Tue, 18 Sep 2018 12:28:28 -0300 Subject: [PATCH 06/14] Misc fixes on the arena allocator (#364) --- Ryujinx.HLE/Memory/ArenaAllocator.cs | 48 +++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/Ryujinx.HLE/Memory/ArenaAllocator.cs b/Ryujinx.HLE/Memory/ArenaAllocator.cs index 5e15f46a51..9bcb7873e0 100644 --- a/Ryujinx.HLE/Memory/ArenaAllocator.cs +++ b/Ryujinx.HLE/Memory/ArenaAllocator.cs @@ -45,6 +45,33 @@ namespace Ryujinx.HLE.Memory Rg.Position += Size; Rg.Size -= Size; + if (Rg.Size == 0) + { + //Region is empty, just remove it. + FreeRegions.Remove(Node); + } + else if (Node.Previous != null) + { + //Re-sort based on size (smaller first). + Node = Node.Previous; + + FreeRegions.Remove(Node.Next); + + while (Node != null && (ulong)Node.Value.Size > (ulong)Rg.Size) + { + Node = Node.Previous; + } + + if (Node != null) + { + FreeRegions.AddAfter(Node, Rg); + } + else + { + FreeRegions.AddFirst(Rg); + } + } + TotalUsedSize += Size; return true; @@ -65,7 +92,7 @@ namespace Ryujinx.HLE.Memory Region NewRg = new Region(Position, Size); LinkedListNode Node = FreeRegions.First; - LinkedListNode PrevSz = Node; + LinkedListNode PrevSz = null; while (Node != null) { @@ -77,27 +104,38 @@ namespace Ryujinx.HLE.Memory if (Rg.Position == End) { + //Current region position matches the end of the freed region, + //just merge the two and remove the current region from the list. NewRg.Size += Rg.Size; FreeRegions.Remove(Node); } else if (RgEnd == Position) { + //End of the current region matches the position of the freed region, + //just merge the two and remove the current region from the list. NewRg.Position = Rg.Position; NewRg.Size += Rg.Size; FreeRegions.Remove(Node); } - else if ((ulong)Rg.Size < (ulong)NewRg.Size && - (ulong)Rg.Size > (ulong)PrevSz.Value.Size) + else { - PrevSz = Node; + if (PrevSz == null) + { + PrevSz = Node; + } + else if ((ulong)Rg.Size < (ulong)NewRg.Size && + (ulong)Rg.Size > (ulong)PrevSz.Value.Size) + { + PrevSz = Node; + } } Node = NextNode; } - if ((ulong)PrevSz.Value.Size < (ulong)Size) + if (PrevSz != null && (ulong)PrevSz.Value.Size < (ulong)Size) { FreeRegions.AddAfter(PrevSz, NewRg); } From 33e2810ef36fe0cf613aecd4c609f425aed02539 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Tue, 18 Sep 2018 13:27:12 -0300 Subject: [PATCH 07/14] Fix d32s8 format on OGLEnumConverter (#420) --- Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs index fac3875e56..187d1eece7 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs @@ -173,7 +173,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL case GalImageFormat.D24_S8 | GalImageFormat.Unorm: return (PixelInternalFormat.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248); case GalImageFormat.D32 | GalImageFormat.Sfloat: return (PixelInternalFormat.DepthComponent32f, PixelFormat.DepthComponent, PixelType.Float); case GalImageFormat.D16 | GalImageFormat.Unorm: return (PixelInternalFormat.DepthComponent16, PixelFormat.DepthComponent, PixelType.UnsignedShort); - case GalImageFormat.D32_S8 | GalImageFormat.Uint: return (PixelInternalFormat.Depth32fStencil8, PixelFormat.DepthStencil, PixelType.Float32UnsignedInt248Rev); + case GalImageFormat.D32_S8 | GalImageFormat.Sfloat: return (PixelInternalFormat.Depth32fStencil8, PixelFormat.DepthStencil, PixelType.Float32UnsignedInt248Rev); } throw new NotImplementedException($"{Format & GalImageFormat.FormatMask} {Format & GalImageFormat.TypeMask}"); From b8133c19971c7a2026af803003fafedbdb70488e Mon Sep 17 00:00:00 2001 From: gdkchan Date: Tue, 18 Sep 2018 20:36:43 -0300 Subject: [PATCH 08/14] Thread scheduler rewrite (#393) * Started to rewrite the thread scheduler * Add a single core-like scheduling mode, enabled by default * Clear exclusive monitor on context switch * Add SetThreadActivity, misc fixes * Implement WaitForAddress and SignalToAddress svcs, misc fixes * Misc fixes (on SetActivity and Arbiter), other tweaks * Rebased * Add missing null check * Rename multicore key on config, fix UpdatePriorityInheritance * Make scheduling data MLQs private * nit: Ordering --- ChocolArm64/AThread.cs | 28 +- ChocolArm64/Instruction/AInstEmitMemoryEx.cs | 3 + ChocolArm64/Memory/AMemory.cs | 28 +- ChocolArm64/State/AThreadState.cs | 21 + Ryujinx.HLE/HOS/Horizon.cs | 61 +- Ryujinx.HLE/HOS/Kernel/AddressArbiter.cs | 111 --- Ryujinx.HLE/HOS/Kernel/ArbitrationType.cs | 9 + Ryujinx.HLE/HOS/Kernel/HleCoreManager.cs | 29 + Ryujinx.HLE/HOS/Kernel/HleScheduler.cs | 140 +++ .../HOS/Kernel/IKFutureSchedulerObject.cs | 7 + Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs | 678 +++++++++++++ Ryujinx.HLE/HOS/Kernel/KCoreContext.cs | 67 ++ Ryujinx.HLE/HOS/Kernel/KEvent.cs | 36 +- Ryujinx.HLE/HOS/Kernel/KProcessScheduler.cs | 370 -------- Ryujinx.HLE/HOS/Kernel/KRecursiveLock.cs | 93 ++ Ryujinx.HLE/HOS/Kernel/KScheduler.cs | 235 +++++ Ryujinx.HLE/HOS/Kernel/KSchedulingData.cs | 207 ++++ Ryujinx.HLE/HOS/Kernel/KSynchronization.cs | 135 +++ .../HOS/Kernel/KSynchronizationObject.cs | 36 +- Ryujinx.HLE/HOS/Kernel/KThread.cs | 893 ++++++++++++++++-- Ryujinx.HLE/HOS/Kernel/KTimeManager.cs | 134 +++ Ryujinx.HLE/HOS/Kernel/KernelErr.cs | 3 +- Ryujinx.HLE/HOS/Kernel/NsTimeConverter.cs | 19 - Ryujinx.HLE/HOS/Kernel/SignalType.cs | 9 + Ryujinx.HLE/HOS/Kernel/SvcHandler.cs | 32 +- Ryujinx.HLE/HOS/Kernel/SvcSystem.cs | 155 +-- Ryujinx.HLE/HOS/Kernel/SvcThread.cs | 287 +++--- Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs | 518 ++++------ Ryujinx.HLE/HOS/Kernel/ThreadQueue.cs | 158 ---- Ryujinx.HLE/HOS/Kernel/ThreadSchedState.cs | 15 + Ryujinx.HLE/HOS/Process.cs | 45 +- .../HOS/Services/Am/IApplicationProxy.cs | 4 +- .../HOS/Services/Am/ICommonStateGetter.cs | 4 +- .../HOS/Services/Am/IHomeMenuFunctions.cs | 4 +- .../HOS/Services/Am/ILibraryAppletAccessor.cs | 6 +- .../HOS/Services/Am/ILibraryAppletCreator.cs | 2 +- .../HOS/Services/Am/ISelfController.cs | 6 +- .../HOS/Services/Am/ISystemAppletProxy.cs | 6 +- .../HOS/Services/Aud/AudioOut/IAudioOut.cs | 2 - .../Aud/AudioRenderer/IAudioRenderer.cs | 12 +- Ryujinx.HLE/HOS/Services/Aud/IAudioDevice.cs | 6 +- .../HOS/Services/Aud/IAudioOutManager.cs | 4 +- .../HOS/Services/Aud/IAudioRendererManager.cs | 8 +- Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs | 20 +- Ryujinx.HLE/HOS/Services/Nfp/IUser.cs | 8 +- Ryujinx.HLE/HOS/Services/Nfp/IUserManager.cs | 2 +- .../HOS/Services/Nifm/IGeneralService.cs | 2 +- Ryujinx.HLE/HOS/Services/Nifm/IRequest.cs | 23 +- Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs | 19 +- Ryujinx.HLE/HOS/Services/ServiceFactory.cs | 8 +- Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs | 2 +- .../Services/Vi/IApplicationDisplayService.cs | 8 +- .../HOS/Services/Vi/IHOSBinderDriver.cs | 8 +- Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs | 36 +- Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs | 24 +- Ryujinx/Config.cs | 13 +- Ryujinx/Ryujinx.conf | 3 + 57 files changed, 3262 insertions(+), 1540 deletions(-) delete mode 100644 Ryujinx.HLE/HOS/Kernel/AddressArbiter.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/ArbitrationType.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/HleCoreManager.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/HleScheduler.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/IKFutureSchedulerObject.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/KCoreContext.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KProcessScheduler.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/KRecursiveLock.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/KScheduler.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/KSchedulingData.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/KSynchronization.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/KTimeManager.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/NsTimeConverter.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/SignalType.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/ThreadQueue.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/ThreadSchedState.cs diff --git a/ChocolArm64/AThread.cs b/ChocolArm64/AThread.cs index 7b8360f8fe..76b36da4cf 100644 --- a/ChocolArm64/AThread.cs +++ b/ChocolArm64/AThread.cs @@ -10,11 +10,9 @@ namespace ChocolArm64 public AThreadState ThreadState { get; private set; } public AMemory Memory { get; private set; } - private long EntryPoint; - private ATranslator Translator; - private Thread Work; + public Thread Work; public event EventHandler WorkFinished; @@ -24,13 +22,21 @@ namespace ChocolArm64 { this.Translator = Translator; this.Memory = Memory; - this.EntryPoint = EntryPoint; ThreadState = new AThreadState(); ThreadState.ExecutionMode = AExecutionMode.AArch64; ThreadState.Running = true; + + Work = new Thread(delegate() + { + Translator.ExecuteSubroutine(this, EntryPoint); + + Memory.RemoveMonitor(ThreadState.Core); + + WorkFinished?.Invoke(this, EventArgs.Empty); + }); } public bool Execute() @@ -40,14 +46,7 @@ namespace ChocolArm64 return false; } - Work = new Thread(delegate() - { - Translator.ExecuteSubroutine(this, EntryPoint); - - Memory.RemoveMonitor(ThreadState); - - WorkFinished?.Invoke(this, EventArgs.Empty); - }); + Work.Name = "cpu_thread_" + Work.ManagedThreadId; Work.Start(); @@ -59,6 +58,11 @@ namespace ChocolArm64 ThreadState.Running = false; } + public void RequestInterrupt() + { + ThreadState.RequestInterrupt(); + } + public bool IsCurrentThread() { return Thread.CurrentThread == Work; diff --git a/ChocolArm64/Instruction/AInstEmitMemoryEx.cs b/ChocolArm64/Instruction/AInstEmitMemoryEx.cs index e59cadd4b8..cf19b4a105 100644 --- a/ChocolArm64/Instruction/AInstEmitMemoryEx.cs +++ b/ChocolArm64/Instruction/AInstEmitMemoryEx.cs @@ -1,5 +1,6 @@ using ChocolArm64.Decoder; using ChocolArm64.Memory; +using ChocolArm64.State; using ChocolArm64.Translation; using System; using System.Reflection.Emit; @@ -170,6 +171,8 @@ namespace ChocolArm64.Instruction Context.EmitLdarg(ATranslatedSub.MemoryArgIdx); Context.EmitLdarg(ATranslatedSub.StateArgIdx); + Context.EmitCallPropGet(typeof(AThreadState), nameof(AThreadState.Core)); + if (Rn != -1) { Context.EmitLdint(Rn); diff --git a/ChocolArm64/Memory/AMemory.cs b/ChocolArm64/Memory/AMemory.cs index 806a0b8608..2cb9b16c26 100644 --- a/ChocolArm64/Memory/AMemory.cs +++ b/ChocolArm64/Memory/AMemory.cs @@ -41,7 +41,7 @@ namespace ChocolArm64.Memory } } - private Dictionary Monitors; + private Dictionary Monitors; private ConcurrentDictionary ObservedPages; @@ -53,7 +53,7 @@ namespace ChocolArm64.Memory public AMemory(IntPtr Ram) { - Monitors = new Dictionary(); + Monitors = new Dictionary(); ObservedPages = new ConcurrentDictionary(); @@ -69,17 +69,17 @@ namespace ChocolArm64.Memory } } - public void RemoveMonitor(AThreadState State) + public void RemoveMonitor(int Core) { lock (Monitors) { - ClearExclusive(State); + ClearExclusive(Core); - Monitors.Remove(State); + Monitors.Remove(Core); } } - public void SetExclusive(AThreadState ThreadState, long Position) + public void SetExclusive(int Core, long Position) { Position &= ~ErgMask; @@ -93,11 +93,11 @@ namespace ChocolArm64.Memory } } - if (!Monitors.TryGetValue(ThreadState, out ArmMonitor ThreadMon)) + if (!Monitors.TryGetValue(Core, out ArmMonitor ThreadMon)) { ThreadMon = new ArmMonitor(); - Monitors.Add(ThreadState, ThreadMon); + Monitors.Add(Core, ThreadMon); } ThreadMon.Position = Position; @@ -105,7 +105,7 @@ namespace ChocolArm64.Memory } } - public bool TestExclusive(AThreadState ThreadState, long Position) + public bool TestExclusive(int Core, long Position) { //Note: Any call to this method also should be followed by a //call to ClearExclusiveForStore if this method returns true. @@ -113,7 +113,7 @@ namespace ChocolArm64.Memory Monitor.Enter(Monitors); - if (!Monitors.TryGetValue(ThreadState, out ArmMonitor ThreadMon)) + if (!Monitors.TryGetValue(Core, out ArmMonitor ThreadMon)) { return false; } @@ -128,9 +128,9 @@ namespace ChocolArm64.Memory return ExState; } - public void ClearExclusiveForStore(AThreadState ThreadState) + public void ClearExclusiveForStore(int Core) { - if (Monitors.TryGetValue(ThreadState, out ArmMonitor ThreadMon)) + if (Monitors.TryGetValue(Core, out ArmMonitor ThreadMon)) { ThreadMon.ExState = false; } @@ -138,11 +138,11 @@ namespace ChocolArm64.Memory Monitor.Exit(Monitors); } - public void ClearExclusive(AThreadState ThreadState) + public void ClearExclusive(int Core) { lock (Monitors) { - if (Monitors.TryGetValue(ThreadState, out ArmMonitor ThreadMon)) + if (Monitors.TryGetValue(Core, out ArmMonitor ThreadMon)) { ThreadMon.ExState = false; } diff --git a/ChocolArm64/State/AThreadState.cs b/ChocolArm64/State/AThreadState.cs index 7b69d81714..783f5a12be 100644 --- a/ChocolArm64/State/AThreadState.cs +++ b/ChocolArm64/State/AThreadState.cs @@ -41,6 +41,9 @@ namespace ChocolArm64.State public bool Negative; public bool Running { get; set; } + public int Core { get; set; } + + private bool Interrupted; public long TpidrEl0 { get; set; } public long Tpidr { get; set; } @@ -73,6 +76,7 @@ namespace ChocolArm64.State } } + public event EventHandler Interrupt; public event EventHandler Break; public event EventHandler SvcCall; public event EventHandler Undefined; @@ -99,9 +103,26 @@ namespace ChocolArm64.State internal bool Synchronize() { + if (Interrupted) + { + Interrupted = false; + + OnInterrupt(); + } + return Running; } + internal void RequestInterrupt() + { + Interrupted = true; + } + + private void OnInterrupt() + { + Interrupt?.Invoke(this, EventArgs.Empty); + } + internal void OnBreak(long Position, int Imm) { Break?.Invoke(this, new AInstExceptionEventArgs(Position, Imm)); diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index 2e216cdf18..c7a824c03f 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -7,6 +7,7 @@ using Ryujinx.HLE.Loaders.Npdm; using Ryujinx.HLE.Logging; using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.IO; using System.Linq; @@ -19,12 +20,22 @@ namespace Ryujinx.HLE.HOS private Switch Device; - private KProcessScheduler Scheduler; - private ConcurrentDictionary Processes; public SystemStateMgr State { get; private set; } + internal KRecursiveLock CriticalSectionLock { get; private set; } + + internal KScheduler Scheduler { get; private set; } + + internal KTimeManager TimeManager { get; private set; } + + internal KAddressArbiter AddressArbiter { get; private set; } + + internal KSynchronization Synchronization { get; private set; } + + internal LinkedList Withholders { get; private set; } + internal KSharedMemory HidSharedMem { get; private set; } internal KSharedMemory FontSharedMem { get; private set; } @@ -34,16 +45,28 @@ namespace Ryujinx.HLE.HOS internal Keyset KeySet { get; private set; } + private bool HasStarted; + public Horizon(Switch Device) { this.Device = Device; - Scheduler = new KProcessScheduler(Device.Log); - Processes = new ConcurrentDictionary(); State = new SystemStateMgr(); + CriticalSectionLock = new KRecursiveLock(this); + + Scheduler = new KScheduler(this); + + TimeManager = new KTimeManager(); + + AddressArbiter = new KAddressArbiter(this); + + Synchronization = new KSynchronization(this); + + Withholders = new LinkedList(); + if (!Device.Memory.Allocator.TryAllocate(HidSize, out long HidPA) || !Device.Memory.Allocator.TryAllocate(FontSize, out long FontPA)) { @@ -55,7 +78,7 @@ namespace Ryujinx.HLE.HOS Font = new SharedFontManager(Device, FontSharedMem.PA); - VsyncEvent = new KEvent(); + VsyncEvent = new KEvent(this); LoadKeySet(); } @@ -371,10 +394,15 @@ namespace Ryujinx.HLE.HOS } } - public void SignalVsync() => VsyncEvent.WaitEvent.Set(); + public void SignalVsync() + { + VsyncEvent.Signal(); + } private Process MakeProcess(Npdm MetaData = null) { + HasStarted = true; + Process Process; lock (Processes) @@ -386,7 +414,7 @@ namespace Ryujinx.HLE.HOS ProcessId++; } - Process = new Process(Device, Scheduler, ProcessId, MetaData); + Process = new Process(Device, ProcessId, MetaData); Processes.TryAdd(ProcessId, Process); } @@ -409,18 +437,29 @@ namespace Ryujinx.HLE.HOS if (Processes.Count == 0) { - Unload(); + Scheduler.Dispose(); + + TimeManager.Dispose(); Device.Unload(); } } } - private void Unload() + public void EnableMultiCoreScheduling() { - VsyncEvent.Dispose(); + if (!HasStarted) + { + Scheduler.MultiCoreScheduling = true; + } + } - Scheduler.Dispose(); + public void DisableMultiCoreScheduling() + { + if (!HasStarted) + { + Scheduler.MultiCoreScheduling = false; + } } public void Dispose() diff --git a/Ryujinx.HLE/HOS/Kernel/AddressArbiter.cs b/Ryujinx.HLE/HOS/Kernel/AddressArbiter.cs deleted file mode 100644 index d7df0a727d..0000000000 --- a/Ryujinx.HLE/HOS/Kernel/AddressArbiter.cs +++ /dev/null @@ -1,111 +0,0 @@ -using ChocolArm64.Memory; -using ChocolArm64.State; - -using static Ryujinx.HLE.HOS.ErrorCode; - -namespace Ryujinx.HLE.HOS.Kernel -{ - static class AddressArbiter - { - static ulong WaitForAddress(Process Process, AThreadState ThreadState, long Address, ulong Timeout) - { - KThread CurrentThread = Process.GetThread(ThreadState.Tpidr); - - Process.Scheduler.SetReschedule(CurrentThread.ProcessorId); - - CurrentThread.ArbiterWaitAddress = Address; - CurrentThread.ArbiterSignaled = false; - - Process.Scheduler.EnterWait(CurrentThread, NsTimeConverter.GetTimeMs(Timeout)); - - if (!CurrentThread.ArbiterSignaled) - { - return MakeError(ErrorModule.Kernel, KernelErr.Timeout); - } - - return 0; - } - - public static ulong WaitForAddressIfLessThan(Process Process, - AThreadState ThreadState, - AMemory Memory, - long Address, - int Value, - ulong Timeout, - bool ShouldDecrement) - { - Memory.SetExclusive(ThreadState, Address); - - int CurrentValue = Memory.ReadInt32(Address); - - while (true) - { - if (Memory.TestExclusive(ThreadState, Address)) - { - if (CurrentValue < Value) - { - if (ShouldDecrement) - { - Memory.WriteInt32(Address, CurrentValue - 1); - } - - Memory.ClearExclusiveForStore(ThreadState); - } - else - { - Memory.ClearExclusiveForStore(ThreadState); - - return MakeError(ErrorModule.Kernel, KernelErr.InvalidState); - } - - break; - } - - Memory.SetExclusive(ThreadState, Address); - - CurrentValue = Memory.ReadInt32(Address); - } - - if (Timeout == 0) - { - return MakeError(ErrorModule.Kernel, KernelErr.Timeout); - } - - return WaitForAddress(Process, ThreadState, Address, Timeout); - } - - public static ulong WaitForAddressIfEqual(Process Process, - AThreadState ThreadState, - AMemory Memory, - long Address, - int Value, - ulong Timeout) - { - if (Memory.ReadInt32(Address) != Value) - { - return MakeError(ErrorModule.Kernel, KernelErr.InvalidState); - } - - if (Timeout == 0) - { - return MakeError(ErrorModule.Kernel, KernelErr.Timeout); - } - - return WaitForAddress(Process, ThreadState, Address, Timeout); - } - } - - enum ArbitrationType : int - { - WaitIfLessThan, - DecrementAndWaitIfLessThan, - WaitIfEqual - } - - enum SignalType : int - { - Signal, - IncrementAndSignalIfEqual, - ModifyByWaitingCountAndSignalIfEqual - } -} diff --git a/Ryujinx.HLE/HOS/Kernel/ArbitrationType.cs b/Ryujinx.HLE/HOS/Kernel/ArbitrationType.cs new file mode 100644 index 0000000000..8a2d47f7b0 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/ArbitrationType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Kernel +{ + enum ArbitrationType + { + WaitIfLessThan = 0, + DecrementAndWaitIfLessThan = 1, + WaitIfEqual = 2 + } +} diff --git a/Ryujinx.HLE/HOS/Kernel/HleCoreManager.cs b/Ryujinx.HLE/HOS/Kernel/HleCoreManager.cs new file mode 100644 index 0000000000..0bfa2710cd --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/HleCoreManager.cs @@ -0,0 +1,29 @@ +using System.Collections.Concurrent; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel +{ + class HleCoreManager + { + private ConcurrentDictionary Threads; + + public HleCoreManager() + { + Threads = new ConcurrentDictionary(); + } + + public ManualResetEvent GetThread(Thread Thread) + { + return Threads.GetOrAdd(Thread, (Key) => new ManualResetEvent(false)); + } + + public void RemoveThread(Thread Thread) + { + if (Threads.TryRemove(Thread, out ManualResetEvent Event)) + { + Event.Set(); + Event.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/HleScheduler.cs b/Ryujinx.HLE/HOS/Kernel/HleScheduler.cs new file mode 100644 index 0000000000..42caeca2d5 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/HleScheduler.cs @@ -0,0 +1,140 @@ +using System; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel +{ + partial class KScheduler + { + private const int RoundRobinTimeQuantumMs = 10; + + private int CurrentCore; + + public bool MultiCoreScheduling { get; set; } + + private HleCoreManager CoreManager; + + private bool KeepPreempting; + + public void ContextSwitch() + { + lock (CoreContexts) + { + if (MultiCoreScheduling) + { + int SelectedCount = 0; + + for (int Core = 0; Core < KScheduler.CpuCoresCount; Core++) + { + KCoreContext CoreContext = CoreContexts[Core]; + + if (CoreContext.ContextSwitchNeeded && (CoreContext.CurrentThread?.Context.IsCurrentThread() ?? false)) + { + CoreContext.ContextSwitch(); + } + + if (CoreContext.CurrentThread?.Context.IsCurrentThread() ?? false) + { + SelectedCount++; + } + } + + if (SelectedCount == 0) + { + CoreManager.GetThread(Thread.CurrentThread).Reset(); + } + else if (SelectedCount == 1) + { + CoreManager.GetThread(Thread.CurrentThread).Set(); + } + else + { + throw new InvalidOperationException("Thread scheduled in more than one core!"); + } + } + else + { + KThread CurrentThread = CoreContexts[CurrentCore].CurrentThread; + + bool HasThreadExecuting = CurrentThread != null; + + if (HasThreadExecuting) + { + //If this is not the thread that is currently executing, we need + //to request an interrupt to allow safely starting another thread. + if (!CurrentThread.Context.IsCurrentThread()) + { + CurrentThread.Context.RequestInterrupt(); + + return; + } + + CoreManager.GetThread(CurrentThread.Context.Work).Reset(); + } + + //Advance current core and try picking a thread, + //keep advancing if it is null. + for (int Core = 0; Core < 4; Core++) + { + CurrentCore = (CurrentCore + 1) % CpuCoresCount; + + KCoreContext CoreContext = CoreContexts[CurrentCore]; + + CoreContext.UpdateCurrentThread(); + + if (CoreContext.CurrentThread != null) + { + CoreContext.CurrentThread.ClearExclusive(); + + CoreManager.GetThread(CoreContext.CurrentThread.Context.Work).Set(); + + CoreContext.CurrentThread.Context.Execute(); + + break; + } + } + + //If nothing was running before, then we are on a "external" + //HLE thread, we don't need to wait. + if (!HasThreadExecuting) + { + return; + } + } + } + + CoreManager.GetThread(Thread.CurrentThread).WaitOne(); + } + + private void PreemptCurrentThread() + { + //Preempts current thread every 10 milliseconds on a round-robin fashion, + //when multi core scheduling is disabled, to try ensuring that all threads + //gets a chance to run. + while (KeepPreempting) + { + lock (CoreContexts) + { + KThread CurrentThread = CoreContexts[CurrentCore].CurrentThread; + + CurrentThread?.Context.RequestInterrupt(); + } + + PreemptThreads(); + + Thread.Sleep(RoundRobinTimeQuantumMs); + } + } + + public void StopThread(KThread Thread) + { + Thread.Context.StopExecution(); + + CoreManager.GetThread(Thread.Context.Work).Set(); + } + + public void RemoveThread(KThread Thread) + { + CoreManager.RemoveThread(Thread.Context.Work); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/IKFutureSchedulerObject.cs b/Ryujinx.HLE/HOS/Kernel/IKFutureSchedulerObject.cs new file mode 100644 index 0000000000..6a255e6552 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/IKFutureSchedulerObject.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Kernel +{ + interface IKFutureSchedulerObject + { + void TimeUp(); + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs b/Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs new file mode 100644 index 0000000000..f2156a5c20 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs @@ -0,0 +1,678 @@ +using ChocolArm64.Memory; +using System.Collections.Generic; +using System.Linq; + +using static Ryujinx.HLE.HOS.ErrorCode; + +namespace Ryujinx.HLE.HOS.Kernel +{ + class KAddressArbiter + { + private const int HasListenersMask = 0x40000000; + + private Horizon System; + + public List CondVarThreads; + public List ArbiterThreads; + + public KAddressArbiter(Horizon System) + { + this.System = System; + + CondVarThreads = new List(); + ArbiterThreads = new List(); + } + + public long ArbitrateLock( + Process Process, + AMemory Memory, + int OwnerHandle, + long MutexAddress, + int RequesterHandle) + { + System.CriticalSectionLock.Lock(); + + KThread CurrentThread = System.Scheduler.GetCurrentThread(); + + CurrentThread.SignaledObj = null; + CurrentThread.ObjSyncResult = 0; + + if (!UserToKernelInt32(Memory, MutexAddress, out int MutexValue)) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);; + } + + if (MutexValue != (OwnerHandle | HasListenersMask)) + { + System.CriticalSectionLock.Unlock(); + + return 0; + } + + KThread MutexOwner = Process.HandleTable.GetData(OwnerHandle); + + if (MutexOwner == null) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); + } + + CurrentThread.MutexAddress = MutexAddress; + CurrentThread.ThreadHandleForUserMutex = RequesterHandle; + + MutexOwner.AddMutexWaiter(CurrentThread); + + CurrentThread.Reschedule(ThreadSchedState.Paused); + + System.CriticalSectionLock.Unlock(); + System.CriticalSectionLock.Lock(); + + if (CurrentThread.MutexOwner != null) + { + CurrentThread.MutexOwner.RemoveMutexWaiter(CurrentThread); + } + + System.CriticalSectionLock.Unlock(); + + return (uint)CurrentThread.ObjSyncResult; + } + + public long ArbitrateUnlock(AMemory Memory, long MutexAddress) + { + System.CriticalSectionLock.Lock(); + + KThread CurrentThread = System.Scheduler.GetCurrentThread(); + + (long Result, KThread NewOwnerThread) = MutexUnlock(Memory, CurrentThread, MutexAddress); + + if (Result != 0 && NewOwnerThread != null) + { + NewOwnerThread.SignaledObj = null; + NewOwnerThread.ObjSyncResult = (int)Result; + } + + System.CriticalSectionLock.Unlock(); + + return Result; + } + + public long WaitProcessWideKeyAtomic( + AMemory Memory, + long MutexAddress, + long CondVarAddress, + int ThreadHandle, + long Timeout) + { + System.CriticalSectionLock.Lock(); + + KThread CurrentThread = System.Scheduler.GetCurrentThread(); + + CurrentThread.SignaledObj = null; + CurrentThread.ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.Timeout); + + if (CurrentThread.ShallBeTerminated || + CurrentThread.SchedFlags == ThreadSchedState.TerminationPending) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating); + } + + (long Result, _) = MutexUnlock(Memory, CurrentThread, MutexAddress); + + if (Result != 0) + { + System.CriticalSectionLock.Unlock(); + + return Result; + } + + CurrentThread.MutexAddress = MutexAddress; + CurrentThread.ThreadHandleForUserMutex = ThreadHandle; + CurrentThread.CondVarAddress = CondVarAddress; + + CondVarThreads.Add(CurrentThread); + + if (Timeout != 0) + { + CurrentThread.Reschedule(ThreadSchedState.Paused); + + if (Timeout > 0) + { + System.TimeManager.ScheduleFutureInvocation(CurrentThread, Timeout); + } + } + + System.CriticalSectionLock.Unlock(); + + if (Timeout > 0) + { + System.TimeManager.UnscheduleFutureInvocation(CurrentThread); + } + + System.CriticalSectionLock.Lock(); + + if (CurrentThread.MutexOwner != null) + { + CurrentThread.MutexOwner.RemoveMutexWaiter(CurrentThread); + } + + CondVarThreads.Remove(CurrentThread); + + System.CriticalSectionLock.Unlock(); + + return (uint)CurrentThread.ObjSyncResult; + } + + private (long, KThread) MutexUnlock(AMemory Memory, KThread CurrentThread, long MutexAddress) + { + KThread NewOwnerThread = CurrentThread.RelinquishMutex(MutexAddress, out int Count); + + int MutexValue = 0; + + if (NewOwnerThread != null) + { + MutexValue = NewOwnerThread.ThreadHandleForUserMutex; + + if (Count >= 2) + { + MutexValue |= HasListenersMask; + } + + NewOwnerThread.SignaledObj = null; + NewOwnerThread.ObjSyncResult = 0; + + NewOwnerThread.ReleaseAndResume(); + } + + long Result = 0; + + if (!KernelToUserInt32(Memory, MutexAddress, MutexValue)) + { + Result = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); + } + + return (Result, NewOwnerThread); + } + + public void SignalProcessWideKey(Process Process, AMemory Memory, long Address, int Count) + { + Queue SignaledThreads = new Queue(); + + System.CriticalSectionLock.Lock(); + + IOrderedEnumerable SortedThreads = CondVarThreads.OrderBy(x => x.DynamicPriority); + + foreach (KThread Thread in SortedThreads.Where(x => x.CondVarAddress == Address)) + { + TryAcquireMutex(Process, Memory, Thread); + + SignaledThreads.Enqueue(Thread); + + //If the count is <= 0, we should signal all threads waiting. + if (Count >= 1 && --Count == 0) + { + break; + } + } + + while (SignaledThreads.TryDequeue(out KThread Thread)) + { + CondVarThreads.Remove(Thread); + } + + System.CriticalSectionLock.Unlock(); + } + + private KThread TryAcquireMutex(Process Process, AMemory Memory, KThread Requester) + { + long Address = Requester.MutexAddress; + + Memory.SetExclusive(0, Address); + + if (!UserToKernelInt32(Memory, Address, out int MutexValue)) + { + //Invalid address. + Memory.ClearExclusive(0); + + Requester.SignaledObj = null; + Requester.ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); + + return null; + } + + while (true) + { + if (Memory.TestExclusive(0, Address)) + { + if (MutexValue != 0) + { + //Update value to indicate there is a mutex waiter now. + Memory.WriteInt32(Address, MutexValue | HasListenersMask); + } + else + { + //No thread owning the mutex, assign to requesting thread. + Memory.WriteInt32(Address, Requester.ThreadHandleForUserMutex); + } + + Memory.ClearExclusiveForStore(0); + + break; + } + + Memory.SetExclusive(0, Address); + + MutexValue = Memory.ReadInt32(Address); + } + + if (MutexValue == 0) + { + //We now own the mutex. + Requester.SignaledObj = null; + Requester.ObjSyncResult = 0; + + Requester.ReleaseAndResume(); + + return null; + } + + MutexValue &= ~HasListenersMask; + + KThread MutexOwner = Process.HandleTable.GetData(MutexValue); + + if (MutexOwner != null) + { + //Mutex already belongs to another thread, wait for it. + MutexOwner.AddMutexWaiter(Requester); + } + else + { + //Invalid mutex owner. + Requester.SignaledObj = null; + Requester.ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); + + Requester.ReleaseAndResume(); + } + + return MutexOwner; + } + + public long WaitForAddressIfEqual(AMemory Memory, long Address, int Value, long Timeout) + { + KThread CurrentThread = System.Scheduler.GetCurrentThread(); + + System.CriticalSectionLock.Lock(); + + if (CurrentThread.ShallBeTerminated || + CurrentThread.SchedFlags == ThreadSchedState.TerminationPending) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating); + } + + CurrentThread.SignaledObj = null; + CurrentThread.ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.Timeout); + + if (!UserToKernelInt32(Memory, Address, out int CurrentValue)) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); + } + + if (CurrentValue == Value) + { + if (Timeout == 0) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.Timeout); + } + + CurrentThread.MutexAddress = Address; + CurrentThread.WaitingInArbitration = true; + + InsertSortedByPriority(ArbiterThreads, CurrentThread); + + CurrentThread.Reschedule(ThreadSchedState.Paused); + + if (Timeout > 0) + { + System.TimeManager.ScheduleFutureInvocation(CurrentThread, Timeout); + } + + System.CriticalSectionLock.Unlock(); + + if (Timeout > 0) + { + System.TimeManager.UnscheduleFutureInvocation(CurrentThread); + } + + System.CriticalSectionLock.Lock(); + + if (CurrentThread.WaitingInArbitration) + { + ArbiterThreads.Remove(CurrentThread); + + CurrentThread.WaitingInArbitration = false; + } + + System.CriticalSectionLock.Unlock(); + + return CurrentThread.ObjSyncResult; + } + + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.InvalidState); + } + + public long WaitForAddressIfLessThan( + AMemory Memory, + long Address, + int Value, + bool ShouldDecrement, + long Timeout) + { + KThread CurrentThread = System.Scheduler.GetCurrentThread(); + + System.CriticalSectionLock.Lock(); + + if (CurrentThread.ShallBeTerminated || + CurrentThread.SchedFlags == ThreadSchedState.TerminationPending) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating); + } + + CurrentThread.SignaledObj = null; + CurrentThread.ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.Timeout); + + //If ShouldDecrement is true, do atomic decrement of the value at Address. + Memory.SetExclusive(0, Address); + + if (!UserToKernelInt32(Memory, Address, out int CurrentValue)) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); + } + + if (ShouldDecrement) + { + while (CurrentValue < Value) + { + if (Memory.TestExclusive(0, Address)) + { + Memory.WriteInt32(Address, CurrentValue - 1); + + Memory.ClearExclusiveForStore(0); + + break; + } + + Memory.SetExclusive(0, Address); + + CurrentValue = Memory.ReadInt32(Address); + } + } + + Memory.ClearExclusive(0); + + if (CurrentValue < Value) + { + if (Timeout == 0) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.Timeout); + } + + CurrentThread.MutexAddress = Address; + CurrentThread.WaitingInArbitration = true; + + InsertSortedByPriority(ArbiterThreads, CurrentThread); + + CurrentThread.Reschedule(ThreadSchedState.Paused); + + if (Timeout > 0) + { + System.TimeManager.ScheduleFutureInvocation(CurrentThread, Timeout); + } + + System.CriticalSectionLock.Unlock(); + + if (Timeout > 0) + { + System.TimeManager.UnscheduleFutureInvocation(CurrentThread); + } + + System.CriticalSectionLock.Lock(); + + if (CurrentThread.WaitingInArbitration) + { + ArbiterThreads.Remove(CurrentThread); + + CurrentThread.WaitingInArbitration = false; + } + + System.CriticalSectionLock.Unlock(); + + return CurrentThread.ObjSyncResult; + } + + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.InvalidState); + } + + private void InsertSortedByPriority(List Threads, KThread Thread) + { + int NextIndex = -1; + + for (int Index = 0; Index < Threads.Count; Index++) + { + if (Threads[Index].DynamicPriority > Thread.DynamicPriority) + { + NextIndex = Index; + + break; + } + } + + if (NextIndex != -1) + { + Threads.Insert(NextIndex, Thread); + } + else + { + Threads.Add(Thread); + } + } + + public long Signal(long Address, int Count) + { + System.CriticalSectionLock.Lock(); + + WakeArbiterThreads(Address, Count); + + System.CriticalSectionLock.Unlock(); + + return 0; + } + + public long SignalAndIncrementIfEqual(AMemory Memory, long Address, int Value, int Count) + { + System.CriticalSectionLock.Lock(); + + Memory.SetExclusive(0, Address); + + if (!UserToKernelInt32(Memory, Address, out int CurrentValue)) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); + } + + while (CurrentValue == Value) + { + if (Memory.TestExclusive(0, Address)) + { + Memory.WriteInt32(Address, CurrentValue + 1); + + Memory.ClearExclusiveForStore(0); + + break; + } + + Memory.SetExclusive(0, Address); + + CurrentValue = Memory.ReadInt32(Address); + } + + Memory.ClearExclusive(0); + + if (CurrentValue != Value) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.InvalidState); + } + + WakeArbiterThreads(Address, Count); + + System.CriticalSectionLock.Unlock(); + + return 0; + } + + public long SignalAndModifyIfEqual(AMemory Memory, long Address, int Value, int Count) + { + System.CriticalSectionLock.Lock(); + + int Offset; + + //The value is decremented if the number of threads waiting is less + //or equal to the Count of threads to be signaled, or Count is zero + //or negative. It is incremented if there are no threads waiting. + int WaitingCount = 0; + + foreach (KThread Thread in ArbiterThreads.Where(x => x.MutexAddress == Address)) + { + if (++WaitingCount > Count) + { + break; + } + } + + if (WaitingCount > 0) + { + Offset = WaitingCount <= Count || Count <= 0 ? -1 : 0; + } + else + { + Offset = 1; + } + + Memory.SetExclusive(0, Address); + + if (!UserToKernelInt32(Memory, Address, out int CurrentValue)) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); + } + + while (CurrentValue == Value) + { + if (Memory.TestExclusive(0, Address)) + { + Memory.WriteInt32(Address, CurrentValue + Offset); + + Memory.ClearExclusiveForStore(0); + + break; + } + + Memory.SetExclusive(0, Address); + + CurrentValue = Memory.ReadInt32(Address); + } + + Memory.ClearExclusive(0); + + if (CurrentValue != Value) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.InvalidState); + } + + WakeArbiterThreads(Address, Count); + + System.CriticalSectionLock.Unlock(); + + return 0; + } + + private void WakeArbiterThreads(long Address, int Count) + { + Queue SignaledThreads = new Queue(); + + foreach (KThread Thread in ArbiterThreads.Where(x => x.MutexAddress == Address)) + { + SignaledThreads.Enqueue(Thread); + + //If the count is <= 0, we should signal all threads waiting. + if (Count >= 1 && --Count == 0) + { + break; + } + } + + while (SignaledThreads.TryDequeue(out KThread Thread)) + { + Thread.SignaledObj = null; + Thread.ObjSyncResult = 0; + + Thread.ReleaseAndResume(); + + Thread.WaitingInArbitration = false; + + ArbiterThreads.Remove(Thread); + } + } + + private bool UserToKernelInt32(AMemory Memory, long Address, out int Value) + { + if (Memory.IsMapped(Address)) + { + Value = Memory.ReadInt32(Address); + + return true; + } + + Value = 0; + + return false; + } + + private bool KernelToUserInt32(AMemory Memory, long Address, int Value) + { + if (Memory.IsMapped(Address)) + { + Memory.WriteInt32ToSharedAddr(Address, Value); + + return true; + } + + return false; + } + } +} diff --git a/Ryujinx.HLE/HOS/Kernel/KCoreContext.cs b/Ryujinx.HLE/HOS/Kernel/KCoreContext.cs new file mode 100644 index 0000000000..70fe1a6141 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KCoreContext.cs @@ -0,0 +1,67 @@ +using System; + +namespace Ryujinx.HLE.HOS.Kernel +{ + class KCoreContext + { + private KScheduler Scheduler; + + private HleCoreManager CoreManager; + + public bool ContextSwitchNeeded { get; private set; } + + public KThread CurrentThread { get; private set; } + public KThread SelectedThread { get; private set; } + + public KCoreContext(KScheduler Scheduler, HleCoreManager CoreManager) + { + this.Scheduler = Scheduler; + this.CoreManager = CoreManager; + } + + public void SelectThread(KThread Thread) + { + SelectedThread = Thread; + + if (Thread != null) + { + Thread.LastScheduledTicks = (uint)Environment.TickCount; + } + + ContextSwitchNeeded = true; + } + + public void UpdateCurrentThread() + { + ContextSwitchNeeded = false; + + CurrentThread = SelectedThread; + } + + public void ContextSwitch() + { + ContextSwitchNeeded = false; + + if (CurrentThread != null) + { + CoreManager.GetThread(CurrentThread.Context.Work).Reset(); + } + + CurrentThread = SelectedThread; + + if (CurrentThread != null) + { + CurrentThread.ClearExclusive(); + + CoreManager.GetThread(CurrentThread.Context.Work).Set(); + + CurrentThread.Context.Execute(); + } + } + + public void RemoveThread(KThread Thread) + { + //TODO. + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KEvent.cs b/Ryujinx.HLE/HOS/Kernel/KEvent.cs index eaaafaba33..1a865aa202 100644 --- a/Ryujinx.HLE/HOS/Kernel/KEvent.cs +++ b/Ryujinx.HLE/HOS/Kernel/KEvent.cs @@ -1,4 +1,38 @@ namespace Ryujinx.HLE.HOS.Kernel { - class KEvent : KSynchronizationObject { } + class KEvent : KSynchronizationObject + { + private bool Signaled; + + public string Name { get; private set; } + + public KEvent(Horizon System, string Name = "") : base(System) + { + this.Name = Name; + } + + public override void Signal() + { + System.CriticalSectionLock.Lock(); + + if (!Signaled) + { + Signaled = true; + + base.Signal(); + } + + System.CriticalSectionLock.Unlock(); + } + + public void Reset() + { + Signaled = false; + } + + public override bool IsSignaled() + { + return Signaled; + } + } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KProcessScheduler.cs b/Ryujinx.HLE/HOS/Kernel/KProcessScheduler.cs deleted file mode 100644 index 2120f16c8e..0000000000 --- a/Ryujinx.HLE/HOS/Kernel/KProcessScheduler.cs +++ /dev/null @@ -1,370 +0,0 @@ -using Ryujinx.HLE.Logging; -using System; -using System.Collections.Concurrent; -using System.Threading; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class KProcessScheduler : IDisposable - { - private ConcurrentDictionary AllThreads; - - private ThreadQueue WaitingToRun; - - private KThread[] CoreThreads; - - private bool[] CoreReschedule; - - private object SchedLock; - - private Logger Log; - - public KProcessScheduler(Logger Log) - { - this.Log = Log; - - AllThreads = new ConcurrentDictionary(); - - WaitingToRun = new ThreadQueue(); - - CoreThreads = new KThread[4]; - - CoreReschedule = new bool[4]; - - SchedLock = new object(); - } - - public void StartThread(KThread Thread) - { - lock (SchedLock) - { - SchedulerThread SchedThread = new SchedulerThread(Thread); - - if (!AllThreads.TryAdd(Thread, SchedThread)) - { - return; - } - - if (TryAddToCore(Thread)) - { - Thread.Thread.Execute(); - - PrintDbgThreadInfo(Thread, "running."); - } - else - { - WaitingToRun.Push(SchedThread); - - PrintDbgThreadInfo(Thread, "waiting to run."); - } - } - } - - public void RemoveThread(KThread Thread) - { - PrintDbgThreadInfo(Thread, "exited."); - - lock (SchedLock) - { - if (AllThreads.TryRemove(Thread, out SchedulerThread SchedThread)) - { - WaitingToRun.Remove(SchedThread); - - SchedThread.Dispose(); - } - - int ActualCore = Thread.ActualCore; - - SchedulerThread NewThread = WaitingToRun.Pop(ActualCore); - - if (NewThread == null) - { - Log.PrintDebug(LogClass.KernelScheduler, $"Nothing to run on core {ActualCore}!"); - - CoreThreads[ActualCore] = null; - - return; - } - - NewThread.Thread.ActualCore = ActualCore; - - RunThread(NewThread); - } - } - - public void SetThreadActivity(KThread Thread, bool Active) - { - SchedulerThread SchedThread = AllThreads[Thread]; - - SchedThread.IsActive = Active; - - if (Active) - { - SchedThread.WaitActivity.Set(); - } - else - { - SchedThread.WaitActivity.Reset(); - } - } - - public void EnterWait(KThread Thread, int TimeoutMs = Timeout.Infinite) - { - SchedulerThread SchedThread = AllThreads[Thread]; - - Suspend(Thread); - - SchedThread.WaitSync.WaitOne(TimeoutMs); - - TryResumingExecution(SchedThread); - } - - public void WakeUp(KThread Thread) - { - AllThreads[Thread].WaitSync.Set(); - } - - public void ForceWakeUp(KThread Thread) - { - if (AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread)) - { - SchedThread.WaitSync.Set(); - SchedThread.WaitActivity.Set(); - SchedThread.WaitSched.Set(); - } - } - - public void ChangeCore(KThread Thread, int IdealCore, int CoreMask) - { - lock (SchedLock) - { - if (IdealCore != -3) - { - Thread.IdealCore = IdealCore; - } - - Thread.CoreMask = CoreMask; - - if (AllThreads.ContainsKey(Thread)) - { - SetReschedule(Thread.ActualCore); - - SchedulerThread SchedThread = AllThreads[Thread]; - - //Note: Aways if the thread is on the queue first, and try - //adding to a new core later, to ensure that a thread that - //is already running won't be added to another core. - if (WaitingToRun.HasThread(SchedThread) && TryAddToCore(Thread)) - { - WaitingToRun.Remove(SchedThread); - - RunThread(SchedThread); - } - } - } - } - - public void Suspend(KThread Thread) - { - lock (SchedLock) - { - PrintDbgThreadInfo(Thread, "suspended."); - - int ActualCore = Thread.ActualCore; - - CoreReschedule[ActualCore] = false; - - SchedulerThread SchedThread = WaitingToRun.Pop(ActualCore); - - if (SchedThread != null) - { - SchedThread.Thread.ActualCore = ActualCore; - - CoreThreads[ActualCore] = SchedThread.Thread; - - RunThread(SchedThread); - } - else - { - Log.PrintDebug(LogClass.KernelScheduler, $"Nothing to run on core {Thread.ActualCore}!"); - - CoreThreads[ActualCore] = null; - } - } - } - - public void SetReschedule(int Core) - { - lock (SchedLock) - { - CoreReschedule[Core] = true; - } - } - - public void Reschedule(KThread Thread) - { - bool NeedsReschedule; - - lock (SchedLock) - { - int ActualCore = Thread.ActualCore; - - NeedsReschedule = CoreReschedule[ActualCore]; - - CoreReschedule[ActualCore] = false; - } - - if (NeedsReschedule) - { - Yield(Thread, Thread.ActualPriority - 1); - } - } - - public void Yield(KThread Thread) - { - Yield(Thread, Thread.ActualPriority); - } - - private void Yield(KThread Thread, int MinPriority) - { - PrintDbgThreadInfo(Thread, "yielded execution."); - - lock (SchedLock) - { - int ActualCore = Thread.ActualCore; - - SchedulerThread NewThread = WaitingToRun.Pop(ActualCore, MinPriority); - - if (NewThread != null) - { - NewThread.Thread.ActualCore = ActualCore; - - CoreThreads[ActualCore] = NewThread.Thread; - - RunThread(NewThread); - } - else - { - CoreThreads[ActualCore] = null; - } - } - - Resume(Thread); - } - - public void Resume(KThread Thread) - { - TryResumingExecution(AllThreads[Thread]); - } - - private void TryResumingExecution(SchedulerThread SchedThread) - { - KThread Thread = SchedThread.Thread; - - PrintDbgThreadInfo(Thread, "trying to resume..."); - - SchedThread.WaitActivity.WaitOne(); - - lock (SchedLock) - { - if (TryAddToCore(Thread)) - { - PrintDbgThreadInfo(Thread, "resuming execution..."); - - return; - } - - WaitingToRun.Push(SchedThread); - - SetReschedule(Thread.ProcessorId); - - PrintDbgThreadInfo(Thread, "entering wait state..."); - } - - SchedThread.WaitSched.WaitOne(); - - PrintDbgThreadInfo(Thread, "resuming execution..."); - } - - private void RunThread(SchedulerThread SchedThread) - { - if (!SchedThread.Thread.Thread.Execute()) - { - PrintDbgThreadInfo(SchedThread.Thread, "waked."); - - SchedThread.WaitSched.Set(); - } - else - { - PrintDbgThreadInfo(SchedThread.Thread, "running."); - } - } - - public void Resort(KThread Thread) - { - if (AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread)) - { - WaitingToRun.Resort(SchedThread); - } - } - - private bool TryAddToCore(KThread Thread) - { - //First, try running it on Ideal Core. - int IdealCore = Thread.IdealCore; - - if (IdealCore != -1 && CoreThreads[IdealCore] == null) - { - Thread.ActualCore = IdealCore; - - CoreThreads[IdealCore] = Thread; - - return true; - } - - //If that fails, then try running on any core allowed by Core Mask. - int CoreMask = Thread.CoreMask; - - for (int Core = 0; Core < CoreThreads.Length; Core++, CoreMask >>= 1) - { - if ((CoreMask & 1) != 0 && CoreThreads[Core] == null) - { - Thread.ActualCore = Core; - - CoreThreads[Core] = Thread; - - return true; - } - } - - return false; - } - - private void PrintDbgThreadInfo(KThread Thread, string Message) - { - Log.PrintDebug(LogClass.KernelScheduler, "(" + - "ThreadId = " + Thread.ThreadId + ", " + - "CoreMask = 0x" + Thread.CoreMask.ToString("x1") + ", " + - "ActualCore = " + Thread.ActualCore + ", " + - "IdealCore = " + Thread.IdealCore + ", " + - "ActualPriority = " + Thread.ActualPriority + ", " + - "WantedPriority = " + Thread.WantedPriority + ") " + Message); - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing) - { - foreach (SchedulerThread SchedThread in AllThreads.Values) - { - SchedThread.Dispose(); - } - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KRecursiveLock.cs b/Ryujinx.HLE/HOS/Kernel/KRecursiveLock.cs new file mode 100644 index 0000000000..a21531deb2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KRecursiveLock.cs @@ -0,0 +1,93 @@ +using ChocolArm64; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel +{ + class KRecursiveLock + { + private Horizon System; + + public object LockObj { get; private set; } + + private int RecursionCount; + + public KRecursiveLock(Horizon System) + { + this.System = System; + + LockObj = new object(); + } + + public void Lock() + { + Monitor.Enter(LockObj); + + RecursionCount++; + } + + public void Unlock() + { + if (RecursionCount == 0) + { + return; + } + + bool DoContextSwitch = false; + + if (--RecursionCount == 0) + { + if (System.Scheduler.ThreadReselectionRequested) + { + System.Scheduler.SelectThreads(); + } + + Monitor.Exit(LockObj); + + if (System.Scheduler.MultiCoreScheduling) + { + lock (System.Scheduler.CoreContexts) + { + for (int Core = 0; Core < KScheduler.CpuCoresCount; Core++) + { + KCoreContext CoreContext = System.Scheduler.CoreContexts[Core]; + + if (CoreContext.ContextSwitchNeeded) + { + AThread CurrentHleThread = CoreContext.CurrentThread?.Context; + + if (CurrentHleThread == null) + { + //Nothing is running, we can perform the context switch immediately. + CoreContext.ContextSwitch(); + } + else if (CurrentHleThread.IsCurrentThread()) + { + //Thread running on the current core, context switch will block. + DoContextSwitch = true; + } + else + { + //Thread running on another core, request a interrupt. + CurrentHleThread.RequestInterrupt(); + } + } + } + } + } + else + { + DoContextSwitch = true; + } + } + else + { + Monitor.Exit(LockObj); + } + + if (DoContextSwitch) + { + System.Scheduler.ContextSwitch(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KScheduler.cs b/Ryujinx.HLE/HOS/Kernel/KScheduler.cs new file mode 100644 index 0000000000..f6382d05d6 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KScheduler.cs @@ -0,0 +1,235 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel +{ + partial class KScheduler : IDisposable + { + public const int PrioritiesCount = 64; + public const int CpuCoresCount = 4; + + private const int PreemptionPriorityCores012 = 59; + private const int PreemptionPriorityCore3 = 63; + + private Horizon System; + + public KSchedulingData SchedulingData { get; private set; } + + public KCoreContext[] CoreContexts { get; private set; } + + public bool ThreadReselectionRequested { get; set; } + + public KScheduler(Horizon System) + { + this.System = System; + + SchedulingData = new KSchedulingData(); + + CoreManager = new HleCoreManager(); + + CoreContexts = new KCoreContext[CpuCoresCount]; + + for (int Core = 0; Core < CpuCoresCount; Core++) + { + CoreContexts[Core] = new KCoreContext(this, CoreManager); + } + + Thread PreemptionThread = new Thread(PreemptCurrentThread); + + KeepPreempting = true; + + PreemptionThread.Start(); + } + + private void PreemptThreads() + { + System.CriticalSectionLock.Lock(); + + PreemptThread(PreemptionPriorityCores012, 0); + PreemptThread(PreemptionPriorityCores012, 1); + PreemptThread(PreemptionPriorityCores012, 2); + PreemptThread(PreemptionPriorityCore3, 3); + + System.CriticalSectionLock.Unlock(); + } + + private void PreemptThread(int Prio, int Core) + { + IEnumerable ScheduledThreads = SchedulingData.ScheduledThreads(Core); + + KThread SelectedThread = ScheduledThreads.FirstOrDefault(x => x.DynamicPriority == Prio); + + //Yield priority queue. + if (SelectedThread != null) + { + SchedulingData.Reschedule(Prio, Core, SelectedThread); + } + + IEnumerable SuitableCandidates() + { + foreach (KThread Thread in SchedulingData.SuggestedThreads(Core)) + { + int SrcCore = Thread.CurrentCore; + + if (SrcCore >= 0) + { + KThread HighestPrioSrcCore = SchedulingData.ScheduledThreads(SrcCore).FirstOrDefault(); + + if (HighestPrioSrcCore != null && HighestPrioSrcCore.DynamicPriority < 2) + { + break; + } + + if (HighestPrioSrcCore == Thread) + { + continue; + } + } + + //If the candidate was scheduled after the current thread, then it's not worth it. + if (SelectedThread == null || SelectedThread.LastScheduledTicks >= Thread.LastScheduledTicks) + { + yield return Thread; + } + } + } + + //Select candidate threads that could run on this core. + //Only take into account threads that are not yet selected. + KThread Dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority == Prio); + + if (Dst != null) + { + SchedulingData.TransferToCore(Prio, Core, Dst); + + SelectedThread = Dst; + } + + //If the priority of the currently selected thread is lower than preemption priority, + //then allow threads with lower priorities to be selected aswell. + if (SelectedThread != null && SelectedThread.DynamicPriority > Prio) + { + Func Predicate = x => x.DynamicPriority >= SelectedThread.DynamicPriority; + + Dst = SuitableCandidates().FirstOrDefault(Predicate); + + if (Dst != null) + { + SchedulingData.TransferToCore(Dst.DynamicPriority, Core, Dst); + } + } + + ThreadReselectionRequested = true; + } + + public void SelectThreads() + { + ThreadReselectionRequested = false; + + for (int Core = 0; Core < CpuCoresCount; Core++) + { + KThread Thread = SchedulingData.ScheduledThreads(Core).FirstOrDefault(); + + CoreContexts[Core].SelectThread(Thread); + } + + for (int Core = 0; Core < CpuCoresCount; Core++) + { + //If the core is not idle (there's already a thread running on it), + //then we don't need to attempt load balancing. + if (SchedulingData.ScheduledThreads(Core).Any()) + { + continue; + } + + int[] SrcCoresHighestPrioThreads = new int[CpuCoresCount]; + + int SrcCoresHighestPrioThreadsCount = 0; + + KThread Dst = null; + + //Select candidate threads that could run on this core. + //Give preference to threads that are not yet selected. + foreach (KThread Thread in SchedulingData.SuggestedThreads(Core)) + { + if (Thread.CurrentCore < 0 || Thread != CoreContexts[Thread.CurrentCore].SelectedThread) + { + Dst = Thread; + + break; + } + + SrcCoresHighestPrioThreads[SrcCoresHighestPrioThreadsCount++] = Thread.CurrentCore; + } + + //Not yet selected candidate found. + if (Dst != null) + { + //Priorities < 2 are used for the kernel message dispatching + //threads, we should skip load balancing entirely. + if (Dst.DynamicPriority >= 2) + { + SchedulingData.TransferToCore(Dst.DynamicPriority, Core, Dst); + + CoreContexts[Core].SelectThread(Dst); + } + + continue; + } + + //All candiates are already selected, choose the best one + //(the first one that doesn't make the source core idle if moved). + for (int Index = 0; Index < SrcCoresHighestPrioThreadsCount; Index++) + { + int SrcCore = SrcCoresHighestPrioThreads[Index]; + + KThread Src = SchedulingData.ScheduledThreads(SrcCore).ElementAtOrDefault(1); + + if (Src != null) + { + //Run the second thread on the queue on the source core, + //move the first one to the current core. + KThread OrigSelectedCoreSrc = CoreContexts[SrcCore].SelectedThread; + + CoreContexts[SrcCore].SelectThread(Src); + + SchedulingData.TransferToCore(OrigSelectedCoreSrc.DynamicPriority, Core, OrigSelectedCoreSrc); + + CoreContexts[Core].SelectThread(OrigSelectedCoreSrc); + } + } + } + } + + public KThread GetCurrentThread() + { + lock (CoreContexts) + { + for (int Core = 0; Core < CpuCoresCount; Core++) + { + if (CoreContexts[Core].CurrentThread?.Context.IsCurrentThread() ?? false) + { + return CoreContexts[Core].CurrentThread; + } + } + } + + throw new InvalidOperationException("Current thread is not scheduled!"); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool Disposing) + { + if (Disposing) + { + KeepPreempting = false; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KSchedulingData.cs b/Ryujinx.HLE/HOS/Kernel/KSchedulingData.cs new file mode 100644 index 0000000000..ba2730a298 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KSchedulingData.cs @@ -0,0 +1,207 @@ +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel +{ + class KSchedulingData + { + private LinkedList[][] ScheduledThreadsPerPrioPerCore; + private LinkedList[][] SuggestedThreadsPerPrioPerCore; + + private long[] ScheduledPrioritiesPerCore; + private long[] SuggestedPrioritiesPerCore; + + public KSchedulingData() + { + SuggestedThreadsPerPrioPerCore = new LinkedList[KScheduler.PrioritiesCount][]; + ScheduledThreadsPerPrioPerCore = new LinkedList[KScheduler.PrioritiesCount][]; + + for (int Prio = 0; Prio < KScheduler.PrioritiesCount; Prio++) + { + SuggestedThreadsPerPrioPerCore[Prio] = new LinkedList[KScheduler.CpuCoresCount]; + ScheduledThreadsPerPrioPerCore[Prio] = new LinkedList[KScheduler.CpuCoresCount]; + + for (int Core = 0; Core < KScheduler.CpuCoresCount; Core++) + { + SuggestedThreadsPerPrioPerCore[Prio][Core] = new LinkedList(); + ScheduledThreadsPerPrioPerCore[Prio][Core] = new LinkedList(); + } + } + + ScheduledPrioritiesPerCore = new long[KScheduler.CpuCoresCount]; + SuggestedPrioritiesPerCore = new long[KScheduler.CpuCoresCount]; + } + + public IEnumerable SuggestedThreads(int Core) + { + return Iterate(SuggestedThreadsPerPrioPerCore, SuggestedPrioritiesPerCore, Core); + } + + public IEnumerable ScheduledThreads(int Core) + { + return Iterate(ScheduledThreadsPerPrioPerCore, ScheduledPrioritiesPerCore, Core); + } + + private IEnumerable Iterate(LinkedList[][] ListPerPrioPerCore, long[] Prios, int Core) + { + long PrioMask = Prios[Core]; + + int Prio = CountTrailingZeros(PrioMask); + + PrioMask &= ~(1L << Prio); + + while (Prio < KScheduler.PrioritiesCount) + { + LinkedList List = ListPerPrioPerCore[Prio][Core]; + + LinkedListNode Node = List.First; + + while (Node != null) + { + yield return Node.Value; + + Node = Node.Next; + } + + Prio = CountTrailingZeros(PrioMask); + + PrioMask &= ~(1L << Prio); + } + } + + private int CountTrailingZeros(long Value) + { + int Count = 0; + + while (((Value >> Count) & 0xf) == 0 && Count < 64) + { + Count += 4; + } + + while (((Value >> Count) & 1) == 0 && Count < 64) + { + Count++; + } + + return Count; + } + + public void TransferToCore(int Prio, int DstCore, KThread Thread) + { + bool Schedulable = Thread.DynamicPriority < KScheduler.PrioritiesCount; + + int SrcCore = Thread.CurrentCore; + + Thread.CurrentCore = DstCore; + + if (SrcCore == DstCore || !Schedulable) + { + return; + } + + if (SrcCore >= 0) + { + Unschedule(Prio, SrcCore, Thread); + } + + if (DstCore >= 0) + { + Unsuggest(Prio, DstCore, Thread); + Schedule(Prio, DstCore, Thread); + } + + if (SrcCore >= 0) + { + Suggest(Prio, SrcCore, Thread); + } + } + + public void Suggest(int Prio, int Core, KThread Thread) + { + if (Prio >= KScheduler.PrioritiesCount) + { + return; + } + + Thread.SiblingsPerCore[Core] = SuggestedQueue(Prio, Core).AddFirst(Thread); + + SuggestedPrioritiesPerCore[Core] |= 1L << Prio; + } + + public void Unsuggest(int Prio, int Core, KThread Thread) + { + if (Prio >= KScheduler.PrioritiesCount) + { + return; + } + + LinkedList Queue = SuggestedQueue(Prio, Core); + + Queue.Remove(Thread.SiblingsPerCore[Core]); + + if (Queue.First == null) + { + SuggestedPrioritiesPerCore[Core] &= ~(1L << Prio); + } + } + + public void Schedule(int Prio, int Core, KThread Thread) + { + if (Prio >= KScheduler.PrioritiesCount) + { + return; + } + + Thread.SiblingsPerCore[Core] = ScheduledQueue(Prio, Core).AddLast(Thread); + + ScheduledPrioritiesPerCore[Core] |= 1L << Prio; + } + + public void SchedulePrepend(int Prio, int Core, KThread Thread) + { + if (Prio >= KScheduler.PrioritiesCount) + { + return; + } + + Thread.SiblingsPerCore[Core] = ScheduledQueue(Prio, Core).AddFirst(Thread); + + ScheduledPrioritiesPerCore[Core] |= 1L << Prio; + } + + public void Reschedule(int Prio, int Core, KThread Thread) + { + LinkedList Queue = ScheduledQueue(Prio, Core); + + Queue.Remove(Thread.SiblingsPerCore[Core]); + + Thread.SiblingsPerCore[Core] = Queue.AddLast(Thread); + } + + public void Unschedule(int Prio, int Core, KThread Thread) + { + if (Prio >= KScheduler.PrioritiesCount) + { + return; + } + + LinkedList Queue = ScheduledQueue(Prio, Core); + + Queue.Remove(Thread.SiblingsPerCore[Core]); + + if (Queue.First == null) + { + ScheduledPrioritiesPerCore[Core] &= ~(1L << Prio); + } + } + + private LinkedList SuggestedQueue(int Prio, int Core) + { + return SuggestedThreadsPerPrioPerCore[Prio][Core]; + } + + private LinkedList ScheduledQueue(int Prio, int Core) + { + return ScheduledThreadsPerPrioPerCore[Prio][Core]; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KSynchronization.cs b/Ryujinx.HLE/HOS/Kernel/KSynchronization.cs new file mode 100644 index 0000000000..57a6296c2b --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KSynchronization.cs @@ -0,0 +1,135 @@ +using System.Collections.Generic; + +using static Ryujinx.HLE.HOS.ErrorCode; + +namespace Ryujinx.HLE.HOS.Kernel +{ + class KSynchronization + { + private Horizon System; + + public KSynchronization(Horizon System) + { + this.System = System; + } + + public long WaitFor(KSynchronizationObject[] SyncObjs, long Timeout, ref int HndIndex) + { + long Result = MakeError(ErrorModule.Kernel, KernelErr.Timeout); + + System.CriticalSectionLock.Lock(); + + //Check if objects are already signaled before waiting. + for (int Index = 0; Index < SyncObjs.Length; Index++) + { + if (!SyncObjs[Index].IsSignaled()) + { + continue; + } + + HndIndex = Index; + + System.CriticalSectionLock.Unlock(); + + return 0; + } + + if (Timeout == 0) + { + System.CriticalSectionLock.Unlock(); + + return Result; + } + + KThread CurrentThread = System.Scheduler.GetCurrentThread(); + + if (CurrentThread.ShallBeTerminated || + CurrentThread.SchedFlags == ThreadSchedState.TerminationPending) + { + Result = MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating); + } + else if (CurrentThread.SyncCancelled) + { + CurrentThread.SyncCancelled = false; + + Result = MakeError(ErrorModule.Kernel, KernelErr.Cancelled); + } + else + { + LinkedListNode[] SyncNodes = new LinkedListNode[SyncObjs.Length]; + + for (int Index = 0; Index < SyncObjs.Length; Index++) + { + SyncNodes[Index] = SyncObjs[Index].AddWaitingThread(CurrentThread); + } + + CurrentThread.WaitingSync = true; + CurrentThread.SignaledObj = null; + CurrentThread.ObjSyncResult = (int)Result; + + CurrentThread.Reschedule(ThreadSchedState.Paused); + + if (Timeout > 0) + { + System.TimeManager.ScheduleFutureInvocation(CurrentThread, Timeout); + } + + System.CriticalSectionLock.Unlock(); + + CurrentThread.WaitingSync = false; + + if (Timeout > 0) + { + System.TimeManager.UnscheduleFutureInvocation(CurrentThread); + } + + System.CriticalSectionLock.Lock(); + + Result = (uint)CurrentThread.ObjSyncResult; + + HndIndex = -1; + + for (int Index = 0; Index < SyncObjs.Length; Index++) + { + SyncObjs[Index].RemoveWaitingThread(SyncNodes[Index]); + + if (SyncObjs[Index] == CurrentThread.SignaledObj) + { + HndIndex = Index; + } + } + } + + System.CriticalSectionLock.Unlock(); + + return Result; + } + + public void SignalObject(KSynchronizationObject SyncObj) + { + System.CriticalSectionLock.Lock(); + + if (SyncObj.IsSignaled()) + { + LinkedListNode Node = SyncObj.WaitingThreads.First; + + while (Node != null) + { + KThread Thread = Node.Value; + + if ((Thread.SchedFlags & ThreadSchedState.LowNibbleMask) == ThreadSchedState.Paused) + { + Thread.SignaledObj = SyncObj; + Thread.ObjSyncResult = 0; + + Thread.Reschedule(ThreadSchedState.Running); + } + + Node = Node.Next; + } + } + + System.CriticalSectionLock.Unlock(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KSynchronizationObject.cs b/Ryujinx.HLE/HOS/Kernel/KSynchronizationObject.cs index b83b00047e..28eac33069 100644 --- a/Ryujinx.HLE/HOS/Kernel/KSynchronizationObject.cs +++ b/Ryujinx.HLE/HOS/Kernel/KSynchronizationObject.cs @@ -1,28 +1,38 @@ -using System; -using System.Threading; +using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Kernel { - class KSynchronizationObject : IDisposable + class KSynchronizationObject { - public ManualResetEvent WaitEvent { get; private set; } + public LinkedList WaitingThreads; - public KSynchronizationObject() + protected Horizon System; + + public KSynchronizationObject(Horizon System) { - WaitEvent = new ManualResetEvent(false); + this.System = System; + + WaitingThreads = new LinkedList(); } - public void Dispose() + public LinkedListNode AddWaitingThread(KThread Thread) { - Dispose(true); + return WaitingThreads.AddLast(Thread); } - protected virtual void Dispose(bool Disposing) + public void RemoveWaitingThread(LinkedListNode Node) { - if (Disposing) - { - WaitEvent.Dispose(); - } + WaitingThreads.Remove(Node); + } + + public virtual void Signal() + { + System.Synchronization.SignalObject(this); + } + + public virtual bool IsSignaled() + { + return false; } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KThread.cs b/Ryujinx.HLE/HOS/Kernel/KThread.cs index 171fefc519..aecaf6394d 100644 --- a/Ryujinx.HLE/HOS/Kernel/KThread.cs +++ b/Ryujinx.HLE/HOS/Kernel/KThread.cs @@ -1,98 +1,883 @@ using ChocolArm64; +using System; using System.Collections.Generic; +using System.Linq; + +using static Ryujinx.HLE.HOS.ErrorCode; namespace Ryujinx.HLE.HOS.Kernel { - class KThread : KSynchronizationObject + class KThread : KSynchronizationObject, IKFutureSchedulerObject { - public AThread Thread { get; private set; } + public AThread Context { get; private set; } - public int CoreMask { get; set; } - - public long MutexAddress { get; set; } - public long CondVarAddress { get; set; } - public long ArbiterWaitAddress { get; set; } - - public bool CondVarSignaled { get; set; } - public bool ArbiterSignaled { get; set; } - - private Process Process; - - public List MutexWaiters { get; private set; } - - public KThread MutexOwner { get; set; } - - public int ActualPriority { get; private set; } - public int WantedPriority { get; private set; } - - public int ActualCore { get; set; } - public int ProcessorId { get; set; } - public int IdealCore { get; set; } - - public int WaitHandle { get; set; } - - public long LastPc { get; set; } + public long AffinityMask { get; set; } public int ThreadId { get; private set; } + public KSynchronizationObject SignaledObj; + + public long CondVarAddress { get; set; } + public long MutexAddress { get; set; } + + public Process Owner { get; private set; } + + public long LastScheduledTicks { get; set; } + + public LinkedListNode[] SiblingsPerCore { get; private set; } + + private LinkedListNode WithholderNode; + + private LinkedList MutexWaiters; + private LinkedListNode MutexWaiterNode; + + public KThread MutexOwner { get; private set; } + + public int ThreadHandleForUserMutex { get; set; } + + private ThreadSchedState ForcePauseFlags; + + public int ObjSyncResult { get; set; } + + public int DynamicPriority { get; set; } + public int CurrentCore { get; set; } + public int BasePriority { get; set; } + public int PreferredCore { get; set; } + + private long AffinityMaskOverride; + private int PreferredCoreOverride; + private int AffinityOverrideCount; + + public ThreadSchedState SchedFlags { get; private set; } + + public bool ShallBeTerminated { get; private set; } + + public bool SyncCancelled { get; set; } + public bool WaitingSync { get; set; } + + private bool HasExited; + + public bool WaitingInArbitration { get; set; } + + private KScheduler Scheduler; + + private KSchedulingData SchedulingData; + + public long LastPc { get; set; } + public KThread( AThread Thread, Process Process, + Horizon System, int ProcessorId, int Priority, - int ThreadId) + int ThreadId) : base(System) { - this.Thread = Thread; - this.Process = Process; - this.ProcessorId = ProcessorId; - this.IdealCore = ProcessorId; - this.ThreadId = ThreadId; + this.ThreadId = ThreadId; - MutexWaiters = new List(); + Context = Thread; + Owner = Process; + PreferredCore = ProcessorId; + Scheduler = System.Scheduler; + SchedulingData = System.Scheduler.SchedulingData; - CoreMask = 1 << ProcessorId; + SiblingsPerCore = new LinkedListNode[KScheduler.CpuCoresCount]; - ActualPriority = WantedPriority = Priority; + MutexWaiters = new LinkedList(); + + AffinityMask = 1 << ProcessorId; + + DynamicPriority = BasePriority = Priority; + + CurrentCore = PreferredCore; + } + + public long Start() + { + long Result = MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating); + + System.CriticalSectionLock.Lock(); + + if (!ShallBeTerminated) + { + KThread CurrentThread = System.Scheduler.GetCurrentThread(); + + while (SchedFlags != ThreadSchedState.TerminationPending && + CurrentThread.SchedFlags != ThreadSchedState.TerminationPending && + !CurrentThread.ShallBeTerminated) + { + if ((SchedFlags & ThreadSchedState.LowNibbleMask) != ThreadSchedState.None) + { + Result = MakeError(ErrorModule.Kernel, KernelErr.InvalidState); + + break; + } + + if (CurrentThread.ForcePauseFlags == ThreadSchedState.None) + { + if (Owner != null && ForcePauseFlags != ThreadSchedState.None) + { + CombineForcePauseFlags(); + } + + SetNewSchedFlags(ThreadSchedState.Running); + + Result = 0; + + break; + } + else + { + CurrentThread.CombineForcePauseFlags(); + + System.CriticalSectionLock.Unlock(); + System.CriticalSectionLock.Lock(); + + if (CurrentThread.ShallBeTerminated) + { + break; + } + } + } + } + + System.CriticalSectionLock.Unlock(); + + return Result; + } + + public void Exit() + { + System.CriticalSectionLock.Lock(); + + ForcePauseFlags &= ~ThreadSchedState.ExceptionalMask; + + ExitImpl(); + + System.CriticalSectionLock.Unlock(); + } + + private void ExitImpl() + { + System.CriticalSectionLock.Lock(); + + SetNewSchedFlags(ThreadSchedState.TerminationPending); + + HasExited = true; + + Signal(); + + System.CriticalSectionLock.Unlock(); + } + + public long Sleep(long Timeout) + { + System.CriticalSectionLock.Lock(); + + if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating); + } + + SetNewSchedFlags(ThreadSchedState.Paused); + + if (Timeout > 0) + { + System.TimeManager.ScheduleFutureInvocation(this, Timeout); + } + + System.CriticalSectionLock.Unlock(); + + if (Timeout > 0) + { + System.TimeManager.UnscheduleFutureInvocation(this); + } + + return 0; + } + + public void Yield() + { + System.CriticalSectionLock.Lock(); + + if (SchedFlags != ThreadSchedState.Running) + { + System.CriticalSectionLock.Unlock(); + + System.Scheduler.ContextSwitch(); + + return; + } + + if (DynamicPriority < KScheduler.PrioritiesCount) + { + //Move current thread to the end of the queue. + SchedulingData.Reschedule(DynamicPriority, CurrentCore, this); + } + + Scheduler.ThreadReselectionRequested = true; + + System.CriticalSectionLock.Unlock(); + + System.Scheduler.ContextSwitch(); + } + + public void YieldWithLoadBalancing() + { + int Prio = DynamicPriority; + int Core = CurrentCore; + + System.CriticalSectionLock.Lock(); + + if (SchedFlags != ThreadSchedState.Running) + { + System.CriticalSectionLock.Unlock(); + + System.Scheduler.ContextSwitch(); + + return; + } + + KThread NextThreadOnCurrentQueue = null; + + if (DynamicPriority < KScheduler.PrioritiesCount) + { + //Move current thread to the end of the queue. + SchedulingData.Reschedule(Prio, Core, this); + + Func Predicate = x => x.DynamicPriority == Prio; + + NextThreadOnCurrentQueue = SchedulingData.ScheduledThreads(Core).FirstOrDefault(Predicate); + } + + IEnumerable SuitableCandidates() + { + foreach (KThread Thread in SchedulingData.SuggestedThreads(Core)) + { + int SrcCore = Thread.CurrentCore; + + if (SrcCore >= 0) + { + KThread SelectedSrcCore = Scheduler.CoreContexts[SrcCore].SelectedThread; + + if (SelectedSrcCore == Thread || ((SelectedSrcCore?.DynamicPriority ?? 2) < 2)) + { + continue; + } + } + + //If the candidate was scheduled after the current thread, then it's not worth it, + //unless the priority is higher than the current one. + if (NextThreadOnCurrentQueue.LastScheduledTicks >= Thread.LastScheduledTicks || + NextThreadOnCurrentQueue.DynamicPriority < Thread.DynamicPriority) + { + yield return Thread; + } + } + } + + KThread Dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority <= Prio); + + if (Dst != null) + { + SchedulingData.TransferToCore(Dst.DynamicPriority, Core, Dst); + + Scheduler.ThreadReselectionRequested = true; + } + + if (this != NextThreadOnCurrentQueue) + { + Scheduler.ThreadReselectionRequested = true; + } + + System.CriticalSectionLock.Unlock(); + + System.Scheduler.ContextSwitch(); + } + + public void YieldAndWaitForLoadBalancing() + { + System.CriticalSectionLock.Lock(); + + if (SchedFlags != ThreadSchedState.Running) + { + System.CriticalSectionLock.Unlock(); + + System.Scheduler.ContextSwitch(); + + return; + } + + int Core = CurrentCore; + + SchedulingData.TransferToCore(DynamicPriority, -1, this); + + KThread SelectedThread = null; + + if (!SchedulingData.ScheduledThreads(Core).Any()) + { + foreach (KThread Thread in SchedulingData.SuggestedThreads(Core)) + { + if (Thread.CurrentCore < 0) + { + continue; + } + + KThread FirstCandidate = SchedulingData.ScheduledThreads(Thread.CurrentCore).FirstOrDefault(); + + if (FirstCandidate == Thread) + { + continue; + } + + if (FirstCandidate == null || FirstCandidate.DynamicPriority >= 2) + { + SchedulingData.TransferToCore(Thread.DynamicPriority, Core, Thread); + + SelectedThread = Thread; + } + + break; + } + } + + if (SelectedThread != this) + { + Scheduler.ThreadReselectionRequested = true; + } + + System.CriticalSectionLock.Unlock(); + + System.Scheduler.ContextSwitch(); } public void SetPriority(int Priority) { - WantedPriority = Priority; + System.CriticalSectionLock.Lock(); - UpdatePriority(); + BasePriority = Priority; + + UpdatePriorityInheritance(); + + System.CriticalSectionLock.Unlock(); } - public void UpdatePriority() + public long SetActivity(bool Pause) { - bool PriorityChanged; + long Result = 0; - lock (Process.ThreadSyncLock) + System.CriticalSectionLock.Lock(); + + ThreadSchedState LowNibble = SchedFlags & ThreadSchedState.LowNibbleMask; + + if (LowNibble != ThreadSchedState.Paused && LowNibble != ThreadSchedState.Running) { - int OldPriority = ActualPriority; + System.CriticalSectionLock.Unlock(); - int CurrPriority = WantedPriority; + return MakeError(ErrorModule.Kernel, KernelErr.InvalidState); + } - foreach (KThread Thread in MutexWaiters) + System.CriticalSectionLock.Lock(); + + if (!ShallBeTerminated && SchedFlags != ThreadSchedState.TerminationPending) + { + if (Pause) { - int WantedPriority = Thread.WantedPriority; - - if (CurrPriority > WantedPriority) + //Pause, the force pause flag should be clear (thread is NOT paused). + if ((ForcePauseFlags & ThreadSchedState.ForcePauseFlag) == 0) { - CurrPriority = WantedPriority; + ForcePauseFlags |= ThreadSchedState.ForcePauseFlag; + + CombineForcePauseFlags(); + } + else + { + Result = MakeError(ErrorModule.Kernel, KernelErr.InvalidState); } } + else + { + //Unpause, the force pause flag should be set (thread is paused). + if ((ForcePauseFlags & ThreadSchedState.ForcePauseFlag) != 0) + { + ThreadSchedState OldForcePauseFlags = ForcePauseFlags; - PriorityChanged = CurrPriority != OldPriority; + ForcePauseFlags &= ~ThreadSchedState.ForcePauseFlag; - ActualPriority = CurrPriority; + if ((OldForcePauseFlags & ~ThreadSchedState.ForcePauseFlag) == ThreadSchedState.None) + { + ThreadSchedState OldSchedFlags = SchedFlags; + + SchedFlags &= ThreadSchedState.LowNibbleMask; + + AdjustScheduling(OldSchedFlags); + } + } + else + { + Result = MakeError(ErrorModule.Kernel, KernelErr.InvalidState); + } + } } - if (PriorityChanged) + System.CriticalSectionLock.Unlock(); + System.CriticalSectionLock.Unlock(); + + return Result; + } + + public void CancelSynchronization() + { + System.CriticalSectionLock.Lock(); + + if ((SchedFlags & ThreadSchedState.LowNibbleMask) != ThreadSchedState.Paused || !WaitingSync) { - Process.Scheduler.Resort(this); - - MutexOwner?.UpdatePriority(); + SyncCancelled = true; } + else if (WithholderNode != null) + { + System.Withholders.Remove(WithholderNode); + + SetNewSchedFlags(ThreadSchedState.Running); + + WithholderNode = null; + + SyncCancelled = true; + } + else + { + SignaledObj = null; + ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.Cancelled); + + SetNewSchedFlags(ThreadSchedState.Running); + + SyncCancelled = false; + } + + System.CriticalSectionLock.Unlock(); + } + + public long SetCoreAndAffinityMask(int NewCore, long NewAffinityMask) + { + System.CriticalSectionLock.Lock(); + + bool UseOverride = AffinityOverrideCount != 0; + + //The value -3 is "do not change the preferred core". + if (NewCore == -3) + { + NewCore = UseOverride ? PreferredCoreOverride : PreferredCore; + + if ((NewAffinityMask & (1 << NewCore)) == 0) + { + System.CriticalSectionLock.Unlock(); + + return MakeError(ErrorModule.Kernel, KernelErr.InvalidMaskValue); + } + } + + if (UseOverride) + { + PreferredCoreOverride = NewCore; + AffinityMaskOverride = NewAffinityMask; + } + else + { + long OldAffinityMask = AffinityMask; + + PreferredCore = NewCore; + AffinityMask = NewAffinityMask; + + if (OldAffinityMask != NewAffinityMask) + { + int OldCore = CurrentCore; + + if (CurrentCore >= 0 && ((AffinityMask >> CurrentCore) & 1) == 0) + { + if (PreferredCore < 0) + { + CurrentCore = HighestSetCore(AffinityMask); + } + else + { + CurrentCore = PreferredCore; + } + } + + AdjustSchedulingForNewAffinity(OldAffinityMask, OldCore); + } + } + + System.CriticalSectionLock.Unlock(); + + return 0; + } + + private static int HighestSetCore(long Mask) + { + for (int Core = KScheduler.CpuCoresCount - 1; Core >= 0; Core--) + { + if (((Mask >> Core) & 1) != 0) + { + return Core; + } + } + + return -1; + } + + private void CombineForcePauseFlags() + { + ThreadSchedState OldFlags = SchedFlags; + ThreadSchedState LowNibble = SchedFlags & ThreadSchedState.LowNibbleMask; + + SchedFlags = LowNibble | ForcePauseFlags; + + AdjustScheduling(OldFlags); + } + + private void SetNewSchedFlags(ThreadSchedState NewFlags) + { + System.CriticalSectionLock.Lock(); + + ThreadSchedState OldFlags = SchedFlags; + + SchedFlags = (OldFlags & ThreadSchedState.HighNibbleMask) | NewFlags; + + if ((OldFlags & ThreadSchedState.LowNibbleMask) != NewFlags) + { + AdjustScheduling(OldFlags); + } + + System.CriticalSectionLock.Unlock(); + } + + public void ReleaseAndResume() + { + System.CriticalSectionLock.Lock(); + + if ((SchedFlags & ThreadSchedState.LowNibbleMask) == ThreadSchedState.Paused) + { + if (WithholderNode != null) + { + System.Withholders.Remove(WithholderNode); + + SetNewSchedFlags(ThreadSchedState.Running); + + WithholderNode = null; + } + else + { + SetNewSchedFlags(ThreadSchedState.Running); + } + } + + System.CriticalSectionLock.Unlock(); + } + + public void Reschedule(ThreadSchedState NewFlags) + { + System.CriticalSectionLock.Lock(); + + ThreadSchedState OldFlags = SchedFlags; + + SchedFlags = (OldFlags & ThreadSchedState.HighNibbleMask) | + (NewFlags & ThreadSchedState.LowNibbleMask); + + AdjustScheduling(OldFlags); + + System.CriticalSectionLock.Unlock(); + } + + public void AddMutexWaiter(KThread Requester) + { + AddToMutexWaitersList(Requester); + + Requester.MutexOwner = this; + + UpdatePriorityInheritance(); + } + + public void RemoveMutexWaiter(KThread Thread) + { + if (Thread.MutexWaiterNode?.List != null) + { + MutexWaiters.Remove(Thread.MutexWaiterNode); + } + + Thread.MutexOwner = null; + + UpdatePriorityInheritance(); + } + + public KThread RelinquishMutex(long MutexAddress, out int Count) + { + Count = 0; + + if (MutexWaiters.First == null) + { + return null; + } + + KThread NewMutexOwner = null; + + LinkedListNode CurrentNode = MutexWaiters.First; + + do + { + //Skip all threads that are not waiting for this mutex. + while (CurrentNode != null && CurrentNode.Value.MutexAddress != MutexAddress) + { + CurrentNode = CurrentNode.Next; + } + + if (CurrentNode == null) + { + break; + } + + LinkedListNode NextNode = CurrentNode.Next; + + MutexWaiters.Remove(CurrentNode); + + CurrentNode.Value.MutexOwner = NewMutexOwner; + + if (NewMutexOwner != null) + { + //New owner was already selected, re-insert on new owner list. + NewMutexOwner.AddToMutexWaitersList(CurrentNode.Value); + } + else + { + //New owner not selected yet, use current thread. + NewMutexOwner = CurrentNode.Value; + } + + Count++; + + CurrentNode = NextNode; + } + while (CurrentNode != null); + + if (NewMutexOwner != null) + { + UpdatePriorityInheritance(); + + NewMutexOwner.UpdatePriorityInheritance(); + } + + return NewMutexOwner; + } + + private void UpdatePriorityInheritance() + { + //If any of the threads waiting for the mutex has + //higher priority than the current thread, then + //the current thread inherits that priority. + int HighestPriority = BasePriority; + + if (MutexWaiters.First != null) + { + int WaitingDynamicPriority = MutexWaiters.First.Value.DynamicPriority; + + if (WaitingDynamicPriority < HighestPriority) + { + HighestPriority = WaitingDynamicPriority; + } + } + + if (HighestPriority != DynamicPriority) + { + int OldPriority = DynamicPriority; + + DynamicPriority = HighestPriority; + + AdjustSchedulingForNewPriority(OldPriority); + + if (MutexOwner != null) + { + //Remove and re-insert to ensure proper sorting based on new priority. + MutexOwner.MutexWaiters.Remove(MutexWaiterNode); + + MutexOwner.AddToMutexWaitersList(this); + + MutexOwner.UpdatePriorityInheritance(); + } + } + } + + private void AddToMutexWaitersList(KThread Thread) + { + LinkedListNode NextPrio = MutexWaiters.First; + + int CurrentPriority = Thread.DynamicPriority; + + while (NextPrio != null && NextPrio.Value.DynamicPriority <= CurrentPriority) + { + NextPrio = NextPrio.Next; + } + + if (NextPrio != null) + { + Thread.MutexWaiterNode = MutexWaiters.AddBefore(NextPrio, Thread); + } + else + { + Thread.MutexWaiterNode = MutexWaiters.AddLast(Thread); + } + } + + private void AdjustScheduling(ThreadSchedState OldFlags) + { + if (OldFlags == SchedFlags) + { + return; + } + + if (OldFlags == ThreadSchedState.Running) + { + //Was running, now it's stopped. + if (CurrentCore >= 0) + { + SchedulingData.Unschedule(DynamicPriority, CurrentCore, this); + } + + for (int Core = 0; Core < KScheduler.CpuCoresCount; Core++) + { + if (Core != CurrentCore && ((AffinityMask >> Core) & 1) != 0) + { + SchedulingData.Unsuggest(DynamicPriority, Core, this); + } + } + } + else if (SchedFlags == ThreadSchedState.Running) + { + //Was stopped, now it's running. + if (CurrentCore >= 0) + { + SchedulingData.Schedule(DynamicPriority, CurrentCore, this); + } + + for (int Core = 0; Core < KScheduler.CpuCoresCount; Core++) + { + if (Core != CurrentCore && ((AffinityMask >> Core) & 1) != 0) + { + SchedulingData.Suggest(DynamicPriority, Core, this); + } + } + } + + Scheduler.ThreadReselectionRequested = true; + } + + private void AdjustSchedulingForNewPriority(int OldPriority) + { + if (SchedFlags != ThreadSchedState.Running) + { + return; + } + + //Remove thread from the old priority queues. + if (CurrentCore >= 0) + { + SchedulingData.Unschedule(OldPriority, CurrentCore, this); + } + + for (int Core = 0; Core < KScheduler.CpuCoresCount; Core++) + { + if (Core != CurrentCore && ((AffinityMask >> Core) & 1) != 0) + { + SchedulingData.Unsuggest(OldPriority, Core, this); + } + } + + //Add thread to the new priority queues. + KThread CurrentThread = Scheduler.GetCurrentThread(); + + if (CurrentCore >= 0) + { + if (CurrentThread == this) + { + SchedulingData.SchedulePrepend(DynamicPriority, CurrentCore, this); + } + else + { + SchedulingData.Schedule(DynamicPriority, CurrentCore, this); + } + } + + for (int Core = 0; Core < KScheduler.CpuCoresCount; Core++) + { + if (Core != CurrentCore && ((AffinityMask >> Core) & 1) != 0) + { + SchedulingData.Suggest(DynamicPriority, Core, this); + } + } + + Scheduler.ThreadReselectionRequested = true; + } + + private void AdjustSchedulingForNewAffinity(long OldAffinityMask, int OldCore) + { + if (SchedFlags != ThreadSchedState.Running || DynamicPriority >= KScheduler.PrioritiesCount) + { + return; + } + + //Remove from old queues. + for (int Core = 0; Core < KScheduler.CpuCoresCount; Core++) + { + if (((OldAffinityMask >> Core) & 1) != 0) + { + if (Core == OldCore) + { + SchedulingData.Unschedule(DynamicPriority, Core, this); + } + else + { + SchedulingData.Unsuggest(DynamicPriority, Core, this); + } + } + } + + //Insert on new queues. + for (int Core = 0; Core < KScheduler.CpuCoresCount; Core++) + { + if (((AffinityMask >> Core) & 1) != 0) + { + if (Core == CurrentCore) + { + SchedulingData.Schedule(DynamicPriority, Core, this); + } + else + { + SchedulingData.Suggest(DynamicPriority, Core, this); + } + } + } + + Scheduler.ThreadReselectionRequested = true; + } + + public override bool IsSignaled() + { + return HasExited; + } + + public void ClearExclusive() + { + Owner.Memory.ClearExclusive(CurrentCore); + } + + public void TimeUp() + { + System.CriticalSectionLock.Lock(); + + SetNewSchedFlags(ThreadSchedState.Running); + + System.CriticalSectionLock.Unlock(); } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KTimeManager.cs b/Ryujinx.HLE/HOS/Kernel/KTimeManager.cs new file mode 100644 index 0000000000..47a3c86cfd --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KTimeManager.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel +{ + class KTimeManager : IDisposable + { + private class WaitingObject + { + public IKFutureSchedulerObject Object { get; private set; } + + public long TimePoint { get; private set; } + + public WaitingObject(IKFutureSchedulerObject Object, long TimePoint) + { + this.Object = Object; + this.TimePoint = TimePoint; + } + } + + private List WaitingObjects; + + private AutoResetEvent WaitEvent; + + private Stopwatch Counter; + + private bool KeepRunning; + + public KTimeManager() + { + WaitingObjects = new List(); + + Counter = new Stopwatch(); + + Counter.Start(); + + KeepRunning = true; + + Thread Work = new Thread(WaitAndCheckScheduledObjects); + + Work.Start(); + } + + public void ScheduleFutureInvocation(IKFutureSchedulerObject Object, long Timeout) + { + lock (WaitingObjects) + { + long TimePoint = Counter.ElapsedMilliseconds + ConvertNanosecondsToMilliseconds(Timeout); + + WaitingObjects.Add(new WaitingObject(Object, TimePoint)); + } + + WaitEvent.Set(); + } + + private long ConvertNanosecondsToMilliseconds(long Timeout) + { + Timeout /= 1000000; + + if ((ulong)Timeout > int.MaxValue) + { + return int.MaxValue; + } + + return Timeout; + } + + public void UnscheduleFutureInvocation(IKFutureSchedulerObject Object) + { + lock (WaitingObjects) + { + WaitingObjects.RemoveAll(x => x.Object == Object); + } + } + + private void WaitAndCheckScheduledObjects() + { + using (WaitEvent = new AutoResetEvent(false)) + { + while (KeepRunning) + { + Monitor.Enter(WaitingObjects); + + WaitingObject Next = WaitingObjects.OrderBy(x => x.TimePoint).FirstOrDefault(); + + Monitor.Exit(WaitingObjects); + + if (Next != null) + { + long TimePoint = Counter.ElapsedMilliseconds; + + if (Next.TimePoint > TimePoint) + { + WaitEvent.WaitOne((int)(Next.TimePoint - TimePoint)); + } + + Monitor.Enter(WaitingObjects); + + bool TimeUp = Counter.ElapsedMilliseconds >= Next.TimePoint && WaitingObjects.Remove(Next); + + Monitor.Exit(WaitingObjects); + + if (TimeUp) + { + Next.Object.TimeUp(); + } + } + else + { + WaitEvent.WaitOne(); + } + } + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool Disposing) + { + if (Disposing) + { + KeepRunning = false; + + WaitEvent?.Set(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KernelErr.cs b/Ryujinx.HLE/HOS/Kernel/KernelErr.cs index e76ae11e5f..0749f3fd08 100644 --- a/Ryujinx.HLE/HOS/Kernel/KernelErr.cs +++ b/Ryujinx.HLE/HOS/Kernel/KernelErr.cs @@ -2,6 +2,7 @@ namespace Ryujinx.HLE.HOS.Kernel { static class KernelErr { + public const int ThreadTerminating = 59; public const int InvalidSize = 101; public const int InvalidAddress = 102; public const int OutOfMemory = 104; @@ -13,7 +14,7 @@ namespace Ryujinx.HLE.HOS.Kernel public const int InvalidHandle = 114; public const int InvalidMaskValue = 116; public const int Timeout = 117; - public const int Canceled = 118; + public const int Cancelled = 118; public const int CountOutOfRange = 119; public const int InvalidEnumValue = 120; public const int InvalidThread = 122; diff --git a/Ryujinx.HLE/HOS/Kernel/NsTimeConverter.cs b/Ryujinx.HLE/HOS/Kernel/NsTimeConverter.cs deleted file mode 100644 index b8008f7579..0000000000 --- a/Ryujinx.HLE/HOS/Kernel/NsTimeConverter.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - static class NsTimeConverter - { - public static int GetTimeMs(ulong Ns) - { - ulong Ms = Ns / 1_000_000; - - if (Ms < int.MaxValue) - { - return (int)Ms; - } - else - { - return int.MaxValue; - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SignalType.cs b/Ryujinx.HLE/HOS/Kernel/SignalType.cs new file mode 100644 index 0000000000..0580315103 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/SignalType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Kernel +{ + enum SignalType + { + Signal = 0, + SignalAndIncrementIfEqual = 1, + SignalAndModifyIfEqual = 2 + } +} diff --git a/Ryujinx.HLE/HOS/Kernel/SvcHandler.cs b/Ryujinx.HLE/HOS/Kernel/SvcHandler.cs index fcb72c140e..a12a0ba0f3 100644 --- a/Ryujinx.HLE/HOS/Kernel/SvcHandler.cs +++ b/Ryujinx.HLE/HOS/Kernel/SvcHandler.cs @@ -1,11 +1,10 @@ using ChocolArm64.Events; using ChocolArm64.Memory; using ChocolArm64.State; +using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.Logging; using System; -using System.Collections.Concurrent; using System.Collections.Generic; -using System.Threading; namespace Ryujinx.HLE.HOS.Kernel { @@ -17,9 +16,28 @@ namespace Ryujinx.HLE.HOS.Kernel private Switch Device; private Process Process; + private Horizon System; private AMemory Memory; - private ConcurrentDictionary SyncWaits; + private struct HleIpcMessage + { + public KThread Thread { get; private set; } + public KSession Session { get; private set; } + public IpcMessage Message { get; private set; } + public long MessagePtr { get; private set; } + + public HleIpcMessage( + KThread Thread, + KSession Session, + IpcMessage Message, + long MessagePtr) + { + this.Thread = Thread; + this.Session = Session; + this.Message = Message; + this.MessagePtr = MessagePtr; + } + } private const uint SelfThreadHandle = 0xffff8000; private const uint SelfProcessHandle = 0xffff8001; @@ -69,14 +87,14 @@ namespace Ryujinx.HLE.HOS.Kernel { 0x2d, SvcUnmapPhysicalMemory }, { 0x32, SvcSetThreadActivity }, { 0x33, SvcGetThreadContext3 }, - { 0x34, SvcWaitForAddress } + { 0x34, SvcWaitForAddress }, + { 0x35, SvcSignalToAddress } }; this.Device = Device; this.Process = Process; + this.System = Process.Device.System; this.Memory = Process.Memory; - - SyncWaits = new ConcurrentDictionary(); } static SvcHandler() @@ -96,8 +114,6 @@ namespace Ryujinx.HLE.HOS.Kernel Func(ThreadState); - Process.Scheduler.Reschedule(Process.GetThread(ThreadState.Tpidr)); - Device.Log.PrintDebug(LogClass.KernelSvc, $"{Func.Method.Name} ended."); } else diff --git a/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs b/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs index d10eb11729..60ccf7f7f8 100644 --- a/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs +++ b/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs @@ -68,7 +68,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (Event != null) { - Event.WaitEvent.Reset(); + Event.Reset(); ThreadState.X0 = 0; } @@ -80,115 +80,6 @@ namespace Ryujinx.HLE.HOS.Kernel } } - private void SvcWaitSynchronization(AThreadState ThreadState) - { - long HandlesPtr = (long)ThreadState.X1; - int HandlesCount = (int)ThreadState.X2; - ulong Timeout = ThreadState.X3; - - Device.Log.PrintDebug(LogClass.KernelSvc, - "HandlesPtr = 0x" + HandlesPtr .ToString("x16") + ", " + - "HandlesCount = 0x" + HandlesCount.ToString("x8") + ", " + - "Timeout = 0x" + Timeout .ToString("x16")); - - if ((uint)HandlesCount > 0x40) - { - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.CountOutOfRange); - - return; - } - - KThread CurrThread = Process.GetThread(ThreadState.Tpidr); - - WaitHandle[] Handles = new WaitHandle[HandlesCount + 1]; - - for (int Index = 0; Index < HandlesCount; Index++) - { - int Handle = Memory.ReadInt32(HandlesPtr + Index * 4); - - KSynchronizationObject SyncObj = Process.HandleTable.GetData(Handle); - - if (SyncObj == null) - { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid handle 0x{Handle:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - return; - } - - Handles[Index] = SyncObj.WaitEvent; - } - - using (AutoResetEvent WaitEvent = new AutoResetEvent(false)) - { - if (!SyncWaits.TryAdd(CurrThread, WaitEvent)) - { - throw new InvalidOperationException(); - } - - Handles[HandlesCount] = WaitEvent; - - Process.Scheduler.Suspend(CurrThread); - - int HandleIndex; - - ulong Result = 0; - - if (Timeout != ulong.MaxValue) - { - HandleIndex = WaitHandle.WaitAny(Handles, NsTimeConverter.GetTimeMs(Timeout)); - } - else - { - HandleIndex = WaitHandle.WaitAny(Handles); - } - - if (HandleIndex == WaitHandle.WaitTimeout) - { - Result = MakeError(ErrorModule.Kernel, KernelErr.Timeout); - } - else if (HandleIndex == HandlesCount) - { - Result = MakeError(ErrorModule.Kernel, KernelErr.Canceled); - } - - SyncWaits.TryRemove(CurrThread, out _); - - Process.Scheduler.Resume(CurrThread); - - ThreadState.X0 = Result; - - if (Result == 0) - { - ThreadState.X1 = (ulong)HandleIndex; - } - } - } - - private void SvcCancelSynchronization(AThreadState ThreadState) - { - int ThreadHandle = (int)ThreadState.X0; - - KThread Thread = GetThread(ThreadState.Tpidr, ThreadHandle); - - if (Thread == null) - { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - return; - } - - if (SyncWaits.TryRemove(Thread, out AutoResetEvent WaitEvent)) - { - WaitEvent.Set(); - } - - ThreadState.X0 = 0; - } - private void SvcGetSystemTick(AThreadState ThreadState) { ThreadState.X0 = ThreadState.CntpctEl0; @@ -203,7 +94,7 @@ namespace Ryujinx.HLE.HOS.Kernel //TODO: Validate that app has perms to access the service, and that the service //actually exists, return error codes otherwise. - KSession Session = new KSession(ServiceFactory.MakeService(Name), Name); + KSession Session = new KSession(ServiceFactory.MakeService(System, Name), Name); ulong Handle = (ulong)Process.HandleTable.OpenHandle(Session); @@ -225,27 +116,38 @@ namespace Ryujinx.HLE.HOS.Kernel (int)ThreadState.X2); } - private void SendSyncRequest(AThreadState ThreadState, long CmdPtr, long Size, int Handle) + private void SendSyncRequest(AThreadState ThreadState, long MessagePtr, long Size, int Handle) { KThread CurrThread = Process.GetThread(ThreadState.Tpidr); - byte[] CmdData = Memory.ReadBytes(CmdPtr, Size); + byte[] MessageData = Memory.ReadBytes(MessagePtr, Size); KSession Session = Process.HandleTable.GetData(Handle); if (Session != null) { - Process.Scheduler.Suspend(CurrThread); + //Process.Scheduler.Suspend(CurrThread); - IpcMessage Cmd = new IpcMessage(CmdData, CmdPtr); + System.CriticalSectionLock.Lock(); - long Result = IpcHandler.IpcCall(Device, Process, Memory, Session, Cmd, CmdPtr); + KThread CurrentThread = System.Scheduler.GetCurrentThread(); - Thread.Yield(); + CurrentThread.SignaledObj = null; + CurrentThread.ObjSyncResult = 0; - Process.Scheduler.Resume(CurrThread); + CurrentThread.Reschedule(ThreadSchedState.Paused); - ThreadState.X0 = (ulong)Result; + IpcMessage Message = new IpcMessage(MessageData, MessagePtr); + + ThreadPool.QueueUserWorkItem(ProcessIpcRequest, new HleIpcMessage( + CurrentThread, + Session, + Message, + MessagePtr)); + + System.CriticalSectionLock.Unlock(); + + ThreadState.X0 = (ulong)CurrentThread.ObjSyncResult; } else { @@ -255,6 +157,21 @@ namespace Ryujinx.HLE.HOS.Kernel } } + private void ProcessIpcRequest(object State) + { + HleIpcMessage IpcMessage = (HleIpcMessage)State; + + IpcMessage.Thread.ObjSyncResult = (int)IpcHandler.IpcCall( + Device, + Process, + Memory, + IpcMessage.Session, + IpcMessage.Message, + IpcMessage.MessagePtr); + + IpcMessage.Thread.Reschedule(ThreadSchedState.Running); + } + private void SvcBreak(AThreadState ThreadState) { long Reason = (long)ThreadState.X0; diff --git a/Ryujinx.HLE/HOS/Kernel/SvcThread.cs b/Ryujinx.HLE/HOS/Kernel/SvcThread.cs index 69e75ec0af..aa6e551b4f 100644 --- a/Ryujinx.HLE/HOS/Kernel/SvcThread.cs +++ b/Ryujinx.HLE/HOS/Kernel/SvcThread.cs @@ -1,6 +1,5 @@ using ChocolArm64.State; using Ryujinx.HLE.Logging; -using System.Threading; using static Ryujinx.HLE.HOS.ErrorCode; @@ -54,14 +53,18 @@ namespace Ryujinx.HLE.HOS.Kernel { int Handle = (int)ThreadState.X0; - KThread NewThread = Process.HandleTable.GetData(Handle); + KThread Thread = Process.HandleTable.GetData(Handle); - if (NewThread != null) + if (Thread != null) { - Process.Scheduler.StartThread(NewThread); - Process.Scheduler.SetReschedule(NewThread.ProcessorId); + long Result = Thread.Start(); - ThreadState.X0 = 0; + if (Result != 0) + { + Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + } + + ThreadState.X0 = (ulong)Result; } else { @@ -73,30 +76,37 @@ namespace Ryujinx.HLE.HOS.Kernel private void SvcExitThread(AThreadState ThreadState) { - KThread CurrThread = Process.GetThread(ThreadState.Tpidr); + KThread CurrentThread = System.Scheduler.GetCurrentThread(); - CurrThread.Thread.StopExecution(); + CurrentThread.Exit(); + + System.Scheduler.StopThread(CurrentThread); + + System.Scheduler.CoreContexts[CurrentThread.CurrentCore].RemoveThread(CurrentThread); } private void SvcSleepThread(AThreadState ThreadState) { - ulong TimeoutNs = ThreadState.X0; + long Timeout = (long)ThreadState.X0; - Device.Log.PrintDebug(LogClass.KernelSvc, "Timeout = 0x" + TimeoutNs.ToString("x16")); + Device.Log.PrintDebug(LogClass.KernelSvc, "Timeout = 0x" + Timeout.ToString("x16")); - KThread CurrThread = Process.GetThread(ThreadState.Tpidr); + KThread CurrentThread = System.Scheduler.GetCurrentThread(); - if (TimeoutNs == 0 || TimeoutNs == ulong.MaxValue) + if (Timeout < 1) { - Process.Scheduler.Yield(CurrThread); + switch (Timeout) + { + case 0: CurrentThread.Yield(); break; + case -1: CurrentThread.YieldWithLoadBalancing(); break; + case -2: CurrentThread.YieldAndWaitForLoadBalancing(); break; + } } else { - Process.Scheduler.Suspend(CurrThread); + CurrentThread.Sleep(Timeout); - Thread.Sleep(NsTimeConverter.GetTimeMs(TimeoutNs)); - - Process.Scheduler.Resume(CurrThread); + ThreadState.X0 = 0; } } @@ -109,7 +119,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (Thread != null) { ThreadState.X0 = 0; - ThreadState.X1 = (ulong)Thread.ActualPriority; + ThreadState.X1 = (ulong)Thread.DynamicPriority; } else { @@ -128,20 +138,22 @@ namespace Ryujinx.HLE.HOS.Kernel "Handle = 0x" + Handle .ToString("x8") + ", " + "Priority = 0x" + Priority.ToString("x8")); + //TODO: NPDM check. + KThread Thread = GetThread(ThreadState.Tpidr, Handle); - if (Thread != null) - { - Thread.SetPriority(Priority); - - ThreadState.X0 = 0; - } - else + if (Thread == null) { Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); + + return; } + + Thread.SetPriority(Priority); + + ThreadState.X0 = 0; } private void SvcGetThreadCoreMask(AThreadState ThreadState) @@ -155,8 +167,8 @@ namespace Ryujinx.HLE.HOS.Kernel if (Thread != null) { ThreadState.X0 = 0; - ThreadState.X1 = (ulong)Thread.IdealCore; - ThreadState.X2 = (ulong)Thread.CoreMask; + ThreadState.X1 = (ulong)Thread.PreferredCore; + ThreadState.X2 = (ulong)Thread.AffinityMask; } else { @@ -168,40 +180,40 @@ namespace Ryujinx.HLE.HOS.Kernel private void SvcSetThreadCoreMask(AThreadState ThreadState) { - int Handle = (int)ThreadState.X0; - int IdealCore = (int)ThreadState.X1; - long CoreMask = (long)ThreadState.X2; + int ThreadHandle = (int)ThreadState.X0; + int PrefferedCore = (int)ThreadState.X1; + long AffinityMask = (long)ThreadState.X2; Device.Log.PrintDebug(LogClass.KernelSvc, - "Handle = 0x" + Handle .ToString("x8") + ", " + - "IdealCore = 0x" + IdealCore.ToString("x8") + ", " + - "CoreMask = 0x" + CoreMask .ToString("x16")); + "ThreadHandle = 0x" + ThreadHandle .ToString("x8") + ", " + + "PrefferedCore = 0x" + PrefferedCore.ToString("x8") + ", " + + "AffinityMask = 0x" + AffinityMask .ToString("x16")); - KThread Thread = GetThread(ThreadState.Tpidr, Handle); - - if (IdealCore == -2) + if (PrefferedCore == -2) { //TODO: Get this value from the NPDM file. - IdealCore = 0; + PrefferedCore = 0; - CoreMask = 1 << IdealCore; + AffinityMask = 1 << PrefferedCore; } else { - if ((uint)IdealCore > 3) + //TODO: Check allowed cores from NPDM file. + + if ((uint)PrefferedCore > 3) { - if ((IdealCore | 2) != -1) + if ((PrefferedCore | 2) != -1) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{IdealCore:x8}!"); + Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{PrefferedCore:x8}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreId); return; } } - else if ((CoreMask & (1 << IdealCore)) == 0) + else if ((AffinityMask & (1 << PrefferedCore)) == 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core mask 0x{CoreMask:x8}!"); + Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core mask 0x{AffinityMask:x8}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMaskValue); @@ -209,35 +221,30 @@ namespace Ryujinx.HLE.HOS.Kernel } } + KThread Thread = GetThread(ThreadState.Tpidr, ThreadHandle); + if (Thread == null) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); + Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); return; } - //-1 is used as "don't care", so the IdealCore value is ignored. - //-2 is used as "use NPDM default core id" (handled above). - //-3 is used as "don't update", the old IdealCore value is kept. - if (IdealCore == -3 && (CoreMask & (1 << Thread.IdealCore)) == 0) + long Result = Thread.SetCoreAndAffinityMask(PrefferedCore, AffinityMask); + + if (Result != 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core mask 0x{CoreMask:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMaskValue); - - return; + Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } - Process.Scheduler.ChangeCore(Thread, IdealCore, (int)CoreMask); - - ThreadState.X0 = 0; + ThreadState.X0 = (ulong)Result; } private void SvcGetCurrentProcessorNumber(AThreadState ThreadState) { - ThreadState.X0 = (ulong)Process.GetThread(ThreadState.Tpidr).ActualCore; + ThreadState.X0 = (ulong)Process.GetThread(ThreadState.Tpidr).CurrentCore; } private void SvcGetThreadId(AThreadState ThreadState) @@ -262,22 +269,36 @@ namespace Ryujinx.HLE.HOS.Kernel private void SvcSetThreadActivity(AThreadState ThreadState) { int Handle = (int)ThreadState.X0; - bool Active = (int)ThreadState.X1 == 0; + bool Pause = (int)ThreadState.X1 == 1; KThread Thread = Process.HandleTable.GetData(Handle); - if (Thread != null) - { - Process.Scheduler.SetThreadActivity(Thread, Active); - - ThreadState.X0 = 0; - } - else + if (Thread == null) { Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); + + return; } + + if (Thread.Owner != Process) + { + Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread owner process!"); + + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); + + return; + } + + long Result = Thread.SetActivity(Pause); + + if (Result != 0) + { + Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + } + + ThreadState.X0 = (ulong)Result; } private void SvcGetThreadContext3(AThreadState ThreadState) @@ -305,79 +326,79 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - Memory.WriteUInt64(Position + 0x0, ThreadState.X0); - Memory.WriteUInt64(Position + 0x8, ThreadState.X1); - Memory.WriteUInt64(Position + 0x10, ThreadState.X2); - Memory.WriteUInt64(Position + 0x18, ThreadState.X3); - Memory.WriteUInt64(Position + 0x20, ThreadState.X4); - Memory.WriteUInt64(Position + 0x28, ThreadState.X5); - Memory.WriteUInt64(Position + 0x30, ThreadState.X6); - Memory.WriteUInt64(Position + 0x38, ThreadState.X7); - Memory.WriteUInt64(Position + 0x40, ThreadState.X8); - Memory.WriteUInt64(Position + 0x48, ThreadState.X9); - Memory.WriteUInt64(Position + 0x50, ThreadState.X10); - Memory.WriteUInt64(Position + 0x58, ThreadState.X11); - Memory.WriteUInt64(Position + 0x60, ThreadState.X12); - Memory.WriteUInt64(Position + 0x68, ThreadState.X13); - Memory.WriteUInt64(Position + 0x70, ThreadState.X14); - Memory.WriteUInt64(Position + 0x78, ThreadState.X15); - Memory.WriteUInt64(Position + 0x80, ThreadState.X16); - Memory.WriteUInt64(Position + 0x88, ThreadState.X17); - Memory.WriteUInt64(Position + 0x90, ThreadState.X18); - Memory.WriteUInt64(Position + 0x98, ThreadState.X19); - Memory.WriteUInt64(Position + 0xa0, ThreadState.X20); - Memory.WriteUInt64(Position + 0xa8, ThreadState.X21); - Memory.WriteUInt64(Position + 0xb0, ThreadState.X22); - Memory.WriteUInt64(Position + 0xb8, ThreadState.X23); - Memory.WriteUInt64(Position + 0xc0, ThreadState.X24); - Memory.WriteUInt64(Position + 0xc8, ThreadState.X25); - Memory.WriteUInt64(Position + 0xd0, ThreadState.X26); - Memory.WriteUInt64(Position + 0xd8, ThreadState.X27); - Memory.WriteUInt64(Position + 0xe0, ThreadState.X28); - Memory.WriteUInt64(Position + 0xe8, ThreadState.X29); - Memory.WriteUInt64(Position + 0xf0, ThreadState.X30); - Memory.WriteUInt64(Position + 0xf8, ThreadState.X31); + Memory.WriteUInt64(Position + 0x0, Thread.Context.ThreadState.X0); + Memory.WriteUInt64(Position + 0x8, Thread.Context.ThreadState.X1); + Memory.WriteUInt64(Position + 0x10, Thread.Context.ThreadState.X2); + Memory.WriteUInt64(Position + 0x18, Thread.Context.ThreadState.X3); + Memory.WriteUInt64(Position + 0x20, Thread.Context.ThreadState.X4); + Memory.WriteUInt64(Position + 0x28, Thread.Context.ThreadState.X5); + Memory.WriteUInt64(Position + 0x30, Thread.Context.ThreadState.X6); + Memory.WriteUInt64(Position + 0x38, Thread.Context.ThreadState.X7); + Memory.WriteUInt64(Position + 0x40, Thread.Context.ThreadState.X8); + Memory.WriteUInt64(Position + 0x48, Thread.Context.ThreadState.X9); + Memory.WriteUInt64(Position + 0x50, Thread.Context.ThreadState.X10); + Memory.WriteUInt64(Position + 0x58, Thread.Context.ThreadState.X11); + Memory.WriteUInt64(Position + 0x60, Thread.Context.ThreadState.X12); + Memory.WriteUInt64(Position + 0x68, Thread.Context.ThreadState.X13); + Memory.WriteUInt64(Position + 0x70, Thread.Context.ThreadState.X14); + Memory.WriteUInt64(Position + 0x78, Thread.Context.ThreadState.X15); + Memory.WriteUInt64(Position + 0x80, Thread.Context.ThreadState.X16); + Memory.WriteUInt64(Position + 0x88, Thread.Context.ThreadState.X17); + Memory.WriteUInt64(Position + 0x90, Thread.Context.ThreadState.X18); + Memory.WriteUInt64(Position + 0x98, Thread.Context.ThreadState.X19); + Memory.WriteUInt64(Position + 0xa0, Thread.Context.ThreadState.X20); + Memory.WriteUInt64(Position + 0xa8, Thread.Context.ThreadState.X21); + Memory.WriteUInt64(Position + 0xb0, Thread.Context.ThreadState.X22); + Memory.WriteUInt64(Position + 0xb8, Thread.Context.ThreadState.X23); + Memory.WriteUInt64(Position + 0xc0, Thread.Context.ThreadState.X24); + Memory.WriteUInt64(Position + 0xc8, Thread.Context.ThreadState.X25); + Memory.WriteUInt64(Position + 0xd0, Thread.Context.ThreadState.X26); + Memory.WriteUInt64(Position + 0xd8, Thread.Context.ThreadState.X27); + Memory.WriteUInt64(Position + 0xe0, Thread.Context.ThreadState.X28); + Memory.WriteUInt64(Position + 0xe8, Thread.Context.ThreadState.X29); + Memory.WriteUInt64(Position + 0xf0, Thread.Context.ThreadState.X30); + Memory.WriteUInt64(Position + 0xf8, Thread.Context.ThreadState.X31); Memory.WriteInt64(Position + 0x100, Thread.LastPc); - Memory.WriteUInt64(Position + 0x108, (ulong)ThreadState.Psr); + Memory.WriteUInt64(Position + 0x108, (ulong)Thread.Context.ThreadState.Psr); - Memory.WriteVector128(Position + 0x110, ThreadState.V0); - Memory.WriteVector128(Position + 0x120, ThreadState.V1); - Memory.WriteVector128(Position + 0x130, ThreadState.V2); - Memory.WriteVector128(Position + 0x140, ThreadState.V3); - Memory.WriteVector128(Position + 0x150, ThreadState.V4); - Memory.WriteVector128(Position + 0x160, ThreadState.V5); - Memory.WriteVector128(Position + 0x170, ThreadState.V6); - Memory.WriteVector128(Position + 0x180, ThreadState.V7); - Memory.WriteVector128(Position + 0x190, ThreadState.V8); - Memory.WriteVector128(Position + 0x1a0, ThreadState.V9); - Memory.WriteVector128(Position + 0x1b0, ThreadState.V10); - Memory.WriteVector128(Position + 0x1c0, ThreadState.V11); - Memory.WriteVector128(Position + 0x1d0, ThreadState.V12); - Memory.WriteVector128(Position + 0x1e0, ThreadState.V13); - Memory.WriteVector128(Position + 0x1f0, ThreadState.V14); - Memory.WriteVector128(Position + 0x200, ThreadState.V15); - Memory.WriteVector128(Position + 0x210, ThreadState.V16); - Memory.WriteVector128(Position + 0x220, ThreadState.V17); - Memory.WriteVector128(Position + 0x230, ThreadState.V18); - Memory.WriteVector128(Position + 0x240, ThreadState.V19); - Memory.WriteVector128(Position + 0x250, ThreadState.V20); - Memory.WriteVector128(Position + 0x260, ThreadState.V21); - Memory.WriteVector128(Position + 0x270, ThreadState.V22); - Memory.WriteVector128(Position + 0x280, ThreadState.V23); - Memory.WriteVector128(Position + 0x290, ThreadState.V24); - Memory.WriteVector128(Position + 0x2a0, ThreadState.V25); - Memory.WriteVector128(Position + 0x2b0, ThreadState.V26); - Memory.WriteVector128(Position + 0x2c0, ThreadState.V27); - Memory.WriteVector128(Position + 0x2d0, ThreadState.V28); - Memory.WriteVector128(Position + 0x2e0, ThreadState.V29); - Memory.WriteVector128(Position + 0x2f0, ThreadState.V30); - Memory.WriteVector128(Position + 0x300, ThreadState.V31); + Memory.WriteVector128(Position + 0x110, Thread.Context.ThreadState.V0); + Memory.WriteVector128(Position + 0x120, Thread.Context.ThreadState.V1); + Memory.WriteVector128(Position + 0x130, Thread.Context.ThreadState.V2); + Memory.WriteVector128(Position + 0x140, Thread.Context.ThreadState.V3); + Memory.WriteVector128(Position + 0x150, Thread.Context.ThreadState.V4); + Memory.WriteVector128(Position + 0x160, Thread.Context.ThreadState.V5); + Memory.WriteVector128(Position + 0x170, Thread.Context.ThreadState.V6); + Memory.WriteVector128(Position + 0x180, Thread.Context.ThreadState.V7); + Memory.WriteVector128(Position + 0x190, Thread.Context.ThreadState.V8); + Memory.WriteVector128(Position + 0x1a0, Thread.Context.ThreadState.V9); + Memory.WriteVector128(Position + 0x1b0, Thread.Context.ThreadState.V10); + Memory.WriteVector128(Position + 0x1c0, Thread.Context.ThreadState.V11); + Memory.WriteVector128(Position + 0x1d0, Thread.Context.ThreadState.V12); + Memory.WriteVector128(Position + 0x1e0, Thread.Context.ThreadState.V13); + Memory.WriteVector128(Position + 0x1f0, Thread.Context.ThreadState.V14); + Memory.WriteVector128(Position + 0x200, Thread.Context.ThreadState.V15); + Memory.WriteVector128(Position + 0x210, Thread.Context.ThreadState.V16); + Memory.WriteVector128(Position + 0x220, Thread.Context.ThreadState.V17); + Memory.WriteVector128(Position + 0x230, Thread.Context.ThreadState.V18); + Memory.WriteVector128(Position + 0x240, Thread.Context.ThreadState.V19); + Memory.WriteVector128(Position + 0x250, Thread.Context.ThreadState.V20); + Memory.WriteVector128(Position + 0x260, Thread.Context.ThreadState.V21); + Memory.WriteVector128(Position + 0x270, Thread.Context.ThreadState.V22); + Memory.WriteVector128(Position + 0x280, Thread.Context.ThreadState.V23); + Memory.WriteVector128(Position + 0x290, Thread.Context.ThreadState.V24); + Memory.WriteVector128(Position + 0x2a0, Thread.Context.ThreadState.V25); + Memory.WriteVector128(Position + 0x2b0, Thread.Context.ThreadState.V26); + Memory.WriteVector128(Position + 0x2c0, Thread.Context.ThreadState.V27); + Memory.WriteVector128(Position + 0x2d0, Thread.Context.ThreadState.V28); + Memory.WriteVector128(Position + 0x2e0, Thread.Context.ThreadState.V29); + Memory.WriteVector128(Position + 0x2f0, Thread.Context.ThreadState.V30); + Memory.WriteVector128(Position + 0x300, Thread.Context.ThreadState.V31); - Memory.WriteInt32(Position + 0x310, ThreadState.Fpcr); - Memory.WriteInt32(Position + 0x314, ThreadState.Fpsr); - Memory.WriteInt64(Position + 0x318, ThreadState.Tpidr); + Memory.WriteInt32(Position + 0x310, Thread.Context.ThreadState.Fpcr); + Memory.WriteInt32(Position + 0x314, Thread.Context.ThreadState.Fpsr); + Memory.WriteInt64(Position + 0x318, Thread.Context.ThreadState.Tpidr); ThreadState.X0 = 0; } diff --git a/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs b/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs index 7097d0f71a..868e017292 100644 --- a/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs +++ b/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs @@ -1,6 +1,5 @@ using ChocolArm64.State; using Ryujinx.HLE.Logging; -using System; using static Ryujinx.HLE.HOS.ErrorCode; @@ -8,18 +7,90 @@ namespace Ryujinx.HLE.HOS.Kernel { partial class SvcHandler { - private const int MutexHasListenersMask = 0x40000000; + private void SvcWaitSynchronization(AThreadState ThreadState) + { + long HandlesPtr = (long)ThreadState.X1; + int HandlesCount = (int)ThreadState.X2; + long Timeout = (long)ThreadState.X3; + + Device.Log.PrintDebug(LogClass.KernelSvc, + "HandlesPtr = 0x" + HandlesPtr .ToString("x16") + ", " + + "HandlesCount = 0x" + HandlesCount.ToString("x8") + ", " + + "Timeout = 0x" + Timeout .ToString("x16")); + + if ((uint)HandlesCount > 0x40) + { + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.CountOutOfRange); + + return; + } + + KSynchronizationObject[] SyncObjs = new KSynchronizationObject[HandlesCount]; + + for (int Index = 0; Index < HandlesCount; Index++) + { + int Handle = Memory.ReadInt32(HandlesPtr + Index * 4); + + KSynchronizationObject SyncObj = Process.HandleTable.GetData(Handle); + + SyncObjs[Index] = SyncObj; + } + + int HndIndex = (int)ThreadState.X1; + + ulong High = ThreadState.X1 & (0xffffffffUL << 32); + + long Result = System.Synchronization.WaitFor(SyncObjs, Timeout, ref HndIndex); + + if (Result != 0) + { + if (Result == MakeError(ErrorModule.Kernel, KernelErr.Timeout) || + Result == MakeError(ErrorModule.Kernel, KernelErr.Cancelled)) + { + Device.Log.PrintDebug(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + } + else + { + Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + } + } + + ThreadState.X0 = (ulong)Result; + ThreadState.X1 = (uint)HndIndex | High; + } + + private void SvcCancelSynchronization(AThreadState ThreadState) + { + int ThreadHandle = (int)ThreadState.X0; + + Device.Log.PrintDebug(LogClass.KernelSvc, "ThreadHandle = 0x" + ThreadHandle.ToString("x8")); + + KThread Thread = Process.HandleTable.GetData(ThreadHandle); + + if (Thread == null) + { + Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!"); + + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); + + return; + } + + Thread.CancelSynchronization(); + + ThreadState.X0 = 0; + } private void SvcArbitrateLock(AThreadState ThreadState) { - int OwnerThreadHandle = (int)ThreadState.X0; - long MutexAddress = (long)ThreadState.X1; - int WaitThreadHandle = (int)ThreadState.X2; + int OwnerHandle = (int)ThreadState.X0; + long MutexAddress = (long)ThreadState.X1; + int RequesterHandle = (int)ThreadState.X2; Device.Log.PrintDebug(LogClass.KernelSvc, - "OwnerThreadHandle = 0x" + OwnerThreadHandle.ToString("x8") + ", " + - "MutexAddress = 0x" + MutexAddress .ToString("x16") + ", " + - "WaitThreadHandle = 0x" + WaitThreadHandle .ToString("x8")); + "OwnerHandle = 0x" + OwnerHandle .ToString("x8") + ", " + + "MutexAddress = 0x" + MutexAddress .ToString("x16") + ", " + + "RequesterHandle = 0x" + RequesterHandle.ToString("x8")); if (IsPointingInsideKernel(MutexAddress)) { @@ -39,33 +110,19 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - KThread OwnerThread = Process.HandleTable.GetData(OwnerThreadHandle); + long Result = System.AddressArbiter.ArbitrateLock( + Process, + Memory, + OwnerHandle, + MutexAddress, + RequesterHandle); - if (OwnerThread == null) + if (Result != 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid owner thread handle 0x{OwnerThreadHandle:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - return; + Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } - KThread WaitThread = Process.HandleTable.GetData(WaitThreadHandle); - - if (WaitThread == null) - { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid requesting thread handle 0x{WaitThreadHandle:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - return; - } - - KThread CurrThread = Process.GetThread(ThreadState.Tpidr); - - MutexLock(CurrThread, WaitThread, OwnerThreadHandle, WaitThreadHandle, MutexAddress); - - ThreadState.X0 = 0; + ThreadState.X0 = (ulong)Result; } private void SvcArbitrateUnlock(AThreadState ThreadState) @@ -92,9 +149,14 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - MutexUnlock(Process.GetThread(ThreadState.Tpidr), MutexAddress); + long Result = System.AddressArbiter.ArbitrateUnlock(Memory, MutexAddress); - ThreadState.X0 = 0; + if (Result != 0) + { + Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + } + + ThreadState.X0 = (ulong)Result; } private void SvcWaitProcessWideKeyAtomic(AThreadState ThreadState) @@ -102,7 +164,7 @@ namespace Ryujinx.HLE.HOS.Kernel long MutexAddress = (long)ThreadState.X0; long CondVarAddress = (long)ThreadState.X1; int ThreadHandle = (int)ThreadState.X2; - ulong Timeout = ThreadState.X3; + long Timeout = (long)ThreadState.X3; Device.Log.PrintDebug(LogClass.KernelSvc, "MutexAddress = 0x" + MutexAddress .ToString("x16") + ", " + @@ -128,86 +190,54 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - KThread Thread = Process.HandleTable.GetData(ThreadHandle); + long Result = System.AddressArbiter.WaitProcessWideKeyAtomic( + Memory, + MutexAddress, + CondVarAddress, + ThreadHandle, + Timeout); - if (Thread == null) + if (Result != 0) { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - return; + if (Result == MakeError(ErrorModule.Kernel, KernelErr.Timeout)) + { + Device.Log.PrintDebug(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + } + else + { + Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + } } - KThread WaitThread = Process.GetThread(ThreadState.Tpidr); - - if (!CondVarWait(WaitThread, ThreadHandle, MutexAddress, CondVarAddress, Timeout)) - { - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.Timeout); - - return; - } - - ThreadState.X0 = 0; + ThreadState.X0 = (ulong)Result; } private void SvcSignalProcessWideKey(AThreadState ThreadState) { - long CondVarAddress = (long)ThreadState.X0; - int Count = (int)ThreadState.X1; + long Address = (long)ThreadState.X0; + int Count = (int)ThreadState.X1; Device.Log.PrintDebug(LogClass.KernelSvc, - "CondVarAddress = 0x" + CondVarAddress.ToString("x16") + ", " + - "Count = 0x" + Count .ToString("x8")); + "Address = 0x" + Address.ToString("x16") + ", " + + "Count = 0x" + Count .ToString("x8")); - KThread CurrThread = Process.GetThread(ThreadState.Tpidr); - - CondVarSignal(ThreadState, CurrThread, CondVarAddress, Count); + System.AddressArbiter.SignalProcessWideKey(Process, Memory, Address, Count); ThreadState.X0 = 0; } - private void MutexLock( - KThread CurrThread, - KThread WaitThread, - int OwnerThreadHandle, - int WaitThreadHandle, - long MutexAddress) - { - lock (Process.ThreadSyncLock) - { - int MutexValue = Memory.ReadInt32(MutexAddress); - - Device.Log.PrintDebug(LogClass.KernelSvc, "MutexValue = 0x" + MutexValue.ToString("x8")); - - if (MutexValue != (OwnerThreadHandle | MutexHasListenersMask)) - { - return; - } - - CurrThread.WaitHandle = WaitThreadHandle; - CurrThread.MutexAddress = MutexAddress; - - InsertWaitingMutexThreadUnsafe(OwnerThreadHandle, WaitThread); - } - - Device.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state..."); - - Process.Scheduler.EnterWait(CurrThread); - } - private void SvcWaitForAddress(AThreadState ThreadState) { - long Address = (long)ThreadState.X0; + long Address = (long)ThreadState.X0; ArbitrationType Type = (ArbitrationType)ThreadState.X1; - int Value = (int)ThreadState.X2; - ulong Timeout = ThreadState.X3; + int Value = (int)ThreadState.X2; + long Timeout = (long)ThreadState.X3; Device.Log.PrintDebug(LogClass.KernelSvc, - "Address = 0x" + Address.ToString("x16") + ", " + - "ArbitrationType = 0x" + Type .ToString() + ", " + - "Value = 0x" + Value .ToString("x8") + ", " + - "Timeout = 0x" + Timeout.ToString("x16")); + "Address = 0x" + Address.ToString("x16") + ", " + + "Type = " + Type .ToString() + ", " + + "Value = 0x" + Value .ToString("x8") + ", " + + "Timeout = 0x" + Timeout.ToString("x16")); if (IsPointingInsideKernel(Address)) { @@ -227,287 +257,93 @@ namespace Ryujinx.HLE.HOS.Kernel return; } + long Result; + switch (Type) { case ArbitrationType.WaitIfLessThan: - ThreadState.X0 = AddressArbiter.WaitForAddressIfLessThan(Process, ThreadState, Memory, Address, Value, Timeout, false); + Result = System.AddressArbiter.WaitForAddressIfLessThan(Memory, Address, Value, false, Timeout); break; case ArbitrationType.DecrementAndWaitIfLessThan: - ThreadState.X0 = AddressArbiter.WaitForAddressIfLessThan(Process, ThreadState, Memory, Address, Value, Timeout, true); + Result = System.AddressArbiter.WaitForAddressIfLessThan(Memory, Address, Value, true, Timeout); break; case ArbitrationType.WaitIfEqual: - ThreadState.X0 = AddressArbiter.WaitForAddressIfEqual(Process, ThreadState, Memory, Address, Value, Timeout); + Result = System.AddressArbiter.WaitForAddressIfEqual(Memory, Address, Value, Timeout); break; default: - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidEnumValue); + Result = MakeError(ErrorModule.Kernel, KernelErr.InvalidEnumValue); break; } + + if (Result != 0) + { + Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + } + + ThreadState.X0 = (ulong)Result; } - private void MutexUnlock(KThread CurrThread, long MutexAddress) + private void SvcSignalToAddress(AThreadState ThreadState) { - lock (Process.ThreadSyncLock) + long Address = (long)ThreadState.X0; + SignalType Type = (SignalType)ThreadState.X1; + int Value = (int)ThreadState.X2; + int Count = (int)ThreadState.X3; + + Device.Log.PrintDebug(LogClass.KernelSvc, + "Address = 0x" + Address.ToString("x16") + ", " + + "Type = " + Type .ToString() + ", " + + "Value = 0x" + Value .ToString("x8") + ", " + + "Count = 0x" + Count .ToString("x8")); + + if (IsPointingInsideKernel(Address)) { - //This is the new thread that will now own the mutex. - //If no threads are waiting for the lock, then it should be null. - (KThread OwnerThread, int Count) = PopMutexThreadUnsafe(CurrThread, MutexAddress); + Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address 0x{Address:x16}!"); - if (OwnerThread == CurrThread) - { - throw new InvalidOperationException(); - } - - if (OwnerThread != null) - { - //Remove all waiting mutex from the old owner, - //and insert then on the new owner. - UpdateMutexOwnerUnsafe(CurrThread, OwnerThread, MutexAddress); - - CurrThread.UpdatePriority(); - - int HasListeners = Count >= 2 ? MutexHasListenersMask : 0; - - Memory.WriteInt32ToSharedAddr(MutexAddress, HasListeners | OwnerThread.WaitHandle); - - OwnerThread.WaitHandle = 0; - OwnerThread.MutexAddress = 0; - OwnerThread.CondVarAddress = 0; - OwnerThread.MutexOwner = null; - - OwnerThread.UpdatePriority(); - - Process.Scheduler.WakeUp(OwnerThread); - - Device.Log.PrintDebug(LogClass.KernelSvc, "Gave mutex to thread id " + OwnerThread.ThreadId + "!"); - } - else - { - Memory.WriteInt32ToSharedAddr(MutexAddress, 0); - - Device.Log.PrintDebug(LogClass.KernelSvc, "No threads waiting mutex!"); - } - } - } - - private bool CondVarWait( - KThread WaitThread, - int WaitThreadHandle, - long MutexAddress, - long CondVarAddress, - ulong Timeout) - { - WaitThread.WaitHandle = WaitThreadHandle; - WaitThread.MutexAddress = MutexAddress; - WaitThread.CondVarAddress = CondVarAddress; - - lock (Process.ThreadSyncLock) - { - MutexUnlock(WaitThread, MutexAddress); - - WaitThread.CondVarSignaled = false; - - Process.ThreadArbiterList.Add(WaitThread); - } - - Device.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state..."); - - if (Timeout != ulong.MaxValue) - { - Process.Scheduler.EnterWait(WaitThread, NsTimeConverter.GetTimeMs(Timeout)); - - lock (Process.ThreadSyncLock) - { - if (!WaitThread.CondVarSignaled || WaitThread.MutexOwner != null) - { - if (WaitThread.MutexOwner != null) - { - WaitThread.MutexOwner.MutexWaiters.Remove(WaitThread); - WaitThread.MutexOwner.UpdatePriority(); - - WaitThread.MutexOwner = null; - } - - Process.ThreadArbiterList.Remove(WaitThread); - - Device.Log.PrintDebug(LogClass.KernelSvc, "Timed out..."); - - return false; - } - } - } - else - { - Process.Scheduler.EnterWait(WaitThread); - } - - return true; - } - - private void CondVarSignal( - AThreadState ThreadState, - KThread CurrThread, - long CondVarAddress, - int Count) - { - lock (Process.ThreadSyncLock) - { - while (Count == -1 || Count-- > 0) - { - KThread WaitThread = PopCondVarThreadUnsafe(CondVarAddress); - - if (WaitThread == null) - { - Device.Log.PrintDebug(LogClass.KernelSvc, "No more threads to wake up!"); - - break; - } - - WaitThread.CondVarSignaled = true; - - long MutexAddress = WaitThread.MutexAddress; - - Memory.SetExclusive(ThreadState, MutexAddress); - - int MutexValue = Memory.ReadInt32(MutexAddress); - - while (MutexValue != 0) - { - if (Memory.TestExclusive(ThreadState, MutexAddress)) - { - //Wait until the lock is released. - InsertWaitingMutexThreadUnsafe(MutexValue & ~MutexHasListenersMask, WaitThread); - - Memory.WriteInt32(MutexAddress, MutexValue | MutexHasListenersMask); - - Memory.ClearExclusiveForStore(ThreadState); - - break; - } - - Memory.SetExclusive(ThreadState, MutexAddress); - - MutexValue = Memory.ReadInt32(MutexAddress); - } - - Device.Log.PrintDebug(LogClass.KernelSvc, "MutexValue = 0x" + MutexValue.ToString("x8")); - - if (MutexValue == 0) - { - //Give the lock to this thread. - Memory.WriteInt32ToSharedAddr(MutexAddress, WaitThread.WaitHandle); - - WaitThread.WaitHandle = 0; - WaitThread.MutexAddress = 0; - WaitThread.CondVarAddress = 0; - - WaitThread.MutexOwner?.UpdatePriority(); - - WaitThread.MutexOwner = null; - - Process.Scheduler.WakeUp(WaitThread); - } - } - } - } - - private void UpdateMutexOwnerUnsafe(KThread CurrThread, KThread NewOwner, long MutexAddress) - { - //Go through all threads waiting for the mutex, - //and update the MutexOwner field to point to the new owner. - for (int Index = 0; Index < CurrThread.MutexWaiters.Count; Index++) - { - KThread Thread = CurrThread.MutexWaiters[Index]; - - if (Thread.MutexAddress == MutexAddress) - { - CurrThread.MutexWaiters.RemoveAt(Index--); - - InsertWaitingMutexThreadUnsafe(NewOwner, Thread); - } - } - } - - private void InsertWaitingMutexThreadUnsafe(int OwnerThreadHandle, KThread WaitThread) - { - KThread OwnerThread = Process.HandleTable.GetData(OwnerThreadHandle); - - if (OwnerThread == null) - { - Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{OwnerThreadHandle:x8}!"); + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); return; } - InsertWaitingMutexThreadUnsafe(OwnerThread, WaitThread); - } - - private void InsertWaitingMutexThreadUnsafe(KThread OwnerThread, KThread WaitThread) - { - WaitThread.MutexOwner = OwnerThread; - - if (!OwnerThread.MutexWaiters.Contains(WaitThread)) + if (IsAddressNotWordAligned(Address)) { - OwnerThread.MutexWaiters.Add(WaitThread); + Device.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned address 0x{Address:x16}!"); - OwnerThread.UpdatePriority(); - } - } + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); - private (KThread, int) PopMutexThreadUnsafe(KThread OwnerThread, long MutexAddress) - { - int Count = 0; - - KThread WakeThread = null; - - foreach (KThread Thread in OwnerThread.MutexWaiters) - { - if (Thread.MutexAddress != MutexAddress) - { - continue; - } - - if (WakeThread == null || Thread.ActualPriority < WakeThread.ActualPriority) - { - WakeThread = Thread; - } - - Count++; + return; } - if (WakeThread != null) + long Result; + + switch (Type) { - OwnerThread.MutexWaiters.Remove(WakeThread); + case SignalType.Signal: + Result = System.AddressArbiter.Signal(Address, Count); + break; + + case SignalType.SignalAndIncrementIfEqual: + Result = System.AddressArbiter.SignalAndIncrementIfEqual(Memory, Address, Value, Count); + break; + + case SignalType.SignalAndModifyIfEqual: + Result = System.AddressArbiter.SignalAndModifyIfEqual(Memory, Address, Value, Count); + break; + + default: + Result = MakeError(ErrorModule.Kernel, KernelErr.InvalidEnumValue); + break; } - return (WakeThread, Count); - } - - private KThread PopCondVarThreadUnsafe(long CondVarAddress) - { - KThread WakeThread = null; - - foreach (KThread Thread in Process.ThreadArbiterList) + if (Result != 0) { - if (Thread.CondVarAddress != CondVarAddress) - { - continue; - } - - if (WakeThread == null || Thread.ActualPriority < WakeThread.ActualPriority) - { - WakeThread = Thread; - } + Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } - if (WakeThread != null) - { - Process.ThreadArbiterList.Remove(WakeThread); - } - - return WakeThread; + ThreadState.X0 = (ulong)Result; } private bool IsPointingInsideKernel(long Address) diff --git a/Ryujinx.HLE/HOS/Kernel/ThreadQueue.cs b/Ryujinx.HLE/HOS/Kernel/ThreadQueue.cs deleted file mode 100644 index 815e86ad2a..0000000000 --- a/Ryujinx.HLE/HOS/Kernel/ThreadQueue.cs +++ /dev/null @@ -1,158 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - class ThreadQueue - { - private const int LowestPriority = 0x3f; - - private SchedulerThread Head; - - private object ListLock; - - public ThreadQueue() - { - ListLock = new object(); - } - - public void Push(SchedulerThread Wait) - { - lock (ListLock) - { - //Ensure that we're not creating circular references - //by adding a thread that is already on the list. - if (HasThread(Wait)) - { - return; - } - - if (Head == null || Head.Thread.ActualPriority >= Wait.Thread.ActualPriority) - { - Wait.Next = Head; - - Head = Wait; - - return; - } - - SchedulerThread Curr = Head; - - while (Curr.Next != null) - { - if (Curr.Next.Thread.ActualPriority >= Wait.Thread.ActualPriority) - { - break; - } - - Curr = Curr.Next; - } - - Wait.Next = Curr.Next; - Curr.Next = Wait; - } - } - - public SchedulerThread Pop(int Core, int MinPriority = LowestPriority) - { - lock (ListLock) - { - int CoreMask = 1 << Core; - - SchedulerThread Prev = null; - SchedulerThread Curr = Head; - - while (Curr != null) - { - KThread Thread = Curr.Thread; - - if (Thread.ActualPriority <= MinPriority && (Thread.CoreMask & CoreMask) != 0) - { - if (Prev != null) - { - Prev.Next = Curr.Next; - } - else - { - Head = Head.Next; - } - - break; - } - - Prev = Curr; - Curr = Curr.Next; - } - - return Curr; - } - } - - public bool Remove(SchedulerThread Thread) - { - lock (ListLock) - { - if (Head == null) - { - return false; - } - else if (Head == Thread) - { - Head = Head.Next; - - return true; - } - - SchedulerThread Prev = Head; - SchedulerThread Curr = Head.Next; - - while (Curr != null) - { - if (Curr == Thread) - { - Prev.Next = Curr.Next; - - return true; - } - - Prev = Curr; - Curr = Curr.Next; - } - - return false; - } - } - - public bool Resort(SchedulerThread Thread) - { - lock (ListLock) - { - if (Remove(Thread)) - { - Push(Thread); - - return true; - } - - return false; - } - } - - public bool HasThread(SchedulerThread Thread) - { - lock (ListLock) - { - SchedulerThread Curr = Head; - - while (Curr != null) - { - if (Curr == Thread) - { - return true; - } - - Curr = Curr.Next; - } - - return false; - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/ThreadSchedState.cs b/Ryujinx.HLE/HOS/Kernel/ThreadSchedState.cs new file mode 100644 index 0000000000..603446f3cf --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/ThreadSchedState.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Kernel +{ + enum ThreadSchedState : byte + { + LowNibbleMask = 0xf, + HighNibbleMask = 0xf0, + ExceptionalMask = 0x70, + ForcePauseFlag = 0x20, + + None = 0, + Paused = 1, + Running = 2, + TerminationPending = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Process.cs b/Ryujinx.HLE/HOS/Process.cs index 72ed6b0d8e..289920bef3 100644 --- a/Ryujinx.HLE/HOS/Process.cs +++ b/Ryujinx.HLE/HOS/Process.cs @@ -40,12 +40,6 @@ namespace Ryujinx.HLE.HOS private List TlsPages; - public KProcessScheduler Scheduler { get; private set; } - - public List ThreadArbiterList { get; private set; } - - public object ThreadSyncLock { get; private set; } - public Npdm MetaData { get; private set; } public KProcessHandleTable HandleTable { get; private set; } @@ -62,14 +56,11 @@ namespace Ryujinx.HLE.HOS private long ImageBase; - private bool ShouldDispose; - private bool Disposed; - public Process(Switch Device, KProcessScheduler Scheduler, int ProcessId, Npdm MetaData) + public Process(Switch Device, int ProcessId, Npdm MetaData) { this.Device = Device; - this.Scheduler = Scheduler; this.MetaData = MetaData; this.ProcessId = ProcessId; @@ -79,13 +70,9 @@ namespace Ryujinx.HLE.HOS TlsPages = new List(); - ThreadArbiterList = new List(); - - ThreadSyncLock = new object(); - HandleTable = new KProcessHandleTable(); - AppletState = new AppletStateMgr(); + AppletState = new AppletStateMgr(Device.System); SvcHandler = new SvcHandler(Device, this); @@ -171,15 +158,17 @@ namespace Ryujinx.HLE.HOS Homebrew.WriteHbAbiData(Memory, HbAbiDataPosition, Handle, SwitchPath); - MainThread.Thread.ThreadState.X0 = (ulong)HbAbiDataPosition; - MainThread.Thread.ThreadState.X1 = ulong.MaxValue; + MainThread.Context.ThreadState.X0 = (ulong)HbAbiDataPosition; + MainThread.Context.ThreadState.X1 = ulong.MaxValue; } - Scheduler.StartThread(MainThread); + MainThread.TimeUp(); return true; } + private int ThreadIdCtr = 1; + public int MakeThread( long EntryPoint, long StackTop, @@ -196,9 +185,9 @@ namespace Ryujinx.HLE.HOS long Tpidr = GetFreeTls(); - int ThreadId = (int)((Tpidr - MemoryManager.TlsIoRegionStart) / 0x200) + 1; + int ThreadId = ThreadIdCtr++; //(int)((Tpidr - MemoryManager.TlsIoRegionStart) / 0x200) + 1; - KThread Thread = new KThread(CpuThread, this, ProcessorId, Priority, ThreadId); + KThread Thread = new KThread(CpuThread, this, Device.System, ProcessorId, Priority, ThreadId); Thread.LastPc = EntryPoint; @@ -211,6 +200,7 @@ namespace Ryujinx.HLE.HOS CpuThread.ThreadState.X1 = (ulong)Handle; CpuThread.ThreadState.X31 = (ulong)StackTop; + CpuThread.ThreadState.Interrupt += InterruptHandler; CpuThread.ThreadState.Break += BreakHandler; CpuThread.ThreadState.SvcCall += SvcHandler.SvcCall; CpuThread.ThreadState.Undefined += UndefinedHandler; @@ -248,6 +238,11 @@ namespace Ryujinx.HLE.HOS return Position; } + private void InterruptHandler(object sender, EventArgs e) + { + Device.System.Scheduler.ContextSwitch(); + } + private void BreakHandler(object sender, AInstExceptionEventArgs e) { throw new GuestBrokeExecutionException(); @@ -359,10 +354,6 @@ namespace Ryujinx.HLE.HOS if (sender is AThread Thread) { Threads.TryRemove(Thread.ThreadState.Tpidr, out KThread KernelThread); - - Scheduler.RemoveThread(KernelThread); - - KernelThread.WaitEvent.Set(); } if (Threads.Count == 0) @@ -400,8 +391,6 @@ namespace Ryujinx.HLE.HOS INvDrvServices.UnloadProcess(this); - AppletState.Dispose(); - if (NeedsHbAbi && Executables.Count > 0 && Executables[0].FilePath.EndsWith(Homebrew.TemporaryNroSuffix)) { File.Delete(Executables[0].FilePath); @@ -423,9 +412,7 @@ namespace Ryujinx.HLE.HOS { foreach (KThread Thread in Threads.Values) { - Thread.Thread.StopExecution(); - - Scheduler.ForceWakeUp(Thread); + Device.System.Scheduler.StopThread(Thread); } } else diff --git a/Ryujinx.HLE/HOS/Services/Am/IApplicationProxy.cs b/Ryujinx.HLE/HOS/Services/Am/IApplicationProxy.cs index 4003f1515a..2aaeda7826 100644 --- a/Ryujinx.HLE/HOS/Services/Am/IApplicationProxy.cs +++ b/Ryujinx.HLE/HOS/Services/Am/IApplicationProxy.cs @@ -26,14 +26,14 @@ namespace Ryujinx.HLE.HOS.Services.Am public long GetCommonStateGetter(ServiceCtx Context) { - MakeObject(Context, new ICommonStateGetter()); + MakeObject(Context, new ICommonStateGetter(Context.Device.System)); return 0; } public long GetSelfController(ServiceCtx Context) { - MakeObject(Context, new ISelfController()); + MakeObject(Context, new ISelfController(Context.Device.System)); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Am/ICommonStateGetter.cs b/Ryujinx.HLE/HOS/Services/Am/ICommonStateGetter.cs index 3cdfbbdb95..72049d6f44 100644 --- a/Ryujinx.HLE/HOS/Services/Am/ICommonStateGetter.cs +++ b/Ryujinx.HLE/HOS/Services/Am/ICommonStateGetter.cs @@ -15,7 +15,7 @@ namespace Ryujinx.HLE.HOS.Services.Am private KEvent DisplayResolutionChangeEvent; - public ICommonStateGetter() + public ICommonStateGetter(Horizon System) { m_Commands = new Dictionary() { @@ -29,7 +29,7 @@ namespace Ryujinx.HLE.HOS.Services.Am { 61, GetDefaultDisplayResolutionChangeEvent } }; - DisplayResolutionChangeEvent = new KEvent(); + DisplayResolutionChangeEvent = new KEvent(System); } public long GetEventHandle(ServiceCtx Context) diff --git a/Ryujinx.HLE/HOS/Services/Am/IHomeMenuFunctions.cs b/Ryujinx.HLE/HOS/Services/Am/IHomeMenuFunctions.cs index 95028ca047..0c271796e8 100644 --- a/Ryujinx.HLE/HOS/Services/Am/IHomeMenuFunctions.cs +++ b/Ryujinx.HLE/HOS/Services/Am/IHomeMenuFunctions.cs @@ -13,7 +13,7 @@ namespace Ryujinx.HLE.HOS.Services.Am private KEvent ChannelEvent; - public IHomeMenuFunctions() + public IHomeMenuFunctions(Horizon System) { m_Commands = new Dictionary() { @@ -22,7 +22,7 @@ namespace Ryujinx.HLE.HOS.Services.Am }; //ToDo: Signal this Event somewhere in future. - ChannelEvent = new KEvent(); + ChannelEvent = new KEvent(System); } public long RequestToGetForeground(ServiceCtx Context) diff --git a/Ryujinx.HLE/HOS/Services/Am/ILibraryAppletAccessor.cs b/Ryujinx.HLE/HOS/Services/Am/ILibraryAppletAccessor.cs index e099ec64a7..a9de3ebd45 100644 --- a/Ryujinx.HLE/HOS/Services/Am/ILibraryAppletAccessor.cs +++ b/Ryujinx.HLE/HOS/Services/Am/ILibraryAppletAccessor.cs @@ -13,7 +13,7 @@ namespace Ryujinx.HLE.HOS.Services.Am private KEvent StateChangedEvent; - public ILibraryAppletAccessor() + public ILibraryAppletAccessor(Horizon System) { m_Commands = new Dictionary() { @@ -24,12 +24,12 @@ namespace Ryujinx.HLE.HOS.Services.Am { 101, PopOutData } }; - StateChangedEvent = new KEvent(); + StateChangedEvent = new KEvent(System); } public long GetAppletStateChangedEvent(ServiceCtx Context) { - StateChangedEvent.WaitEvent.Set(); + StateChangedEvent.Signal(); int Handle = Context.Process.HandleTable.OpenHandle(StateChangedEvent); diff --git a/Ryujinx.HLE/HOS/Services/Am/ILibraryAppletCreator.cs b/Ryujinx.HLE/HOS/Services/Am/ILibraryAppletCreator.cs index 065574c784..5535a43c7c 100644 --- a/Ryujinx.HLE/HOS/Services/Am/ILibraryAppletCreator.cs +++ b/Ryujinx.HLE/HOS/Services/Am/ILibraryAppletCreator.cs @@ -20,7 +20,7 @@ namespace Ryujinx.HLE.HOS.Services.Am public long CreateLibraryApplet(ServiceCtx Context) { - MakeObject(Context, new ILibraryAppletAccessor()); + MakeObject(Context, new ILibraryAppletAccessor(Context.Device.System)); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Am/ISelfController.cs b/Ryujinx.HLE/HOS/Services/Am/ISelfController.cs index ccd96e0d2d..fe8822735b 100644 --- a/Ryujinx.HLE/HOS/Services/Am/ISelfController.cs +++ b/Ryujinx.HLE/HOS/Services/Am/ISelfController.cs @@ -13,7 +13,7 @@ namespace Ryujinx.HLE.HOS.Services.Am private KEvent LaunchableEvent; - public ISelfController() + public ISelfController(Horizon System) { m_Commands = new Dictionary() { @@ -31,7 +31,7 @@ namespace Ryujinx.HLE.HOS.Services.Am { 50, SetHandlesRequestToDisplay } }; - LaunchableEvent = new KEvent(); + LaunchableEvent = new KEvent(System); } public long Exit(ServiceCtx Context) @@ -57,7 +57,7 @@ namespace Ryujinx.HLE.HOS.Services.Am public long GetLibraryAppletLaunchableEvent(ServiceCtx Context) { - LaunchableEvent.WaitEvent.Set(); + LaunchableEvent.Signal(); int Handle = Context.Process.HandleTable.OpenHandle(LaunchableEvent); diff --git a/Ryujinx.HLE/HOS/Services/Am/ISystemAppletProxy.cs b/Ryujinx.HLE/HOS/Services/Am/ISystemAppletProxy.cs index c08d401802..85e11e0fd0 100644 --- a/Ryujinx.HLE/HOS/Services/Am/ISystemAppletProxy.cs +++ b/Ryujinx.HLE/HOS/Services/Am/ISystemAppletProxy.cs @@ -28,14 +28,14 @@ namespace Ryujinx.HLE.HOS.Services.Am public long GetCommonStateGetter(ServiceCtx Context) { - MakeObject(Context, new ICommonStateGetter()); + MakeObject(Context, new ICommonStateGetter(Context.Device.System)); return 0; } public long GetSelfController(ServiceCtx Context) { - MakeObject(Context, new ISelfController()); + MakeObject(Context, new ISelfController(Context.Device.System)); return 0; } @@ -70,7 +70,7 @@ namespace Ryujinx.HLE.HOS.Services.Am public long GetHomeMenuFunctions(ServiceCtx Context) { - MakeObject(Context, new IHomeMenuFunctions()); + MakeObject(Context, new IHomeMenuFunctions(Context.Device.System)); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioOut/IAudioOut.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioOut/IAudioOut.cs index 81561f046f..2b0b5293ed 100644 --- a/Ryujinx.HLE/HOS/Services/Aud/AudioOut/IAudioOut.cs +++ b/Ryujinx.HLE/HOS/Services/Aud/AudioOut/IAudioOut.cs @@ -155,8 +155,6 @@ namespace Ryujinx.HLE.HOS.Services.Aud.AudioOut if (Disposing) { AudioOut.CloseTrack(Track); - - ReleaseEvent.Dispose(); } } } diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/IAudioRenderer.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/IAudioRenderer.cs index 8c83338d1d..ae85bf0189 100644 --- a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/IAudioRenderer.cs +++ b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/IAudioRenderer.cs @@ -38,7 +38,11 @@ namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer private int Track; - public IAudioRenderer(AMemory Memory, IAalOutput AudioOut, AudioRendererParameter Params) + public IAudioRenderer( + Horizon System, + AMemory Memory, + IAalOutput AudioOut, + AudioRendererParameter Params) { m_Commands = new Dictionary() { @@ -48,7 +52,7 @@ namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer { 7, QuerySystemEvent } }; - UpdateEvent = new KEvent(); + UpdateEvent = new KEvent(System); this.Memory = Memory; this.AudioOut = AudioOut; @@ -68,7 +72,7 @@ namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer private void AudioCallback() { - UpdateEvent.WaitEvent.Set(); + UpdateEvent.Signal(); } private static T[] CreateArray(int Size) where T : new() @@ -310,8 +314,6 @@ namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer if (Disposing) { AudioOut.CloseTrack(Track); - - UpdateEvent.Dispose(); } } } diff --git a/Ryujinx.HLE/HOS/Services/Aud/IAudioDevice.cs b/Ryujinx.HLE/HOS/Services/Aud/IAudioDevice.cs index 2e6056efb4..adecc7210a 100644 --- a/Ryujinx.HLE/HOS/Services/Aud/IAudioDevice.cs +++ b/Ryujinx.HLE/HOS/Services/Aud/IAudioDevice.cs @@ -15,7 +15,7 @@ namespace Ryujinx.HLE.HOS.Services.Aud private KEvent SystemEvent; - public IAudioDevice() + public IAudioDevice(Horizon System) { m_Commands = new Dictionary() { @@ -32,10 +32,10 @@ namespace Ryujinx.HLE.HOS.Services.Aud { 12, QueryAudioDeviceOutputEvent } }; - SystemEvent = new KEvent(); + SystemEvent = new KEvent(System); //TODO: We shouldn't be signaling this here. - SystemEvent.WaitEvent.Set(); + SystemEvent.Signal(); } public long ListAudioDeviceName(ServiceCtx Context) diff --git a/Ryujinx.HLE/HOS/Services/Aud/IAudioOutManager.cs b/Ryujinx.HLE/HOS/Services/Aud/IAudioOutManager.cs index 8d2435b083..ef9250d926 100644 --- a/Ryujinx.HLE/HOS/Services/Aud/IAudioOutManager.cs +++ b/Ryujinx.HLE/HOS/Services/Aud/IAudioOutManager.cs @@ -146,11 +146,11 @@ namespace Ryujinx.HLE.HOS.Services.Aud Channels = DefaultChannelsCount; } - KEvent ReleaseEvent = new KEvent(); + KEvent ReleaseEvent = new KEvent(Context.Device.System); ReleaseCallback Callback = () => { - ReleaseEvent.WaitEvent.Set(); + ReleaseEvent.Signal(); }; IAalOutput AudioOut = Context.Device.AudioOut; diff --git a/Ryujinx.HLE/HOS/Services/Aud/IAudioRendererManager.cs b/Ryujinx.HLE/HOS/Services/Aud/IAudioRendererManager.cs index faa422901b..7ebe2b5873 100644 --- a/Ryujinx.HLE/HOS/Services/Aud/IAudioRendererManager.cs +++ b/Ryujinx.HLE/HOS/Services/Aud/IAudioRendererManager.cs @@ -40,7 +40,11 @@ namespace Ryujinx.HLE.HOS.Services.Aud AudioRendererParameter Params = GetAudioRendererParameter(Context); - MakeObject(Context, new IAudioRenderer(Context.Memory, AudioOut, Params)); + MakeObject(Context, new IAudioRenderer( + Context.Device.System, + Context.Memory, + AudioOut, + Params)); return 0; } @@ -161,7 +165,7 @@ namespace Ryujinx.HLE.HOS.Services.Aud { long UserId = Context.RequestData.ReadInt64(); - MakeObject(Context, new IAudioDevice()); + MakeObject(Context, new IAudioDevice(Context.Device.System)); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs b/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs index 70f1f1f1f5..2fd07ec768 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs @@ -2,12 +2,11 @@ using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.Input; using Ryujinx.HLE.Logging; -using System; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Hid { - class IHidServer : IpcService, IDisposable + class IHidServer : IpcService { private Dictionary m_Commands; @@ -15,7 +14,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid public override IReadOnlyDictionary Commands => m_Commands; - public IHidServer() + public IHidServer(Horizon System) { m_Commands = new Dictionary() { @@ -45,7 +44,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid { 206, SendVibrationValues } }; - NpadStyleSetUpdateEvent = new KEvent(); + NpadStyleSetUpdateEvent = new KEvent(System); } public long CreateAppletResource(ServiceCtx Context) @@ -282,18 +281,5 @@ namespace Ryujinx.HLE.HOS.Services.Hid return 0; } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing) - { - NpadStyleSetUpdateEvent.Dispose(); - } - } } } diff --git a/Ryujinx.HLE/HOS/Services/Nfp/IUser.cs b/Ryujinx.HLE/HOS/Services/Nfp/IUser.cs index eac90da421..33f739677f 100644 --- a/Ryujinx.HLE/HOS/Services/Nfp/IUser.cs +++ b/Ryujinx.HLE/HOS/Services/Nfp/IUser.cs @@ -24,7 +24,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfp private KEvent AvailabilityChangeEvent; - public IUser() + public IUser(Horizon System) { m_Commands = new Dictionary() { @@ -37,9 +37,9 @@ namespace Ryujinx.HLE.HOS.Services.Nfp { 23, AttachAvailabilityChangeEvent } }; - ActivateEvent = new KEvent(); - DeactivateEvent = new KEvent(); - AvailabilityChangeEvent = new KEvent(); + ActivateEvent = new KEvent(System); + DeactivateEvent = new KEvent(System); + AvailabilityChangeEvent = new KEvent(System); } public long Initialize(ServiceCtx Context) diff --git a/Ryujinx.HLE/HOS/Services/Nfp/IUserManager.cs b/Ryujinx.HLE/HOS/Services/Nfp/IUserManager.cs index 770f0341ec..e5d5a4f1cb 100644 --- a/Ryujinx.HLE/HOS/Services/Nfp/IUserManager.cs +++ b/Ryujinx.HLE/HOS/Services/Nfp/IUserManager.cs @@ -19,7 +19,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfp public long GetUserInterface(ServiceCtx Context) { - MakeObject(Context, new IUser()); + MakeObject(Context, new IUser(Context.Device.System)); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Nifm/IGeneralService.cs b/Ryujinx.HLE/HOS/Services/Nifm/IGeneralService.cs index ec68247ba9..6adbf00a15 100644 --- a/Ryujinx.HLE/HOS/Services/Nifm/IGeneralService.cs +++ b/Ryujinx.HLE/HOS/Services/Nifm/IGeneralService.cs @@ -30,7 +30,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm { int Unknown = Context.RequestData.ReadInt32(); - MakeObject(Context, new IRequest()); + MakeObject(Context, new IRequest(Context.Device.System)); Context.Device.Log.PrintStub(LogClass.ServiceNifm, "Stubbed."); diff --git a/Ryujinx.HLE/HOS/Services/Nifm/IRequest.cs b/Ryujinx.HLE/HOS/Services/Nifm/IRequest.cs index 7bd30ff905..3f4df719cf 100644 --- a/Ryujinx.HLE/HOS/Services/Nifm/IRequest.cs +++ b/Ryujinx.HLE/HOS/Services/Nifm/IRequest.cs @@ -1,12 +1,11 @@ using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.Logging; -using System; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Nifm { - class IRequest : IpcService, IDisposable + class IRequest : IpcService { private Dictionary m_Commands; @@ -15,7 +14,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm private KEvent Event0; private KEvent Event1; - public IRequest() + public IRequest(Horizon System) { m_Commands = new Dictionary() { @@ -27,8 +26,8 @@ namespace Ryujinx.HLE.HOS.Services.Nifm { 11, SetConnectionConfirmationOption } }; - Event0 = new KEvent(); - Event1 = new KEvent(); + Event0 = new KEvent(System); + Event1 = new KEvent(System); } public long GetRequestState(ServiceCtx Context) @@ -77,19 +76,5 @@ namespace Ryujinx.HLE.HOS.Services.Nifm return 0; } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing) - { - Event0.Dispose(); - Event1.Dispose(); - } - } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs b/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs index bfc76931de..7d5589920f 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs @@ -12,7 +12,7 @@ using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Nv { - class INvDrvServices : IpcService, IDisposable + class INvDrvServices : IpcService { private delegate int IoctlProcessor(ServiceCtx Context, int Cmd); @@ -34,7 +34,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv private KEvent Event; - public INvDrvServices() + public INvDrvServices(Horizon System) { m_Commands = new Dictionary() { @@ -48,7 +48,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv { 13, FinishInitialize } }; - Event = new KEvent(); + Event = new KEvent(System); } static INvDrvServices() @@ -214,18 +214,5 @@ namespace Ryujinx.HLE.HOS.Services.Nv NvMapIoctl.UnloadProcess(Process); } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing) - { - Event.Dispose(); - } - } } } diff --git a/Ryujinx.HLE/HOS/Services/ServiceFactory.cs b/Ryujinx.HLE/HOS/Services/ServiceFactory.cs index 5e65d1d104..5e1e780aac 100644 --- a/Ryujinx.HLE/HOS/Services/ServiceFactory.cs +++ b/Ryujinx.HLE/HOS/Services/ServiceFactory.cs @@ -26,7 +26,7 @@ namespace Ryujinx.HLE.HOS.Services { static class ServiceFactory { - public static IpcService MakeService(string Name) + public static IpcService MakeService(Horizon System, string Name) { switch (Name) { @@ -94,7 +94,7 @@ namespace Ryujinx.HLE.HOS.Services return new IFileSystemProxy(); case "hid": - return new IHidServer(); + return new IHidServer(System); case "lm": return new ILogService(); @@ -118,10 +118,10 @@ namespace Ryujinx.HLE.HOS.Services return new IVulnerabilityManagerInterface(); case "nvdrv": - return new INvDrvServices(); + return new INvDrvServices(System); case "nvdrv:a": - return new INvDrvServices(); + return new INvDrvServices(System); case "pctl:s": return new IParentalControlServiceFactory(); diff --git a/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs b/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs index efa64ee7dd..c56d65dbc0 100644 --- a/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs +++ b/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs @@ -57,7 +57,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm return 0; } - KSession Session = new KSession(ServiceFactory.MakeService(Name), Name); + KSession Session = new KSession(ServiceFactory.MakeService(Context.Device.System, Name), Name); int Handle = Context.Process.HandleTable.OpenHandle(Session); diff --git a/Ryujinx.HLE/HOS/Services/Vi/IApplicationDisplayService.cs b/Ryujinx.HLE/HOS/Services/Vi/IApplicationDisplayService.cs index 3006b73a50..5423827932 100644 --- a/Ryujinx.HLE/HOS/Services/Vi/IApplicationDisplayService.cs +++ b/Ryujinx.HLE/HOS/Services/Vi/IApplicationDisplayService.cs @@ -41,7 +41,9 @@ namespace Ryujinx.HLE.HOS.Services.Vi public long GetRelayService(ServiceCtx Context) { - MakeObject(Context, new IHOSBinderDriver(Context.Device.Gpu.Renderer)); + MakeObject(Context, new IHOSBinderDriver( + Context.Device.System, + Context.Device.Gpu.Renderer)); return 0; } @@ -62,7 +64,9 @@ namespace Ryujinx.HLE.HOS.Services.Vi public long GetIndirectDisplayTransactionService(ServiceCtx Context) { - MakeObject(Context, new IHOSBinderDriver(Context.Device.Gpu.Renderer)); + MakeObject(Context, new IHOSBinderDriver( + Context.Device.System, + Context.Device.Gpu.Renderer)); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Vi/IHOSBinderDriver.cs b/Ryujinx.HLE/HOS/Services/Vi/IHOSBinderDriver.cs index 19e0d949ed..d47fc30a7d 100644 --- a/Ryujinx.HLE/HOS/Services/Vi/IHOSBinderDriver.cs +++ b/Ryujinx.HLE/HOS/Services/Vi/IHOSBinderDriver.cs @@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi private NvFlinger Flinger; - public IHOSBinderDriver(IGalRenderer Renderer) + public IHOSBinderDriver(Horizon System, IGalRenderer Renderer) { m_Commands = new Dictionary() { @@ -27,9 +27,9 @@ namespace Ryujinx.HLE.HOS.Services.Vi { 3, TransactParcelAuto } }; - BinderEvent = new KEvent(); + BinderEvent = new KEvent(System); - BinderEvent.WaitEvent.Set(); + BinderEvent.Signal(); Flinger = new NvFlinger(Renderer, BinderEvent); } @@ -93,8 +93,6 @@ namespace Ryujinx.HLE.HOS.Services.Vi { if (Disposing) { - BinderEvent.Dispose(); - Flinger.Dispose(); } } diff --git a/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs b/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs index a8493758d5..dcdf5d1747 100644 --- a/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs +++ b/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs @@ -64,7 +64,7 @@ namespace Ryujinx.HLE.HOS.Services.Android private BufferEntry[] BufferQueue; - private ManualResetEvent WaitBufferFree; + private AutoResetEvent WaitBufferFree; private bool Disposed; @@ -88,7 +88,7 @@ namespace Ryujinx.HLE.HOS.Services.Android BufferQueue = new BufferEntry[0x40]; - WaitBufferFree = new ManualResetEvent(false); + WaitBufferFree = new AutoResetEvent(false); } public long ProcessParcelRequest(ServiceCtx Context, byte[] ParcelData, int Code) @@ -220,6 +220,8 @@ namespace Ryujinx.HLE.HOS.Services.Android BufferQueue[Slot].State = BufferState.Free; + WaitBufferFree.Set(); + return MakeReplyParcel(Context, 0); } @@ -336,12 +338,9 @@ namespace Ryujinx.HLE.HOS.Services.Android { BufferQueue[Slot].State = BufferState.Free; - BinderEvent.WaitEvent.Set(); + BinderEvent.Signal(); - lock (WaitBufferFree) - { - WaitBufferFree.Set(); - } + WaitBufferFree.Set(); } private int GetFreeSlotBlocking(int Width, int Height) @@ -350,19 +349,14 @@ namespace Ryujinx.HLE.HOS.Services.Android do { - lock (WaitBufferFree) + if ((Slot = GetFreeSlot(Width, Height)) != -1) { - if ((Slot = GetFreeSlot(Width, Height)) != -1) - { - break; - } + break; + } - if (Disposed) - { - break; - } - - WaitBufferFree.Reset(); + if (Disposed) + { + break; } WaitBufferFree.WaitOne(); @@ -409,11 +403,7 @@ namespace Ryujinx.HLE.HOS.Services.Android { Disposed = true; - lock (WaitBufferFree) - { - WaitBufferFree.Set(); - } - + WaitBufferFree.Set(); WaitBufferFree.Dispose(); } } diff --git a/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs b/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs index ee0e6fea3c..b537b06acf 100644 --- a/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs +++ b/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs @@ -1,11 +1,10 @@ using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Services.Am; -using System; using System.Collections.Concurrent; namespace Ryujinx.HLE.HOS.SystemState { - class AppletStateMgr : IDisposable + class AppletStateMgr { private ConcurrentQueue Messages; @@ -13,11 +12,11 @@ namespace Ryujinx.HLE.HOS.SystemState public KEvent MessageEvent { get; private set; } - public AppletStateMgr() + public AppletStateMgr(Horizon System) { Messages = new ConcurrentQueue(); - MessageEvent = new KEvent(); + MessageEvent = new KEvent(System); } public void SetFocus(bool IsFocused) @@ -33,30 +32,17 @@ namespace Ryujinx.HLE.HOS.SystemState { Messages.Enqueue(Message); - MessageEvent.WaitEvent.Set(); + MessageEvent.Signal(); } public bool TryDequeueMessage(out MessageInfo Message) { if (Messages.Count < 2) { - MessageEvent.WaitEvent.Reset(); + MessageEvent.Reset(); } return Messages.TryDequeue(out Message); } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing) - { - MessageEvent.Dispose(); - } - } } } \ No newline at end of file diff --git a/Ryujinx/Config.cs b/Ryujinx/Config.cs index 748d1dbf87..f4dd77ba4e 100644 --- a/Ryujinx/Config.cs +++ b/Ryujinx/Config.cs @@ -31,10 +31,6 @@ namespace Ryujinx Device.Log.SetEnable(LogLevel.Warning, Convert.ToBoolean(Parser.Value("Logging_Enable_Warn"))); Device.Log.SetEnable(LogLevel.Error, Convert.ToBoolean(Parser.Value("Logging_Enable_Error"))); - Device.System.State.DockedMode = Convert.ToBoolean(Parser.Value("Docked_Mode")); - - Device.EnableDeviceVsync = Convert.ToBoolean(Parser.Value("Enable_Vsync")); - string[] FilteredLogClasses = Parser.Value("Logging_Filtered_Classes").Split(',', StringSplitOptions.RemoveEmptyEntries); //When the classes are specified on the list, we only @@ -63,6 +59,15 @@ namespace Ryujinx } } + Device.System.State.DockedMode = Convert.ToBoolean(Parser.Value("Docked_Mode")); + + Device.EnableDeviceVsync = Convert.ToBoolean(Parser.Value("Enable_Vsync")); + + if (Convert.ToBoolean(Parser.Value("Enable_MultiCore_Scheduling"))) + { + Device.System.EnableMultiCoreScheduling(); + } + JoyConKeyboard = new JoyConKeyboard( new JoyConKeyboardLeft diff --git a/Ryujinx/Ryujinx.conf b/Ryujinx/Ryujinx.conf index c497c08112..bf361db3e5 100644 --- a/Ryujinx/Ryujinx.conf +++ b/Ryujinx/Ryujinx.conf @@ -28,6 +28,9 @@ Docked_Mode = false #Enable Game Vsync Enable_Vsync = true +#Enable or Disable Multi-core scheduling of threads +Enable_MultiCore_Scheduling = false + #Controller Device Index GamePad_Index = 0 From fae097408e5ef28848e97022766017e540b0da37 Mon Sep 17 00:00:00 2001 From: emmauss Date: Wed, 19 Sep 2018 15:09:49 +0300 Subject: [PATCH 09/14] Show Game Title on Titlebar (#408) * support reading control data * show game info on titlebar * use first language is default is not available * use seperate language enums for titles * fix hex display --- Ryujinx.HLE/HOS/Horizon.cs | 102 +++++++++++++++--- Ryujinx.HLE/HOS/Process.cs | 3 + Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs | 4 + Ryujinx.HLE/HOS/SystemState/TitleLanguage.cs | 21 ++++ Ryujinx/Ui/GLScreen.cs | 7 +- 5 files changed, 119 insertions(+), 18 deletions(-) create mode 100644 Ryujinx.HLE/HOS/SystemState/TitleLanguage.cs diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index c7a824c03f..f8ec89140e 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -47,6 +47,10 @@ namespace Ryujinx.HLE.HOS private bool HasStarted; + public Nacp ControlData { get; set; } + + public string CurrentTitle { get; private set; } + public Horizon(Switch Device) { this.Device = Device; @@ -137,6 +141,8 @@ namespace Ryujinx.HLE.HOS throw new NotImplementedException("32-bit titles are unsupported!"); } + CurrentTitle = MainProcess.MetaData.ACI0.TitleId.ToString("x16"); + LoadNso("rtld"); MainProcess.SetEmptyArgs(); @@ -154,27 +160,28 @@ namespace Ryujinx.HLE.HOS Xci Xci = new Xci(KeySet, File); - Nca Nca = GetXciMainNca(Xci); + (Nca MainNca, Nca ControlNca) = GetXciGameData(Xci); - if (Nca == null) + if (MainNca == null) { Device.Log.PrintError(LogClass.Loader, "Unable to load XCI"); return; } - LoadNca(Nca); + LoadNca(MainNca, ControlNca); } - private Nca GetXciMainNca(Xci Xci) + private (Nca Main, Nca Control) GetXciGameData(Xci Xci) { if (Xci.SecurePartition == null) { throw new InvalidDataException("Could not find XCI secure partition"); } - Nca MainNca = null; - Nca PatchNca = null; + Nca MainNca = null; + Nca PatchNca = null; + Nca ControlNca = null; foreach (PfsFileEntry FileEntry in Xci.SecurePartition.Files.Where(x => x.Name.EndsWith(".nca"))) { @@ -193,6 +200,10 @@ namespace Ryujinx.HLE.HOS PatchNca = Nca; } } + else if (Nca.Header.ContentType == ContentType.Control) + { + ControlNca = Nca; + } } if (MainNca == null) @@ -201,8 +212,24 @@ namespace Ryujinx.HLE.HOS } MainNca.SetBaseNca(PatchNca); + + if (ControlNca != null) + { + ReadControlData(ControlNca); + } - return MainNca; + return (MainNca, ControlNca); + } + + public void ReadControlData(Nca ControlNca) + { + Romfs ControlRomfs = new Romfs(ControlNca.OpenSection(0, false)); + + byte[] ControlFile = ControlRomfs.GetFile("/control.nacp"); + + BinaryReader Reader = new BinaryReader(new MemoryStream(ControlFile)); + + ControlData = new Nacp(Reader); } public void LoadNca(string NcaFile) @@ -211,7 +238,7 @@ namespace Ryujinx.HLE.HOS Nca Nca = new Nca(KeySet, File, true); - LoadNca(Nca); + LoadNca(Nca, null); } public void LoadNsp(string NspFile) @@ -231,25 +258,37 @@ namespace Ryujinx.HLE.HOS KeySet.TitleKeys[Ticket.RightsId] = Ticket.GetTitleKey(KeySet); } + Nca MainNca = null; + Nca ControlNca = null; + foreach (PfsFileEntry NcaFile in Nsp.Files.Where(x => x.Name.EndsWith(".nca"))) { Nca Nca = new Nca(KeySet, Nsp.OpenFile(NcaFile), true); if (Nca.Header.ContentType == ContentType.Program) { - LoadNca(Nca); - - return; + MainNca = Nca; } + else if (Nca.Header.ContentType == ContentType.Control) + { + ControlNca = Nca; + } + } + + if (MainNca != null) + { + LoadNca(MainNca, ControlNca); + + return; } Device.Log.PrintError(LogClass.Loader, "Could not find an Application NCA in the provided NSP file"); } - public void LoadNca(Nca Nca) + public void LoadNca(Nca MainNca, Nca ControlNca) { - NcaSection RomfsSection = Nca.Sections.FirstOrDefault(x => x?.Type == SectionType.Romfs); - NcaSection ExefsSection = Nca.Sections.FirstOrDefault(x => x?.IsExefs == true); + NcaSection RomfsSection = MainNca.Sections.FirstOrDefault(x => x?.Type == SectionType.Romfs); + NcaSection ExefsSection = MainNca.Sections.FirstOrDefault(x => x?.IsExefs == true); if (ExefsSection == null) { @@ -265,10 +304,12 @@ namespace Ryujinx.HLE.HOS return; } - Stream RomfsStream = Nca.OpenSection(RomfsSection.SectionNum, false); + Stream RomfsStream = MainNca.OpenSection(RomfsSection.SectionNum, false); + Device.FileSystem.SetRomFs(RomfsStream); - Stream ExefsStream = Nca.OpenSection(ExefsSection.SectionNum, false); + Stream ExefsStream = MainNca.OpenSection(ExefsSection.SectionNum, false); + Pfs Exefs = new Pfs(ExefsStream); Npdm MetaData = null; @@ -305,6 +346,35 @@ namespace Ryujinx.HLE.HOS } } + Nacp ReadControlData() + { + Romfs ControlRomfs = new Romfs(ControlNca.OpenSection(0, false)); + + byte[] ControlFile = ControlRomfs.GetFile("/control.nacp"); + + BinaryReader Reader = new BinaryReader(new MemoryStream(ControlFile)); + + Nacp ControlData = new Nacp(Reader); + + CurrentTitle = ControlData.Languages[(int)State.DesiredTitleLanguage].Title; + + if (string.IsNullOrWhiteSpace(CurrentTitle)) + { + CurrentTitle = ControlData.Languages.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title; + } + + return ControlData; + } + + if (ControlNca != null) + { + MainProcess.ControlData = ReadControlData(); + } + else + { + CurrentTitle = MainProcess.MetaData.ACI0.TitleId.ToString("x16"); + } + if (!MainProcess.MetaData.Is64Bits) { throw new NotImplementedException("32-bit titles are unsupported!"); diff --git a/Ryujinx.HLE/HOS/Process.cs b/Ryujinx.HLE/HOS/Process.cs index 289920bef3..7900705dfa 100644 --- a/Ryujinx.HLE/HOS/Process.cs +++ b/Ryujinx.HLE/HOS/Process.cs @@ -2,6 +2,7 @@ using ChocolArm64; using ChocolArm64.Events; using ChocolArm64.Memory; using ChocolArm64.State; +using LibHac; using Ryujinx.HLE.Exceptions; using Ryujinx.HLE.HOS.Diagnostics.Demangler; using Ryujinx.HLE.HOS.Kernel; @@ -42,6 +43,8 @@ namespace Ryujinx.HLE.HOS public Npdm MetaData { get; private set; } + public Nacp ControlData { get; set; } + public KProcessHandleTable HandleTable { get; private set; } public AppletStateMgr AppletState { get; private set; } diff --git a/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs b/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs index bd1dbd78d6..2a3c8288b2 100644 --- a/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs +++ b/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs @@ -37,6 +37,8 @@ namespace Ryujinx.HLE.HOS.SystemState internal long DesiredLanguageCode { get; private set; } + public TitleLanguage DesiredTitleLanguage { get; private set; } + internal string ActiveAudioOutput { get; private set; } public bool DockedMode { get; set; } @@ -64,6 +66,8 @@ namespace Ryujinx.HLE.HOS.SystemState public void SetLanguage(SystemLanguage Language) { DesiredLanguageCode = GetLanguageCode((int)Language); + + DesiredTitleLanguage = Enum.Parse(Enum.GetName(typeof(SystemLanguage), Language)); } public void SetAudioOutputAsTv() diff --git a/Ryujinx.HLE/HOS/SystemState/TitleLanguage.cs b/Ryujinx.HLE/HOS/SystemState/TitleLanguage.cs new file mode 100644 index 0000000000..f481ac2932 --- /dev/null +++ b/Ryujinx.HLE/HOS/SystemState/TitleLanguage.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.SystemState +{ + public enum TitleLanguage + { + AmericanEnglish, + BritishEnglish, + Japanese, + French, + German, + LatinAmericanSpanish, + Spanish, + Italian, + Dutch, + CanadianFrench, + Portuguese, + Russian, + Korean, + Taiwanese, + Chinese + } +} diff --git a/Ryujinx/Ui/GLScreen.cs b/Ryujinx/Ui/GLScreen.cs index 8adff9c00d..27f3f08b56 100644 --- a/Ryujinx/Ui/GLScreen.cs +++ b/Ryujinx/Ui/GLScreen.cs @@ -258,8 +258,11 @@ namespace Ryujinx double HostFps = Device.Statistics.GetSystemFrameRate(); double GameFps = Device.Statistics.GetGameFrameRate(); - NewTitle = $"Ryujinx | Host FPS: {HostFps:0.0} | Game FPS: {GameFps:0.0} | Game Vsync: " + - (Device.EnableDeviceVsync ? "On" : "Off"); + string TitleSection = string.IsNullOrWhiteSpace(Device.System.CurrentTitle) ? string.Empty + : " | " + Device.System.CurrentTitle; + + NewTitle = $"Ryujinx{TitleSection} | Host FPS: {HostFps:0.0} | Game FPS: {GameFps:0.0} | " + + $"Game Vsync: {(Device.EnableDeviceVsync ? "On" : "Off")}"; TitleEvent = true; From 99b2692425ff4045f103cde0745624b9b41d6fe6 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Wed, 19 Sep 2018 12:16:20 -0300 Subject: [PATCH 10/14] Fix performance regression caused by the new scheduler changes (#422) * Call interrupt less often, remove some leftovers from the old scheduler code * Remove unneeded attribute --- ChocolArm64/AThread.cs | 2 - ChocolArm64/State/AThreadState.cs | 30 ++++++++++---- ChocolArm64/Translation/AILEmitterCtx.cs | 2 + Ryujinx.HLE/HOS/Kernel/KCoreContext.cs | 10 ++--- Ryujinx.HLE/HOS/Kernel/SchedulerThread.cs | 48 ----------------------- Ryujinx.HLE/HOS/Kernel/SvcThread.cs | 2 - Ryujinx.HLE/HOS/Process.cs | 5 ++- 7 files changed, 33 insertions(+), 66 deletions(-) delete mode 100644 Ryujinx.HLE/HOS/Kernel/SchedulerThread.cs diff --git a/ChocolArm64/AThread.cs b/ChocolArm64/AThread.cs index 76b36da4cf..38c2b53db0 100644 --- a/ChocolArm64/AThread.cs +++ b/ChocolArm64/AThread.cs @@ -46,8 +46,6 @@ namespace ChocolArm64 return false; } - Work.Name = "cpu_thread_" + Work.ManagedThreadId; - Work.Start(); return true; diff --git a/ChocolArm64/State/AThreadState.cs b/ChocolArm64/State/AThreadState.cs index 783f5a12be..22e3df144c 100644 --- a/ChocolArm64/State/AThreadState.cs +++ b/ChocolArm64/State/AThreadState.cs @@ -2,6 +2,7 @@ using ChocolArm64.Events; using System; using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; namespace ChocolArm64.State @@ -14,6 +15,8 @@ namespace ChocolArm64.State internal const int ErgSizeLog2 = 4; internal const int DczSizeLog2 = 4; + private const int MinInstForCheck = 4000000; + internal AExecutionMode ExecutionMode; //AArch32 state. @@ -45,6 +48,8 @@ namespace ChocolArm64.State private bool Interrupted; + private int SyncCount; + public long TpidrEl0 { get; set; } public long Tpidr { get; set; } @@ -101,13 +106,16 @@ namespace ChocolArm64.State TickCounter.Start(); } - internal bool Synchronize() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool Synchronize(int BbWeight) { - if (Interrupted) - { - Interrupted = false; + //Firing a interrupt frequently is expensive, so we only + //do it after a given number of instructions has executed. + SyncCount += BbWeight; - OnInterrupt(); + if (SyncCount >= MinInstForCheck) + { + CheckInterrupt(); } return Running; @@ -118,9 +126,17 @@ namespace ChocolArm64.State Interrupted = true; } - private void OnInterrupt() + [MethodImpl(MethodImplOptions.NoInlining)] + private void CheckInterrupt() { - Interrupt?.Invoke(this, EventArgs.Empty); + SyncCount = 0; + + if (Interrupted) + { + Interrupted = false; + + Interrupt?.Invoke(this, EventArgs.Empty); + } } internal void OnBreak(long Position, int Imm) diff --git a/ChocolArm64/Translation/AILEmitterCtx.cs b/ChocolArm64/Translation/AILEmitterCtx.cs index 40e33ba8ea..487007155f 100644 --- a/ChocolArm64/Translation/AILEmitterCtx.cs +++ b/ChocolArm64/Translation/AILEmitterCtx.cs @@ -123,6 +123,8 @@ namespace ChocolArm64.Translation { EmitLdarg(ATranslatedSub.StateArgIdx); + EmitLdc_I4(CurrBlock.OpCodes.Count); + EmitPrivateCall(typeof(AThreadState), nameof(AThreadState.Synchronize)); EmitLdc_I4(0); diff --git a/Ryujinx.HLE/HOS/Kernel/KCoreContext.cs b/Ryujinx.HLE/HOS/Kernel/KCoreContext.cs index 70fe1a6141..51f27e2a1e 100644 --- a/Ryujinx.HLE/HOS/Kernel/KCoreContext.cs +++ b/Ryujinx.HLE/HOS/Kernel/KCoreContext.cs @@ -28,7 +28,10 @@ namespace Ryujinx.HLE.HOS.Kernel Thread.LastScheduledTicks = (uint)Environment.TickCount; } - ContextSwitchNeeded = true; + if (SelectedThread != CurrentThread) + { + ContextSwitchNeeded = true; + } } public void UpdateCurrentThread() @@ -58,10 +61,5 @@ namespace Ryujinx.HLE.HOS.Kernel CurrentThread.Context.Execute(); } } - - public void RemoveThread(KThread Thread) - { - //TODO. - } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SchedulerThread.cs b/Ryujinx.HLE/HOS/Kernel/SchedulerThread.cs deleted file mode 100644 index bab7b03e61..0000000000 --- a/Ryujinx.HLE/HOS/Kernel/SchedulerThread.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Threading; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class SchedulerThread : IDisposable - { - public KThread Thread { get; private set; } - - public SchedulerThread Next { get; set; } - - public bool IsActive { get; set; } - - public AutoResetEvent WaitSync { get; private set; } - public ManualResetEvent WaitActivity { get; private set; } - public AutoResetEvent WaitSched { get; private set; } - - public SchedulerThread(KThread Thread) - { - this.Thread = Thread; - - IsActive = true; - - WaitSync = new AutoResetEvent(false); - - WaitActivity = new ManualResetEvent(true); - - WaitSched = new AutoResetEvent(false); - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing) - { - WaitSync.Dispose(); - - WaitActivity.Dispose(); - - WaitSched.Dispose(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SvcThread.cs b/Ryujinx.HLE/HOS/Kernel/SvcThread.cs index aa6e551b4f..dc296b060e 100644 --- a/Ryujinx.HLE/HOS/Kernel/SvcThread.cs +++ b/Ryujinx.HLE/HOS/Kernel/SvcThread.cs @@ -81,8 +81,6 @@ namespace Ryujinx.HLE.HOS.Kernel CurrentThread.Exit(); System.Scheduler.StopThread(CurrentThread); - - System.Scheduler.CoreContexts[CurrentThread.CurrentCore].RemoveThread(CurrentThread); } private void SvcSleepThread(AThreadState ThreadState) diff --git a/Ryujinx.HLE/HOS/Process.cs b/Ryujinx.HLE/HOS/Process.cs index 7900705dfa..0bc95f0c1f 100644 --- a/Ryujinx.HLE/HOS/Process.cs +++ b/Ryujinx.HLE/HOS/Process.cs @@ -356,7 +356,10 @@ namespace Ryujinx.HLE.HOS { if (sender is AThread Thread) { - Threads.TryRemove(Thread.ThreadState.Tpidr, out KThread KernelThread); + if (Threads.TryRemove(Thread.ThreadState.Tpidr, out KThread KernelThread)) + { + Device.System.Scheduler.RemoveThread(KernelThread); + } } if (Threads.Count == 0) From 6d65e536642a7cff7afa34be10fdc8ca66a6e79c Mon Sep 17 00:00:00 2001 From: gdkchan Date: Wed, 19 Sep 2018 17:07:56 -0300 Subject: [PATCH 11/14] Remove cold methods from the CPU cache (#224) * Remove unused tracing functionality from the CPU * GetNsoExecutable -> GetExecutable * Unsigned comparison * Re-add cpu tracing * Config change * Remove cold methods from the translation cache on the cpu * Replace lock with try lock, pass new ATranslatorCache instead of ATranslator * Rebase fixups --- ChocolArm64/AOptimizations.cs | 2 - ChocolArm64/ATranslatedSub.cs | 8 +- ChocolArm64/ATranslator.cs | 89 ++++-------- ChocolArm64/ATranslatorCache.cs | 164 +++++++++++++++++++++++ ChocolArm64/Decoder/ADecoder.cs | 16 +-- ChocolArm64/Events/ACpuTraceEventArgs.cs | 5 +- ChocolArm64/Instruction/AInstEmitFlow.cs | 31 ----- ChocolArm64/State/AThreadState.cs | 30 ----- ChocolArm64/Translation/AILEmitterCtx.cs | 44 +++--- Ryujinx.HLE/HOS/Process.cs | 142 +++++++++++++------- Ryujinx.HLE/Loaders/Executable.cs | 19 +-- 11 files changed, 318 insertions(+), 232 deletions(-) create mode 100644 ChocolArm64/ATranslatorCache.cs diff --git a/ChocolArm64/AOptimizations.cs b/ChocolArm64/AOptimizations.cs index fbf26a4910..40e1674a41 100644 --- a/ChocolArm64/AOptimizations.cs +++ b/ChocolArm64/AOptimizations.cs @@ -2,8 +2,6 @@ using System.Runtime.Intrinsics.X86; public static class AOptimizations { - public static bool GenerateCallStack = true; - private static bool UseAllSseIfAvailable = true; private static bool UseSseIfAvailable = true; diff --git a/ChocolArm64/ATranslatedSub.cs b/ChocolArm64/ATranslatedSub.cs index 9dbc378ec0..a11da2646f 100644 --- a/ChocolArm64/ATranslatedSub.cs +++ b/ChocolArm64/ATranslatedSub.cs @@ -13,6 +13,8 @@ namespace ChocolArm64 { private delegate long AA64Subroutine(AThreadState Register, AMemory Memory); + private const int MinCallCountForReJit = 250; + private AA64Subroutine ExecDelegate; public static int StateArgIdx { get; private set; } @@ -32,8 +34,6 @@ namespace ChocolArm64 private bool NeedsReJit; - private int MinCallCountForReJit = 250; - public ATranslatedSub(DynamicMethod Method, List Params) { if (Method == null) @@ -46,8 +46,8 @@ namespace ChocolArm64 throw new ArgumentNullException(nameof(Params)); } - this.Method = Method; - this.Params = Params.AsReadOnly(); + this.Method = Method; + this.Params = Params.AsReadOnly(); Callers = new HashSet(); diff --git a/ChocolArm64/ATranslator.cs b/ChocolArm64/ATranslator.cs index 2d9fcb1415..5be41d3ef7 100644 --- a/ChocolArm64/ATranslator.cs +++ b/ChocolArm64/ATranslator.cs @@ -1,38 +1,24 @@ using ChocolArm64.Decoder; using ChocolArm64.Events; -using ChocolArm64.Instruction; using ChocolArm64.Memory; using ChocolArm64.State; using ChocolArm64.Translation; using System; -using System.Collections.Concurrent; -using System.Collections.Generic; using System.Reflection.Emit; namespace ChocolArm64 { public class ATranslator { - private ConcurrentDictionary CachedSubs; - - private ConcurrentDictionary SymbolTable; + private ATranslatorCache Cache; public event EventHandler CpuTrace; public bool EnableCpuTrace { get; set; } - public ATranslator(IReadOnlyDictionary SymbolTable = null) + public ATranslator() { - CachedSubs = new ConcurrentDictionary(); - - if (SymbolTable != null) - { - this.SymbolTable = new ConcurrentDictionary(SymbolTable); - } - else - { - this.SymbolTable = new ConcurrentDictionary(); - } + Cache = new ATranslatorCache(); } internal void ExecuteSubroutine(AThread Thread, long Position) @@ -70,15 +56,10 @@ namespace ChocolArm64 { if (EnableCpuTrace) { - if (!SymbolTable.TryGetValue(Position, out string SubName)) - { - SubName = string.Empty; - } - - CpuTrace?.Invoke(this, new ACpuTraceEventArgs(Position, SubName)); + CpuTrace?.Invoke(this, new ACpuTraceEventArgs(Position)); } - if (!CachedSubs.TryGetValue(Position, out ATranslatedSub Sub)) + if (!Cache.TryGetSubroutine(Position, out ATranslatedSub Sub)) { Sub = TranslateTier0(State, Memory, Position); } @@ -93,37 +74,20 @@ namespace ChocolArm64 while (Position != 0 && State.Running); } - internal bool TryGetCachedSub(AOpCode OpCode, out ATranslatedSub Sub) - { - if (OpCode.Emitter != AInstEmit.Bl) - { - Sub = null; - - return false; - } - - return TryGetCachedSub(((AOpCodeBImmAl)OpCode).Imm, out Sub); - } - - internal bool TryGetCachedSub(long Position, out ATranslatedSub Sub) - { - return CachedSubs.TryGetValue(Position, out Sub); - } - internal bool HasCachedSub(long Position) { - return CachedSubs.ContainsKey(Position); + return Cache.HasSubroutine(Position); } private ATranslatedSub TranslateTier0(AThreadState State, AMemory Memory, long Position) { - ABlock Block = ADecoder.DecodeBasicBlock(State, this, Memory, Position); + ABlock Block = ADecoder.DecodeBasicBlock(State, Memory, Position); ABlock[] Graph = new ABlock[] { Block }; - string SubName = GetSubName(Position); + string SubName = GetSubroutineName(Position); - AILEmitterCtx Context = new AILEmitterCtx(this, Graph, Block, SubName); + AILEmitterCtx Context = new AILEmitterCtx(Cache, Graph, Block, SubName); do { @@ -135,7 +99,7 @@ namespace ChocolArm64 Subroutine.SetType(ATranslatedSubType.SubTier0); - CachedSubs.AddOrUpdate(Position, Subroutine, (Key, OldVal) => Subroutine); + Cache.AddOrUpdate(Position, Subroutine, Block.OpCodes.Count); AOpCode LastOp = Block.GetLastOp(); @@ -144,13 +108,11 @@ namespace ChocolArm64 private void TranslateTier1(AThreadState State, AMemory Memory, long Position) { - (ABlock[] Graph, ABlock Root) Cfg = ADecoder.DecodeSubroutine(State, this, Memory, Position); + (ABlock[] Graph, ABlock Root) = ADecoder.DecodeSubroutine(Cache, State, Memory, Position); - string SubName = GetSubName(Position); + string SubName = GetSubroutineName(Position); - PropagateName(Cfg.Graph, SubName); - - AILEmitterCtx Context = new AILEmitterCtx(this, Cfg.Graph, Cfg.Root, SubName); + AILEmitterCtx Context = new AILEmitterCtx(Cache, Graph, Root, SubName); if (Context.CurrBlock.Position != Position) { @@ -165,11 +127,11 @@ namespace ChocolArm64 //Mark all methods that calls this method for ReJiting, //since we can now call it directly which is faster. - if (CachedSubs.TryGetValue(Position, out ATranslatedSub OldSub)) + if (Cache.TryGetSubroutine(Position, out ATranslatedSub OldSub)) { foreach (long CallerPos in OldSub.GetCallerPositions()) { - if (CachedSubs.TryGetValue(Position, out ATranslatedSub CallerSub)) + if (Cache.TryGetSubroutine(Position, out ATranslatedSub CallerSub)) { CallerSub.MarkForReJit(); } @@ -180,27 +142,24 @@ namespace ChocolArm64 Subroutine.SetType(ATranslatedSubType.SubTier1); - CachedSubs.AddOrUpdate(Position, Subroutine, (Key, OldVal) => Subroutine); + Cache.AddOrUpdate(Position, Subroutine, GetGraphInstCount(Graph)); } - private string GetSubName(long Position) + private string GetSubroutineName(long Position) { - return SymbolTable.GetOrAdd(Position, $"Sub{Position:x16}"); + return $"Sub{Position:x16}"; } - private void PropagateName(ABlock[] Graph, string Name) + private int GetGraphInstCount(ABlock[] Graph) { + int Size = 0; + foreach (ABlock Block in Graph) { - AOpCode LastOp = Block.GetLastOp(); - - if (LastOp != null && - (LastOp.Emitter == AInstEmit.Bl || - LastOp.Emitter == AInstEmit.Blr)) - { - SymbolTable.TryAdd(LastOp.Position + 4, Name); - } + Size += Block.OpCodes.Count; } + + return Size; } } } \ No newline at end of file diff --git a/ChocolArm64/ATranslatorCache.cs b/ChocolArm64/ATranslatorCache.cs new file mode 100644 index 0000000000..e34cc397b6 --- /dev/null +++ b/ChocolArm64/ATranslatorCache.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace ChocolArm64 +{ + class ATranslatorCache + { + private const int MaxTotalSize = 2 * 1024 * 256; + private const int MaxTimeDelta = 30000; + private const int MinCallCountForUpdate = 1000; + + private class CacheBucket + { + public ATranslatedSub Subroutine { get; private set; } + + public LinkedListNode Node { get; private set; } + + public int CallCount { get; set; } + + public int Size { get; private set; } + + public int Timestamp { get; private set; } + + public CacheBucket(ATranslatedSub Subroutine, LinkedListNode Node, int Size) + { + this.Subroutine = Subroutine; + this.Size = Size; + + UpdateNode(Node); + } + + public void UpdateNode(LinkedListNode Node) + { + this.Node = Node; + + Timestamp = Environment.TickCount; + } + } + + private ConcurrentDictionary Cache; + + private LinkedList SortedCache; + + private int TotalSize; + + public ATranslatorCache() + { + Cache = new ConcurrentDictionary(); + + SortedCache = new LinkedList(); + } + + public void AddOrUpdate(long Position, ATranslatedSub Subroutine, int Size) + { + ClearCacheIfNeeded(); + + TotalSize += Size; + + lock (SortedCache) + { + LinkedListNode Node = SortedCache.AddLast(Position); + + CacheBucket NewBucket = new CacheBucket(Subroutine, Node, Size); + + Cache.AddOrUpdate(Position, NewBucket, (Key, Bucket) => + { + TotalSize -= Bucket.Size; + + SortedCache.Remove(Bucket.Node); + + return NewBucket; + }); + } + } + + public bool HasSubroutine(long Position) + { + return Cache.ContainsKey(Position); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetSubroutine(long Position, out ATranslatedSub Subroutine) + { + if (Cache.TryGetValue(Position, out CacheBucket Bucket)) + { + if (Bucket.CallCount++ > MinCallCountForUpdate) + { + if (Monitor.TryEnter(SortedCache)) + { + try + { + Bucket.CallCount = 0; + + SortedCache.Remove(Bucket.Node); + + Bucket.UpdateNode(SortedCache.AddLast(Position)); + } + finally + { + Monitor.Exit(SortedCache); + } + } + } + + Subroutine = Bucket.Subroutine; + + return true; + } + + Subroutine = default(ATranslatedSub); + + return false; + } + + private void ClearCacheIfNeeded() + { + int Timestamp = Environment.TickCount; + + while (TotalSize > MaxTotalSize) + { + lock (SortedCache) + { + LinkedListNode Node = SortedCache.First; + + if (Node == null) + { + break; + } + + CacheBucket Bucket = Cache[Node.Value]; + + int TimeDelta = RingDelta(Bucket.Timestamp, Timestamp); + + if ((uint)TimeDelta <= (uint)MaxTimeDelta) + { + break; + } + + if (Cache.TryRemove(Node.Value, out Bucket)) + { + TotalSize -= Bucket.Size; + + SortedCache.Remove(Bucket.Node); + } + } + } + } + + private static int RingDelta(int Old, int New) + { + if ((uint)New < (uint)Old) + { + return New + (~Old + 1); + } + else + { + return New - Old; + } + } + } +} \ No newline at end of file diff --git a/ChocolArm64/Decoder/ADecoder.cs b/ChocolArm64/Decoder/ADecoder.cs index b154a54cd2..64beebd2a3 100644 --- a/ChocolArm64/Decoder/ADecoder.cs +++ b/ChocolArm64/Decoder/ADecoder.cs @@ -19,11 +19,7 @@ namespace ChocolArm64.Decoder OpActivators = new ConcurrentDictionary(); } - public static ABlock DecodeBasicBlock( - AThreadState State, - ATranslator Translator, - AMemory Memory, - long Start) + public static ABlock DecodeBasicBlock(AThreadState State, AMemory Memory, long Start) { ABlock Block = new ABlock(Start); @@ -33,10 +29,10 @@ namespace ChocolArm64.Decoder } public static (ABlock[] Graph, ABlock Root) DecodeSubroutine( - AThreadState State, - ATranslator Translator, - AMemory Memory, - long Start) + ATranslatorCache Cache, + AThreadState State, + AMemory Memory, + long Start) { Dictionary Visited = new Dictionary(); Dictionary VisitedEnd = new Dictionary(); @@ -79,7 +75,7 @@ namespace ChocolArm64.Decoder { if (Op.Emitter == AInstEmit.Bl) { - HasCachedSub = Translator.HasCachedSub(Op.Imm); + HasCachedSub = Cache.HasSubroutine(Op.Imm); } else { diff --git a/ChocolArm64/Events/ACpuTraceEventArgs.cs b/ChocolArm64/Events/ACpuTraceEventArgs.cs index fedf3865b1..0284f4eeb9 100644 --- a/ChocolArm64/Events/ACpuTraceEventArgs.cs +++ b/ChocolArm64/Events/ACpuTraceEventArgs.cs @@ -6,12 +6,9 @@ namespace ChocolArm64.Events { public long Position { get; private set; } - public string SubName { get; private set; } - - public ACpuTraceEventArgs(long Position, string SubName) + public ACpuTraceEventArgs(long Position) { this.Position = Position; - this.SubName = SubName; } } } \ No newline at end of file diff --git a/ChocolArm64/Instruction/AInstEmitFlow.cs b/ChocolArm64/Instruction/AInstEmitFlow.cs index 89979d0509..91262834f9 100644 --- a/ChocolArm64/Instruction/AInstEmitFlow.cs +++ b/ChocolArm64/Instruction/AInstEmitFlow.cs @@ -35,14 +35,6 @@ namespace ChocolArm64.Instruction { AOpCodeBImmAl Op = (AOpCodeBImmAl)Context.CurrOp; - if (AOptimizations.GenerateCallStack) - { - Context.EmitLdarg(ATranslatedSub.StateArgIdx); - Context.EmitLdc_I8(Op.Imm); - - Context.EmitPrivateCall(typeof(AThreadState), nameof(AThreadState.EnterMethod)); - } - Context.EmitLdc_I(Op.Position + 4); Context.EmitStint(AThreadState.LRIndex); Context.EmitStoreState(); @@ -80,14 +72,6 @@ namespace ChocolArm64.Instruction { AOpCodeBReg Op = (AOpCodeBReg)Context.CurrOp; - if (AOptimizations.GenerateCallStack) - { - Context.EmitLdarg(ATranslatedSub.StateArgIdx); - Context.EmitLdintzr(Op.Rn); - - Context.EmitPrivateCall(typeof(AThreadState), nameof(AThreadState.EnterMethod)); - } - Context.EmitLdc_I(Op.Position + 4); Context.EmitStint(AThreadState.LRIndex); Context.EmitStoreState(); @@ -100,14 +84,6 @@ namespace ChocolArm64.Instruction { AOpCodeBReg Op = (AOpCodeBReg)Context.CurrOp; - if (AOptimizations.GenerateCallStack) - { - Context.EmitLdarg(ATranslatedSub.StateArgIdx); - Context.EmitLdintzr(Op.Rn); - - Context.EmitPrivateCall(typeof(AThreadState), nameof(AThreadState.JumpMethod)); - } - Context.EmitStoreState(); Context.EmitLdintzr(Op.Rn); @@ -129,13 +105,6 @@ namespace ChocolArm64.Instruction public static void Ret(AILEmitterCtx Context) { - if (AOptimizations.GenerateCallStack) - { - Context.EmitLdarg(ATranslatedSub.StateArgIdx); - - Context.EmitPrivateCall(typeof(AThreadState), nameof(AThreadState.ExitMethod)); - } - Context.EmitStoreState(); Context.EmitLdint(AThreadState.LRIndex); diff --git a/ChocolArm64/State/AThreadState.cs b/ChocolArm64/State/AThreadState.cs index 22e3df144c..e4953b021c 100644 --- a/ChocolArm64/State/AThreadState.cs +++ b/ChocolArm64/State/AThreadState.cs @@ -1,6 +1,5 @@ using ChocolArm64.Events; using System; -using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; @@ -86,17 +85,10 @@ namespace ChocolArm64.State public event EventHandler SvcCall; public event EventHandler Undefined; - private Stack CallStack; - private static Stopwatch TickCounter; private static double HostTickFreq; - public AThreadState() - { - CallStack = new Stack(); - } - static AThreadState() { HostTickFreq = 1.0 / Stopwatch.Frequency; @@ -153,27 +145,5 @@ namespace ChocolArm64.State { Undefined?.Invoke(this, new AInstUndefinedEventArgs(Position, RawOpCode)); } - - internal void EnterMethod(long Position) - { - CallStack.Push(Position); - } - - internal void ExitMethod() - { - CallStack.TryPop(out _); - } - - internal void JumpMethod(long Position) - { - CallStack.TryPop(out _); - - CallStack.Push(Position); - } - - public long[] GetCallStack() - { - return CallStack.ToArray(); - } } } \ No newline at end of file diff --git a/ChocolArm64/Translation/AILEmitterCtx.cs b/ChocolArm64/Translation/AILEmitterCtx.cs index 487007155f..cad0d323db 100644 --- a/ChocolArm64/Translation/AILEmitterCtx.cs +++ b/ChocolArm64/Translation/AILEmitterCtx.cs @@ -10,7 +10,7 @@ namespace ChocolArm64.Translation { class AILEmitterCtx { - private ATranslator Translator; + private ATranslatorCache Cache; private Dictionary Labels; @@ -40,29 +40,14 @@ namespace ChocolArm64.Translation private const int Tmp5Index = -5; public AILEmitterCtx( - ATranslator Translator, - ABlock[] Graph, - ABlock Root, - string SubName) + ATranslatorCache Cache, + ABlock[] Graph, + ABlock Root, + string SubName) { - if (Translator == null) - { - throw new ArgumentNullException(nameof(Translator)); - } - - if (Graph == null) - { - throw new ArgumentNullException(nameof(Graph)); - } - - if (Root == null) - { - throw new ArgumentNullException(nameof(Root)); - } - - this.Translator = Translator; - this.Graph = Graph; - this.Root = Root; + this.Cache = Cache ?? throw new ArgumentNullException(nameof(Cache)); + this.Graph = Graph ?? throw new ArgumentNullException(nameof(Graph)); + this.Root = Root ?? throw new ArgumentNullException(nameof(Root)); Labels = new Dictionary(); @@ -147,7 +132,12 @@ namespace ChocolArm64.Translation return false; } - if (!Translator.TryGetCachedSub(CurrOp, out ATranslatedSub Sub)) + if (CurrOp.Emitter != AInstEmit.Bl) + { + return false; + } + + if (!Cache.TryGetSubroutine(((AOpCodeBImmAl)CurrOp).Imm, out ATranslatedSub Subroutine)) { return false; } @@ -157,7 +147,7 @@ namespace ChocolArm64.Translation EmitLdarg(Index); } - foreach (ARegister Reg in Sub.Params) + foreach (ARegister Reg in Subroutine.Params) { switch (Reg.Type) { @@ -167,9 +157,9 @@ namespace ChocolArm64.Translation } } - EmitCall(Sub.Method); + EmitCall(Subroutine.Method); - Sub.AddCaller(Root.Position); + Subroutine.AddCaller(Root.Position); return true; } diff --git a/Ryujinx.HLE/HOS/Process.cs b/Ryujinx.HLE/HOS/Process.cs index 0bc95f0c1f..f7ec2604cc 100644 --- a/Ryujinx.HLE/HOS/Process.cs +++ b/Ryujinx.HLE/HOS/Process.cs @@ -55,8 +55,6 @@ namespace Ryujinx.HLE.HOS private List Executables; - private Dictionary SymbolTable; - private long ImageBase; private bool Disposed; @@ -122,8 +120,6 @@ namespace Ryujinx.HLE.HOS return false; } - MakeSymbolTable(); - long MainStackTop = MemoryManager.CodeRegionEnd - KMemoryManager.PageSize; long MainStackSize = 1 * 1024 * 1024; @@ -256,31 +252,6 @@ namespace Ryujinx.HLE.HOS throw new UndefinedInstructionException(e.Position, e.RawOpCode); } - private void MakeSymbolTable() - { - SymbolTable = new Dictionary(); - - foreach (Executable Exe in Executables) - { - foreach (KeyValuePair KV in Exe.SymbolTable) - { - SymbolTable.TryAdd(Exe.ImageBase + KV.Key, KV.Value); - } - } - } - - private ATranslator GetTranslator() - { - if (Translator == null) - { - Translator = new ATranslator(SymbolTable); - - Translator.CpuTrace += CpuTraceHandler; - } - - return Translator; - } - public void EnableCpuTracing() { Translator.EnableCpuTrace = true; @@ -293,32 +264,53 @@ namespace Ryujinx.HLE.HOS private void CpuTraceHandler(object sender, ACpuTraceEventArgs e) { - string NsoName = string.Empty; + Executable Exe = GetExecutable(e.Position); - for (int Index = Executables.Count - 1; Index >= 0; Index--) + if (Exe == null) { - if (e.Position >= Executables[Index].ImageBase) - { - NsoName = $"{(e.Position - Executables[Index].ImageBase):x16}"; - - break; - } + return; } - Device.Log.PrintDebug(LogClass.Cpu, $"Executing at 0x{e.Position:x16} {e.SubName} {NsoName}"); + if (!TryGetSubName(Exe, e.Position, out string SubName)) + { + SubName = string.Empty; + } + + long Offset = e.Position - Exe.ImageBase; + + string ExeNameWithAddr = $"{Exe.Name}:0x{Offset:x8}"; + + Device.Log.PrintDebug(LogClass.Cpu, ExeNameWithAddr + " " + SubName); + } + + private ATranslator GetTranslator() + { + if (Translator == null) + { + Translator = new ATranslator(); + + Translator.CpuTrace += CpuTraceHandler; + } + + return Translator; } public void PrintStackTrace(AThreadState ThreadState) { - long[] Positions = ThreadState.GetCallStack(); - StringBuilder Trace = new StringBuilder(); Trace.AppendLine("Guest stack trace:"); - foreach (long Position in Positions) + void AppendTrace(long Position) { - if (!SymbolTable.TryGetValue(Position, out string SubName)) + Executable Exe = GetExecutable(Position); + + if (Exe == null) + { + return; + } + + if (!TryGetSubName(Exe, Position, out string SubName)) { SubName = $"Sub{Position:x16}"; } @@ -327,29 +319,77 @@ namespace Ryujinx.HLE.HOS SubName = Demangler.Parse(SubName); } - Trace.AppendLine(" " + SubName + " (" + GetNsoNameAndAddress(Position) + ")"); + long Offset = Position - Exe.ImageBase; + + string ExeNameWithAddr = $"{Exe.Name}:0x{Offset:x8}"; + + Trace.AppendLine(" " + ExeNameWithAddr + " " + SubName); + } + + long FramePointer = (long)ThreadState.X29; + + while (FramePointer != 0) + { + AppendTrace(Memory.ReadInt64(FramePointer + 8)); + + FramePointer = Memory.ReadInt64(FramePointer); } Device.Log.PrintInfo(LogClass.Cpu, Trace.ToString()); } - private string GetNsoNameAndAddress(long Position) + private bool TryGetSubName(Executable Exe, long Position, out string Name) + { + Position -= Exe.ImageBase; + + int Left = 0; + int Right = Exe.SymbolTable.Count - 1; + + while (Left <= Right) + { + int Size = Right - Left; + + int Middle = Left + (Size >> 1); + + ElfSym Symbol = Exe.SymbolTable[Middle]; + + long EndPosition = Symbol.Value + Symbol.Size; + + if ((ulong)Position >= (ulong)Symbol.Value && (ulong)Position < (ulong)EndPosition) + { + Name = Symbol.Name; + + return true; + } + + if ((ulong)Position < (ulong)Symbol.Value) + { + Right = Middle - 1; + } + else + { + Left = Middle + 1; + } + } + + Name = null; + + return false; + } + + private Executable GetExecutable(long Position) { string Name = string.Empty; for (int Index = Executables.Count - 1; Index >= 0; Index--) { - if (Position >= Executables[Index].ImageBase) + if ((ulong)Position >= (ulong)Executables[Index].ImageBase) { - long Offset = Position - Executables[Index].ImageBase; - - Name = $"{Executables[Index].Name}:{Offset:x8}"; - - break; + return Executables[Index]; } } - return Name; + return null; } private void ThreadFinished(object sender, EventArgs e) diff --git a/Ryujinx.HLE/Loaders/Executable.cs b/Ryujinx.HLE/Loaders/Executable.cs index 6a3f0b9735..a9850e4af9 100644 --- a/Ryujinx.HLE/Loaders/Executable.cs +++ b/Ryujinx.HLE/Loaders/Executable.cs @@ -3,18 +3,21 @@ using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.Loaders.Executables; using Ryujinx.HLE.Utilities; +using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; +using System.Linq; namespace Ryujinx.HLE.Loaders { class Executable { + private AMemory Memory; + private List Dynamic; - private Dictionary m_SymbolTable; - - public IReadOnlyDictionary SymbolTable => m_SymbolTable; + public ReadOnlyCollection SymbolTable; public string Name { get; private set; } @@ -23,16 +26,12 @@ namespace Ryujinx.HLE.Loaders public long ImageBase { get; private set; } public long ImageEnd { get; private set; } - private AMemory Memory; - private KMemoryManager MemoryManager; public Executable(IExecutable Exe, KMemoryManager MemoryManager, AMemory Memory, long ImageBase) { Dynamic = new List(); - m_SymbolTable = new Dictionary(); - FilePath = Exe.FilePath; if (FilePath != null) @@ -103,14 +102,18 @@ namespace Ryujinx.HLE.Loaders long SymEntSize = GetFirstValue(ElfDynTag.DT_SYMENT); + List Symbols = new List(); + while ((ulong)SymTblAddr < (ulong)StrTblAddr) { ElfSym Sym = GetSymbol(SymTblAddr, StrTblAddr); - m_SymbolTable.TryAdd(Sym.Value, Sym.Name); + Symbols.Add(Sym); SymTblAddr += SymEntSize; } + + SymbolTable = Array.AsReadOnly(Symbols.OrderBy(x => x.Value).ToArray()); } private ElfRel GetRelocation(long Position) From e04221b293370dac53e4e874cddd4f9a2a64b52f Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Wed, 19 Sep 2018 17:11:05 -0300 Subject: [PATCH 12/14] Fixup SSY (#424) --- Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs | 2 +- Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs index 60fe91c280..11f0444948 100644 --- a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs +++ b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs @@ -360,7 +360,7 @@ namespace Ryujinx.Graphics.Gal.Shader private void PrintDeclSsy() { - SB.AppendLine("uint " + GlslDecl.SsyCursorName + ";"); + SB.AppendLine("uint " + GlslDecl.SsyCursorName + "= 0;"); SB.AppendLine("uint " + GlslDecl.SsyStackName + "[" + GlslDecl.SsyStackSize + "];" + Environment.NewLine); } diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs index 1e76eab169..d44659c755 100644 --- a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs +++ b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs @@ -112,7 +112,7 @@ namespace Ryujinx.Graphics.Gal.Shader Set("0100110000101x", ShaderDecode.Shr_C); Set("0011100x00101x", ShaderDecode.Shr_I); Set("0101110000101x", ShaderDecode.Shr_R); - Set("1110001010010x", ShaderDecode.Ssy); + Set("111000101001xx", ShaderDecode.Ssy); Set("1110111111110x", ShaderDecode.St_A); Set("1111000011111x", ShaderDecode.Sync); Set("110000xxxx111x", ShaderDecode.Tex); From bed13f2022e3b81d694e51e1c29ee66f4a18f5f9 Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Wed, 19 Sep 2018 18:26:49 -0300 Subject: [PATCH 13/14] General improvements for GpuResourceManager (#421) * General improvements to GpuResourceManager * Address feedback * Address feedback --- Ryujinx.Graphics/Gal/IGalRenderTarget.cs | 2 - .../Gal/OpenGL/OGLRenderTarget.cs | 48 ++++------ Ryujinx.Graphics/GpuResourceManager.cs | 88 +++++++++++-------- 3 files changed, 72 insertions(+), 66 deletions(-) diff --git a/Ryujinx.Graphics/Gal/IGalRenderTarget.cs b/Ryujinx.Graphics/Gal/IGalRenderTarget.cs index 6c9166f24d..7ccf0981d1 100644 --- a/Ryujinx.Graphics/Gal/IGalRenderTarget.cs +++ b/Ryujinx.Graphics/Gal/IGalRenderTarget.cs @@ -35,7 +35,5 @@ namespace Ryujinx.Graphics.Gal int DstY1); void Reinterpret(long Key, GalImage NewImage); - - byte[] GetData(long Key); } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs index 7dde32d828..ff5dc1b895 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs @@ -56,6 +56,8 @@ namespace Ryujinx.Graphics.Gal.OpenGL private int DepthAttachment; private int StencilAttachment; + private int CopyPBO; + public OGLRenderTarget(OGLTexture Texture) { ColorAttachments = new int[8]; @@ -358,45 +360,33 @@ namespace Ryujinx.Graphics.Gal.OpenGL return; } - byte[] Data = GetData(Key); + if (CopyPBO == 0) + { + CopyPBO = GL.GenBuffer(); + } - GL.PixelStore(PixelStoreParameter.UnpackRowLength, OldImage.Width); + GL.BindBuffer(BufferTarget.PixelPackBuffer, CopyPBO); - Texture.Create(Key, Data, NewImage); + GL.BufferData(BufferTarget.PixelPackBuffer, Math.Max(ImageUtils.GetSize(OldImage), ImageUtils.GetSize(NewImage)), IntPtr.Zero, BufferUsageHint.StreamCopy); - GL.PixelStore(PixelStoreParameter.UnpackRowLength, 0); - } - - public byte[] GetData(long Key) - { if (!Texture.TryGetImageHandler(Key, out ImageHandler CachedImage)) { - return null; + throw new InvalidOperationException(); } - if (SrcFb == 0) - { - SrcFb = GL.GenFramebuffer(); - } - - GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, SrcFb); - - FramebufferAttachment Attachment = GetAttachment(CachedImage); - - GL.FramebufferTexture(FramebufferTarget.ReadFramebuffer, Attachment, CachedImage.Handle, 0); - - int Size = ImageUtils.GetSize(CachedImage.Image); - - byte[] Data = new byte[Size]; - - int Width = CachedImage.Width; - int Height = CachedImage.Height; - (_, PixelFormat Format, PixelType Type) = OGLEnumConverter.GetImageFormat(CachedImage.Format); - GL.ReadPixels(0, 0, Width, Height, Format, Type, Data); + GL.BindTexture(TextureTarget.Texture2D, CachedImage.Handle); - return Data; + GL.GetTexImage(TextureTarget.Texture2D, 0, Format, Type, IntPtr.Zero); + + GL.BindBuffer(BufferTarget.PixelPackBuffer, 0); + + GL.BindBuffer(BufferTarget.PixelUnpackBuffer, CopyPBO); + + Texture.Create(Key, ImageUtils.GetSize(NewImage), NewImage); + + GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0); } private static FramebufferAttachment GetAttachment(ImageHandler CachedImage) diff --git a/Ryujinx.Graphics/GpuResourceManager.cs b/Ryujinx.Graphics/GpuResourceManager.cs index ba35910d30..0a8d201452 100644 --- a/Ryujinx.Graphics/GpuResourceManager.cs +++ b/Ryujinx.Graphics/GpuResourceManager.cs @@ -7,10 +7,20 @@ namespace Ryujinx.Graphics { public class GpuResourceManager { + private enum ImageType + { + None, + Texture, + ColorBuffer, + ZetaBuffer + } + private NvGpu Gpu; private HashSet[] UploadedKeys; + private Dictionary ImageTypes; + public GpuResourceManager(NvGpu Gpu) { this.Gpu = Gpu; @@ -21,26 +31,21 @@ namespace Ryujinx.Graphics { UploadedKeys[Index] = new HashSet(); } + + ImageTypes = new Dictionary(); } public void SendColorBuffer(NvGpuVmm Vmm, long Position, int Attachment, GalImage NewImage) { long Size = (uint)ImageUtils.GetSize(NewImage); - MarkAsCached(Vmm, Position, Size, NvGpuBufferType.Texture); + ImageTypes[Position] = ImageType.ColorBuffer; - bool IsCached = Gpu.Renderer.Texture.TryGetImage(Position, out GalImage CachedImage); - - if (IsCached && CachedImage.SizeMatches(NewImage)) + if (!TryReuse(Vmm, Position, NewImage)) { - Gpu.Renderer.RenderTarget.Reinterpret(Position, NewImage); - Gpu.Renderer.RenderTarget.BindColor(Position, Attachment, NewImage); - - return; + Gpu.Renderer.Texture.Create(Position, (int)Size, NewImage); } - Gpu.Renderer.Texture.Create(Position, (int)Size, NewImage); - Gpu.Renderer.RenderTarget.BindColor(Position, Attachment, NewImage); } @@ -48,38 +53,49 @@ namespace Ryujinx.Graphics { long Size = (uint)ImageUtils.GetSize(NewImage); - MarkAsCached(Vmm, Position, Size, NvGpuBufferType.Texture); + ImageTypes[Position] = ImageType.ZetaBuffer; - bool IsCached = Gpu.Renderer.Texture.TryGetImage(Position, out GalImage CachedImage); - - if (IsCached && CachedImage.SizeMatches(NewImage)) + if (!TryReuse(Vmm, Position, NewImage)) { - Gpu.Renderer.RenderTarget.Reinterpret(Position, NewImage); - Gpu.Renderer.RenderTarget.BindZeta(Position, NewImage); - - return; + Gpu.Renderer.Texture.Create(Position, (int)Size, NewImage); } - Gpu.Renderer.Texture.Create(Position, (int)Size, NewImage); - Gpu.Renderer.RenderTarget.BindZeta(Position, NewImage); } public void SendTexture(NvGpuVmm Vmm, long Position, GalImage NewImage, int TexIndex = -1) { - long Size = (uint)ImageUtils.GetSize(NewImage); + PrepareSendTexture(Vmm, Position, NewImage); - if (!MemoryRegionModified(Vmm, Position, Size, NvGpuBufferType.Texture)) + if (TexIndex >= 0) { - if (Gpu.Renderer.Texture.TryGetImage(Position, out GalImage CachedImage) && CachedImage.SizeMatches(NewImage)) + Gpu.Renderer.Texture.Bind(Position, TexIndex, NewImage); + } + + ImageTypes[Position] = ImageType.Texture; + } + + private void PrepareSendTexture(NvGpuVmm Vmm, long Position, GalImage NewImage) + { + long Size = ImageUtils.GetSize(NewImage); + + bool SkipCheck = false; + + if (ImageTypes.TryGetValue(Position, out ImageType OldType)) + { + if (OldType == ImageType.ColorBuffer || OldType == ImageType.ZetaBuffer) { - Gpu.Renderer.RenderTarget.Reinterpret(Position, NewImage); + //Avoid data destruction + MemoryRegionModified(Vmm, Position, Size, NvGpuBufferType.Texture); - if (TexIndex >= 0) - { - Gpu.Renderer.Texture.Bind(Position, TexIndex, NewImage); - } + SkipCheck = true; + } + } + if (SkipCheck || !MemoryRegionModified(Vmm, Position, Size, NvGpuBufferType.Texture)) + { + if (TryReuse(Vmm, Position, NewImage)) + { return; } } @@ -87,16 +103,18 @@ namespace Ryujinx.Graphics byte[] Data = ImageUtils.ReadTexture(Vmm, NewImage, Position); Gpu.Renderer.Texture.Create(Position, Data, NewImage); - - if (TexIndex >= 0) - { - Gpu.Renderer.Texture.Bind(Position, TexIndex, NewImage); - } } - private void MarkAsCached(NvGpuVmm Vmm, long Position, long Size, NvGpuBufferType Type) + private bool TryReuse(NvGpuVmm Vmm, long Position, GalImage NewImage) { - Vmm.IsRegionModified(Position, Size, Type); + if (Gpu.Renderer.Texture.TryGetImage(Position, out GalImage CachedImage) && CachedImage.SizeMatches(NewImage)) + { + Gpu.Renderer.RenderTarget.Reinterpret(Position, NewImage); + + return true; + } + + return false; } private bool MemoryRegionModified(NvGpuVmm Vmm, long Position, long Size, NvGpuBufferType Type) From 47a62e826fe15fce9b7e33f5aa8a04807fe3d172 Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Wed, 19 Sep 2018 22:02:11 -0300 Subject: [PATCH 14/14] Implement DepthWriteMask and add R16G16 (#425) --- Ryujinx.Graphics/Gal/GalPipelineState.cs | 1 + Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs | 14 +++++++--- Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs | 15 +++++++++++ Ryujinx.Graphics/NvGpuEngine3d.cs | 27 ++++++++++++-------- Ryujinx.Graphics/NvGpuEngine3dReg.cs | 1 + Ryujinx.Graphics/Texture/ImageUtils.cs | 2 +- 6 files changed, 46 insertions(+), 14 deletions(-) diff --git a/Ryujinx.Graphics/Gal/GalPipelineState.cs b/Ryujinx.Graphics/Gal/GalPipelineState.cs index 6ee6c93e0c..7b0f17d177 100644 --- a/Ryujinx.Graphics/Gal/GalPipelineState.cs +++ b/Ryujinx.Graphics/Gal/GalPipelineState.cs @@ -34,6 +34,7 @@ public GalCullFace CullFace; public bool DepthTestEnabled; + public bool DepthWriteEnabled; public GalComparisonOp DepthFunc; public bool StencilTestEnabled; diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs index cf856a15a5..20e92ff2c6 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs @@ -85,6 +85,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL CullFace = GalCullFace.Back, DepthTestEnabled = false, + DepthWriteEnabled = true, DepthFunc = GalComparisonOp.Less, StencilTestEnabled = false, @@ -138,19 +139,19 @@ namespace Ryujinx.Graphics.Gal.OpenGL //Note: Uncomment SetFrontFace and SetCullFace when flipping issues are solved - //if (New.FrontFace != O.FrontFace) + //if (New.FrontFace != Old.FrontFace) //{ // GL.FrontFace(OGLEnumConverter.GetFrontFace(New.FrontFace)); //} - //if (New.CullFaceEnabled != O.CullFaceEnabled) + //if (New.CullFaceEnabled != Old.CullFaceEnabled) //{ // Enable(EnableCap.CullFace, New.CullFaceEnabled); //} //if (New.CullFaceEnabled) //{ - // if (New.CullFace != O.CullFace) + // if (New.CullFace != Old.CullFace) // { // GL.CullFace(OGLEnumConverter.GetCullFace(New.CullFace)); // } @@ -161,6 +162,13 @@ namespace Ryujinx.Graphics.Gal.OpenGL Enable(EnableCap.DepthTest, New.DepthTestEnabled); } + if (New.DepthWriteEnabled != Old.DepthWriteEnabled) + { + Rasterizer.DepthWriteEnabled = New.DepthWriteEnabled; + + GL.DepthMask(New.DepthWriteEnabled); + } + if (New.DepthTestEnabled) { if (New.DepthFunc != Old.DepthFunc) diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs index ebfba63d6c..a74aee0773 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs @@ -5,6 +5,8 @@ namespace Ryujinx.Graphics.Gal.OpenGL { class OGLRasterizer : IGalRasterizer { + public bool DepthWriteEnabled { set; private get; } + private int[] VertexBuffers; private OGLCachedResource VboCache; @@ -28,6 +30,8 @@ namespace Ryujinx.Graphics.Gal.OpenGL IboCache = new OGLCachedResource(GL.DeleteBuffer); IndexBuffer = new IbInfo(); + + DepthWriteEnabled = true; } public void LockCaches() @@ -49,6 +53,12 @@ namespace Ryujinx.Graphics.Gal.OpenGL float Depth, int Stencil) { + //OpenGL needs glDepthMask to be enabled to clear it + if (!DepthWriteEnabled) + { + GL.DepthMask(true); + } + GL.ColorMask( Flags.HasFlag(GalClearBufferFlags.ColorRed), Flags.HasFlag(GalClearBufferFlags.ColorGreen), @@ -68,6 +78,11 @@ namespace Ryujinx.Graphics.Gal.OpenGL } GL.ColorMask(true, true, true, true); + + if (!DepthWriteEnabled) + { + GL.DepthMask(false); + } } public bool IsVboCached(long Key, long DataSize) diff --git a/Ryujinx.Graphics/NvGpuEngine3d.cs b/Ryujinx.Graphics/NvGpuEngine3d.cs index a1d0ec803a..b19f3063bf 100644 --- a/Ryujinx.Graphics/NvGpuEngine3d.cs +++ b/Ryujinx.Graphics/NvGpuEngine3d.cs @@ -209,7 +209,7 @@ namespace Ryujinx.Graphics private void SetFrameBuffer(GalPipelineState State) { - State.FramebufferSrgb = (ReadRegister(NvGpuEngine3dReg.FrameBufferSrgb) & 1) != 0; + State.FramebufferSrgb = ReadRegisterBool(NvGpuEngine3dReg.FrameBufferSrgb); State.FlipX = GetFlipSign(NvGpuEngine3dReg.ViewportNScaleX); State.FlipY = GetFlipSign(NvGpuEngine3dReg.ViewportNScaleY); @@ -227,7 +227,7 @@ namespace Ryujinx.Graphics GalMemoryLayout Layout = (GalMemoryLayout)((BlockDim >> 12) & 1); //? - bool ZetaEnable = (ReadRegister(NvGpuEngine3dReg.ZetaEnable) & 1) != 0; + bool ZetaEnable = ReadRegisterBool(NvGpuEngine3dReg.ZetaEnable); if (VA == 0 || ZetaFormat == 0 || !ZetaEnable) { @@ -352,7 +352,7 @@ namespace Ryujinx.Graphics private void SetCullFace(GalPipelineState State) { - State.CullFaceEnabled = (ReadRegister(NvGpuEngine3dReg.CullFaceEnable) & 1) != 0; + State.CullFaceEnabled = ReadRegisterBool(NvGpuEngine3dReg.CullFaceEnable); if (State.CullFaceEnabled) { @@ -362,7 +362,9 @@ namespace Ryujinx.Graphics private void SetDepth(GalPipelineState State) { - State.DepthTestEnabled = (ReadRegister(NvGpuEngine3dReg.DepthTestEnable) & 1) != 0; + State.DepthTestEnabled = ReadRegisterBool(NvGpuEngine3dReg.DepthTestEnable); + + State.DepthWriteEnabled = ReadRegisterBool(NvGpuEngine3dReg.DepthWriteEnable); if (State.DepthTestEnabled) { @@ -372,7 +374,7 @@ namespace Ryujinx.Graphics private void SetStencil(GalPipelineState State) { - State.StencilTestEnabled = (ReadRegister(NvGpuEngine3dReg.StencilEnable) & 1) != 0; + State.StencilTestEnabled = ReadRegisterBool(NvGpuEngine3dReg.StencilEnable); if (State.StencilTestEnabled) { @@ -397,11 +399,11 @@ namespace Ryujinx.Graphics private void SetAlphaBlending(GalPipelineState State) { //TODO: Support independent blend properly. - State.BlendEnabled = (ReadRegister(NvGpuEngine3dReg.IBlendNEnable) & 1) != 0; + State.BlendEnabled = ReadRegisterBool(NvGpuEngine3dReg.IBlendNEnable); if (State.BlendEnabled) { - State.BlendSeparateAlpha = (ReadRegister(NvGpuEngine3dReg.IBlendNSeparateAlpha) & 1) != 0; + State.BlendSeparateAlpha = ReadRegisterBool(NvGpuEngine3dReg.IBlendNSeparateAlpha); State.BlendEquationRgb = (GalBlendEquation)ReadRegister(NvGpuEngine3dReg.IBlendNEquationRgb); State.BlendFuncSrcRgb = (GalBlendFactor)ReadRegister(NvGpuEngine3dReg.IBlendNFuncSrcRgb); @@ -414,7 +416,7 @@ namespace Ryujinx.Graphics private void SetPrimitiveRestart(GalPipelineState State) { - State.PrimitiveRestartEnabled = (ReadRegister(NvGpuEngine3dReg.PrimRestartEnable) & 1) != 0; + State.PrimitiveRestartEnabled = ReadRegisterBool(NvGpuEngine3dReg.PrimRestartEnable); if (State.PrimitiveRestartEnabled) { @@ -424,7 +426,7 @@ namespace Ryujinx.Graphics private void SetRenderTargets() { - bool SeparateFragData = (ReadRegister(NvGpuEngine3dReg.RTSeparateFragData) & 1) != 0; + bool SeparateFragData = ReadRegisterBool(NvGpuEngine3dReg.RTSeparateFragData); if (SeparateFragData) { @@ -635,7 +637,7 @@ namespace Ryujinx.Graphics int VertexDivisor = ReadRegister(NvGpuEngine3dReg.VertexArrayNDivisor + Index * 4); - bool Instanced = (ReadRegister(NvGpuEngine3dReg.VertexArrayNInstance + Index) & 1) != 0; + bool Instanced = ReadRegisterBool(NvGpuEngine3dReg.VertexArrayNInstance + Index); int Stride = Control & 0xfff; @@ -845,6 +847,11 @@ namespace Ryujinx.Graphics return BitConverter.Int32BitsToSingle(ReadRegister(Reg)); } + private bool ReadRegisterBool(NvGpuEngine3dReg Reg) + { + return (ReadRegister(Reg) & 1) != 0; + } + private void WriteRegister(NvGpuEngine3dReg Reg, int Value) { Registers[(int)Reg] = Value; diff --git a/Ryujinx.Graphics/NvGpuEngine3dReg.cs b/Ryujinx.Graphics/NvGpuEngine3dReg.cs index 3abcf6f5ca..418e5b6b40 100644 --- a/Ryujinx.Graphics/NvGpuEngine3dReg.cs +++ b/Ryujinx.Graphics/NvGpuEngine3dReg.cs @@ -35,6 +35,7 @@ namespace Ryujinx.Graphics ZetaArrayMode = 0x48c, DepthTestEnable = 0x4b3, IBlendEnable = 0x4b9, + DepthWriteEnable = 0x4ba, DepthTestFunction = 0x4c3, BlendSeparateAlpha = 0x4cf, BlendEquationRgb = 0x4d0, diff --git a/Ryujinx.Graphics/Texture/ImageUtils.cs b/Ryujinx.Graphics/Texture/ImageUtils.cs index 7d3dde49e7..18a179fbfa 100644 --- a/Ryujinx.Graphics/Texture/ImageUtils.cs +++ b/Ryujinx.Graphics/Texture/ImageUtils.cs @@ -52,7 +52,7 @@ namespace Ryujinx.Graphics.Texture { GalTextureFormat.G8R8, GalImageFormat.G8R8 | Snorm | Unorm | Sint | Uint }, { GalTextureFormat.R16, GalImageFormat.R16 | Snorm | Unorm | Sint | Uint | Sfloat }, { GalTextureFormat.R8, GalImageFormat.R8 | Snorm | Unorm | Sint | Uint }, - { GalTextureFormat.R16G16, GalImageFormat.R16G16 | Snorm }, + { GalTextureFormat.R16G16, GalImageFormat.R16G16 | Snorm | Sfloat }, { GalTextureFormat.R32, GalImageFormat.R32 | Sint | Uint | Sfloat }, { GalTextureFormat.A4B4G4R4, GalImageFormat.A4B4G4R4 | Unorm }, { GalTextureFormat.A1B5G5R5, GalImageFormat.A1R5G5B5 | Unorm },