diff --git a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs
index 022a3839f..dca6263aa 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -101,6 +101,11 @@ namespace Ryujinx.Graphics.Gpu.Image
///
public bool AlwaysFlushOnOverlap { get; private set; }
+ ///
+ /// Indicates that the texture was fully unmapped since the modified flag was set, and flushes should be ignored until it is modified again.
+ ///
+ public bool FlushStale { get; private set; }
+
///
/// Increments when the host texture is swapped, or when the texture is removed from all pools.
///
@@ -149,6 +154,7 @@ namespace Ryujinx.Graphics.Gpu.Image
///
public bool HadPoolOwner { get; private set; }
+ ///
/// Physical memory ranges where the texture data is located.
///
public MultiRange Range { get; private set; }
@@ -1411,6 +1417,7 @@ namespace Ryujinx.Graphics.Gpu.Image
///
public void SignalModified()
{
+ FlushStale = false;
_scaledSetScore = Math.Max(0, _scaledSetScore - 1);
if (_modifiedStale || Group.HasCopyDependencies)
@@ -1431,6 +1438,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
if (bound)
{
+ FlushStale = false;
_scaledSetScore = Math.Max(0, _scaledSetScore - 1);
}
@@ -1695,12 +1703,17 @@ namespace Ryujinx.Graphics.Gpu.Image
/// The range of memory being unmapped
public void Unmapped(MultiRange unmapRange)
{
+ if (unmapRange.Contains(Range))
+ {
+ // If this is a full unmap, prevent flushes until the texture is mapped again.
+ FlushStale = true;
+ }
+
ChangedMapping = true;
if (Group.Storage == this)
{
Group.Unmapped();
-
Group.ClearModified(unmapRange);
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
index 5048ccca4..6b92c0aaf 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
@@ -107,8 +107,6 @@ namespace Ryujinx.Graphics.Gpu.Image
// 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();
@@ -735,9 +733,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
if (overlap.IsView)
{
- overlapCompatibility = overlapCompatibility == TextureViewCompatibility.FormatAlias ?
- TextureViewCompatibility.Incompatible :
- TextureViewCompatibility.CopyOnly;
+ overlapCompatibility = TextureViewCompatibility.CopyOnly;
}
else
{
@@ -815,7 +811,7 @@ namespace Ryujinx.Graphics.Gpu.Image
Texture overlap = _textureOverlaps[index];
OverlapInfo oInfo = _overlapInfo[index];
- if (oInfo.Compatibility <= TextureViewCompatibility.LayoutIncompatible || oInfo.Compatibility == TextureViewCompatibility.FormatAlias)
+ if (oInfo.Compatibility <= TextureViewCompatibility.LayoutIncompatible)
{
if (!overlap.IsView && texture.DataOverlaps(overlap, oInfo.Compatibility))
{
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
index 3a0efcdda..5af0471c0 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
@@ -226,7 +226,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
// D32F and R32F texture have the same representation internally,
// however the R32F format is used to sample from depth textures.
- if (lhs.FormatInfo.Format == Format.D32Float && rhs.FormatInfo.Format == Format.R32Float && (forSampler || depthAlias))
+ if (IsValidDepthAsColorAlias(lhs.FormatInfo.Format, rhs.FormatInfo.Format) && (forSampler || depthAlias))
{
return TextureMatchQuality.FormatAlias;
}
@@ -239,14 +239,8 @@ namespace Ryujinx.Graphics.Gpu.Image
{
return TextureMatchQuality.FormatAlias;
}
-
- if (lhs.FormatInfo.Format == Format.D16Unorm && rhs.FormatInfo.Format == Format.R16Unorm)
- {
- return TextureMatchQuality.FormatAlias;
- }
-
- if ((lhs.FormatInfo.Format == Format.D24UnormS8Uint ||
- lhs.FormatInfo.Format == Format.S8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm)
+ else if ((lhs.FormatInfo.Format == Format.D24UnormS8Uint ||
+ lhs.FormatInfo.Format == Format.S8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm)
{
return TextureMatchQuality.FormatAlias;
}
@@ -632,12 +626,27 @@ namespace Ryujinx.Graphics.Gpu.Image
if (lhsFormat.Format.IsDepthOrStencil() || rhsFormat.Format.IsDepthOrStencil())
{
- return FormatMatches(lhs, rhs, flags.HasFlag(TextureSearchFlags.ForSampler), flags.HasFlag(TextureSearchFlags.DepthAlias)) switch
+ bool forSampler = flags.HasFlag(TextureSearchFlags.ForSampler);
+ bool depthAlias = flags.HasFlag(TextureSearchFlags.DepthAlias);
+
+ TextureMatchQuality matchQuality = FormatMatches(lhs, rhs, forSampler, depthAlias);
+
+ if (matchQuality == TextureMatchQuality.Perfect)
{
- TextureMatchQuality.Perfect => TextureViewCompatibility.Full,
- TextureMatchQuality.FormatAlias => TextureViewCompatibility.FormatAlias,
- _ => TextureViewCompatibility.Incompatible,
- };
+ return TextureViewCompatibility.Full;
+ }
+ else if (matchQuality == TextureMatchQuality.FormatAlias)
+ {
+ return TextureViewCompatibility.FormatAlias;
+ }
+ else if (IsValidColorAsDepthAlias(lhsFormat.Format, rhsFormat.Format) || IsValidDepthAsColorAlias(lhsFormat.Format, rhsFormat.Format))
+ {
+ return TextureViewCompatibility.CopyOnly;
+ }
+ else
+ {
+ return TextureViewCompatibility.Incompatible;
+ }
}
if (IsFormatHostIncompatible(lhs, caps) || IsFormatHostIncompatible(rhs, caps))
@@ -666,6 +675,30 @@ namespace Ryujinx.Graphics.Gpu.Image
return TextureViewCompatibility.Incompatible;
}
+ ///
+ /// Checks if it's valid to alias a color format as a depth format.
+ ///
+ /// Source format to be checked
+ /// Target format to be checked
+ /// True if it's valid to alias the formats
+ private static bool IsValidColorAsDepthAlias(Format lhsFormat, Format rhsFormat)
+ {
+ return (lhsFormat == Format.R32Float && rhsFormat == Format.D32Float) ||
+ (lhsFormat == Format.R16Unorm && rhsFormat == Format.D16Unorm);
+ }
+
+ ///
+ /// Checks if it's valid to alias a depth format as a color format.
+ ///
+ /// Source format to be checked
+ /// Target format to be checked
+ /// True if it's valid to alias the formats
+ private static bool IsValidDepthAsColorAlias(Format lhsFormat, Format rhsFormat)
+ {
+ return (lhsFormat == Format.D32Float && rhsFormat == Format.R32Float) ||
+ (lhsFormat == Format.D16Unorm && rhsFormat == Format.R16Unorm);
+ }
+
///
/// Checks if aliasing of two formats that would normally be considered incompatible be allowed,
/// using copy dependencies.
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
index 746a95ffc..21d7939ad 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
@@ -1659,6 +1659,14 @@ namespace Ryujinx.Graphics.Gpu.Image
return;
}
+ // If size is zero, we have nothing to flush.
+ // If the flush is stale, we should ignore it because the texture was unmapped since the modified
+ // flag was set, and flushing it is not safe anymore as the GPU might no longer own the memory.
+ if (size == 0 || Storage.FlushStale)
+ {
+ return;
+ }
+
// There is a small gap here where the action is removed but _actionRegistered is still 1.
// In this case it will skip registering the action, but here we are already handling it,
// so there shouldn't be any issue as it's the same handler for all actions.
diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs
index e33940cb1..128f481f6 100644
--- a/src/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs
@@ -367,7 +367,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
return to;
}
- private TextureView PboCopy(TextureView from, TextureView to, int srcLayer, int dstLayer, int srcLevel, int dstLevel, int width, int height)
+ public void PboCopy(TextureView from, TextureView to, int srcLayer, int dstLayer, int srcLevel, int dstLevel, int width, int height)
{
int dstWidth = width;
int dstHeight = height;
@@ -445,8 +445,6 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0);
-
- return to;
}
private void EnsurePbo(TextureView view)
diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
index 0f5fe46a5..7f1b1c382 100644
--- a/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
@@ -140,6 +140,28 @@ namespace Ryujinx.Graphics.OpenGL.Image
int levels = Math.Min(Info.Levels, destinationView.Info.Levels - firstLevel);
_renderer.TextureCopyIncompatible.CopyIncompatibleFormats(this, destinationView, 0, firstLayer, 0, firstLevel, layers, levels);
}
+ else if (destinationView.Format.IsDepthOrStencil() != Format.IsDepthOrStencil())
+ {
+ int layers = Math.Min(Info.GetLayers(), destinationView.Info.GetLayers() - firstLayer);
+ int levels = Math.Min(Info.Levels, destinationView.Info.Levels - firstLevel);
+
+ for (int level = 0; level < levels; level++)
+ {
+ int srcWidth = Math.Max(1, Width >> level);
+ int srcHeight = Math.Max(1, Height >> level);
+
+ int dstWidth = Math.Max(1, destinationView.Width >> (firstLevel + level));
+ int dstHeight = Math.Max(1, destinationView.Height >> (firstLevel + level));
+
+ int minWidth = Math.Min(srcWidth, dstWidth);
+ int minHeight = Math.Min(srcHeight, dstHeight);
+
+ for (int layer = 0; layer < layers; layer++)
+ {
+ _renderer.TextureCopy.PboCopy(this, destinationView, 0, firstLayer + layer, 0, firstLevel + level, minWidth, minHeight);
+ }
+ }
+ }
else
{
_renderer.TextureCopy.CopyUnscaled(this, destinationView, 0, firstLayer, 0, firstLevel);
@@ -169,6 +191,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
{
_renderer.TextureCopyIncompatible.CopyIncompatibleFormats(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
}
+ else if (destinationView.Format.IsDepthOrStencil() != Format.IsDepthOrStencil())
+ {
+ int minWidth = Math.Min(Width, destinationView.Width);
+ int minHeight = Math.Min(Height, destinationView.Height);
+
+ _renderer.TextureCopy.PboCopy(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, minWidth, minHeight);
+ }
else
{
_renderer.TextureCopy.CopyUnscaled(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
diff --git a/src/Ryujinx.Graphics.Vulkan/TextureView.cs b/src/Ryujinx.Graphics.Vulkan/TextureView.cs
index 09128f007..05dbd15ce 100644
--- a/src/Ryujinx.Graphics.Vulkan/TextureView.cs
+++ b/src/Ryujinx.Graphics.Vulkan/TextureView.cs
@@ -211,6 +211,13 @@ namespace Ryujinx.Graphics.Vulkan
int levels = Math.Min(Info.Levels, dst.Info.Levels - firstLevel);
_gd.HelperShader.CopyIncompatibleFormats(_gd, cbs, src, dst, 0, firstLayer, 0, firstLevel, layers, levels);
}
+ else if (src.Info.Format.IsDepthOrStencil() != dst.Info.Format.IsDepthOrStencil())
+ {
+ int layers = Math.Min(Info.GetLayers(), dst.Info.GetLayers() - firstLayer);
+ int levels = Math.Min(Info.Levels, dst.Info.Levels - firstLevel);
+
+ _gd.HelperShader.CopyColor(_gd, cbs, src, dst, 0, firstLayer, 0, FirstLevel, layers, levels);
+ }
else
{
TextureCopy.Copy(
@@ -260,6 +267,10 @@ namespace Ryujinx.Graphics.Vulkan
{
_gd.HelperShader.CopyIncompatibleFormats(_gd, cbs, src, dst, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
}
+ else if (src.Info.Format.IsDepthOrStencil() != dst.Info.Format.IsDepthOrStencil())
+ {
+ _gd.HelperShader.CopyColor(_gd, cbs, src, dst, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
+ }
else
{
TextureCopy.Copy(
diff --git a/src/Ryujinx.HLE/HOS/Services/ServerBase.cs b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs
index 9d7e4d4c5..e892d6ab6 100644
--- a/src/Ryujinx.HLE/HOS/Services/ServerBase.cs
+++ b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs
@@ -39,6 +39,8 @@ namespace Ryujinx.HLE.HOS.Services
private readonly KernelContext _context;
private KProcess _selfProcess;
private KThread _selfThread;
+ private KEvent _wakeEvent;
+ private int _wakeHandle = 0;
private readonly ReaderWriterLockSlim _handleLock = new();
private readonly Dictionary _sessions = new();
@@ -125,6 +127,8 @@ namespace Ryujinx.HLE.HOS.Services
_handleLock.ExitWriteLock();
}
}
+
+ _wakeEvent.WritableEvent.Signal();
}
private IpcService GetSessionObj(int serverSessionHandle)
@@ -187,6 +191,9 @@ namespace Ryujinx.HLE.HOS.Services
AddPort(serverPortHandle, SmObjectFactory);
}
+ _wakeEvent = new KEvent(_context);
+ Result result = _selfProcess.HandleTable.GenerateHandle(_wakeEvent.ReadableEvent, out _wakeHandle);
+
InitDone.Set();
ulong messagePtr = _selfThread.TlsAddress;
@@ -195,7 +202,6 @@ namespace Ryujinx.HLE.HOS.Services
_selfProcess.CpuMemory.Write(messagePtr + 0x0, 0);
_selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10);
_selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48));
-
int replyTargetHandle = 0;
while (true)
@@ -211,13 +217,15 @@ namespace Ryujinx.HLE.HOS.Services
portHandleCount = _ports.Count;
- handleCount = portHandleCount + _sessions.Count;
+ handleCount = portHandleCount + _sessions.Count + 1;
handles = ArrayPool.Shared.Rent(handleCount);
- _ports.Keys.CopyTo(handles, 0);
+ handles[0] = _wakeHandle;
- _sessions.Keys.CopyTo(handles, portHandleCount);
+ _ports.Keys.CopyTo(handles, 1);
+
+ _sessions.Keys.CopyTo(handles, portHandleCount + 1);
}
finally
{
@@ -227,8 +235,7 @@ namespace Ryujinx.HLE.HOS.Services
}
}
- // We still need a timeout here to allow the service to pick up and listen new sessions...
- var rc = _context.Syscall.ReplyAndReceive(out int signaledIndex, handles.AsSpan(0, handleCount), replyTargetHandle, 1000000L);
+ var rc = _context.Syscall.ReplyAndReceive(out int signaledIndex, handles.AsSpan(0, handleCount), replyTargetHandle, -1);
_selfThread.HandlePostSyscall();
@@ -239,7 +246,7 @@ namespace Ryujinx.HLE.HOS.Services
replyTargetHandle = 0;
- if (rc == Result.Success && signaledIndex >= portHandleCount)
+ if (rc == Result.Success && signaledIndex >= portHandleCount + 1)
{
// We got a IPC request, process it, pass to the appropriate service if needed.
int signaledHandle = handles[signaledIndex];
@@ -253,24 +260,32 @@ namespace Ryujinx.HLE.HOS.Services
{
if (rc == Result.Success)
{
- // We got a new connection, accept the session to allow servicing future requests.
- if (_context.Syscall.AcceptSession(out int serverSessionHandle, handles[signaledIndex]) == Result.Success)
+ if (signaledIndex > 0)
{
- bool handleWriteLockTaken = false;
- try
+ // We got a new connection, accept the session to allow servicing future requests.
+ if (_context.Syscall.AcceptSession(out int serverSessionHandle, handles[signaledIndex]) == Result.Success)
{
- handleWriteLockTaken = _handleLock.TryEnterWriteLock(Timeout.Infinite);
- IpcService obj = _ports[handles[signaledIndex]].Invoke();
- _sessions.Add(serverSessionHandle, obj);
- }
- finally
- {
- if (handleWriteLockTaken)
+ bool handleWriteLockTaken = false;
+ try
{
- _handleLock.ExitWriteLock();
+ handleWriteLockTaken = _handleLock.TryEnterWriteLock(Timeout.Infinite);
+ IpcService obj = _ports[handles[signaledIndex]].Invoke();
+ _sessions.Add(serverSessionHandle, obj);
+ }
+ finally
+ {
+ if (handleWriteLockTaken)
+ {
+ _handleLock.ExitWriteLock();
+ }
}
}
}
+ else
+ {
+ // The _wakeEvent signalled, which means we have a new session.
+ _wakeEvent.WritableEvent.Clear();
+ }
}
_selfProcess.CpuMemory.Write(messagePtr + 0x0, 0);
@@ -499,6 +514,8 @@ namespace Ryujinx.HLE.HOS.Services
if (Interlocked.Exchange(ref _isDisposed, 1) == 0)
{
+ _selfProcess.HandleTable.CloseHandle(_wakeHandle);
+
foreach (IpcService service in _sessions.Values)
{
(service as IDisposable)?.Dispose();