Compare commits

...

10 Commits

Author SHA1 Message Date
c9c65af59e Perform unscaled 2d engine copy on CPU if source texture isn't in cache. (#3112)
* Initial implementation of fast 2d copy

TODO: Partial copy for mismatching region/size.

* WIP

* Cleanup

* Update Ryujinx.Graphics.Gpu/Engine/Twod/TwodClass.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2022-02-22 11:21:29 -03:00
dc063eac83 ARMeilleure: Implement single stepping (#3133)
* Decoder: Implement SingleInstruction decoder mode

* Translator: Implement Step

* DecoderMode: Rename Normal to MultipleBlocks
2022-02-22 11:11:42 -03:00
ccf23fc629 gui: Fixes the games icon when there is an update (#3148)
* gui: Fixes the games icon when there is a game update

Currently we just load the version of the update, instead of the whole NACP file. This PR fixes that. A little cleanup is made into the code to avoid duplicate things.
(Closes #3039)

* Fix condition
2022-02-22 14:53:39 +01:00
f1460d5494 A32: Fix BLX and BXWritePC (#3151) 2022-02-22 10:41:56 -03:00
644b497df1 Collapse AsSpan().Slice(..) calls into AsSpan(..) (#3145)
* Collapse AsSpan().Slice(..) calls into AsSpan(..)

Less code and a bit faster

* Collapse an Array.Clear(array, 0, array.Length) call to Array.Clear(array)
2022-02-22 10:32:10 -03:00
fb935fd201 Add dedicated ServerBase for FileSystem services (#3142)
This should prevent filesystem services from blocking other services that don't have their own ServerBase. May improve filesystem related stutters in certain titles.

Improves button advanced cutscenes such as Miqol's Request in Xenoblade: DE when the game is on a network share (used to stutter when voice lines played).

Should probably be tested to make sure no mysterious bugs have been unearthed, and to see if any other filesystem related perf issues are improved.
2022-02-19 15:29:11 +01:00
f2087ca29e PPTC version increment (#3139) 2022-02-17 23:52:42 -03:00
92d166ecb7 Enable CPU JIT cache invalidation (#2965)
* Enable CPU JIT cache invalidation

* Invalidate cache on IC IVAU
2022-02-18 02:53:18 +01:00
72e543e946 Prefer texture over textureSize for sampler type (#3132)
* Prefer texture over textureSize for sampler type

* Shader cache version bump
2022-02-18 02:44:46 +01:00
98c838b24c Use BitOperations methods and delete now unused BitUtils methods (#3134)
Replaces BitUtils.CountTrailingZeros/CountLeadingZeros/IsPowerOfTwo with BitOperations methods
2022-02-18 02:35:23 +01:00
43 changed files with 1234 additions and 148 deletions

View File

@ -18,7 +18,7 @@ namespace ARMeilleure.Decoders
// For lower code quality translation, we set a lower limit since we're blocking execution.
private const int MaxInstsPerFunctionLowCq = 500;
public static Block[] Decode(IMemoryManager memory, ulong address, ExecutionMode mode, bool highCq, bool singleBlock)
public static Block[] Decode(IMemoryManager memory, ulong address, ExecutionMode mode, bool highCq, DecoderMode dMode)
{
List<Block> blocks = new List<Block>();
@ -38,7 +38,7 @@ namespace ARMeilleure.Decoders
{
block = new Block(blkAddress);
if ((singleBlock && visited.Count >= 1) || opsCount > instructionLimit || !memory.IsMapped(blkAddress))
if ((dMode != DecoderMode.MultipleBlocks && visited.Count >= 1) || opsCount > instructionLimit || !memory.IsMapped(blkAddress))
{
block.Exit = true;
block.EndAddress = blkAddress;
@ -96,6 +96,12 @@ namespace ARMeilleure.Decoders
}
}
if (dMode == DecoderMode.SingleInstruction)
{
// Only read at most one instruction
limitAddress = currBlock.Address + 1;
}
FillBlock(memory, mode, currBlock, limitAddress);
opsCount += currBlock.OpCodes.Count;
@ -143,7 +149,7 @@ namespace ARMeilleure.Decoders
throw new InvalidOperationException($"Decoded a single empty exit block. Entry point = 0x{address:X}.");
}
if (!singleBlock)
if (dMode == DecoderMode.MultipleBlocks)
{
return TailCallRemover.RunPass(address, blocks);
}

View File

@ -0,0 +1,9 @@
namespace ARMeilleure.Decoders
{
enum DecoderMode
{
MultipleBlocks,
SingleBlock,
SingleInstruction,
}
}

View File

@ -71,7 +71,7 @@ namespace ARMeilleure.Instructions
SetFlag(context, PState.TFlag, bitOne);
EmitVirtualCall(context, addr);
EmitBxWritePc(context, addr);
}
public static void Bx(ArmEmitterContext context)

View File

@ -186,7 +186,7 @@ namespace ARMeilleure.Instructions
SetFlag(context, PState.TFlag, mode);
Operand addr = context.ConditionalSelect(mode, pc, context.BitwiseAnd(pc, Const(~3)));
Operand addr = context.ConditionalSelect(mode, context.BitwiseAnd(pc, Const(~1)), context.BitwiseAnd(pc, Const(~3)));
InstEmitFlowHelper.EmitVirtualJump(context, addr, isReturn);
}

View File

@ -12,7 +12,8 @@ namespace ARMeilleure.Instructions
{
static partial class InstEmit
{
private const int DczSizeLog2 = 4;
private const int DczSizeLog2 = 4; // Log2 size in words
public const int DczSizeInBytes = 4 << DczSizeLog2;
public static void Hint(ArmEmitterContext context)
{
@ -87,7 +88,7 @@ namespace ARMeilleure.Instructions
// DC ZVA
Operand t = GetIntOrZR(context, op.Rt);
for (long offset = 0; offset < (4 << DczSizeLog2); offset += 8)
for (long offset = 0; offset < DczSizeInBytes; offset += 8)
{
Operand address = context.Add(t, Const(offset));
@ -98,7 +99,12 @@ namespace ARMeilleure.Instructions
}
// No-op
case 0b11_011_0111_1110_001: //DC CIVAC
case 0b11_011_0111_1110_001: // DC CIVAC
break;
case 0b11_011_0111_0101_001: // IC IVAU
Operand target = Register(op.Rt, RegisterType.Integer, OperandType.I64);
context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.InvalidateCacheLine)), target);
break;
}
}

View File

@ -242,6 +242,11 @@ namespace ARMeilleure.Instructions
return (ulong)function.FuncPtr.ToInt64();
}
public static void InvalidateCacheLine(ulong address)
{
Context.Translator.InvalidateJitCacheRegion(address, InstEmit.DczSizeInBytes);
}
public static bool CheckSynchronization()
{
Statistics.PauseTimer();

View File

@ -114,6 +114,7 @@ namespace ARMeilleure.Translation
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFpscr))); // A32 only.
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFpsr)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.InvalidateCacheLine)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr32))); // A32 only.
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl0)));

View File

