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();