Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1fc90e57d2 |
@@ -28,5 +28,10 @@ namespace Ryujinx.Cpu.Tracking
|
||||
public void Reprotect(bool asDirty = false) => _impl.Reprotect(asDirty);
|
||||
|
||||
public bool OverlapsWith(ulong address, ulong size) => _impl.OverlapsWith(address, size);
|
||||
|
||||
public bool RangeEquals(CpuRegionHandle other)
|
||||
{
|
||||
return _impl.RealAddress == other._impl.RealAddress && _impl.RealSize == other._impl.RealSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -20,5 +20,15 @@ namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
return target == Target.Texture2DMultisample || target == Target.Texture2DMultisampleArray;
|
||||
}
|
||||
|
||||
public static bool HasDepthOrLayers(this Target target)
|
||||
{
|
||||
return target == Target.Texture3D ||
|
||||
target == Target.Texture1DArray ||
|
||||
target == Target.Texture2DArray ||
|
||||
target == Target.Texture2DMultisampleArray ||
|
||||
target == Target.Cubemap ||
|
||||
target == Target.CubemapArray;
|
||||
}
|
||||
}
|
||||
}
|
@@ -36,6 +36,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
public TexturePool Pool;
|
||||
public int ID;
|
||||
public ulong GpuAddress;
|
||||
}
|
||||
|
||||
private GpuContext _context;
|
||||
@@ -162,6 +163,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
public bool IsView => _viewStorage != this;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not this texture has views.
|
||||
/// </summary>
|
||||
public bool HasViews => _views.Count > 0;
|
||||
|
||||
private int _referenceCount;
|
||||
private List<TexturePoolOwner> _poolOwners;
|
||||
|
||||
@@ -383,6 +389,17 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
DecrementReferenceCount();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the texture's physical memory range. This forces tracking to regenerate.
|
||||
/// </summary>
|
||||
/// <param name="range">New physical memory range backing the texture</param>
|
||||
public void ReplaceRange(MultiRange range)
|
||||
{
|
||||
Range = range;
|
||||
|
||||
Group.RangeChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a copy dependency to a texture that is view compatible with this one.
|
||||
/// When either texture is modified, the texture data will be copied to the other to keep them in sync.
|
||||
@@ -715,6 +732,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
height = Math.Max(height >> level, 1);
|
||||
depth = Math.Max(depth >> level, 1);
|
||||
|
||||
int sliceDepth = single ? 1 : depth;
|
||||
|
||||
SpanOrArray<byte> result;
|
||||
|
||||
if (Info.IsLinear)
|
||||
@@ -735,7 +754,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
width,
|
||||
height,
|
||||
depth,
|
||||
single ? 1 : depth,
|
||||
sliceDepth,
|
||||
levels,
|
||||
layers,
|
||||
Info.FormatInfo.BlockWidth,
|
||||
@@ -759,7 +778,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
Info.FormatInfo.BlockHeight,
|
||||
width,
|
||||
height,
|
||||
depth,
|
||||
sliceDepth,
|
||||
levels,
|
||||
layers,
|
||||
out byte[] decoded))
|
||||
@@ -771,7 +790,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
if (GraphicsConfig.EnableTextureRecompression)
|
||||
{
|
||||
decoded = BCnEncoder.EncodeBC7(decoded, width, height, depth, levels, layers);
|
||||
decoded = BCnEncoder.EncodeBC7(decoded, width, height, sliceDepth, levels, layers);
|
||||
}
|
||||
|
||||
result = decoded;
|
||||
@@ -782,15 +801,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
case Format.Etc2RgbaSrgb:
|
||||
case Format.Etc2RgbaUnorm:
|
||||
result = ETC2Decoder.DecodeRgba(result, width, height, depth, levels, layers);
|
||||
result = ETC2Decoder.DecodeRgba(result, width, height, sliceDepth, levels, layers);
|
||||
break;
|
||||
case Format.Etc2RgbPtaSrgb:
|
||||
case Format.Etc2RgbPtaUnorm:
|
||||
result = ETC2Decoder.DecodePta(result, width, height, depth, levels, layers);
|
||||
result = ETC2Decoder.DecodePta(result, width, height, sliceDepth, levels, layers);
|
||||
break;
|
||||
case Format.Etc2RgbSrgb:
|
||||
case Format.Etc2RgbUnorm:
|
||||
result = ETC2Decoder.DecodeRgb(result, width, height, depth, levels, layers);
|
||||
result = ETC2Decoder.DecodeRgb(result, width, height, sliceDepth, levels, layers);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -800,31 +819,31 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
case Format.Bc1RgbaSrgb:
|
||||
case Format.Bc1RgbaUnorm:
|
||||
result = BCnDecoder.DecodeBC1(result, width, height, depth, levels, layers);
|
||||
result = BCnDecoder.DecodeBC1(result, width, height, sliceDepth, levels, layers);
|
||||
break;
|
||||
case Format.Bc2Srgb:
|
||||
case Format.Bc2Unorm:
|
||||
result = BCnDecoder.DecodeBC2(result, width, height, depth, levels, layers);
|
||||
result = BCnDecoder.DecodeBC2(result, width, height, sliceDepth, levels, layers);
|
||||
break;
|
||||
case Format.Bc3Srgb:
|
||||
case Format.Bc3Unorm:
|
||||
result = BCnDecoder.DecodeBC3(result, width, height, depth, levels, layers);
|
||||
result = BCnDecoder.DecodeBC3(result, width, height, sliceDepth, levels, layers);
|
||||
break;
|
||||
case Format.Bc4Snorm:
|
||||
case Format.Bc4Unorm:
|
||||
result = BCnDecoder.DecodeBC4(result, width, height, depth, levels, layers, Format == Format.Bc4Snorm);
|
||||
result = BCnDecoder.DecodeBC4(result, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm);
|
||||
break;
|
||||
case Format.Bc5Snorm:
|
||||
case Format.Bc5Unorm:
|
||||
result = BCnDecoder.DecodeBC5(result, width, height, depth, levels, layers, Format == Format.Bc5Snorm);
|
||||
result = BCnDecoder.DecodeBC5(result, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm);
|
||||
break;
|
||||
case Format.Bc6HSfloat:
|
||||
case Format.Bc6HUfloat:
|
||||
result = BCnDecoder.DecodeBC6(result, width, height, depth, levels, layers, Format == Format.Bc6HSfloat);
|
||||
result = BCnDecoder.DecodeBC6(result, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat);
|
||||
break;
|
||||
case Format.Bc7Srgb:
|
||||
case Format.Bc7Unorm:
|
||||
result = BCnDecoder.DecodeBC7(result, width, height, depth, levels, layers);
|
||||
result = BCnDecoder.DecodeBC7(result, width, height, sliceDepth, levels, layers);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1484,11 +1503,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
/// <param name="pool">The texture pool this texture has been added to</param>
|
||||
/// <param name="id">The ID of the reference to this texture in the pool</param>
|
||||
public void IncrementReferenceCount(TexturePool pool, int id)
|
||||
/// <param name="gpuVa">GPU VA of the pool reference</param>
|
||||
public void IncrementReferenceCount(TexturePool pool, int id, ulong gpuVa)
|
||||
{
|
||||
lock (_poolOwners)
|
||||
{
|
||||
_poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id });
|
||||
_poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id, GpuAddress = gpuVa });
|
||||
}
|
||||
_referenceCount++;
|
||||
|
||||
@@ -1585,6 +1605,36 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
InvalidatedSequence++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue updating texture mappings on the pool. Happens from another thread.
|
||||
/// </summary>
|
||||
public void UpdatePoolMappings()
|
||||
{
|
||||
lock (_poolOwners)
|
||||
{
|
||||
ulong address = 0;
|
||||
|
||||
foreach (var owner in _poolOwners)
|
||||
{
|
||||
if (address == 0 || address == owner.GpuAddress)
|
||||
{
|
||||
address = owner.GpuAddress;
|
||||
|
||||
owner.Pool.QueueUpdateMapping(this, owner.ID);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If there is a different GPU VA mapping, prefer the first and delete the others.
|
||||
owner.Pool.ForceRemove(this, owner.ID, true);
|
||||
}
|
||||
}
|
||||
|
||||
_poolOwners.Clear();
|
||||
}
|
||||
|
||||
InvalidatedSequence++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete the texture if it is not used anymore.
|
||||
/// The texture is considered unused when the reference count is zero,
|
||||
@@ -1636,7 +1686,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
Group.ClearModified(unmapRange);
|
||||
}
|
||||
|
||||
RemoveFromPools(true);
|
||||
UpdatePoolMappings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -194,6 +194,39 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
_cache.Lift(texture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to update a texture's physical memory range.
|
||||
/// Returns false if there is an existing texture that matches with the updated range.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture to update</param>
|
||||
/// <param name="range">New physical memory range</param>
|
||||
/// <returns>True if the mapping was updated, false otherwise</returns>
|
||||
public bool UpdateMapping(Texture texture, MultiRange range)
|
||||
{
|
||||
// There cannot be an existing texture compatible with this mapping in the texture cache already.
|
||||
int overlapCount = _textures.FindOverlaps(range, ref _textureOverlaps);
|
||||
|
||||
for (int i = 0; i < overlapCount; i++)
|
||||
{
|
||||
var other = _textureOverlaps[i];
|
||||
|
||||
if (texture != other &&
|
||||
(texture.IsViewCompatible(other.Info, other.Range, true, other.LayerSize, _context.Capabilities, out _, out _) != TextureViewCompatibility.Incompatible ||
|
||||
other.IsViewCompatible(texture.Info, texture.Range, true, texture.LayerSize, _context.Capabilities, out _, out _) != TextureViewCompatibility.Incompatible))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_textures.Remove(texture);
|
||||
|
||||
texture.ReplaceRange(range);
|
||||
|
||||
_textures.Add(texture);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find an existing texture, or create a new one if not found.
|
||||
/// </summary>
|
||||
|
@@ -39,6 +39,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
class TextureGroup : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Threshold of layers to force granular handles (and thus partial loading) on array/3D textures.
|
||||
/// </summary>
|
||||
private const int GranularLayerThreshold = 8;
|
||||
|
||||
private delegate void HandlesCallbackDelegate(int baseHandle, int regionCount, bool split = false);
|
||||
|
||||
/// <summary>
|
||||
@@ -116,7 +121,29 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
_allOffsets = size.AllOffsets;
|
||||
_sliceSizes = size.SliceSizes;
|
||||
|
||||
(_hasLayerViews, _hasMipViews) = PropagateGranularity(hasLayerViews, hasMipViews);
|
||||
if (Storage.Target.HasDepthOrLayers() && Storage.Info.GetSlices() > GranularLayerThreshold)
|
||||
{
|
||||
_hasLayerViews = true;
|
||||
_hasMipViews = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
(_hasLayerViews, _hasMipViews) = PropagateGranularity(hasLayerViews, hasMipViews);
|
||||
|
||||
// If the texture is partially mapped, fully subdivide handles immediately.
|
||||
|
||||
MultiRange range = Storage.Range;
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
{
|
||||
if (range.GetSubRange(i).Address == MemoryManager.PteUnmapped)
|
||||
{
|
||||
_hasLayerViews = true;
|
||||
_hasMipViews = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RecalculateHandleRegions();
|
||||
}
|
||||
@@ -249,7 +276,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
bool dirty = false;
|
||||
bool anyModified = false;
|
||||
bool anyUnmapped = false;
|
||||
bool anyNotDirty = false;
|
||||
|
||||
for (int i = 0; i < regionCount; i++)
|
||||
{
|
||||
@@ -294,20 +321,21 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
dirty |= handleDirty;
|
||||
}
|
||||
|
||||
anyUnmapped |= handleUnmapped;
|
||||
|
||||
if (group.NeedsCopy)
|
||||
{
|
||||
// The texture we copied from is still being written to. Copy from it again the next time this texture is used.
|
||||
texture.SignalGroupDirty();
|
||||
}
|
||||
|
||||
_loadNeeded[baseHandle + i] = handleDirty && !handleUnmapped;
|
||||
bool loadNeeded = handleDirty && !handleUnmapped;
|
||||
|
||||
anyNotDirty |= !loadNeeded;
|
||||
_loadNeeded[baseHandle + i] = loadNeeded;
|
||||
}
|
||||
|
||||
if (dirty)
|
||||
{
|
||||
if (anyUnmapped || (_handles.Length > 1 && (anyModified || split)))
|
||||
if (anyNotDirty || (_handles.Length > 1 && (anyModified || split)))
|
||||
{
|
||||
// Partial texture invalidation. Only update the layers/levels with dirty flags of the storage.
|
||||
|
||||
@@ -331,24 +359,56 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="regionCount">The number of handles to synchronize</param>
|
||||
private void SynchronizePartial(int baseHandle, int regionCount)
|
||||
{
|
||||
int spanEndIndex = -1;
|
||||
int spanBase = 0;
|
||||
ReadOnlySpan<byte> dataSpan = ReadOnlySpan<byte>.Empty;
|
||||
|
||||
for (int i = 0; i < regionCount; i++)
|
||||
{
|
||||
if (_loadNeeded[baseHandle + i])
|
||||
{
|
||||
var info = GetHandleInformation(baseHandle + i);
|
||||
|
||||
// Ensure the data for this handle is loaded in the span.
|
||||
if (spanEndIndex <= i - 1)
|
||||
{
|
||||
spanEndIndex = i;
|
||||
|
||||
if (_is3D)
|
||||
{
|
||||
// Look ahead to see how many handles need to be loaded.
|
||||
for (int j = i + 1; j < regionCount; j++)
|
||||
{
|
||||
if (_loadNeeded[baseHandle + j])
|
||||
{
|
||||
spanEndIndex = j;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var endInfo = spanEndIndex == i ? info : GetHandleInformation(baseHandle + spanEndIndex);
|
||||
|
||||
spanBase = _allOffsets[info.Index];
|
||||
int spanLast = _allOffsets[endInfo.Index + endInfo.Layers * endInfo.Levels - 1];
|
||||
int endOffset = Math.Min(spanLast + _sliceSizes[endInfo.BaseLevel + endInfo.Levels - 1], (int)Storage.Size);
|
||||
int size = endOffset - spanBase;
|
||||
|
||||
dataSpan = _physicalMemory.GetSpan(Storage.Range.GetSlice((ulong)spanBase, (ulong)size));
|
||||
}
|
||||
|
||||
// Only one of these will be greater than 1, as partial sync is only called when there are sub-image views.
|
||||
for (int layer = 0; layer < info.Layers; layer++)
|
||||
{
|
||||
for (int level = 0; level < info.Levels; level++)
|
||||
{
|
||||
int offsetIndex = GetOffsetIndex(info.BaseLayer + layer, info.BaseLevel + level);
|
||||
|
||||
int offset = _allOffsets[offsetIndex];
|
||||
int endOffset = Math.Min(offset + _sliceSizes[info.BaseLevel + level], (int)Storage.Size);
|
||||
int size = endOffset - offset;
|
||||
|
||||
ReadOnlySpan<byte> data = _physicalMemory.GetSpan(Storage.Range.GetSlice((ulong)offset, (ulong)size));
|
||||
ReadOnlySpan<byte> data = dataSpan.Slice(offset - spanBase);
|
||||
|
||||
SpanOrArray<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true);
|
||||
|
||||
@@ -865,8 +925,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <returns>A TextureGroupHandle covering the given views</returns>
|
||||
private TextureGroupHandle GenerateHandles(int viewStart, int views)
|
||||
{
|
||||
int viewEnd = viewStart + views - 1;
|
||||
(_, int lastLevel) = GetLayerLevelForView(viewEnd);
|
||||
|
||||
int offset = _allOffsets[viewStart];
|
||||
int endOffset = (viewStart + views == _allOffsets.Length) ? (int)Storage.Size : _allOffsets[viewStart + views];
|
||||
int endOffset = _allOffsets[viewEnd] + _sliceSizes[lastLevel];
|
||||
int size = endOffset - offset;
|
||||
|
||||
var result = new List<CpuRegionHandle>();
|
||||
@@ -1057,7 +1120,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// The dirty flags from the previous handles will be kept.
|
||||
/// </summary>
|
||||
/// <param name="handles">The handles to replace the current handles with</param>
|
||||
private void ReplaceHandles(TextureGroupHandle[] handles)
|
||||
/// <param name="rangeChanged">True if the storage memory range changed since the last region handle generation</param>
|
||||
private void ReplaceHandles(TextureGroupHandle[] handles, bool rangeChanged)
|
||||
{
|
||||
if (_handles != null)
|
||||
{
|
||||
@@ -1065,9 +1129,50 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
foreach (TextureGroupHandle groupHandle in handles)
|
||||
{
|
||||
foreach (CpuRegionHandle handle in groupHandle.Handles)
|
||||
if (rangeChanged)
|
||||
{
|
||||
handle.Reprotect();
|
||||
// When the storage range changes, this becomes a little different.
|
||||
// If a range does not match one in the original, treat it as modified.
|
||||
// It has been newly mapped and its data must be synchronized.
|
||||
|
||||
if (groupHandle.Handles.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var oldGroup in _handles)
|
||||
{
|
||||
if (!groupHandle.OverlapsWith(oldGroup.Offset, oldGroup.Size))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (CpuRegionHandle handle in groupHandle.Handles)
|
||||
{
|
||||
bool hasMatch = false;
|
||||
|
||||
foreach (var oldHandle in oldGroup.Handles)
|
||||
{
|
||||
if (oldHandle.RangeEquals(handle))
|
||||
{
|
||||
hasMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasMatch)
|
||||
{
|
||||
handle.Reprotect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (CpuRegionHandle handle in groupHandle.Handles)
|
||||
{
|
||||
handle.Reprotect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1089,7 +1194,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <summary>
|
||||
/// Recalculate handle regions for this texture group, and inherit existing state into the new handles.
|
||||
/// </summary>
|
||||
private void RecalculateHandleRegions()
|
||||
/// <param name="rangeChanged">True if the storage memory range changed since the last region handle generation</param>
|
||||
private void RecalculateHandleRegions(bool rangeChanged = false)
|
||||
{
|
||||
TextureGroupHandle[] handles;
|
||||
|
||||
@@ -1171,7 +1277,21 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
}
|
||||
|
||||
ReplaceHandles(handles);
|
||||
ReplaceHandles(handles, rangeChanged);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Regenerates handles when the storage range has been remapped.
|
||||
/// This forces the regions to be fully subdivided.
|
||||
/// </summary>
|
||||
public void RangeChanged()
|
||||
{
|
||||
_hasLayerViews = true;
|
||||
_hasMipViews = true;
|
||||
|
||||
RecalculateHandleRegions(true);
|
||||
|
||||
SignalAllDirty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -1,9 +1,12 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
@@ -12,8 +15,63 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
class TexturePool : Pool<Texture, TextureDescriptor>, IPool<TexturePool>
|
||||
{
|
||||
/// <summary>
|
||||
/// A request to dereference a texture from a pool.
|
||||
/// </summary>
|
||||
private struct DereferenceRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the dereference is due to a mapping change or not.
|
||||
/// </summary>
|
||||
public readonly bool IsRemapped;
|
||||
|
||||
/// <summary>
|
||||
/// The texture being dereferenced.
|
||||
/// </summary>
|
||||
public readonly Texture Texture;
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the pool entry this reference belonged to.
|
||||
/// </summary>
|
||||
public readonly int ID;
|
||||
|
||||
/// <summary>
|
||||
/// Create a dereference request for a texture with a specific pool ID, and remapped flag.
|
||||
/// </summary>
|
||||
/// <param name="isRemapped">Whether the dereference is due to a mapping change or not</param>
|
||||
/// <param name="texture">The texture being dereferenced</param>
|
||||
/// <param name="id">The ID of the pool entry, used to restore remapped textures</param>
|
||||
private DereferenceRequest(bool isRemapped, Texture texture, int id)
|
||||
{
|
||||
IsRemapped = isRemapped;
|
||||
Texture = texture;
|
||||
ID = id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a dereference request for a texture removal.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture being removed</param>
|
||||
/// <returns>A texture removal dereference request</returns>
|
||||
public static DereferenceRequest Remove(Texture texture)
|
||||
{
|
||||
return new DereferenceRequest(false, texture, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a dereference request for a texture remapping with a specific pool ID.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture being remapped</param>
|
||||
/// <param name="id">The ID of the pool entry, used to restore remapped textures</param>
|
||||
/// <returns>A remap dereference request</returns>
|
||||
public static DereferenceRequest Remap(Texture texture, int id)
|
||||
{
|
||||
return new DereferenceRequest(true, texture, id);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly GpuChannel _channel;
|
||||
private readonly ConcurrentQueue<Texture> _dereferenceQueue = new ConcurrentQueue<Texture>();
|
||||
private readonly ConcurrentQueue<DereferenceRequest> _dereferenceQueue = new ConcurrentQueue<DereferenceRequest>();
|
||||
private TextureDescriptor _defaultDescriptor;
|
||||
|
||||
/// <summary>
|
||||
@@ -58,7 +116,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
TextureInfo info = GetInfo(descriptor, out int layerSize);
|
||||
|
||||
ProcessDereferenceQueue();
|
||||
// The dereference queue can put our texture back on the cache.
|
||||
if ((texture = ProcessDereferenceQueue(id)) != null)
|
||||
{
|
||||
return ref descriptor;
|
||||
}
|
||||
|
||||
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
|
||||
|
||||
@@ -69,10 +131,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
}
|
||||
|
||||
texture.IncrementReferenceCount(this, id);
|
||||
|
||||
Items[id] = texture;
|
||||
|
||||
texture.IncrementReferenceCount(this, id, descriptor.UnpackAddress());
|
||||
|
||||
DescriptorCache[id] = descriptor;
|
||||
}
|
||||
else
|
||||
@@ -155,11 +217,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="deferred">If true, queue the dereference to happen on the render thread, otherwise dereference immediately</param>
|
||||
public void ForceRemove(Texture texture, int id, bool deferred)
|
||||
{
|
||||
Items[id] = null;
|
||||
var previous = Interlocked.Exchange(ref Items[id], null);
|
||||
|
||||
if (deferred)
|
||||
{
|
||||
_dereferenceQueue.Enqueue(texture);
|
||||
if (previous != null)
|
||||
{
|
||||
_dereferenceQueue.Enqueue(DereferenceRequest.Remove(texture));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -167,16 +232,91 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queues a request to update a texture's mapping.
|
||||
/// Mapping is updated later to avoid deleting the texture if it is still sparsely mapped.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture with potential mapping change</param>
|
||||
/// <param name="id">ID in cache of texture with potential mapping change</param>
|
||||
public void QueueUpdateMapping(Texture texture, int id)
|
||||
{
|
||||
if (Interlocked.Exchange(ref Items[id], null) == texture)
|
||||
{
|
||||
_dereferenceQueue.Enqueue(DereferenceRequest.Remap(texture, id));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the dereference queue, decrementing the reference count for each texture in it.
|
||||
/// This is used to ensure that texture disposal happens on the render thread.
|
||||
/// </summary>
|
||||
private void ProcessDereferenceQueue()
|
||||
/// <param name="id">The ID of the entry that triggered this method</param>
|
||||
/// <returns>Texture that matches the entry ID if it has been readded to the cache.</returns>
|
||||
private Texture ProcessDereferenceQueue(int id = -1)
|
||||
{
|
||||
while (_dereferenceQueue.TryDequeue(out Texture toRemove))
|
||||
while (_dereferenceQueue.TryDequeue(out DereferenceRequest request))
|
||||
{
|
||||
toRemove.DecrementReferenceCount();
|
||||
Texture texture = request.Texture;
|
||||
|
||||
// Unmapped storage textures can swap their ranges. The texture must be storage with no views or dependencies.
|
||||
// TODO: Would need to update ranges on views, or guarantee that ones where the range changes can be instantly deleted.
|
||||
|
||||
if (request.IsRemapped && texture.Group.Storage == texture && !texture.HasViews && !texture.Group.HasCopyDependencies)
|
||||
{
|
||||
// Has the mapping for this texture changed?
|
||||
ref readonly TextureDescriptor descriptor = ref GetDescriptorRef(request.ID);
|
||||
|
||||
ulong address = descriptor.UnpackAddress();
|
||||
|
||||
MultiRange range = _channel.MemoryManager.GetPhysicalRegions(address, texture.Size);
|
||||
|
||||
// If the texture is not mapped at all, delete its reference.
|
||||
|
||||
if (range.Count == 1 && range.GetSubRange(0).Address == MemoryManager.PteUnmapped)
|
||||
{
|
||||
texture.DecrementReferenceCount();
|
||||
continue;
|
||||
}
|
||||
|
||||
Items[request.ID] = texture;
|
||||
|
||||
// Create a new pool reference, as the last one was removed on unmap.
|
||||
|
||||
texture.IncrementReferenceCount(this, request.ID, address);
|
||||
texture.DecrementReferenceCount();
|
||||
|
||||
// Refetch the range. Changes since the last check could have been lost
|
||||
// as the cache entry was not restored (required to queue mapping change).
|
||||
|
||||
range = _channel.MemoryManager.GetPhysicalRegions(address, texture.Size);
|
||||
|
||||
if (!range.Equals(texture.Range))
|
||||
{
|
||||
// Part of the texture was mapped or unmapped. Replace the range and regenerate tracking handles.
|
||||
if (!_channel.MemoryManager.Physical.TextureCache.UpdateMapping(texture, range))
|
||||
{
|
||||
// Texture could not be remapped due to a collision, just delete it.
|
||||
if (Interlocked.Exchange(ref Items[request.ID], null) != null)
|
||||
{
|
||||
// If this is null, a request was already queued to decrement reference.
|
||||
texture.DecrementReferenceCount(this, request.ID);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (request.ID == id)
|
||||
{
|
||||
return texture;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
texture.DecrementReferenceCount();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -213,9 +353,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
_channel.MemoryManager.Physical.TextureCache.AddShortCache(texture, ref cachedDescriptor);
|
||||
}
|
||||
|
||||
texture.DecrementReferenceCount(this, id);
|
||||
|
||||
Items[id] = null;
|
||||
if (Interlocked.Exchange(ref Items[id], null) != null)
|
||||
{
|
||||
texture.DecrementReferenceCount(this, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -112,7 +112,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
int outSize = GetTextureSize(
|
||||
width,
|
||||
height,
|
||||
depth,
|
||||
sliceDepth,
|
||||
levels,
|
||||
layers,
|
||||
blockWidth,
|
||||
|
@@ -8,6 +8,8 @@ namespace Ryujinx.Memory.Range
|
||||
/// </summary>
|
||||
public readonly struct MultiRange : IEquatable<MultiRange>
|
||||
{
|
||||
private const ulong InvalidAddress = ulong.MaxValue;
|
||||
|
||||
private readonly MemoryRange _singleRange;
|
||||
private readonly MemoryRange[] _ranges;
|
||||
|
||||
@@ -107,7 +109,16 @@ namespace Ryujinx.Memory.Range
|
||||
else if (offset < range.Size)
|
||||
{
|
||||
ulong sliceSize = Math.Min(size, range.Size - offset);
|
||||
ranges.Add(new MemoryRange(range.Address + offset, sliceSize));
|
||||
|
||||
if (range.Address == InvalidAddress)
|
||||
{
|
||||
ranges.Add(new MemoryRange(range.Address, sliceSize));
|
||||
}
|
||||
else
|
||||
{
|
||||
ranges.Add(new MemoryRange(range.Address + offset, sliceSize));
|
||||
}
|
||||
|
||||
size -= sliceSize;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user