@ -0,0 +1,756 @@
using System;
using System.Collections.Generic;
namespace ARMeilleure.Translation
{
/// <summary>
/// An Augmented Interval Tree based off of the "TreeDictionary"'s Red-Black Tree. Allows fast overlap checking of ranges.
/// </summary>
/// <typeparam name="K">Key</typeparam>
/// <typeparam name="V">Value</typeparam>
public class IntervalTree<K, V> where K : IComparable<K>
{
private const int ArrayGrowthSize = 32;
private const bool Black = true;
private const bool Red = false;
private IntervalTreeNode<K, V> _root = null;
private int _count = 0;
public int Count => _count;
public IntervalTree() { }
#region Public Methods
/// <summary>
/// Gets the values of the interval whose key is <paramref name="key"/>.
/// </summary>
/// <param name="key">Key of the node value to get</param>
/// <param name="value">Value with the given <paramref name="key"/></param>
/// <returns>True if the key is on the dictionary, false otherwise</returns>
public bool TryGet(K key, out V value)
{
IntervalTreeNode<K, V> node = GetNode(key);
if (node == null)
{
value = default;
return false;
}
value = node.Value;
return true;
}
/// <summary>
/// Returns the start addresses of the intervals whose start and end keys overlap the given range.
/// </summary>
/// <param name="start">Start of the range</param>
/// <param name="end">End of the range</param>
/// <param name="overlaps">Overlaps array to place results in</param>
/// <param name="overlapCount">Index to start writing results into the array. Defaults to 0</param>
/// <returns>Number of intervals found</returns>
public int Get(K start, K end, ref K[] overlaps, int overlapCount = 0)
{
GetValues(_root, start, end, ref overlaps, ref overlapCount);
return overlapCount;
}
/// <summary>
/// Adds a new interval into the tree whose start is <paramref name="start"/>, end is <paramref name="end"/> and value is <paramref name="value"/>.
/// </summary>
/// <param name="start">Start of the range to add</param>
/// <param name="end">End of the range to insert</param>
/// <param name="value">Value to add</param>
/// <param name="updateFactoryCallback">Optional factory used to create a new value if <paramref name="start"/> is already on the tree</param>
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null</exception>
/// <returns>True if the value was added, false if the start key was already in the dictionary</returns>
public bool AddOrUpdate(K start, K end, V value, Func<K, V, V> updateFactoryCallback)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
return BSTInsert(start, end, value, updateFactoryCallback, out IntervalTreeNode<K, V> node);
}
/// <summary>
/// Gets an existing or adds a new interval into the tree whose start is <paramref name="start"/>, end is <paramref name="end"/> and value is <paramref name="value"/>.
/// </summary>
/// <param name="start">Start of the range to add</param>
/// <param name="end">End of the range to insert</param>
/// <param name="value">Value to add</param>
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null</exception>
/// <returns><paramref name="value"/> if <paramref name="start"/> is not yet on the tree, or the existing value otherwise</returns>
public V GetOrAdd(K start, K end, V value)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
BSTInsert(start, end, value, null, out IntervalTreeNode<K, V> node);
return node.Value;
}
/// <summary>
/// Removes a value from the tree, searching for it with <paramref name="key"/>.
/// </summary>
/// <param name="key">Key of the node to remove</param>
/// <returns>Number of deleted values</returns>
public int Remove(K key)
{
int removed = Delete(key);
_count -= removed;
return removed;
}
/// <summary>
/// Adds all the nodes in the dictionary into <paramref name="list"/>.
/// </summary>
/// <returns>A list of all values sorted by Key Order</returns>
public List<V> AsList()
{
List<V> list = new List<V>();
AddToList(_root, list);
return list;
}
#endregion
#region Private Methods (BST)
/// <summary>
/// Adds all values that are children of or contained within <paramref name="node"/> into <paramref name="list"/>, in Key Order.
/// </summary>
/// <param name="node">The node to search for values within</param>
/// <param name="list">The list to add values to</param>
private void AddToList(IntervalTreeNode<K, V> node, List<V> list)
{
if (node == null)
{
return;
}
AddToList(node.Left, list);
list.Add(node.Value);
AddToList(node.Right, list);
}
/// <summary>
/// Retrieve the node reference whose key is <paramref name="key"/>, or null if no such node exists.
/// </summary>
/// <param name="key">Key of the node to get</param>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
/// <returns>Node reference in the tree</returns>
private IntervalTreeNode<K, V> GetNode(K key)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
IntervalTreeNode<K, V> node = _root;
while (node != null)
{
int cmp = key.CompareTo(node.Start);
if (cmp < 0)
{
node = node.Left;
}
else if (cmp > 0)
{
node = node.Right;
}
else
{
return node;
}
}
return null;
}
/// <summary>
/// Retrieve all values that overlap the given start and end keys.
/// </summary>
/// <param name="start">Start of the range</param>
/// <param name="end">End of the range</param>
/// <param name="overlaps">Overlaps array to place results in</param>
/// <param name="overlapCount">Overlaps count to update</param>
private void GetValues(IntervalTreeNode<K, V> node, K start, K end, ref K[] overlaps, ref int overlapCount)
{
if (node == null || start.CompareTo(node.Max) >= 0)
{
return;
}
GetValues(node.Left, start, end, ref overlaps, ref overlapCount);
bool endsOnRight = end.CompareTo(node.Start) > 0;
if (endsOnRight)
{
if (start.CompareTo(node.End) < 0)
{
if (overlaps.Length >= overlapCount)
{
Array.Resize(ref overlaps, overlapCount + ArrayGrowthSize);
}
overlaps[overlapCount++] = node.Start;
}
GetValues(node.Right, start, end, ref overlaps, ref overlapCount);
}
}
/// <summary>
/// Propagate an increase in max value starting at the given node, heading up the tree.
/// This should only be called if the max increases - not for rebalancing or removals.
/// </summary>
/// <param name="node">The node to start propagating from</param>
private void PropagateIncrease(IntervalTreeNode<K, V> node)
{
K max = node.Max;
IntervalTreeNode<K, V> ptr = node;
while ((ptr = ptr.Parent) != null)
{
if (max.CompareTo(ptr.Max) > 0)
{
ptr.Max = max;
}
else
{
break;
}
}
}
/// <summary>
/// Propagate recalculating max value starting at the given node, heading up the tree.
/// This fully recalculates the max value from all children when there is potential for it to decrease.
/// </summary>
/// <param name="node">The node to start propagating from</param>
private void PropagateFull(IntervalTreeNode<K, V> node)
{
IntervalTreeNode<K, V> ptr = node;
do
{
K max = ptr.End;
if (ptr.Left != null && ptr.Left.Max.CompareTo(max) > 0)
{
max = ptr.Left.Max;
}
if (ptr.Right != null && ptr.Right.Max.CompareTo(max) > 0)
{
max = ptr.Right.Max;
}
ptr.Max = max;
} while ((ptr = ptr.Parent) != null);
}
/// <summary>
/// Insertion Mechanism for the interval tree. Similar to a BST insert, with the start of the range as the key.
/// Iterates the tree starting from the root and inserts a new node where all children in the left subtree are less than <paramref name="start"/>, and all children in the right subtree are greater than <paramref name="start"/>.
/// Each node can contain multiple values, and has an end address which is the maximum of all those values.
/// Post insertion, the "max" value of the node and all parents are updated.
/// </summary>
/// <param name="start">Start of the range to insert</param>
/// <param name="end">End of the range to insert</param>
/// <param name="value">Value to insert</param>
/// <param name="updateFactoryCallback">Optional factory used to create a new value if <paramref name="start"/> is already on the tree</param>
/// <param name="outNode">Node that was inserted or modified</param>
/// <returns>True if <paramref name="start"/> was not yet on the tree, false otherwise</returns>
private bool BSTInsert(K start, K end, V value, Func<K, V, V> updateFactoryCallback, out IntervalTreeNode<K, V> outNode)
{
IntervalTreeNode<K, V> parent = null;
IntervalTreeNode<K, V> node = _root;
while (node != null)
{
parent = node;
int cmp = start.CompareTo(node.Start);
if (cmp < 0)
{
node = node.Left;
}
else if (cmp > 0)
{
node = node.Right;
}
else
{
outNode = node;
if (updateFactoryCallback != null)
{
// Replace
node.Value = updateFactoryCallback(start, node.Value);
int endCmp = end.CompareTo(node.End);
if (endCmp > 0)
{
node.End = end;
if (end.CompareTo(node.Max) > 0)
{
node.Max = end;
PropagateIncrease(node);
RestoreBalanceAfterInsertion(node);
}
}
else if (endCmp < 0)
{
node.End = end;
PropagateFull(node);
}
}
return false;
}
}
IntervalTreeNode<K, V> newNode = new IntervalTreeNode<K, V>(start, end, value, parent);
if (newNode.Parent == null)
{
_root = newNode;
}
else if (start.CompareTo(parent.Start) < 0)
{
parent.Left = newNode;
}
else
{
parent.Right = newNode;
}
PropagateIncrease(newNode);
_count++;
RestoreBalanceAfterInsertion(newNode);
outNode = newNode;
return true;
}
/// <summary>
/// Removes the value from the dictionary after searching for it with <paramref name="key">.
/// </summary>
/// <param name="key">Key to search for</param>
/// <returns>Number of deleted values</returns>
private int Delete(K key)
{
IntervalTreeNode<K, V> nodeToDelete = GetNode(key);
if (nodeToDelete == null)
{
return 0;
}
IntervalTreeNode<K, V> replacementNode;
if (LeftOf(nodeToDelete) == null || RightOf(nodeToDelete) == null)
{
replacementNode = nodeToDelete;
}
else
{
replacementNode = PredecessorOf(nodeToDelete);
}
IntervalTreeNode<K, V> tmp = LeftOf(replacementNode) ?? RightOf(replacementNode);
if (tmp != null)
{
tmp.Parent = ParentOf(replacementNode);
}
if (ParentOf(replacementNode) == null)
{
_root = tmp;
}
else if (replacementNode == LeftOf(ParentOf(replacementNode)))
{
ParentOf(replacementNode).Left = tmp;
}
else
{
ParentOf(replacementNode).Right = tmp;
}
if (replacementNode != nodeToDelete)
{
nodeToDelete.Start = replacementNode.Start;
nodeToDelete.Value = replacementNode.Value;
nodeToDelete.End = replacementNode.End;
nodeToDelete.Max = replacementNode.Max;
}
PropagateFull(replacementNode);
if (tmp != null && ColorOf(replacementNode) == Black)
{
RestoreBalanceAfterRemoval(tmp);
}
return 1;
}
/// <summary>
/// Returns the node with the largest key where <paramref name="node"/> is considered the root node.
/// </summary>
/// <param name="node">Root Node</param>
/// <returns>Node with the maximum key in the tree of <paramref name="node"/></returns>
private static IntervalTreeNode<K, V> Maximum(IntervalTreeNode<K, V> node)
{
IntervalTreeNode<K, V> tmp = node;
while (tmp.Right != null)
{
tmp = tmp.Right;
}
return tmp;
}
/// <summary>
/// Finds the node whose key is immediately less than <paramref name="node"/>.
/// </summary>
/// <param name="node">Node to find the predecessor of</param>
/// <returns>Predecessor of <paramref name="node"/></returns>
private static IntervalTreeNode<K, V> PredecessorOf(IntervalTreeNode<K, V> node)
{
if (node.Left != null)
{
return Maximum(node.Left);
}
IntervalTreeNode<K, V> parent = node.Parent;
while (parent != null && node == parent.Left)
{
node = parent;
parent = parent.Parent;
}
return parent;
}
#endregion
#region Private Methods (RBL)
private void RestoreBalanceAfterRemoval(IntervalTreeNode<K, V> balanceNode)
{
IntervalTreeNode<K, V> ptr = balanceNode;
while (ptr != _root && ColorOf(ptr) == Black)
{
if (ptr == LeftOf(ParentOf(ptr)))
{
IntervalTreeNode<K, V> sibling = RightOf(ParentOf(ptr));
if (ColorOf(sibling) == Red)
{
SetColor(sibling, Black);
SetColor(ParentOf(ptr), Red);
RotateLeft(ParentOf(ptr));
sibling = RightOf(ParentOf(ptr));
}
if (ColorOf(LeftOf(sibling)) == Black && ColorOf(RightOf(sibling)) == Black)
{
SetColor(sibling, Red);
ptr = ParentOf(ptr);
}
else
{
if (ColorOf(RightOf(sibling)) == Black)
{
SetColor(LeftOf(sibling), Black);
SetColor(sibling, Red);
RotateRight(sibling);
sibling = RightOf(ParentOf(ptr));
}
SetColor(sibling, ColorOf(ParentOf(ptr)));
SetColor(ParentOf(ptr), Black);
SetColor(RightOf(sibling), Black);
RotateLeft(ParentOf(ptr));
ptr = _root;
}
}
else
{
IntervalTreeNode<K, V> sibling = LeftOf(ParentOf(ptr));
if (ColorOf(sibling) == Red)
{
SetColor(sibling, Black);
SetColor(ParentOf(ptr), Red);
RotateRight(ParentOf(ptr));
sibling = LeftOf(ParentOf(ptr));
}
if (ColorOf(RightOf(sibling)) == Black && ColorOf(LeftOf(sibling)) == Black)
{
SetColor(sibling, Red);
ptr = ParentOf(ptr);
}
else
{
if (ColorOf(LeftOf(sibling)) == Black)
{
SetColor(RightOf(sibling), Black);
SetColor(sibling, Red);
RotateLeft(sibling);
sibling = LeftOf(ParentOf(ptr));
}
SetColor(sibling, ColorOf(ParentOf(ptr)));
SetColor(ParentOf(ptr), Black);
SetColor(LeftOf(sibling), Black);
RotateRight(ParentOf(ptr));
ptr = _root;
}
}
}
SetColor(ptr, Black);
}
private void RestoreBalanceAfterInsertion(IntervalTreeNode<K, V> balanceNode)
{
SetColor(balanceNode, Red);
while (balanceNode != null && balanceNode != _root && ColorOf(ParentOf(balanceNode)) == Red)
{
if (ParentOf(balanceNode) == LeftOf(ParentOf(ParentOf(balanceNode))))
{
IntervalTreeNode<K, V> sibling = RightOf(ParentOf(ParentOf(balanceNode)));
if (ColorOf(sibling) == Red)
{
SetColor(ParentOf(balanceNode), Black);
SetColor(sibling, Black);
SetColor(ParentOf(ParentOf(balanceNode)), Red);
balanceNode = ParentOf(ParentOf(balanceNode));
}
else
{
if (balanceNode == RightOf(ParentOf(balanceNode)))
{
balanceNode = ParentOf(balanceNode);
RotateLeft(balanceNode);
}
SetColor(ParentOf(balanceNode), Black);
SetColor(ParentOf(ParentOf(balanceNode)), Red);
RotateRight(ParentOf(ParentOf(balanceNode)));
}
}
else
{
IntervalTreeNode<K, V> sibling = LeftOf(ParentOf(ParentOf(balanceNode)));
if (ColorOf(sibling) == Red)
{
SetColor(ParentOf(balanceNode), Black);
SetColor(sibling, Black);
SetColor(ParentOf(ParentOf(balanceNode)), Red);
balanceNode = ParentOf(ParentOf(balanceNode));
}
else
{
if (balanceNode == LeftOf(ParentOf(balanceNode)))
{
balanceNode = ParentOf(balanceNode);
RotateRight(balanceNode);
}
SetColor(ParentOf(balanceNode), Black);
SetColor(ParentOf(ParentOf(balanceNode)), Red);
RotateLeft(ParentOf(ParentOf(balanceNode)));
}
}
}
SetColor(_root, Black);
}
private void RotateLeft(IntervalTreeNode<K, V> node)
{
if (node != null)
{
IntervalTreeNode<K, V> right = RightOf(node);
node.Right = LeftOf(right);
if (node.Right != null)
{
node.Right.Parent = node;
}
IntervalTreeNode<K, V> nodeParent = ParentOf(node);
right.Parent = nodeParent;
if (nodeParent == null)
{
_root = right;
}
else if (node == LeftOf(nodeParent))
{
nodeParent.Left = right;
}
else
{
nodeParent.Right = right;
}
right.Left = node;
node.Parent = right;
PropagateFull(node);
}
}
private void RotateRight(IntervalTreeNode<K, V> node)
{
if (node != null)
{
IntervalTreeNode<K, V> left = LeftOf(node);
node.Left = RightOf(left);
if (node.Left != null)
{
node.Left.Parent = node;
}
IntervalTreeNode<K, V> nodeParent = ParentOf(node);
left.Parent = nodeParent;
if (nodeParent == null)
{
_root = left;
}
else if (node == RightOf(nodeParent))
{
nodeParent.Right = left;
}
else
{
nodeParent.Left = left;
}
left.Right = node;
node.Parent = left;
PropagateFull(node);
}
}
#endregion
#region Safety-Methods
// These methods save memory by allowing us to forego sentinel nil nodes, as well as serve as protection against NullReferenceExceptions.
/// <summary>
/// Returns the color of <paramref name="node"/>, or Black if it is null.
/// </summary>
/// <param name="node">Node</param>
/// <returns>The boolean color of <paramref name="node"/>, or black if null</returns>
private static bool ColorOf(IntervalTreeNode<K, V> node)
{
return node == null || node.Color;
}
/// <summary>
/// Sets the color of <paramref name="node"/> node to <paramref name="color"/>.
/// <br></br>
/// This method does nothing if <paramref name="node"/> is null.
/// </summary>
/// <param name="node">Node to set the color of</param>
/// <param name="color">Color (Boolean)</param>
private static void SetColor(IntervalTreeNode<K, V> node, bool color)
{
if (node != null)
{
node.Color = color;
}
}
/// <summary>
/// This method returns the left node of <paramref name="node"/>, or null if <paramref name="node"/> is null.
/// </summary>
/// <param name="node">Node to retrieve the left child from</param>
/// <returns>Left child of <paramref name="node"/></returns>
private static IntervalTreeNode<K, V> LeftOf(IntervalTreeNode<K, V> node)
{
return node?.Left;
}
/// <summary>
/// This method returns the right node of <paramref name="node"/>, or null if <paramref name="node"/> is null.
/// </summary>
/// <param name="node">Node to retrieve the right child from</param>
/// <returns>Right child of <paramref name="node"/></returns>
private static IntervalTreeNode<K, V> RightOf(IntervalTreeNode<K, V> node)
{
return node?.Right;
}
/// <summary>
/// Returns the parent node of <paramref name="node"/>, or null if <paramref name="node"/> is null.
/// </summary>
/// <param name="node">Node to retrieve the parent from</param>
/// <returns>Parent of <paramref name="node"/></returns>
private static IntervalTreeNode<K, V> ParentOf(IntervalTreeNode<K, V> node)
{
return node?.Parent;
}
#endregion
public bool ContainsKey(K key)
{
return GetNode(key) != null;
}
public void Clear()
{
_root = null;
_count = 0;
}
}
/// <summary>
/// Represents a node in the IntervalTree which contains start and end keys of type K, and a value of generic type V.
/// </summary>
/// <typeparam name="K">Key type of the node</typeparam>
/// <typeparam name="V">Value type of the node</typeparam>
internal class IntervalTreeNode<K, V>
{
internal bool Color = true;
internal IntervalTreeNode<K, V> Left = null;
internal IntervalTreeNode<K, V> Right = null;
internal IntervalTreeNode<K, V> Parent = null;
/// <summary>
/// The start of the range.
/// </summary>
internal K Start;
/// <summary>
/// The end of the range.
/// </summary>
internal K End;
/// <summary>
/// The maximum end value of this node and all its children.
/// </summary>
internal K Max;
/// <summary>
/// Value stored on this node.
/// </summary>
internal V Value;
public IntervalTreeNode(K start, K end, V value, IntervalTreeNode<K, V> parent)
{
this.Start = start;
this.End = end;
this.Max = end;
this.Value = value;
this.Parent = parent;
}
}
}

