GPU: Fix errors handling texture remapping (#4745)
* GPU: Fix errors handling texture remapping - Fixes an error where a pool entry and memory mapping changing at the same time could cause a texture to rebind its data from the wrong GPU VA (data swaps) - Fixes an error where the texture pool could act on a mapping change before the mapping has actually been changed ("Unmapped" event happens before change, we need to signal it changed _after_ it completes) TODO: remove textures from partially mapped list... if they aren't. * Add Remap actions for handling post-mapping behaviours * Remove unused code. * Address feedback * Nit
This commit is contained in:
parent
680e548022
commit
36f10df775
7 changed files with 135 additions and 19 deletions
|
@ -1610,6 +1610,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void UpdatePoolMappings()
|
public void UpdatePoolMappings()
|
||||||
{
|
{
|
||||||
|
ChangedMapping = true;
|
||||||
|
|
||||||
lock (_poolOwners)
|
lock (_poolOwners)
|
||||||
{
|
{
|
||||||
ulong address = 0;
|
ulong address = 0;
|
||||||
|
@ -1685,8 +1687,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
Group.ClearModified(unmapRange);
|
Group.ClearModified(unmapRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdatePoolMappings();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -64,7 +64,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles removal of textures written to a memory region being unmapped.
|
/// Handles marking of textures written to a memory region being (partially) remapped.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">Sender object</param>
|
/// <param name="sender">Sender object</param>
|
||||||
/// <param name="e">Event arguments</param>
|
/// <param name="e">Event arguments</param>
|
||||||
|
@ -80,26 +80,41 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
overlapCount = _textures.FindOverlaps(unmapped, ref overlaps);
|
overlapCount = _textures.FindOverlaps(unmapped, ref overlaps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (overlapCount > 0)
|
||||||
|
{
|
||||||
for (int i = 0; i < overlapCount; i++)
|
for (int i = 0; i < overlapCount; i++)
|
||||||
{
|
{
|
||||||
overlaps[i].Unmapped(unmapped);
|
overlaps[i].Unmapped(unmapped);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If any range was previously unmapped, we also need to purge
|
lock (_partiallyMappedTextures)
|
||||||
// all partially mapped texture, as they might be fully mapped now.
|
|
||||||
for (int i = 0; i < unmapped.Count; i++)
|
|
||||||
{
|
{
|
||||||
if (unmapped.GetSubRange(i).Address == MemoryManager.PteUnmapped)
|
if (overlapCount > 0 || _partiallyMappedTextures.Count > 0)
|
||||||
|
{
|
||||||
|
e.AddRemapAction(() =>
|
||||||
{
|
{
|
||||||
lock (_partiallyMappedTextures)
|
lock (_partiallyMappedTextures)
|
||||||
{
|
{
|
||||||
foreach (var texture in _partiallyMappedTextures)
|
if (overlapCount > 0)
|
||||||
{
|
{
|
||||||
texture.Unmapped(unmapped);
|
for (int i = 0; i < overlapCount; i++)
|
||||||
|
{
|
||||||
|
_partiallyMappedTextures.Add(overlaps[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
// Any texture that has been unmapped at any point or is partially unmapped
|
||||||
|
// should update their pool references after the remap completes.
|
||||||
|
|
||||||
|
MultiRange unmapped = ((MemoryManager)sender).GetPhysicalRegions(e.Address, e.Size);
|
||||||
|
|
||||||
|
foreach (var texture in _partiallyMappedTextures)
|
||||||
|
{
|
||||||
|
texture.UpdatePoolMappings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1135,6 +1150,44 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queries a texture's memory range and marks it as partially mapped or not.
|
||||||
|
/// Partially mapped textures re-evaluate their memory range after each time GPU memory is mapped.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="memoryManager">GPU memory manager where the texture is mapped</param>
|
||||||
|
/// <param name="address">The virtual address of the texture</param>
|
||||||
|
/// <param name="texture">The texture to be marked</param>
|
||||||
|
/// <returns>The physical regions for the texture, found when evaluating whether the texture was partially mapped</returns>
|
||||||
|
public MultiRange UpdatePartiallyMapped(MemoryManager memoryManager, ulong address, Texture texture)
|
||||||
|
{
|
||||||
|
MultiRange range;
|
||||||
|
lock (_partiallyMappedTextures)
|
||||||
|
{
|
||||||
|
range = memoryManager.GetPhysicalRegions(address, texture.Size);
|
||||||
|
bool partiallyMapped = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < range.Count; i++)
|
||||||
|
{
|
||||||
|
if (range.GetSubRange(i).Address == MemoryManager.PteUnmapped)
|
||||||
|
{
|
||||||
|
partiallyMapped = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (partiallyMapped)
|
||||||
|
{
|
||||||
|
_partiallyMappedTextures.Add(texture);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_partiallyMappedTextures.Remove(texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a texture to the short duration cache. This typically keeps it alive for two ticks.
|
/// Adds a texture to the short duration cache. This typically keeps it alive for two ticks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -272,7 +272,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
|
|
||||||
ulong address = descriptor.UnpackAddress();
|
ulong address = descriptor.UnpackAddress();
|
||||||
|
|
||||||
MultiRange range = _channel.MemoryManager.GetPhysicalRegions(address, texture.Size);
|
if (!descriptor.Equals(ref DescriptorCache[request.ID]))
|
||||||
|
{
|
||||||
|
// If the pool entry has already been replaced, just remove the texture.
|
||||||
|
|
||||||
|
texture.DecrementReferenceCount();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiRange range = _channel.MemoryManager.Physical.TextureCache.UpdatePartiallyMapped(_channel.MemoryManager, address, texture);
|
||||||
|
|
||||||
// If the texture is not mapped at all, delete its reference.
|
// If the texture is not mapped at all, delete its reference.
|
||||||
|
|
||||||
|
|
|
@ -365,6 +365,22 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs remap actions that are added to an unmap event.
|
||||||
|
/// These must run after the mapping completes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="e">Event with remap actions</param>
|
||||||
|
private void RunRemapActions(UnmapEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.RemapActions != null)
|
||||||
|
{
|
||||||
|
foreach (Action action in e.RemapActions)
|
||||||
|
{
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maps a given range of pages to the specified CPU virtual address.
|
/// Maps a given range of pages to the specified CPU virtual address.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -379,12 +395,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
{
|
{
|
||||||
lock (_pageTable)
|
lock (_pageTable)
|
||||||
{
|
{
|
||||||
MemoryUnmapped?.Invoke(this, new UnmapEventArgs(va, size));
|
UnmapEventArgs e = new(va, size);
|
||||||
|
MemoryUnmapped?.Invoke(this, e);
|
||||||
|
|
||||||
for (ulong offset = 0; offset < size; offset += PageSize)
|
for (ulong offset = 0; offset < size; offset += PageSize)
|
||||||
{
|
{
|
||||||
SetPte(va + offset, PackPte(pa + offset, kind));
|
SetPte(va + offset, PackPte(pa + offset, kind));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RunRemapActions(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,12 +417,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
lock (_pageTable)
|
lock (_pageTable)
|
||||||
{
|
{
|
||||||
// Event handlers are not expected to be thread safe.
|
// Event handlers are not expected to be thread safe.
|
||||||
MemoryUnmapped?.Invoke(this, new UnmapEventArgs(va, size));
|
UnmapEventArgs e = new(va, size);
|
||||||
|
MemoryUnmapped?.Invoke(this, e);
|
||||||
|
|
||||||
for (ulong offset = 0; offset < size; offset += PageSize)
|
for (ulong offset = 0; offset < size; offset += PageSize)
|
||||||
{
|
{
|
||||||
SetPte(va + offset, PteUnmapped);
|
SetPte(va + offset, PteUnmapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RunRemapActions(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,24 @@
|
||||||
namespace Ryujinx.Graphics.Gpu.Memory
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
{
|
{
|
||||||
public class UnmapEventArgs
|
public class UnmapEventArgs
|
||||||
{
|
{
|
||||||
public ulong Address { get; }
|
public ulong Address { get; }
|
||||||
public ulong Size { get; }
|
public ulong Size { get; }
|
||||||
|
public List<Action> RemapActions { get; private set; }
|
||||||
|
|
||||||
public UnmapEventArgs(ulong address, ulong size)
|
public UnmapEventArgs(ulong address, ulong size)
|
||||||
{
|
{
|
||||||
Address = address;
|
Address = address;
|
||||||
Size = size;
|
Size = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddRemapAction(Action action)
|
||||||
|
{
|
||||||
|
RemapActions ??= new List<Action>();
|
||||||
|
RemapActions.Add(action);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,5 +57,19 @@
|
||||||
|
|
||||||
return thisAddress < otherEndAddress && otherAddress < thisEndAddress;
|
return thisAddress < otherEndAddress && otherAddress < thisEndAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a string summary of the memory range.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A string summary of the memory range</returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
if (Address == ulong.MaxValue)
|
||||||
|
{
|
||||||
|
return $"[Unmapped 0x{Size:X}]";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"[0x{Address:X}, 0x{EndAddress:X})";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -319,5 +319,14 @@ namespace Ryujinx.Memory.Range
|
||||||
|
|
||||||
return hash.ToHashCode();
|
return hash.ToHashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a string summary of the ranges contained in the MultiRange.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A string summary of the ranges contained within</returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return HasSingleRange ? _singleRange.ToString() : string.Join(", ", _ranges);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue