diff --git a/Ryujinx.Cpu/MemoryManager.cs b/Ryujinx.Cpu/MemoryManager.cs
index 75ecca1c..abbeee5f 100644
--- a/Ryujinx.Cpu/MemoryManager.cs
+++ b/Ryujinx.Cpu/MemoryManager.cs
@@ -145,10 +145,36 @@ namespace Ryujinx.Cpu
return;
}
+ MarkRegionAsModified(va, (ulong)data.Length);
+
+ WriteImpl(va, data);
+ }
+
+ ///
+ /// Writes data to CPU mapped memory, without tracking.
+ ///
+ /// Virtual address to write the data into
+ /// Data to be written
+ public void WriteUntracked(ulong va, ReadOnlySpan data)
+ {
+ if (data.Length == 0)
+ {
+ return;
+ }
+
+ WriteImpl(va, data);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ ///
+ /// Writes data to CPU mapped memory.
+ ///
+ /// Virtual address to write the data into
+ /// Data to be written
+ private void WriteImpl(ulong va, ReadOnlySpan data)
+ {
try
{
- MarkRegionAsModified(va, (ulong)data.Length);
-
if (IsContiguousAndMapped(va, data.Length))
{
data.CopyTo(_backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length));
diff --git a/Ryujinx.Graphics.GAL/IRenderer.cs b/Ryujinx.Graphics.GAL/IRenderer.cs
index fec8d3be..73fafe49 100644
--- a/Ryujinx.Graphics.GAL/IRenderer.cs
+++ b/Ryujinx.Graphics.GAL/IRenderer.cs
@@ -29,6 +29,8 @@ namespace Ryujinx.Graphics.GAL
void UpdateCounters();
+ void PreFrame();
+
ICounterEvent ReportCounter(CounterType type, EventHandler resultHandler);
void ResetCounter(CounterType type);
diff --git a/Ryujinx.Graphics.GAL/ITexture.cs b/Ryujinx.Graphics.GAL/ITexture.cs
index 1c5b6ba5..543f9de0 100644
--- a/Ryujinx.Graphics.GAL/ITexture.cs
+++ b/Ryujinx.Graphics.GAL/ITexture.cs
@@ -2,7 +2,7 @@ using System;
namespace Ryujinx.Graphics.GAL
{
- public interface ITexture : IDisposable
+ public interface ITexture
{
int Width { get; }
int Height { get; }
@@ -17,5 +17,6 @@ namespace Ryujinx.Graphics.GAL
void SetData(ReadOnlySpan data);
void SetStorage(BufferRange buffer);
+ void Release();
}
}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/TextureCreateInfo.cs b/Ryujinx.Graphics.GAL/TextureCreateInfo.cs
index d74ac62d..eedf58a0 100644
--- a/Ryujinx.Graphics.GAL/TextureCreateInfo.cs
+++ b/Ryujinx.Graphics.GAL/TextureCreateInfo.cs
@@ -3,7 +3,7 @@ using System;
namespace Ryujinx.Graphics.GAL
{
- public struct TextureCreateInfo
+ public struct TextureCreateInfo : IEquatable
{
public int Width { get; }
public int Height { get; }
@@ -116,5 +116,29 @@ namespace Ryujinx.Graphics.GAL
{
return Math.Max(1, size >> level);
}
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Width, Height);
+ }
+
+ bool IEquatable.Equals(TextureCreateInfo other)
+ {
+ return Width == other.Width &&
+ Height == other.Height &&
+ Depth == other.Depth &&
+ Levels == other.Levels &&
+ Samples == other.Samples &&
+ BlockWidth == other.BlockWidth &&
+ BlockHeight == other.BlockHeight &&
+ BytesPerPixel == other.BytesPerPixel &&
+ Format == other.Format &&
+ DepthStencilMode == other.DepthStencilMode &&
+ Target == other.Target &&
+ SwizzleR == other.SwizzleR &&
+ SwizzleG == other.SwizzleG &&
+ SwizzleB == other.SwizzleB &&
+ SwizzleA == other.SwizzleA;
+ }
}
}
diff --git a/Ryujinx.Graphics.Gpu/Engine/Methods.cs b/Ryujinx.Graphics.Gpu/Engine/Methods.cs
index 9497b045..79ed3c90 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Methods.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Methods.cs
@@ -58,6 +58,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
TextureManager = new TextureManager(context);
context.MemoryManager.MemoryUnmapped += _counterCache.MemoryUnmappedHandler;
+ context.MemoryManager.MemoryUnmapped += TextureManager.MemoryUnmappedHandler;
}
///
diff --git a/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs b/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs
index d66eab93..634f9448 100644
--- a/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs
+++ b/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Common.Logging;
using System.Collections;
using System.Collections.Generic;
@@ -40,6 +41,16 @@ namespace Ryujinx.Graphics.Gpu.Image
{
Texture oldestTexture = _textures.First.Value;
+ oldestTexture.SynchronizeMemory();
+
+ if (oldestTexture.IsModified)
+ {
+ // The texture must be flushed if it falls out of the auto delete cache.
+ // Flushes out of the auto delete cache do not trigger write tracking,
+ // as it is expected that other overlapping textures exist that have more up-to-date contents.
+ oldestTexture.Flush(false);
+ }
+
_textures.RemoveFirst();
oldestTexture.DecrementReferenceCount();
@@ -74,6 +85,26 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
+ public bool Remove(Texture texture, bool flush)
+ {
+ if (texture.CacheNode == null)
+ {
+ return false;
+ }
+
+ // Remove our reference to this texture.
+ if (flush && texture.IsModified)
+ {
+ texture.Flush(false);
+ }
+
+ _textures.Remove(texture.CacheNode);
+
+ texture.CacheNode = null;
+
+ return texture.DecrementReferenceCount();
+ }
+
public IEnumerator GetEnumerator()
{
return _textures.GetEnumerator();
diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs
index 50b184d5..fe129f52 100644
--- a/Ryujinx.Graphics.Gpu/Image/Texture.cs
+++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -39,6 +39,13 @@ namespace Ryujinx.Graphics.Gpu.Image
///
public TextureScaleMode ScaleMode { get; private set; }
+ ///
+ /// Set when a texture has been modified since it was last flushed.
+ ///
+ public bool IsModified { get; internal set; }
+
+ private bool _everModified;
+
private int _depth;
private int _layers;
private int _firstLayer;
@@ -121,7 +128,7 @@ namespace Ryujinx.Graphics.Gpu.Image
ScaleFactor = scaleFactor;
ScaleMode = scaleMode;
- _hasData = true;
+ InitializeData(true);
}
///
@@ -137,16 +144,6 @@ namespace Ryujinx.Graphics.Gpu.Image
ScaleMode = scaleMode;
InitializeTexture(context, info, sizeInfo);
-
- TextureCreateInfo createInfo = TextureManager.GetCreateInfo(info, context.Capabilities);
-
- HostTexture = _context.Renderer.CreateTexture(createInfo, ScaleFactor);
-
- if (scaleMode == TextureScaleMode.Scaled)
- {
- SynchronizeMemory(); // Load the data and then scale it up.
- SetScale(GraphicsConfig.ResScale);
- }
}
///
@@ -171,6 +168,47 @@ namespace Ryujinx.Graphics.Gpu.Image
_views = new List();
}
+ ///
+ /// Initializes the data for a texture. Can optionally initialize the texture with or without data.
+ /// If the texture is a view, it will initialize memory tracking to be non-dirty.
+ ///
+ /// True if the texture is a view, false otherwise
+ /// True if the texture is to be initialized with data
+ public void InitializeData(bool isView, bool withData = false)
+ {
+ if (withData)
+ {
+ Debug.Assert(!isView);
+
+ TextureCreateInfo createInfo = TextureManager.GetCreateInfo(Info, _context.Capabilities);
+ HostTexture = _context.Renderer.CreateTexture(createInfo, ScaleFactor);
+
+ SynchronizeMemory(); // Load the data.
+ if (ScaleMode == TextureScaleMode.Scaled)
+ {
+ SetScale(GraphicsConfig.ResScale); // Scale the data up.
+ }
+ }
+ else
+ {
+ // Don't update this texture the next time we synchronize.
+ ConsumeModified();
+ _hasData = true;
+
+ if (!isView)
+ {
+ if (ScaleMode == TextureScaleMode.Scaled)
+ {
+ // Don't need to start at 1x as there is no data to scale, just go straight to the target scale.
+ ScaleFactor = GraphicsConfig.ResScale;
+ }
+
+ TextureCreateInfo createInfo = TextureManager.GetCreateInfo(Info, _context.Capabilities);
+ HostTexture = _context.Renderer.CreateTexture(createInfo, ScaleFactor);
+ }
+ }
+ }
+
///
/// Create a texture view from this texture.
/// A texture view is defined as a child texture, from a sub-range of their parent texture.
@@ -238,6 +276,9 @@ namespace Ryujinx.Graphics.Gpu.Image
/// The new texture depth (for 3D textures) or layers (for layered textures)
public void ChangeSize(int width, int height, int depthOrLayers)
{
+ int blockWidth = Info.FormatInfo.BlockWidth;
+ int blockHeight = Info.FormatInfo.BlockHeight;
+
width <<= _firstLevel;
height <<= _firstLevel;
@@ -250,7 +291,7 @@ namespace Ryujinx.Graphics.Gpu.Image
depthOrLayers = _viewStorage.Info.DepthOrLayers;
}
- _viewStorage.RecreateStorageOrView(width, height, depthOrLayers);
+ _viewStorage.RecreateStorageOrView(width, height, blockWidth, blockHeight, depthOrLayers);
foreach (Texture view in _viewStorage._views)
{
@@ -268,10 +309,28 @@ namespace Ryujinx.Graphics.Gpu.Image
viewDepthOrLayers = view.Info.DepthOrLayers;
}
- view.RecreateStorageOrView(viewWidth, viewHeight, viewDepthOrLayers);
+ view.RecreateStorageOrView(viewWidth, viewHeight, blockWidth, blockHeight, viewDepthOrLayers);
}
}
+ ///
+ /// Recreates the texture storage (or view, in the case of child textures) of this texture.
+ /// This allows recreating the texture with a new size.
+ /// A copy is automatically performed from the old to the new texture.
+ ///
+ /// The new texture width
+ /// The new texture height
+ /// The block width related to the given width
+ /// The block height related to the given height
+ /// The new texture depth (for 3D textures) or layers (for layered textures)
+ private void RecreateStorageOrView(int width, int height, int blockWidth, int blockHeight, int depthOrLayers)
+ {
+ RecreateStorageOrView(
+ BitUtils.DivRoundUp(width * Info.FormatInfo.BlockWidth, blockWidth),
+ BitUtils.DivRoundUp(height * Info.FormatInfo.BlockHeight, blockHeight),
+ depthOrLayers);
+ }
+
///
/// Recreates the texture storage (or view, in the case of child textures) of this texture.
/// This allows recreating the texture with a new size.
@@ -388,8 +447,8 @@ namespace Ryujinx.Graphics.Gpu.Image
from.CopyTo(to, new Extents2D(0, 0, from.Width, from.Height), new Extents2D(0, 0, to.Width, to.Height), true);
- from.Dispose();
- to.Dispose();
+ from.Release();
+ to.Release();
}
}
@@ -462,6 +521,16 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
+ ///
+ /// Checks if the memory for this texture was modified, and returns true if it was.
+ /// The modified flags are consumed as a result.
+ ///
+ /// True if the texture was modified, false otherwise.
+ public bool ConsumeModified()
+ {
+ return _context.PhysicalMemory.QueryModified(Address, Size, ResourceName.Texture, _modifiedRanges) > 0;
+ }
+
///
/// Synchronizes guest and host memory.
/// This will overwrite the texture data with the texture data on the guest memory, if a CPU
@@ -494,15 +563,15 @@ namespace Ryujinx.Graphics.Gpu.Image
ReadOnlySpan data = _context.PhysicalMemory.GetSpan(Address, (int)Size);
- // If the texture was modified by the host GPU, we do partial invalidation
+ // If the texture was ever modified by the host GPU, we do partial invalidation
// of the texture by getting GPU data and merging in the pages of memory
// that were modified.
// Note that if ASTC is not supported by the GPU we can't read it back since
// it will use a different format. Since applications shouldn't be writing
// ASTC textures from the GPU anyway, ignoring it should be safe.
- if (_context.Methods.TextureManager.IsTextureModified(this) && !Info.FormatInfo.Format.IsAstc())
+ if (_everModified && !Info.FormatInfo.Format.IsAstc())
{
- Span gpuData = GetTextureDataFromGpu();
+ Span gpuData = GetTextureDataFromGpu(true);
ulong endAddress = Address + Size;
@@ -533,6 +602,8 @@ namespace Ryujinx.Graphics.Gpu.Image
data = gpuData;
}
+ IsModified = false;
+
data = ConvertToHostCompatibleFormat(data);
HostTexture.SetData(data);
@@ -607,10 +678,24 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Be aware that this is an expensive operation, avoid calling it unless strictly needed.
/// This may cause data corruption if the memory is already being used for something else on the CPU side.
///
- public void Flush()
+ /// Whether or not the flush triggers write tracking. If it doesn't, the texture will not be blacklisted for scaling either.
+ public void Flush(bool tracked = true)
{
- BlacklistScale();
- _context.PhysicalMemory.Write(Address, GetTextureDataFromGpu());
+ IsModified = false;
+
+ if (Info.FormatInfo.Format.IsAstc())
+ {
+ return; // Flushing this format is not supported, as it may have been converted to another host format.
+ }
+
+ if (tracked)
+ {
+ _context.PhysicalMemory.Write(Address, GetTextureDataFromGpu(tracked));
+ }
+ else
+ {
+ _context.PhysicalMemory.WriteUntracked(Address, GetTextureDataFromGpu(tracked));
+ }
}
///
@@ -621,10 +706,26 @@ namespace Ryujinx.Graphics.Gpu.Image
/// This is not cheap, avoid doing that unless strictly needed.
///
/// Host texture data
- private Span GetTextureDataFromGpu()
+ private Span GetTextureDataFromGpu(bool blacklist)
{
- BlacklistScale();
- Span data = HostTexture.GetData();
+ Span data;
+
+ if (blacklist)
+ {
+ BlacklistScale();
+ data = HostTexture.GetData();
+ }
+ else if (ScaleFactor != 1f)
+ {
+ float scale = ScaleFactor;
+ SetScale(1f);
+ data = HostTexture.GetData();
+ SetScale(scale);
+ }
+ else
+ {
+ data = HostTexture.GetData();
+ }
if (Info.IsLinear)
{
@@ -713,31 +814,12 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Texture view size
/// Texture view initial layer on this texture
/// Texture view first mipmap level on this texture
- /// True if a view with the given parameters can be created from this texture, false otherwise
- public bool IsViewCompatible(
+ /// The level of compatiblilty a view with the given parameters created from this texture has
+ public TextureViewCompatibility IsViewCompatible(
TextureInfo info,
ulong size,
out int firstLayer,
out int firstLevel)
- {
- return IsViewCompatible(info, size, isCopy: false, out firstLayer, out firstLevel);
- }
-
- ///
- /// Check if it's possible to create a view, with the given parameters, from this texture.
- ///
- /// Texture view information
- /// Texture view size
- /// True to check for copy compability, instead of view compatibility
- /// Texture view initial layer on this texture
- /// Texture view first mipmap level on this texture
- /// True if a view with the given parameters can be created from this texture, false otherwise
- public bool IsViewCompatible(
- TextureInfo info,
- ulong size,
- bool isCopy,
- out int firstLayer,
- out int firstLevel)
{
// Out of range.
if (info.Address < Address || info.Address + size > EndAddress)
@@ -745,38 +827,46 @@ namespace Ryujinx.Graphics.Gpu.Image
firstLayer = 0;
firstLevel = 0;
- return false;
+ return TextureViewCompatibility.Incompatible;
}
int offset = (int)(info.Address - Address);
if (!_sizeInfo.FindView(offset, (int)size, out firstLayer, out firstLevel))
{
- return false;
+ return TextureViewCompatibility.Incompatible;
}
if (!TextureCompatibility.ViewLayoutCompatible(Info, info, firstLevel))
{
- return false;
+ return TextureViewCompatibility.Incompatible;
}
if (!TextureCompatibility.ViewFormatCompatible(Info, info))
{
- return false;
+ return TextureViewCompatibility.Incompatible;
}
- if (!TextureCompatibility.ViewSizeMatches(Info, info, firstLevel, isCopy))
- {
- return false;
- }
+ TextureViewCompatibility result = TextureViewCompatibility.Full;
- if (!TextureCompatibility.ViewTargetCompatible(Info, info, isCopy))
- {
- return false;
- }
+ result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewSizeMatches(Info, info, firstLevel));
+ result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info));
- return Info.SamplesInX == info.SamplesInX &&
- Info.SamplesInY == info.SamplesInY;
+ return (Info.SamplesInX == info.SamplesInX &&
+ Info.SamplesInY == info.SamplesInY) ? result : TextureViewCompatibility.Incompatible;
+ }
+
+ ///
+ /// Checks if the view format is compatible with this texture format.
+ /// In general, the formats are considered compatible if the bytes per pixel values are equal,
+ /// but there are more complex rules for some formats, like compressed or depth-stencil formats.
+ /// This follows the host API copy compatibility rules.
+ ///
+ /// Texture information of the texture view
+ /// True if the formats are compatible, false otherwise
+ private bool ViewFormatCompatible(TextureInfo info)
+ {
+ return TextureCompatibility.FormatCompatible(Info.FormatInfo, info.FormatInfo);
}
///
@@ -902,7 +992,15 @@ namespace Ryujinx.Graphics.Gpu.Image
///
public void SignalModified()
{
+ IsModified = true;
+ _everModified = true;
+
Modified?.Invoke(this);
+
+ if (_viewStorage != this)
+ {
+ _viewStorage.SignalModified();
+ }
}
///
@@ -927,6 +1025,29 @@ namespace Ryujinx.Graphics.Gpu.Image
return Address < address + size && address < EndAddress;
}
+ ///
+ /// Determine if any of our child textures are compaible as views of the given texture.
+ ///
+ /// The texture to check against
+ /// True if any child is view compatible, false otherwise
+ public bool HasViewCompatibleChild(Texture texture)
+ {
+ if (_viewStorage != this || _views.Count == 0)
+ {
+ return false;
+ }
+
+ foreach (Texture view in _views)
+ {
+ if (texture.IsViewCompatible(view.Info, view.Size, out int _, out int _) != TextureViewCompatibility.Incompatible)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
///
/// Increments the texture reference count.
///
@@ -939,7 +1060,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Decrements the texture reference count.
/// When the reference count hits zero, the texture may be deleted and can't be used anymore.
///
- public void DecrementReferenceCount()
+ /// True if the texture is now referenceless, false otherwise
+ public bool DecrementReferenceCount()
{
int newRefCount = --_referenceCount;
@@ -956,6 +1078,8 @@ namespace Ryujinx.Graphics.Gpu.Image
Debug.Assert(newRefCount >= 0);
DeleteIfNotUsed();
+
+ return newRefCount <= 0;
}
///
@@ -980,12 +1104,21 @@ namespace Ryujinx.Graphics.Gpu.Image
///
private void DisposeTextures()
{
- HostTexture.Dispose();
+ HostTexture.Release();
- _arrayViewTexture?.Dispose();
+ _arrayViewTexture?.Release();
_arrayViewTexture = null;
}
+ ///
+ /// Called when the memory for this texture has been unmapped.
+ /// Calls are from non-gpu threads.
+ ///
+ public void Unmapped()
+ {
+ IsModified = false; // We shouldn't flush this texture, as its memory is no longer mapped.
+ }
+
///
/// Performs texture disposal, deleting the texture.
///
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
index cc7b0dc2..e8e3c2c2 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
@@ -147,28 +147,51 @@ namespace Ryujinx.Graphics.Gpu.Image
}
///
- /// Checks if the view sizes of a two given texture informations match.
+ /// Obtain the minimum compatibility level of two provided view compatibility results.
+ ///
+ /// The first compatibility level
+ /// The second compatibility level
+ /// The minimum compatibility level of two provided view compatibility results
+ public static TextureViewCompatibility PropagateViewCompatibility(TextureViewCompatibility first, TextureViewCompatibility second)
+ {
+ if (first == TextureViewCompatibility.Incompatible || second == TextureViewCompatibility.Incompatible)
+ {
+ return TextureViewCompatibility.Incompatible;
+ }
+ else if (first == TextureViewCompatibility.CopyOnly || second == TextureViewCompatibility.CopyOnly)
+ {
+ return TextureViewCompatibility.CopyOnly;
+ }
+ else
+ {
+ return TextureViewCompatibility.Full;
+ }
+ }
+
+ ///
+ /// Checks if the sizes of two given textures are view compatible.
///
/// Texture information of the texture view
/// Texture information of the texture view to match against
/// Mipmap level of the texture view in relation to this texture
- /// True to check for copy compatibility rather than view compatibility
/// True if the sizes are compatible, false otherwise
- public static bool ViewSizeMatches(TextureInfo lhs, TextureInfo rhs, int level, bool isCopy)
+ public static TextureViewCompatibility ViewSizeMatches(TextureInfo lhs, TextureInfo rhs, int level)
{
Size size = GetAlignedSize(lhs, level);
Size otherSize = GetAlignedSize(rhs);
+ TextureViewCompatibility result = TextureViewCompatibility.Full;
+
// For copies, we can copy a subset of the 3D texture slices,
// so the depth may be different in this case.
- if (!isCopy && rhs.Target == Target.Texture3D && size.Depth != otherSize.Depth)
+ if (rhs.Target == Target.Texture3D && size.Depth != otherSize.Depth)
{
- return false;
+ result = TextureViewCompatibility.CopyOnly;
}
- return size.Width == otherSize.Width &&
- size.Height == otherSize.Height;
+ return (size.Width == otherSize.Width &&
+ size.Height == otherSize.Height) ? result : TextureViewCompatibility.Incompatible;
}
///
@@ -330,38 +353,48 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Texture information of the texture view
/// True to check for copy rather than view compatibility
/// True if the targets are compatible, false otherwise
- public static bool ViewTargetCompatible(TextureInfo lhs, TextureInfo rhs, bool isCopy)
+ public static TextureViewCompatibility ViewTargetCompatible(TextureInfo lhs, TextureInfo rhs)
{
+ bool result = false;
switch (lhs.Target)
{
case Target.Texture1D:
case Target.Texture1DArray:
- return rhs.Target == Target.Texture1D ||
- rhs.Target == Target.Texture1DArray;
+ result = rhs.Target == Target.Texture1D ||
+ rhs.Target == Target.Texture1DArray;
+ break;
case Target.Texture2D:
- return rhs.Target == Target.Texture2D ||
- rhs.Target == Target.Texture2DArray;
+ result = rhs.Target == Target.Texture2D ||
+ rhs.Target == Target.Texture2DArray;
+ break;
case Target.Texture2DArray:
case Target.Cubemap:
case Target.CubemapArray:
- return rhs.Target == Target.Texture2D ||
- rhs.Target == Target.Texture2DArray ||
- rhs.Target == Target.Cubemap ||
- rhs.Target == Target.CubemapArray;
+ result = rhs.Target == Target.Texture2D ||
+ rhs.Target == Target.Texture2DArray ||
+ rhs.Target == Target.Cubemap ||
+ rhs.Target == Target.CubemapArray;
+ break;
case Target.Texture2DMultisample:
case Target.Texture2DMultisampleArray:
- return rhs.Target == Target.Texture2DMultisample ||
- rhs.Target == Target.Texture2DMultisampleArray;
+ result = rhs.Target == Target.Texture2DMultisample ||
+ rhs.Target == Target.Texture2DMultisampleArray;
+ break;
case Target.Texture3D:
- return rhs.Target == Target.Texture3D ||
- (rhs.Target == Target.Texture2D && isCopy);
+ if (rhs.Target == Target.Texture2D)
+ {
+ return TextureViewCompatibility.CopyOnly;
+ }
+
+ result = rhs.Target == Target.Texture3D;
+ break;
}
- return false;
+ return result ? TextureViewCompatibility.Full : TextureViewCompatibility.Incompatible;
}
///
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
index cab76da1..0b1d38d1 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
@@ -14,6 +14,20 @@ namespace Ryujinx.Graphics.Gpu.Image
///
class TextureManager : IDisposable
{
+ private struct OverlapInfo
+ {
+ public TextureViewCompatibility Compatibility { get; }
+ public int FirstLayer { get; }
+ public int FirstLevel { get; }
+
+ public OverlapInfo(TextureViewCompatibility compatibility, int firstLayer, int firstLevel)
+ {
+ Compatibility = compatibility;
+ FirstLayer = firstLayer;
+ FirstLevel = firstLevel;
+ }
+ }
+
private const int OverlapsBufferInitialCapacity = 10;
private const int OverlapsBufferMaxCapacity = 10000;
@@ -33,6 +47,7 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly RangeList _textures;
private Texture[] _textureOverlaps;
+ private OverlapInfo[] _overlapInfo;
private readonly AutoDeleteCache _cache;
@@ -64,6 +79,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_textures = new RangeList();
_textureOverlaps = new Texture[OverlapsBufferInitialCapacity];
+ _overlapInfo = new OverlapInfo[OverlapsBufferInitialCapacity];
_cache = new AutoDeleteCache();
@@ -407,6 +423,27 @@ namespace Ryujinx.Graphics.Gpu.Image
return true;
}
+ ///
+ /// Handles removal of textures written to a memory region being unmapped.
+ ///
+ /// Sender object
+ /// Event arguments
+ public void MemoryUnmappedHandler(object sender, UnmapEventArgs e)
+ {
+ Texture[] overlaps = new Texture[10];
+ int overlapCount;
+
+ lock (_textures)
+ {
+ overlapCount = _textures.FindOverlaps(_context.MemoryManager.Translate(e.Address), e.Size, ref overlaps);
+ }
+
+ for (int i = 0; i < overlapCount; i++)
+ {
+ overlaps[i].Unmapped();
+ }
+ }
+
///
/// Tries to find an existing texture, or create a new one if not found.
///
@@ -618,8 +655,13 @@ namespace Ryujinx.Graphics.Gpu.Image
scaleMode = (flags & TextureSearchFlags.WithUpscale) != 0 ? TextureScaleMode.Scaled : TextureScaleMode.Eligible;
}
- // Try to find a perfect texture match, with the same address and parameters.
- int sameAddressOverlapsCount = _textures.FindOverlaps(info.Address, ref _textureOverlaps);
+ int sameAddressOverlapsCount;
+
+ lock (_textures)
+ {
+ // Try to find a perfect texture match, with the same address and parameters.
+ sameAddressOverlapsCount = _textures.FindOverlaps(info.Address, ref _textureOverlaps);
+ }
for (int index = 0; index < sameAddressOverlapsCount; index++)
{
@@ -681,8 +723,12 @@ namespace Ryujinx.Graphics.Gpu.Image
// Find view compatible matches.
ulong size = (ulong)sizeInfo.TotalSize;
+ int overlapsCount;
- int overlapsCount = _textures.FindOverlaps(info.Address, size, ref _textureOverlaps);
+ lock (_textures)
+ {
+ overlapsCount = _textures.FindOverlaps(info.Address, size, ref _textureOverlaps);
+ }
Texture texture = null;
@@ -690,7 +736,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
Texture overlap = _textureOverlaps[index];
- if (overlap.IsViewCompatible(info, size, out int firstLayer, out int firstLevel))
+ if (overlap.IsViewCompatible(info, size, out int firstLayer, out int firstLevel) == TextureViewCompatibility.Full)
{
if (!isSamplerTexture)
{
@@ -701,7 +747,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (IsTextureModified(overlap))
{
- CacheTextureModified(texture);
+ texture.SignalModified();
}
// The size only matters (and is only really reliable) when the
@@ -721,65 +767,114 @@ namespace Ryujinx.Graphics.Gpu.Image
{
texture = new Texture(_context, info, sizeInfo, scaleMode);
- // We need to synchronize before copying the old view data to the texture,
- // otherwise the copied data would be overwritten by a future synchronization.
- texture.SynchronizeMemory();
+ // Step 1: Find textures that are view compatible with the new texture.
+ // Any textures that are incompatible will contain garbage data, so they should be removed where possible.
+
+ int viewCompatible = 0;
+ bool setData = isSamplerTexture || overlapsCount == 0;
for (int index = 0; index < overlapsCount; index++)
{
Texture overlap = _textureOverlaps[index];
+ bool overlapInCache = overlap.CacheNode != null;
- if (texture.IsViewCompatible(overlap.Info, overlap.Size, out int firstLayer, out int firstLevel))
+ TextureViewCompatibility compatibility = texture.IsViewCompatible(overlap.Info, overlap.Size, out int firstLayer, out int firstLevel);
+
+ if (compatibility != TextureViewCompatibility.Incompatible)
{
- TextureInfo overlapInfo = AdjustSizes(texture, overlap.Info, firstLevel);
-
- TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities);
-
- if (texture.ScaleFactor != overlap.ScaleFactor)
+ if (_overlapInfo.Length != _textureOverlaps.Length)
{
- // A bit tricky, our new texture may need to contain an existing texture that is upscaled, but isn't itself.
- // In that case, we prefer the higher scale only if our format is render-target-like, otherwise we scale the view down before copy.
-
- texture.PropagateScale(overlap);
+ Array.Resize(ref _overlapInfo, _textureOverlaps.Length);
}
- ITexture newView = texture.HostTexture.CreateView(createInfo, firstLayer, firstLevel);
-
- overlap.HostTexture.CopyTo(newView, 0, 0);
-
- // Inherit modification from overlapping texture, do that before replacing
- // the view since the replacement operation removes it from the list.
- if (IsTextureModified(overlap))
- {
- CacheTextureModified(texture);
- }
-
- overlap.ReplaceView(texture, overlapInfo, newView, firstLayer, firstLevel);
+ _overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel);
+ _textureOverlaps[viewCompatible++] = overlap;
}
+ else if (overlapInCache || !setData)
+ {
+ if (info.GobBlocksInZ > 1 && info.GobBlocksInZ == overlap.Info.GobBlocksInZ)
+ {
+ // Allow overlapping slices of 3D textures. Could be improved in future by making sure the textures don't overlap.
+ continue;
+ }
+
+ // The overlap texture is going to contain garbage data after we draw, or is generally incompatible.
+ // If the texture cannot be entirely contained in the new address space, and one of its view children is compatible with us,
+ // it must be flushed before removal, so that the data is not lost.
+
+ // If the texture was modified since its last use, then that data is probably meant to go into this texture.
+ // If the data has been modified by the CPU, then it also shouldn't be flushed.
+ bool modified = overlap.ConsumeModified();
+
+ bool flush = overlapInCache && !modified && (overlap.Address < texture.Address || overlap.EndAddress > texture.EndAddress) && overlap.HasViewCompatibleChild(texture);
+
+ setData |= modified || flush;
+
+ if (overlapInCache)
+ {
+ _cache.Remove(overlap, flush);
+ }
+ }
+ }
+
+ // We need to synchronize before copying the old view data to the texture,
+ // otherwise the copied data would be overwritten by a future synchronization.
+ texture.InitializeData(false, setData);
+
+ for (int index = 0; index < viewCompatible; index++)
+ {
+ Texture overlap = _textureOverlaps[index];
+ OverlapInfo oInfo = _overlapInfo[index];
+
+ if (oInfo.Compatibility != TextureViewCompatibility.Full)
+ {
+ continue; // Copy only compatibilty.
+ }
+
+ TextureInfo overlapInfo = AdjustSizes(texture, overlap.Info, oInfo.FirstLevel);
+
+ TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities);
+
+ if (texture.ScaleFactor != overlap.ScaleFactor)
+ {
+ // A bit tricky, our new texture may need to contain an existing texture that is upscaled, but isn't itself.
+ // In that case, we prefer the higher scale only if our format is render-target-like, otherwise we scale the view down before copy.
+
+ texture.PropagateScale(overlap);
+ }
+
+ ITexture newView = texture.HostTexture.CreateView(createInfo, oInfo.FirstLayer, oInfo.FirstLevel);
+
+ overlap.HostTexture.CopyTo(newView, 0, 0);
+
+ // Inherit modification from overlapping texture, do that before replacing
+ // the view since the replacement operation removes it from the list.
+ if (IsTextureModified(overlap))
+ {
+ texture.SignalModified();
+ }
+
+ overlap.ReplaceView(texture, overlapInfo, newView, oInfo.FirstLayer, oInfo.FirstLevel);
}
// If the texture is a 3D texture, we need to additionally copy any slice
// of the 3D texture to the newly created 3D texture.
if (info.Target == Target.Texture3D)
{
- for (int index = 0; index < overlapsCount; index++)
+ for (int index = 0; index < viewCompatible; index++)
{
Texture overlap = _textureOverlaps[index];
+ OverlapInfo oInfo = _overlapInfo[index];
- if (texture.IsViewCompatible(
- overlap.Info,
- overlap.Size,
- isCopy: true,
- out int firstLayer,
- out int firstLevel))
+ if (oInfo.Compatibility != TextureViewCompatibility.Incompatible)
{
overlap.BlacklistScale();
- overlap.HostTexture.CopyTo(texture.HostTexture, firstLayer, firstLevel);
+ overlap.HostTexture.CopyTo(texture.HostTexture, oInfo.FirstLayer, oInfo.FirstLevel);
if (IsTextureModified(overlap))
{
- CacheTextureModified(texture);
+ texture.SignalModified();
}
}
}
@@ -795,7 +890,10 @@ namespace Ryujinx.Graphics.Gpu.Image
texture.Disposed += CacheTextureDisposed;
}
- _textures.Add(texture);
+ lock (_textures)
+ {
+ _textures.Add(texture);
+ }
ShrinkOverlapsBufferIfNeeded();
@@ -818,6 +916,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// The texture that was modified.
private void CacheTextureModified(Texture texture)
{
+ texture.IsModified = true;
_modified.Add(texture);
if (texture.Info.IsLinear)
@@ -992,7 +1091,10 @@ namespace Ryujinx.Graphics.Gpu.Image
{
foreach (Texture texture in _modifiedLinear)
{
- texture.Flush();
+ if (texture.IsModified)
+ {
+ texture.Flush();
+ }
}
_modifiedLinear.Clear();
@@ -1007,7 +1109,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
foreach (Texture texture in _modified)
{
- if (texture.OverlapsWith(address, size))
+ if (texture.OverlapsWith(address, size) && texture.IsModified)
{
texture.Flush();
}
@@ -1024,7 +1126,10 @@ namespace Ryujinx.Graphics.Gpu.Image
/// The texture to be removed
public void RemoveTextureFromCache(Texture texture)
{
- _textures.Remove(texture);
+ lock (_textures)
+ {
+ _textures.Remove(texture);
+ }
}
///
@@ -1033,10 +1138,13 @@ namespace Ryujinx.Graphics.Gpu.Image
///
public void Dispose()
{
- foreach (Texture texture in _textures)
+ lock (_textures)
{
- _modified.Remove(texture);
- texture.Dispose();
+ foreach (Texture texture in _textures)
+ {
+ _modified.Remove(texture);
+ texture.Dispose();
+ }
}
}
}
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureViewCompatibility.cs b/Ryujinx.Graphics.Gpu/Image/TextureViewCompatibility.cs
new file mode 100644
index 00000000..4671af46
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureViewCompatibility.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ ///
+ /// The level of view compatibility one texture has to another.
+ /// Values are increasing in compatibility from 0 (incompatible).
+ ///
+ enum TextureViewCompatibility
+ {
+ Incompatible = 0,
+ CopyOnly,
+ Full
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
index 5fe85d2e..2394f90d 100644
--- a/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
@@ -151,7 +151,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
byte[] data = _context.Renderer.GetBufferData(Handle, offset, (int)size);
- _context.PhysicalMemory.Write(address, data);
+ // TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers.
+ _context.PhysicalMemory.WriteUntracked(address, data);
}
///
diff --git a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
index 88beab8f..ed325369 100644
--- a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
@@ -67,6 +67,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
_cpuMemory.Write(address, data);
}
+ ///
+ /// Writes data to the application process, without any tracking.
+ ///
+ /// Address to write into
+ /// Data to be written
+ public void WriteUntracked(ulong address, ReadOnlySpan data)
+ {
+ _cpuMemory.WriteUntracked(address, data);
+ }
+
///
/// Checks if a specified virtual memory region has been modified by the CPU since the last call.
///
diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs b/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs
index 2c69571c..6df2b630 100644
--- a/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs
+++ b/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs
@@ -67,5 +67,10 @@ namespace Ryujinx.Graphics.OpenGL.Image
Handle = 0;
}
}
+
+ public void Release()
+ {
+ Dispose();
+ }
}
}
diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureStorage.cs b/Ryujinx.Graphics.OpenGL/Image/TextureStorage.cs
index ed258aee..635b6c2c 100644
--- a/Ryujinx.Graphics.OpenGL/Image/TextureStorage.cs
+++ b/Ryujinx.Graphics.OpenGL/Image/TextureStorage.cs
@@ -16,6 +16,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
private int _viewsCount;
+ internal ITexture DefaultView { get; private set; }
+
public TextureStorage(Renderer renderer, TextureCreateInfo info, float scaleFactor)
{
_renderer = renderer;
@@ -147,7 +149,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
public ITexture CreateDefaultView()
{
- return CreateView(Info, 0, 0);
+ DefaultView = CreateView(Info, 0, 0);
+
+ return DefaultView;
}
public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel)
@@ -167,12 +171,37 @@ namespace Ryujinx.Graphics.OpenGL.Image
// If we don't have any views, then the storage is now useless, delete it.
if (--_viewsCount == 0)
{
- Dispose();
+ if (DefaultView == null)
+ {
+ Dispose();
+ }
+ else
+ {
+ // If the default view still exists, we can put it into the resource pool.
+ Release();
+ }
}
}
+ ///
+ /// Release the TextureStorage to the resource pool without disposing its handle.
+ ///
+ public void Release()
+ {
+ _viewsCount = 1; // When we are used again, we will have the default view.
+
+ _renderer.ResourcePool.AddTexture((TextureView)DefaultView);
+ }
+
+ public void DeleteDefault()
+ {
+ DefaultView = null;
+ }
+
public void Dispose()
{
+ DefaultView = null;
+
if (Handle != 0)
{
GL.DeleteTexture(Handle);
diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
index 2d50eba5..04cadae7 100644
--- a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
+++ b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
@@ -434,7 +434,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
throw new NotSupportedException();
}
- public void Dispose()
+ private void DisposeHandles()
{
if (_incompatibleFormatView != null)
{
@@ -447,10 +447,38 @@ namespace Ryujinx.Graphics.OpenGL.Image
{
GL.DeleteTexture(Handle);
- _parent.DecrementViewsCount();
-
Handle = 0;
}
}
+
+ ///
+ /// Release the view without necessarily disposing the parent if we are the default view.
+ /// This allows it to be added to the resource pool and reused later.
+ ///
+ public void Release()
+ {
+ bool hadHandle = Handle != 0;
+
+ if (_parent.DefaultView != this)
+ {
+ DisposeHandles();
+ }
+
+ if (hadHandle)
+ {
+ _parent.DecrementViewsCount();
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_parent.DefaultView == this)
+ {
+ // Remove the default view (us), so that the texture cannot be released to the cache.
+ _parent.DeleteDefault();
+ }
+
+ Release();
+ }
}
}
diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs
index ee2fe93d..061821eb 100644
--- a/Ryujinx.Graphics.OpenGL/Renderer.cs
+++ b/Ryujinx.Graphics.OpenGL/Renderer.cs
@@ -23,6 +23,8 @@ namespace Ryujinx.Graphics.OpenGL
internal TextureCopy TextureCopy { get; }
+ internal ResourcePool ResourcePool { get; }
+
public string GpuVendor { get; private set; }
public string GpuRenderer { get; private set; }
public string GpuVersion { get; private set; }
@@ -33,6 +35,7 @@ namespace Ryujinx.Graphics.OpenGL
_counters = new Counters();
_window = new Window(this);
TextureCopy = new TextureCopy(this);
+ ResourcePool = new ResourcePool();
}
public IShader CompileShader(ShaderProgram shader)
@@ -57,7 +60,14 @@ namespace Ryujinx.Graphics.OpenGL
public ITexture CreateTexture(TextureCreateInfo info, float scaleFactor)
{
- return info.Target == Target.TextureBuffer ? new TextureBuffer(info) : new TextureStorage(this, info, scaleFactor).CreateDefaultView();
+ if (info.Target == Target.TextureBuffer)
+ {
+ return new TextureBuffer(info);
+ }
+ else
+ {
+ return ResourcePool.GetTextureOrNull(info, scaleFactor) ?? new TextureStorage(this, info, scaleFactor).CreateDefaultView();
+ }
}
public void DeleteBuffer(BufferHandle buffer)
@@ -92,6 +102,11 @@ namespace Ryujinx.Graphics.OpenGL
_counters.Update();
}
+ public void PreFrame()
+ {
+ ResourcePool.Tick();
+ }
+
public ICounterEvent ReportCounter(CounterType type, EventHandler resultHandler)
{
return _counters.QueueReport(type, resultHandler);
@@ -123,6 +138,7 @@ namespace Ryujinx.Graphics.OpenGL
public void Dispose()
{
TextureCopy.Dispose();
+ ResourcePool.Dispose();
_pipeline.Dispose();
_window.Dispose();
_counters.Dispose();
diff --git a/Ryujinx.Graphics.OpenGL/ResourcePool.cs b/Ryujinx.Graphics.OpenGL/ResourcePool.cs
new file mode 100644
index 00000000..57231cd6
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/ResourcePool.cs
@@ -0,0 +1,122 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.OpenGL.Image;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ class DisposedTexture
+ {
+ public TextureCreateInfo Info;
+ public TextureView View;
+ public float ScaleFactor;
+ public int RemainingFrames;
+ }
+
+ ///
+ /// A structure for pooling resources that can be reused without recreation, such as textures.
+ ///
+ class ResourcePool : IDisposable
+ {
+ private const int DisposedLiveFrames = 2;
+
+ private readonly object _lock = new object();
+ private readonly Dictionary> _textures = new Dictionary>();
+
+ ///
+ /// Add a texture that is not being used anymore to the resource pool to be used later.
+ /// Both the texture's view and storage should be completely unused.
+ ///
+ /// The texture's view
+ public void AddTexture(TextureView view)
+ {
+ lock (_lock)
+ {
+ List list;
+ if (!_textures.TryGetValue(view.Info, out list))
+ {
+ list = new List();
+ _textures.Add(view.Info, list);
+ }
+
+ list.Add(new DisposedTexture()
+ {
+ Info = view.Info,
+ View = view,
+ ScaleFactor = view.ScaleFactor,
+ RemainingFrames = DisposedLiveFrames
+ });
+ }
+ }
+
+ ///
+ /// Attempt to obtain a texture from the resource cache with the desired parameters.
+ ///
+ /// The creation info for the desired texture
+ /// The scale factor for the desired texture
+ /// A TextureView with the description specified, or null if one was not found.
+ public TextureView GetTextureOrNull(TextureCreateInfo info, float scaleFactor)
+ {
+ lock (_lock)
+ {
+ List list;
+ if (!_textures.TryGetValue(info, out list))
+ {
+ return null;
+ }
+
+ foreach (DisposedTexture texture in list)
+ {
+ if (scaleFactor == texture.ScaleFactor)
+ {
+ list.Remove(texture);
+ return texture.View;
+ }
+ }
+
+ return null;
+ }
+ }
+
+ ///
+ /// Update the pool, removing any resources that have expired.
+ ///
+ public void Tick()
+ {
+ lock (_lock)
+ {
+ foreach (List list in _textures.Values)
+ {
+ for (int i = 0; i < list.Count; i++)
+ {
+ DisposedTexture tex = list[i];
+
+ if (--tex.RemainingFrames < 0)
+ {
+ tex.View.Dispose();
+ list.RemoveAt(i--);
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Disposes the resource pool.
+ ///
+ public void Dispose()
+ {
+ lock (_lock)
+ {
+ foreach (List list in _textures.Values)
+ {
+ foreach (DisposedTexture texture in list)
+ {
+ texture.View.Dispose();
+ }
+ }
+ _textures.Clear();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs
index df02e5e5..5401f1cc 100644
--- a/Ryujinx.HLE/Switch.cs
+++ b/Ryujinx.HLE/Switch.cs
@@ -159,6 +159,8 @@ namespace Ryujinx.HLE
public void ProcessFrame()
{
+ Gpu.Renderer.PreFrame();
+
Gpu.GPFifo.DispatchCalls();
}