View File

@ -27,7 +27,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0";
private const uint InternalVersion = 3061; //! To be incremented manually for each change to the ARMeilleure project.
private const uint InternalVersion = 3138; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0";
private const string BackupDir = "1";
@ -585,7 +585,7 @@ namespace ARMeilleure.Translation.PTC
translator.RegisterFunction(infoEntry.Address, func);
bool isAddressUnique = translator.Functions.TryAdd(infoEntry.Address, func);
bool isAddressUnique = translator.Functions.TryAdd(infoEntry.Address, infoEntry.GuestSize, func);
Debug.Assert(isAddressUnique, $"The address 0x{infoEntry.Address:X16} is not unique.");
}
@ -815,7 +815,7 @@ namespace ARMeilleure.Translation.PTC
TranslatedFunction func = translator.Translate(address, item.funcProfile.Mode, item.funcProfile.HighCq);
bool isAddressUnique = translator.Functions.TryAdd(address, func);
bool isAddressUnique = translator.Functions.TryAdd(address, func.GuestSize, func);
Debug.Assert(isAddressUnique, $"The address 0x{address:X16} is not unique.");

View File

@ -96,7 +96,7 @@ namespace ARMeilleure.Translation.PTC
return address >= StaticCodeStart && address < StaticCodeStart + StaticCodeSize;
}
internal static ConcurrentQueue<(ulong address, FuncProfile funcProfile)> GetProfiledFuncsToTranslate(ConcurrentDictionary<ulong, TranslatedFunction> funcs)
internal static ConcurrentQueue<(ulong address, FuncProfile funcProfile)> GetProfiledFuncsToTranslate(TranslatorCache<TranslatedFunction> funcs)
{
var profiledFuncsToTranslate = new ConcurrentQueue<(ulong address, FuncProfile funcProfile)>();

View File

@ -113,7 +113,7 @@ namespace ARMeilleure.Translation
}
}
Array.Clear(localDefs, 0, localDefs.Length);
Array.Clear(localDefs);
}
// Second pass, rename variables with definitions on different blocks.

View File

@ -49,7 +49,7 @@ namespace ARMeilleure.Translation
private readonly AutoResetEvent _backgroundTranslatorEvent;
private readonly ReaderWriterLock _backgroundTranslatorLock;
internal ConcurrentDictionary<ulong, TranslatedFunction> Functions { get; }
internal TranslatorCache<TranslatedFunction> Functions { get; }
internal AddressTable<ulong> FunctionTable { get; }
internal EntryTable<uint> CountTable { get; }
internal TranslatorStubs Stubs { get; }
@ -75,7 +75,7 @@ namespace ARMeilleure.Translation
JitCache.Initialize(allocator);
CountTable = new EntryTable<uint>();
Functions = new ConcurrentDictionary<ulong, TranslatedFunction>();
Functions = new TranslatorCache<TranslatedFunction>();
FunctionTable = new AddressTable<ulong>(for64Bits ? Levels64Bit : Levels32Bit);
Stubs = new TranslatorStubs(this);
@ -93,12 +93,12 @@ namespace ARMeilleure.Translation
{
_backgroundTranslatorLock.AcquireReaderLock(Timeout.Infinite);
if (_backgroundStack.TryPop(out RejitRequest request) &&
if (_backgroundStack.TryPop(out RejitRequest request) &&
_backgroundSet.TryRemove(request.Address, out _))
{
TranslatedFunction func = Translate(request.Address, request.Mode, highCq: true);
Functions.AddOrUpdate(request.Address, func, (key, oldFunc) =>
Functions.AddOrUpdate(request.Address, func.GuestSize, func, (key, oldFunc) =>
{
EnqueueForDeletion(key, oldFunc);
return func;
@ -196,7 +196,7 @@ namespace ARMeilleure.Translation
}
}
public ulong ExecuteSingle(State.ExecutionContext context, ulong address)
private ulong ExecuteSingle(State.ExecutionContext context, ulong address)
{
TranslatedFunction func = GetOrTranslate(address, context.ExecutionMode);
@ -209,13 +209,24 @@ namespace ARMeilleure.Translation
return nextAddr;
}
public ulong Step(State.ExecutionContext context, ulong address)
{
TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true);
address = func.Execute(context);
EnqueueForDeletion(address, func);
return address;
}
internal TranslatedFunction GetOrTranslate(ulong address, ExecutionMode mode)
{
if (!Functions.TryGetValue(address, out TranslatedFunction func))
{
func = Translate(address, mode, highCq: false);
TranslatedFunction oldFunc = Functions.GetOrAdd(address, func);
TranslatedFunction oldFunc = Functions.GetOrAdd(address, func.GuestSize, func);
if (oldFunc != func)
{
@ -242,7 +253,7 @@ namespace ARMeilleure.Translation
}
}
internal TranslatedFunction Translate(ulong address, ExecutionMode mode, bool highCq)
internal TranslatedFunction Translate(ulong address, ExecutionMode mode, bool highCq, bool singleStep = false)
{
var context = new ArmEmitterContext(
Memory,
@ -255,7 +266,7 @@ namespace ARMeilleure.Translation
Logger.StartPass(PassName.Decoding);
Block[] blocks = Decoder.Decode(Memory, address, mode, highCq, singleBlock: false);
Block[] blocks = Decoder.Decode(Memory, address, mode, highCq, singleStep ? DecoderMode.SingleInstruction : DecoderMode.MultipleBlocks);
Logger.EndPass(PassName.Decoding);
@ -285,14 +296,14 @@ namespace ARMeilleure.Translation
var options = highCq ? CompilerOptions.HighCq : CompilerOptions.None;
if (context.HasPtc)
if (context.HasPtc && !singleStep)
{
options |= CompilerOptions.Relocatable;
}
CompiledFunction compiledFunc = Compiler.Compile(cfg, argTypes, retType, options);
if (context.HasPtc)
if (context.HasPtc && !singleStep)
{
Hash128 hash = Ptc.ComputeHash(Memory, address, funcSize);
@ -471,7 +482,24 @@ namespace ARMeilleure.Translation
// If rejit is running, stop it as it may be trying to rejit a function on the invalidated region.
ClearRejitQueue(allowRequeue: true);
// TODO: Completely remove functions overlapping the specified range from the cache.
ulong[] overlapAddresses = Array.Empty<ulong>();
int overlapsCount = Functions.GetOverlaps(address, size, ref overlapAddresses);
for (int index = 0; index < overlapsCount; index++)
{
ulong overlapAddress = overlapAddresses[index];
if (Functions.TryGetValue(overlapAddress, out TranslatedFunction overlap))
{
Functions.Remove(overlapAddress);
Volatile.Write(ref FunctionTable.GetValue(overlapAddress), FunctionTable.Fill);
EnqueueForDeletion(overlapAddress, overlap);
}
}
// TODO: Remove overlapping functions from the JitCache aswell.
// This should be done safely, with a mechanism to ensure the function is not being executed.
}
internal void EnqueueForRejit(ulong guestAddress, ExecutionMode mode)
@ -493,7 +521,9 @@ namespace ARMeilleure.Translation
// Ensure no attempt will be made to compile new functions due to rejit.
ClearRejitQueue(allowRequeue: false);
foreach (var func in Functions.Values)
List<TranslatedFunction> functions = Functions.AsList();
foreach (var func in functions)
{
JitCache.Unmap(func.FuncPtr);

View File

@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Threading;
namespace ARMeilleure.Translation
{
internal class TranslatorCache<T>
{
private readonly IntervalTree<ulong, T> _tree;
private readonly ReaderWriterLock _treeLock;
public int Count => _tree.Count;
public TranslatorCache()
{
_tree = new IntervalTree<ulong, T>();
_treeLock = new ReaderWriterLock();
}
public bool TryAdd(ulong address, ulong size, T value)
{
return AddOrUpdate(address, size, value, null);
}
public bool AddOrUpdate(ulong address, ulong size, T value, Func<ulong, T, T> updateFactoryCallback)
{
_treeLock.AcquireWriterLock(Timeout.Infinite);
bool result = _tree.AddOrUpdate(address, address + size, value, updateFactoryCallback);
_treeLock.ReleaseWriterLock();
return result;
}
public T GetOrAdd(ulong address, ulong size, T value)
{
_treeLock.AcquireWriterLock(Timeout.Infinite);
value = _tree.GetOrAdd(address, address + size, value);
_treeLock.ReleaseWriterLock();
return value;
}
public bool Remove(ulong address)
{
_treeLock.AcquireWriterLock(Timeout.Infinite);
bool removed = _tree.Remove(address) != 0;
_treeLock.ReleaseWriterLock();
return removed;
}
public void Clear()
{
_treeLock.AcquireWriterLock(Timeout.Infinite);
_tree.Clear();
_treeLock.ReleaseWriterLock();
}
public bool ContainsKey(ulong address)
{
_treeLock.AcquireReaderLock(Timeout.Infinite);
bool result = _tree.ContainsKey(address);
_treeLock.ReleaseReaderLock();
return result;
}
public bool TryGetValue(ulong address, out T value)
{
_treeLock.AcquireReaderLock(Timeout.Infinite);
bool result = _tree.TryGet(address, out value);
_treeLock.ReleaseReaderLock();
return result;
}
public int GetOverlaps(ulong address, ulong size, ref ulong[] overlaps)
{
_treeLock.AcquireReaderLock(Timeout.Infinite);
int count = _tree.Get(address, address + size, ref overlaps);
_treeLock.ReleaseReaderLock();
return count;
}
public List<T> AsList()
{
_treeLock.AcquireReaderLock(Timeout.Infinite);
List<T> list = _tree.AsList();
_treeLock.ReleaseReaderLock();
return list;
}
}
}

View File

@ -112,12 +112,12 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
private ReadOnlySpan<float> GetFdnDelayTimesByLateMode(ReverbLateMode lateMode)
{
return FdnDelayTimes.AsSpan().Slice((int)lateMode * 4, 4);
return FdnDelayTimes.AsSpan((int)lateMode * 4, 4);
}
private ReadOnlySpan<float> GetDecayDelayTimesByLateMode(ReverbLateMode lateMode)
{
return DecayDelayTimes.AsSpan().Slice((int)lateMode * 4, 4);
return DecayDelayTimes.AsSpan((int)lateMode * 4, 4);
}
public ReverbState(ref ReverbParameter parameter, ulong workBuffer, bool isLongSizePreDelaySupported)

View File

@ -459,7 +459,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
for (int i = 0; i < Constants.VoiceWaveBufferCount; i++)
{
UpdateWaveBuffer(errorInfos.AsSpan().Slice(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], ref mapper, ref behaviourContext);
UpdateWaveBuffer(errorInfos.AsSpan(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], ref mapper, ref behaviourContext);
}
}

View File

@ -1,11 +1,10 @@
using System;
using System.Numerics;
namespace Ryujinx.Common
{
public static class BitUtils
{
private static ReadOnlySpan<byte> ClzNibbleTbl => new byte[] { 4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 };
public static uint AlignUp(uint value, int size)
{
return (uint)AlignUp((int)value, size);
@ -76,60 +75,7 @@ namespace Ryujinx.Common
public static int Pow2RoundDown(int value)
{
return IsPowerOfTwo32(value) ? value : Pow2RoundUp(value) >> 1;
}
public static bool IsPowerOfTwo32(int value)
{
return value != 0 && (value & (value - 1)) == 0;
}
public static bool IsPowerOfTwo64(long value)
{
return value != 0 && (value & (value - 1)) == 0;
}
public static int CountLeadingZeros32(int value)
{
return (int)CountLeadingZeros((ulong)value, 32);
}
public static int CountLeadingZeros64(long value)
{
return (int)CountLeadingZeros((ulong)value, 64);
}
private static ulong CountLeadingZeros(ulong value, int size)
{
if (value == 0ul)
{
return (ulong)size;
}
int nibbleIdx = size;
int preCount, count = 0;
do
{
nibbleIdx -= 4;
preCount = ClzNibbleTbl[(int)(value >> nibbleIdx) & 0b1111];
count += preCount;
}
while (preCount == 4);
return (ulong)count;
}
public static int CountTrailingZeros32(int value)
{
int count = 0;
while (((value >> count) & 1) == 0)
{
count++;
}
return count;
return BitOperations.IsPow2(value) ? value : Pow2RoundUp(value) >> 1;
}
public static long ReverseBits64(long value)

View File

@ -28,5 +28,10 @@ namespace Ryujinx.Cpu
{
_translator.Execute(context, address);
}
public void InvalidateCacheRegion(ulong address, ulong size)
{
_translator.InvalidateJitCacheRegion(address, size);
}
}
}

View File

@ -1,11 +1,15 @@
using Ryujinx.Graphics.Device;
using Ryujinx.Common;
using Ryujinx.Graphics.Device;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Texture;
using Ryujinx.Memory;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
namespace Ryujinx.Graphics.Gpu.Engine.Twod
{
@ -44,6 +48,180 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
/// <param name="data">Data to be written</param>
public void Write(int offset, int data) => _state.Write(offset, data);
/// <summary>
/// Determines if data is compatible between the source and destination texture.
/// The two textures must have the same size, layout, and bytes per pixel.
/// </summary>
/// <param name="lhs">Info for the first texture</param>
/// <param name="rhs">Info for the second texture</param>
/// <param name="lhsFormat">Format of the first texture</param>
/// <param name="rhsFormat">Format of the second texture</param>
/// <returns>True if the data is compatible, false otherwise</returns>
private bool IsDataCompatible(TwodTexture lhs, TwodTexture rhs, FormatInfo lhsFormat, FormatInfo rhsFormat)
{
if (lhsFormat.BytesPerPixel != rhsFormat.BytesPerPixel ||
lhs.Height != rhs.Height ||
lhs.Depth != rhs.Depth ||
lhs.LinearLayout != rhs.LinearLayout ||
lhs.MemoryLayout.Packed != rhs.MemoryLayout.Packed)
{
return false;
}
if (lhs.LinearLayout)
{
return lhs.Stride == rhs.Stride;
}
else
{
return lhs.Width == rhs.Width;
}
}
/// <summary>
/// Determine if the given region covers the full texture, also considering width alignment.
/// </summary>
/// <param name="texture">The texture to check</param>
/// <param name="formatInfo"></param>
/// <param name="x1">Region start x</param>
/// <param name="y1">Region start y</param>
/// <param name="x2">Region end x</param>
/// <param name="y2">Region end y</param>
/// <returns>True if the region covers the full texture, false otherwise</returns>
private bool IsCopyRegionComplete(TwodTexture texture, FormatInfo formatInfo, int x1, int y1, int x2, int y2)
{
if (x1 != 0 || y1 != 0 || y2 != texture.Height)
{
return false;
}
int width;
int widthAlignment;
if (texture.LinearLayout)
{
widthAlignment = 1;
width = texture.Stride / formatInfo.BytesPerPixel;
}
else
{
widthAlignment = Constants.GobAlignment / formatInfo.BytesPerPixel;
width = texture.Width;
}
return width == BitUtils.AlignUp(x2, widthAlignment);
}
/// <summary>
/// Performs a full data copy between two textures, reading and writing guest memory directly.
/// The textures must have a matching layout, size, and bytes per pixel.
/// </summary>
/// <param name="src">The source texture</param>
/// <param name="dst">The destination texture</param>
/// <param name="w">Copy width</param>
/// <param name="h">Copy height</param>
/// <param name="bpp">Bytes per pixel</param>
private void UnscaledFullCopy(TwodTexture src, TwodTexture dst, int w, int h, int bpp)
{
var srcCalculator = new OffsetCalculator(
w,
h,
src.Stride,
src.LinearLayout,
src.MemoryLayout.UnpackGobBlocksInY(),
src.MemoryLayout.UnpackGobBlocksInZ(),
bpp);
(int _, int srcSize) = srcCalculator.GetRectangleRange(0, 0, w, h);
var memoryManager = _channel.MemoryManager;
ulong srcGpuVa = src.Address.Pack();
ulong dstGpuVa = dst.Address.Pack();
ReadOnlySpan<byte> srcSpan = memoryManager.GetSpan(srcGpuVa, srcSize, true);
int width;
int height = src.Height;
if (src.LinearLayout)
{
width = src.Stride / bpp;
}
else
{
width = src.Width;
}
// If the copy is not equal to the width and height of the texture, we will need to copy partially.
// It's worth noting that it has already been established that the src and dst are the same size.
if (w == width && h == height)
{
memoryManager.Write(dstGpuVa, srcSpan);
}
else
{
using WritableRegion dstRegion = memoryManager.GetWritableRegion(dstGpuVa, srcSize, true);
Span<byte> dstSpan = dstRegion.Memory.Span;
if (src.LinearLayout)
{
int stride = src.Stride;
int offset = 0;
int lineSize = width * bpp;
for (int y = 0; y < height; y++)
{
srcSpan.Slice(offset, lineSize).CopyTo(dstSpan.Slice(offset));
offset += stride;
}
}
else
{
// Copy with the block linear layout in mind.
// Recreate the offset calculate with bpp 1 for copy.
int stride = w * bpp;
srcCalculator = new OffsetCalculator(
stride,
h,
0,
false,
src.MemoryLayout.UnpackGobBlocksInY(),
src.MemoryLayout.UnpackGobBlocksInZ(),
1);
int strideTrunc = BitUtils.AlignDown(stride, 16);
ReadOnlySpan<Vector128<byte>> srcVec = MemoryMarshal.Cast<byte, Vector128<byte>>(srcSpan);
Span<Vector128<byte>> dstVec = MemoryMarshal.Cast<byte, Vector128<byte>>(dstSpan);
for (int y = 0; y < h; y++)
{
int x = 0;
srcCalculator.SetY(y);
for (; x < strideTrunc; x += 16)
{
int offset = srcCalculator.GetOffset(x) >> 4;
dstVec[offset] = srcVec[offset];
}
for (; x < stride; x++)
{
int offset = srcCalculator.GetOffset(x);
dstSpan[offset] = srcSpan[offset];
}
}
}
}
}
/// <summary>
/// Performs the blit operation, triggered by the register write.
/// </summary>
@ -114,16 +292,31 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
srcX1 = 0;
}
FormatInfo dstCopyTextureFormat = dstCopyTexture.Format.Convert();
bool canDirectCopy = GraphicsConfig.Fast2DCopy &&
srcX2 == dstX2 && srcY2 == dstY2 &&
IsDataCompatible(srcCopyTexture, dstCopyTexture, srcCopyTextureFormat, dstCopyTextureFormat) &&
IsCopyRegionComplete(srcCopyTexture, srcCopyTextureFormat, srcX1, srcY1, srcX2, srcY2) &&
IsCopyRegionComplete(dstCopyTexture, dstCopyTextureFormat, dstX1, dstY1, dstX2, dstY2);
var srcTexture = memoryManager.Physical.TextureCache.FindOrCreateTexture(
memoryManager,
srcCopyTexture,
offset,
srcCopyTextureFormat,
!canDirectCopy,
false,
srcHint);
if (srcTexture == null)
{
if (canDirectCopy)
{
// Directly copy the data on CPU.
UnscaledFullCopy(srcCopyTexture, dstCopyTexture, srcX2, srcY2, srcCopyTextureFormat.BytesPerPixel);
}
return;
}
@ -132,7 +325,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
// When the source texture that was found has a depth format,
// we must enforce the target texture also has a depth format,
// as copies between depth and color formats are not allowed.
FormatInfo dstCopyTextureFormat;
if (srcTexture.Format.IsDepthOrStencil())
{
@ -148,6 +340,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
dstCopyTexture,
0,
dstCopyTextureFormat,
true,
srcTexture.ScaleMode == TextureScaleMode.Scaled,
dstHint);

View File

@ -28,6 +28,14 @@ namespace Ryujinx.Graphics.Gpu
/// </summary>
public static bool FastGpuTime = true;
/// <summary>
/// Enables or disables fast 2d engine texture copies entirely on CPU when possible.
/// Reduces stuttering and # of textures in games that copy textures around for streaming,
/// as textures will not need to be created for the copy, and the data does not need to be
/// flushed from GPU.
/// </summary>
public static bool Fast2DCopy = true;
/// <summary>
/// Enables or disables the Just-in-Time compiler for GPU Macro code.
/// </summary>

View File

@ -194,6 +194,7 @@ namespace Ryujinx.Graphics.Gpu.Image
TwodTexture copyTexture,
ulong offset,
FormatInfo formatInfo,
bool shouldCreate,
bool preferScaling = true,
Size? sizeHint = null)
{
@ -234,6 +235,11 @@ namespace Ryujinx.Graphics.Gpu.Image
flags |= TextureSearchFlags.WithUpscale;
}
if (!shouldCreate)
{
flags |= TextureSearchFlags.NoCreate;
}
Texture texture = FindOrCreateTexture(memoryManager, flags, info, 0, sizeHint);
texture?.SynchronizeMemory();
@ -480,6 +486,10 @@ namespace Ryujinx.Graphics.Gpu.Image
return texture;
}
else if (flags.HasFlag(TextureSearchFlags.NoCreate))
{
return null;
}
// Calculate texture sizes, used to find all overlapping textures.
SizeInfo sizeInfo = info.CalculateSizeInfo(layerSize);

View File

@ -12,6 +12,7 @@ namespace Ryujinx.Graphics.Gpu.Image
Strict = 1 << 0,
ForSampler = 1 << 1,
ForCopy = 1 << 2,
WithUpscale = 1 << 3
WithUpscale = 1 << 3,
NoCreate = 1 << 4
}
}

View File

@ -154,14 +154,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary>
/// Gets a writable region from GPU mapped memory.
/// </summary>
/// <param name="address">Start address of the range</param>
/// <param name="va">Start address of the range</param>
/// <param name="size">Size in bytes to be range</param>
/// <param name="tracked">True if write tracking is triggered on the span</param>
/// <returns>A writable region with the data at the specified memory location</returns>
public WritableRegion GetWritableRegion(ulong va, int size)
public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
{
if (IsContiguous(va, size))
{
return Physical.GetWritableRegion(Translate(va), size);
return Physical.GetWritableRegion(Translate(va), size, tracked);
}
else
{
@ -169,7 +170,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
GetSpan(va, size).CopyTo(memory.Span);
return new WritableRegion(this, va, memory);
return new WritableRegion(this, va, memory, tracked);
}
}

View File

@ -113,7 +113,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
dataSpan[i++] = hash;
}
manifestHeader.UpdateChecksum(data.AsSpan().Slice(Unsafe.SizeOf<CacheManifestHeader>()));
manifestHeader.UpdateChecksum(data.AsSpan(Unsafe.SizeOf<CacheManifestHeader>()));
MemoryMarshal.Write(data, ref manifestHeader);
@ -447,12 +447,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
if (cb1DataAddress != 0 && cb1DataSize != 0)
{
memoryManager.Physical.GetSpan(cb1DataAddress, cb1DataSize).CopyTo(code.AsSpan().Slice(size, cb1DataSize));
memoryManager.Physical.GetSpan(cb1DataAddress, cb1DataSize).CopyTo(code.AsSpan(size, cb1DataSize));
}
if (translatorContext2 != null)
{
memoryManager.GetSpan(translatorContext2.Address, sizeA).CopyTo(code.AsSpan().Slice(size + cb1DataSize, sizeA));
memoryManager.GetSpan(translatorContext2.Address, sizeA).CopyTo(code.AsSpan(size + cb1DataSize, sizeA));
}
GuestGpuAccessorHeader gpuAccessorHeader = CreateGuestGpuAccessorCache(context.GpuAccessor);

View File

@ -40,7 +40,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <summary>
/// Version of the codegen (to be changed when codegen or guest format change).
/// </summary>
private const ulong ShaderCodeGenVersion = 3063;
private const ulong ShaderCodeGenVersion = 3132;
// Progress reporting helpers
private volatile int _shaderCount;
@ -206,7 +206,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
program = new ShaderProgram(entry.Header.Stage, "");
shaderProgramInfo = hostShaderEntries[0].ToShaderProgramInfo();
byte[] code = entry.Code.AsSpan().Slice(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
ShaderCodeHolder shader = new ShaderCodeHolder(program, shaderProgramInfo, code);
@ -244,7 +244,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
return true; // Exit early, the decoding step failed.
}
byte[] code = entry.Code.AsSpan().Slice(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
ShaderCodeHolder shader = new ShaderCodeHolder(program, shaderProgramInfo, code);
@ -394,8 +394,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
// NOTE: Vertex B comes first in the shader cache.
byte[] code = entry.Code.AsSpan().Slice(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
byte[] code2 = entry.Header.SizeA != 0 ? entry.Code.AsSpan().Slice(entry.Header.Size, entry.Header.SizeA).ToArray() : null;
byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
byte[] code2 = entry.Header.SizeA != 0 ? entry.Code.AsSpan(entry.Header.Size, entry.Header.SizeA).ToArray() : null;
shaders[i] = new ShaderCodeHolder(program, shaderProgramInfo, code, code2);

View File

@ -124,7 +124,7 @@ namespace Ryujinx.Graphics.OpenGL
GL.GetProgramBinary(Handle, size, out _, out BinaryFormat binFormat, data);
BinaryPrimitives.WriteInt32LittleEndian(data.AsSpan().Slice(size, 4), (int)binFormat);
BinaryPrimitives.WriteInt32LittleEndian(data.AsSpan(size, 4), (int)binFormat);
return data;
}

View File

@ -369,7 +369,7 @@ namespace Ryujinx.Graphics.Shader.Translation
inst &= Instruction.Mask;
bool isImage = inst == Instruction.ImageLoad || inst == Instruction.ImageStore || inst == Instruction.ImageAtomic;
bool isWrite = inst == Instruction.ImageStore || inst == Instruction.ImageAtomic;
bool accurateType = inst != Instruction.Lod;
bool accurateType = inst != Instruction.Lod && inst != Instruction.TextureSize;
bool coherent = flags.HasFlag(TextureFlags.Coherent);
if (isImage)

View File

@ -1,4 +1,5 @@
using Ryujinx.Common;
using System.Numerics;
using System.Runtime.CompilerServices;
using static Ryujinx.Graphics.Texture.BlockLinearConstants;
@ -47,15 +48,15 @@ namespace Ryujinx.Graphics.Texture
{
_texBpp = bpp;
_bppShift = BitUtils.CountTrailingZeros32(bpp);
_bppShift = BitOperations.TrailingZeroCount(bpp);
_bhMask = gobBlocksInY - 1;
_bdMask = gobBlocksInZ - 1;
_bhShift = BitUtils.CountTrailingZeros32(gobBlocksInY);
_bdShift = BitUtils.CountTrailingZeros32(gobBlocksInZ);
_bhShift = BitOperations.TrailingZeroCount(gobBlocksInY);
_bdShift = BitOperations.TrailingZeroCount(gobBlocksInZ);
_xShift = BitUtils.CountTrailingZeros32(GobSize * gobBlocksInY * gobBlocksInZ);
_xShift = BitOperations.TrailingZeroCount(GobSize * gobBlocksInY * gobBlocksInZ);
RobAndSliceSizes rsSizes = GetRobAndSliceSizes(width, height, gobBlocksInY, gobBlocksInZ);

View File

@ -36,6 +36,11 @@ namespace Ryujinx.HLE.HOS
_cpuContext.Execute(context, codeAddress);
}
public void InvalidateCacheRegion(ulong address, ulong size)
{
_cpuContext.InvalidateCacheRegion(address, size);
}
public void Dispose()
{
if (_memoryManager is IRefCounted rc)

View File

@ -76,6 +76,7 @@ namespace Ryujinx.HLE.HOS
internal ServerBase BsdServer { get; private set; }
internal ServerBase AudRenServer { get; private set; }
internal ServerBase AudOutServer { get; private set; }
internal ServerBase FsServer { get; private set; }
internal ServerBase HidServer { get; private set; }
internal ServerBase NvDrvServer { get; private set; }
internal ServerBase TimeServer { get; private set; }
@ -298,6 +299,7 @@ namespace Ryujinx.HLE.HOS
BsdServer = new ServerBase(KernelContext, "BsdServer");
AudRenServer = new ServerBase(KernelContext, "AudioRendererServer");
AudOutServer = new ServerBase(KernelContext, "AudioOutServer");
FsServer = new ServerBase(KernelContext, "FsServer");
HidServer = new ServerBase(KernelContext, "HidServer");
NvDrvServer = new ServerBase(KernelContext, "NvservicesServer");
TimeServer = new ServerBase(KernelContext, "TimeServer");

View File

@ -1,4 +1,5 @@
using Ryujinx.Common;
using System.Numerics;
namespace Ryujinx.HLE.HOS.Kernel.Common
{
@ -41,10 +42,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
range++;
// This is log2(Range) plus one.
int nextRangeLog2 = 64 - BitUtils.CountLeadingZeros64(range);
int nextRangeLog2 = 64 - BitOperations.LeadingZeroCount((ulong)range);
// If Range is already power of 2, subtract one to use log2(Range) directly.
int rangeLog2 = nextRangeLog2 - (BitUtils.IsPowerOfTwo64(range) ? 1 : 0);
int rangeLog2 = nextRangeLog2 - (BitOperations.IsPow2(range) ? 1 : 0);
int parts = rangeLog2 > 32 ? 2 : 1;
int bitsPerPart = rangeLog2 / parts;

View File

@ -1,6 +1,7 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Kernel.Common;
using System.Diagnostics;
using System.Numerics;
namespace Ryujinx.HLE.HOS.Kernel.Memory
{
@ -259,11 +260,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
if (backwards)
{
index = (index * 64 + 63) - BitUtils.CountLeadingZeros64(mask);
index = (index * 64 + 63) - BitOperations.LeadingZeroCount((ulong)mask);
}
else
{
index = index * 64 + BitUtils.CountLeadingZeros64(BitUtils.ReverseBits64(mask));
index = index * 64 + BitOperations.LeadingZeroCount((ulong)BitUtils.ReverseBits64(mask));
}
}
@ -312,11 +313,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
if (backwards)
{
index = index * 64 + BitUtils.CountLeadingZeros64(BitUtils.ReverseBits64(mask));
index = index * 64 + BitOperations.LeadingZeroCount((ulong)BitUtils.ReverseBits64(mask));
}
else
{
index = (index * 64 + 63) - BitUtils.CountLeadingZeros64(mask);
index = (index * 64 + 63) - BitOperations.LeadingZeroCount((ulong)mask);
}
}

View File

@ -9,5 +9,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
IVirtualMemoryManager AddressSpace { get; }
void Execute(ExecutionContext context, ulong codeAddress);
void InvalidateCacheRegion(ulong address, ulong size);
}
}

View File

@ -1,5 +1,6 @@
using Ryujinx.Common;
using System;
using System.Numerics;
namespace Ryujinx.HLE.HOS.Kernel.Process
{
@ -32,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
int mask = _idMasks[index];
int firstFreeBit = BitUtils.CountLeadingZeros32((mask + 1) & ~mask);
int firstFreeBit = BitOperations.LeadingZeroCount((uint)((mask + 1) & ~mask));
if (firstFreeBit < 32)
{

View File

@ -3,6 +3,7 @@ using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Threading;
using System;
using System.Numerics;
namespace Ryujinx.HLE.HOS.Kernel.Process
{
@ -130,7 +131,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return KernelResult.Success;
}
int codeMask = 1 << (32 - BitUtils.CountLeadingZeros32(code + 1));
int codeMask = 1 << (32 - BitOperations.LeadingZeroCount((uint)code + 1));
// Check if the property was already set.
if (((mask0 & codeMask) & 0x1e008) != 0)

View File

@ -18,6 +18,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
throw new NotSupportedException();
}
public void InvalidateCacheRegion(ulong address, ulong size)
{
}
public void Dispose()
{
}

View File

@ -7,6 +7,8 @@ namespace Ryujinx.HLE.HOS.Services
{
private int _disposeState;
public DisposableIpcService(ServerBase server = null) : base(server) { }
protected abstract void Dispose(bool isDisposing);
public void Dispose()

View File

@ -28,7 +28,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
{
private SharedRef<LibHac.FsSrv.Sf.IFileSystemProxy> _baseFileSystemProxy;
public IFileSystemProxy(ServiceCtx context)
public IFileSystemProxy(ServiceCtx context) : base(context.Device.System.FsServer)
{
var applicationClient = context.Device.System.LibHacHorizonManager.ApplicationClient;
_baseFileSystemProxy = applicationClient.Fs.Impl.GetFileSystemProxyServiceObject();

View File

@ -185,7 +185,7 @@ namespace Ryujinx.HLE.HOS.Services
{
for (int i = 0; i < request.RecvListBuff.Count; i++)
{
ulong size = (ulong)BinaryPrimitives.ReadInt16LittleEndian(request.RawData.AsSpan().Slice(sizesOffset + i * 2, 2));
ulong size = (ulong)BinaryPrimitives.ReadInt16LittleEndian(request.RawData.AsSpan(sizesOffset + i * 2, 2));
response.PtrBuff.Add(new IpcPtrBuffDesc(tempAddr, (uint)i, size));

View File

@ -8,9 +8,9 @@ namespace Ryujinx.HLE.Loaders.Executables
class KipExecutable : IExecutable
{
public byte[] Program { get; }
public Span<byte> Text => Program.AsSpan().Slice((int)TextOffset, (int)TextSize);
public Span<byte> Ro => Program.AsSpan().Slice((int)RoOffset, (int)RoSize);
public Span<byte> Data => Program.AsSpan().Slice((int)DataOffset, (int)DataSize);
public Span<byte> Text => Program.AsSpan((int)TextOffset, (int)TextSize);
public Span<byte> Ro => Program.AsSpan((int)RoOffset, (int)RoSize);
public Span<byte> Data => Program.AsSpan((int)DataOffset, (int)DataSize);
public uint TextOffset { get; }
public uint RoOffset { get; }
@ -76,7 +76,7 @@ namespace Ryujinx.HLE.Loaders.Executables
{
reader.GetSegmentSize(segmentType, out int uncompressedSize).ThrowIfFailure();
var span = program.AsSpan().Slice((int)offset, uncompressedSize);
var span = program.AsSpan((int)offset, uncompressedSize);
reader.ReadSegment(segmentType, span).ThrowIfFailure();

View File

@ -7,9 +7,9 @@ namespace Ryujinx.HLE.Loaders.Executables
class NroExecutable : Nro, IExecutable
{
public byte[] Program { get; }
public Span<byte> Text => Program.AsSpan().Slice((int)TextOffset, (int)Header.NroSegments[0].Size);
public Span<byte> Ro => Program.AsSpan().Slice((int)RoOffset, (int)Header.NroSegments[1].Size);
public Span<byte> Data => Program.AsSpan().Slice((int)DataOffset, (int)Header.NroSegments[2].Size);
public Span<byte> Text => Program.AsSpan((int)TextOffset, (int)Header.NroSegments[0].Size);
public Span<byte> Ro => Program.AsSpan((int)RoOffset, (int)Header.NroSegments[1].Size);
public Span<byte> Data => Program.AsSpan((int)DataOffset, (int)Header.NroSegments[2].Size);
public uint TextOffset => Header.NroSegments[0].FileOffset;
public uint RoOffset => Header.NroSegments[1].FileOffset;

View File

@ -12,9 +12,9 @@ namespace Ryujinx.HLE.Loaders.Executables
class NsoExecutable : IExecutable
{
public byte[] Program { get; }
public Span<byte> Text => Program.AsSpan().Slice((int)TextOffset, (int)TextSize);
public Span<byte> Ro => Program.AsSpan().Slice((int)RoOffset, (int)RoSize);
public Span<byte> Data => Program.AsSpan().Slice((int)DataOffset, (int)DataSize);
public Span<byte> Text => Program.AsSpan((int)TextOffset, (int)TextSize);
public Span<byte> Ro => Program.AsSpan((int)RoOffset, (int)RoSize);
public Span<byte> Data => Program.AsSpan((int)DataOffset, (int)DataSize);
public uint TextOffset { get; }
public uint RoOffset { get; }
@ -58,7 +58,7 @@ namespace Ryujinx.HLE.Loaders.Executables
{
reader.GetSegmentSize(segmentType, out uint uncompressedSize).ThrowIfFailure();
var span = Program.AsSpan().Slice((int)offset, (int)uncompressedSize);
var span = Program.AsSpan((int)offset, (int)uncompressedSize);
reader.ReadSegment(segmentType, span).ThrowIfFailure();

View File

@ -85,7 +85,7 @@ namespace Ryujinx.HLE.Loaders.Mods
Logger.Info?.Print(LogClass.ModLoader, $"Patching address offset {patchOffset:x} <= {BitConverter.ToString(patch).Replace('-', ' ')} len={patchSize}");
patch.AsSpan().Slice(0, patchSize).CopyTo(memory.Slice(patchOffset, patchSize));
patch.AsSpan(0, patchSize).CopyTo(memory.Slice(patchOffset, patchSize));
count++;
}

View File

@ -242,15 +242,18 @@ namespace Ryujinx.Ui.App
}
else
{
// Store the ControlFS in variable called controlFs
GetControlFsAndTitleId(pfs, out IFileSystem controlFs, out titleId);
// Check if there is an update available.
if (IsUpdateApplied(titleId, out IFileSystem updatedControlFs))
{
// Replace the original ControlFs by the updated one.
controlFs = updatedControlFs;
}
ReadControlData(controlFs, controlHolder.ByteSpan);
// Get the title name, title ID, developer name and version number from the NACP
version = IsUpdateApplied(titleId, out string updateVersion) ? updateVersion : controlHolder.Value.DisplayVersion.ToString();
GetNameIdDeveloper(ref controlHolder.Value, out titleName, out _, out developer);
GetGameInformation(ref controlHolder.Value, out titleName, out _, out developer, out version);
// Read the icon from the ControlFS and store it as a byte array
try
@ -351,10 +354,7 @@ namespace Ryujinx.Ui.App
// Read the NACP data
Read(assetOffset + (int)nacpOffset, (int)nacpSize).AsSpan().CopyTo(controlHolder.ByteSpan);
// Get the title name, title ID, developer name and version number from the NACP
version = controlHolder.Value.DisplayVersion.ToString();
GetNameIdDeveloper(ref controlHolder.Value, out titleName, out titleId, out developer);
GetGameInformation(ref controlHolder.Value, out titleName, out titleId, out developer, out version);
}
else
{
@ -554,7 +554,7 @@ namespace Ryujinx.Ui.App
return readableString;
}
private void GetNameIdDeveloper(ref ApplicationControlProperty controlData, out string titleName, out string titleId, out string publisher)
private void GetGameInformation(ref ApplicationControlProperty controlData, out string titleName, out string titleId, out string publisher, out string version)
{
_ = Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
@ -611,10 +611,14 @@ namespace Ryujinx.Ui.App
{
titleId = "0000000000000000";
}
version = controlData.DisplayVersion.ToString();
}
private bool IsUpdateApplied(string titleId, out string version)
private bool IsUpdateApplied(string titleId, out IFileSystem updatedControlFs)
{
updatedControlFs = null;
string updatePath = "(unknown)";
try
@ -623,14 +627,7 @@ namespace Ryujinx.Ui.App
if (patchNca != null && controlNca != null)
{
ApplicationControlProperty controlData = new ApplicationControlProperty();
using var nacpFile = new UniqueRef<IFile>();
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
version = controlData.DisplayVersion.ToString();
updatedControlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
return true;
}
@ -645,8 +642,6 @@ namespace Ryujinx.Ui.App
Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}. Errored File: {updatePath}");
}
version = "";
return false;
}
}