diff --git a/Ryujinx.Graphics.GAL/IRenderer.cs b/Ryujinx.Graphics.GAL/IRenderer.cs index cf67f648b..c72320a2f 100644 --- a/Ryujinx.Graphics.GAL/IRenderer.cs +++ b/Ryujinx.Graphics.GAL/IRenderer.cs @@ -29,6 +29,7 @@ namespace Ryujinx.Graphics.GAL ReadOnlySpan GetBufferData(BufferHandle buffer, int offset, int size); Capabilities GetCapabilities(); + ulong GetCurrentSync(); HardwareInfo GetHardwareInfo(); IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info); diff --git a/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs b/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs index 7a625af38..a9e3b75c4 100644 --- a/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs +++ b/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs @@ -338,6 +338,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading return box.Result; } + public ulong GetCurrentSync() + { + return _baseRenderer.GetCurrentSync(); + } + public HardwareInfo GetHardwareInfo() { return _baseRenderer.GetHardwareInfo(); diff --git a/Ryujinx.Graphics.Gpu/GpuContext.cs b/Ryujinx.Graphics.Gpu/GpuContext.cs index 5913ac947..52733165b 100644 --- a/Ryujinx.Graphics.Gpu/GpuContext.cs +++ b/Ryujinx.Graphics.Gpu/GpuContext.cs @@ -69,6 +69,12 @@ namespace Ryujinx.Graphics.Gpu /// internal List SyncpointActions { get; } + /// + /// Buffer migrations that are currently in-flight. These are checked whenever sync is created to determine if buffer migration + /// copies have completed on the GPU, and their data can be freed. + /// + internal List BufferMigrations { get; } + /// /// Queue with deferred actions that must run on the render thread. /// @@ -90,6 +96,7 @@ namespace Ryujinx.Graphics.Gpu public event Action ShaderCacheStateChanged; private Thread _gpuThread; + private bool _pendingSync; /// /// Creates a new instance of the GPU emulation context. @@ -109,6 +116,7 @@ namespace Ryujinx.Graphics.Gpu SyncActions = new List(); SyncpointActions = new List(); + BufferMigrations = new List(); DeferredActions = new Queue(); @@ -273,6 +281,17 @@ namespace Ryujinx.Graphics.Gpu SequenceNumber++; } + /// + /// Registers a buffer migration. These are checked to see if they can be disposed when the sync number increases, + /// and the migration copy has completed. + /// + /// The buffer migration + internal void RegisterBufferMigration(BufferMigration migration) + { + BufferMigrations.Add(migration); + _pendingSync = true; + } + /// /// Registers an action to be performed the next time a syncpoint is incremented. /// This will also ensure a host sync object is created, and is incremented. @@ -288,6 +307,7 @@ namespace Ryujinx.Graphics.Gpu else { SyncActions.Add(action); + _pendingSync = true; } } @@ -298,7 +318,24 @@ namespace Ryujinx.Graphics.Gpu /// True if host sync is being created by a syncpoint public void CreateHostSyncIfNeeded(bool syncpoint) { - if (SyncActions.Count > 0 || (syncpoint && SyncpointActions.Count > 0)) + if (BufferMigrations.Count > 0) + { + ulong currentSyncNumber = Renderer.GetCurrentSync(); + + for (int i = 0; i < BufferMigrations.Count; i++) + { + BufferMigration migration = BufferMigrations[i]; + long diff = (long)(currentSyncNumber - migration.SyncNumber); + + if (diff >= 0) + { + migration.Dispose(); + BufferMigrations.RemoveAt(i--); + } + } + } + + if (_pendingSync || (syncpoint && SyncpointActions.Count > 0)) { Renderer.CreateSync(SyncNumber); @@ -317,6 +354,8 @@ namespace Ryujinx.Graphics.Gpu SyncActions.Clear(); SyncpointActions.Clear(); } + + _pendingSync = false; } /// diff --git a/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/Ryujinx.Graphics.Gpu/Memory/Buffer.cs index 1e49f6bfa..842249f34 100644 --- a/Ryujinx.Graphics.Gpu/Memory/Buffer.cs +++ b/Ryujinx.Graphics.Gpu/Memory/Buffer.cs @@ -65,6 +65,8 @@ namespace Ryujinx.Graphics.Gpu.Memory private bool _useGranular; private bool _syncActionRegistered; + private int _referenceCount = 1; + /// /// Creates a new instance of the buffer. /// @@ -229,7 +231,7 @@ namespace Ryujinx.Graphics.Gpu.Memory { if (_modifiedRanges == null) { - _modifiedRanges = new BufferModifiedRangeList(_context); + _modifiedRanges = new BufferModifiedRangeList(_context, this, Flush); } } @@ -290,7 +292,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// The buffer to inherit from public void InheritModifiedRanges(Buffer from) { - if (from._modifiedRanges != null) + if (from._modifiedRanges != null && from._modifiedRanges.HasRanges) { if (from._syncActionRegistered && !_syncActionRegistered) { @@ -310,17 +312,9 @@ namespace Ryujinx.Graphics.Gpu.Memory } }; - if (_modifiedRanges == null) - { - _modifiedRanges = from._modifiedRanges; - _modifiedRanges.ReregisterRanges(registerRangeAction); + EnsureRangeList(); - from._modifiedRanges = null; - } - else - { - _modifiedRanges.InheritRanges(from._modifiedRanges, registerRangeAction); - } + _modifiedRanges.InheritRanges(from._modifiedRanges, registerRangeAction); } } @@ -456,7 +450,7 @@ namespace Ryujinx.Graphics.Gpu.Memory if (ranges != null) { (address, size) = PageAlign(address, size); - ranges.WaitForAndGetRanges(address, size, Flush); + ranges.WaitForAndFlushRanges(address, size); } }, true); } @@ -508,6 +502,25 @@ namespace Ryujinx.Graphics.Gpu.Memory UnmappedSequence++; } + /// + /// Increments the buffer reference count. + /// + public void IncrementReferenceCount() + { + _referenceCount++; + } + + /// + /// Decrements the buffer reference count. + /// + public void DecrementReferenceCount() + { + if (--_referenceCount == 0) + { + DisposeData(); + } + } + /// /// Disposes the host buffer's data, not its tracking handles. /// @@ -528,7 +541,7 @@ namespace Ryujinx.Graphics.Gpu.Memory _memoryTrackingGranular?.Dispose(); _memoryTracking?.Dispose(); - DisposeData(); + DecrementReferenceCount(); } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs b/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs index a523c76fb..00f590831 100644 --- a/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs +++ b/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs @@ -273,7 +273,7 @@ namespace Ryujinx.Graphics.Gpu.Memory buffer.CopyTo(newBuffer, dstOffset); newBuffer.InheritModifiedRanges(buffer); - buffer.DisposeData(); + buffer.DecrementReferenceCount(); } newBuffer.SynchronizeMemory(address, newSize); diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs b/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs new file mode 100644 index 000000000..e020c0c39 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs @@ -0,0 +1,125 @@ +using System; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// A record of when buffer data was copied from one buffer to another, along with the SyncNumber when the migration will be complete. + /// Keeps the source buffer alive for data flushes until the migration is complete. + /// + internal class BufferMigration : IDisposable + { + /// + /// The offset for the migrated region. + /// + private readonly ulong _offset; + + /// + /// The size for the migrated region. + /// + private readonly ulong _size; + + /// + /// The buffer that was migrated from. + /// + private readonly Buffer _buffer; + + /// + /// The source range action, to be called on overlap with an unreached sync number. + /// + private readonly Action _sourceRangeAction; + + /// + /// The source range list. + /// + private readonly BufferModifiedRangeList _source; + + /// + /// The destination range list. This range list must be updated when flushing the source. + /// + public readonly BufferModifiedRangeList Destination; + + /// + /// The sync number that needs to be reached for this migration to be removed. This is set to the pending sync number on creation. + /// + public readonly ulong SyncNumber; + + /// + /// Creates a record for a buffer migration. + /// + /// The source buffer for this migration + /// The flush action for the source buffer + /// The modified range list for the source buffer + /// The modified range list for the destination buffer + /// The sync number for when the migration is complete + public BufferMigration( + Buffer buffer, + Action sourceRangeAction, + BufferModifiedRangeList source, + BufferModifiedRangeList dest, + ulong syncNumber) + { + _offset = buffer.Address; + _size = buffer.Size; + _buffer = buffer; + _sourceRangeAction = sourceRangeAction; + _source = source; + Destination = dest; + SyncNumber = syncNumber; + } + + /// + /// Determine if the given range overlaps this migration, and has not been completed yet. + /// + /// Start offset + /// Range size + /// The sync number that was waited on + /// True if overlapping and in progress, false otherwise + public bool Overlaps(ulong offset, ulong size, ulong syncNumber) + { + ulong end = offset + size; + ulong destEnd = _offset + _size; + long syncDiff = (long)(syncNumber - SyncNumber); // syncNumber is less if the copy has not completed. + + return !(end <= _offset || offset >= destEnd) && syncDiff < 0; + } + + /// + /// Determine if the given range matches this migration. + /// + /// Start offset + /// Range size + /// True if the range exactly matches, false otherwise + public bool FullyMatches(ulong offset, ulong size) + { + return _offset == offset && _size == size; + } + + /// + /// Perform the migration source's range action on the range provided, clamped to the bounds of the source buffer. + /// + /// Start offset + /// Range size + /// Current sync number + /// The modified range list that originally owned this range + public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferModifiedRangeList parent) + { + ulong end = offset + size; + end = Math.Min(_offset + _size, end); + offset = Math.Max(_offset, offset); + + size = end - offset; + + _source.RangeActionWithMigration(offset, size, syncNumber, parent, _sourceRangeAction); + } + + /// + /// Removes this reference to the range list, potentially allowing for the source buffer to be disposed. + /// + public void Dispose() + { + Destination.RemoveMigration(this); + + _buffer.DecrementReferenceCount(); + } + } +} diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs b/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs index 07dbd2094..d0230b629 100644 --- a/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs +++ b/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs @@ -1,6 +1,8 @@ -using Ryujinx.Common.Pools; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Pools; using Ryujinx.Memory.Range; using System; +using System.Collections.Generic; using System.Linq; namespace Ryujinx.Graphics.Gpu.Memory @@ -30,17 +32,24 @@ namespace Ryujinx.Graphics.Gpu.Memory /// public ulong SyncNumber { get; internal set; } + /// + /// The range list that originally owned this range. + /// + public BufferModifiedRangeList Parent { get; internal set; } + /// /// Creates a new instance of a modified range. /// /// Start address of the range /// Size of the range in bytes /// The GPU sync number at the time of creation - public BufferModifiedRange(ulong address, ulong size, ulong syncNumber) + /// The range list that owns this range + public BufferModifiedRange(ulong address, ulong size, ulong syncNumber, BufferModifiedRangeList parent) { Address = address; Size = size; SyncNumber = syncNumber; + Parent = parent; } /// @@ -63,16 +72,39 @@ namespace Ryujinx.Graphics.Gpu.Memory private const int BackingInitialSize = 8; private GpuContext _context; + private Buffer _parent; + private Action _flushAction; + + private List _sources; + private BufferMigration _migrationTarget; private object _lock = new object(); + /// + /// Whether the modified range list has any entries or not. + /// + public bool HasRanges + { + get + { + lock (_lock) + { + return Count > 0; + } + } + } + /// /// Creates a new instance of a modified range list. /// /// GPU context that the buffer range list belongs to - public BufferModifiedRangeList(GpuContext context) : base(BackingInitialSize) + /// The parent buffer that owns this range list + /// The flush action for the parent buffer + public BufferModifiedRangeList(GpuContext context, Buffer parent, Action flushAction) : base(BackingInitialSize) { _context = context; + _parent = parent; + _flushAction = flushAction; } /// @@ -142,6 +174,7 @@ namespace Ryujinx.Graphics.Gpu.Memory { // Region already exists. Just update the existing sync number. overlap.SyncNumber = syncNumber; + overlap.Parent = this; return; } @@ -152,18 +185,18 @@ namespace Ryujinx.Graphics.Gpu.Memory { // A split item must be created behind this overlap. - Add(new BufferModifiedRange(overlap.Address, address - overlap.Address, overlap.SyncNumber)); + Add(new BufferModifiedRange(overlap.Address, address - overlap.Address, overlap.SyncNumber, overlap.Parent)); } if (overlap.Address < endAddress && overlap.EndAddress > endAddress) { // A split item must be created after this overlap. - Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber)); + Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent)); } } - Add(new BufferModifiedRange(address, size, syncNumber)); + Add(new BufferModifiedRange(address, size, syncNumber, this)); } } @@ -207,9 +240,102 @@ namespace Ryujinx.Graphics.Gpu.Memory } } + /// + /// Performs the given range action, or one from a migration that overlaps and has not synced yet. + /// + /// The offset to pass to the action + /// The size to pass to the action + /// The sync number that has been reached + /// The modified range list that originally owned this range + /// The action to perform + public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferModifiedRangeList parent, Action rangeAction) + { + bool firstSource = true; + + if (parent != this) + { + lock (_lock) + { + if (_sources != null) + { + foreach (BufferMigration source in _sources) + { + if (source.Overlaps(offset, size, syncNumber)) + { + if (firstSource && !source.FullyMatches(offset, size)) + { + // Perform this buffer's action first. The migrations will run after. + rangeAction(offset, size); + } + + source.RangeActionWithMigration(offset, size, syncNumber, parent); + + firstSource = false; + } + } + } + } + } + + if (firstSource) + { + // No overlapping migrations, or they are not meant for this range, flush the data using the given action. + rangeAction(offset, size); + } + } + + /// + /// Removes modified ranges ready by the sync number from the list, and flushes their buffer data within a given address range. + /// + /// Overlapping ranges to check + /// Number of overlapping ranges + /// The highest difference between an overlapping range's sync number and the current one + /// The current sync number + /// The start address of the flush range + /// The end address of the flush range + private void RemoveRangesAndFlush( + BufferModifiedRange[] overlaps, + int rangeCount, + long highestDiff, + ulong currentSync, + ulong address, + ulong endAddress) + { + lock (_lock) + { + if (_migrationTarget == null) + { + ulong waitSync = currentSync + (ulong)highestDiff; + + for (int i = 0; i < rangeCount; i++) + { + BufferModifiedRange overlap = overlaps[i]; + + long diff = (long)(overlap.SyncNumber - currentSync); + + if (diff <= highestDiff) + { + ulong clampAddress = Math.Max(address, overlap.Address); + ulong clampEnd = Math.Min(endAddress, overlap.EndAddress); + + ClearPart(overlap, clampAddress, clampEnd); + + RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, overlap.Parent, _flushAction); + } + } + + return; + } + } + + // There is a migration target to call instead. This can't be changed after set so accessing it outside the lock is fine. + + _migrationTarget.Destination.RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress); + } + /// /// Gets modified ranges within the specified region, waits on ones from a previous sync number, - /// and then fires the given action for each range individually. + /// and then fires the flush action for each range individually. /// /// /// This function assumes it is called from the background thread. @@ -218,8 +344,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// /// Start address to query /// Size to query - /// The action to call for each modified range - public void WaitForAndGetRanges(ulong address, ulong size, Action rangeAction) + public void WaitForAndFlushRanges(ulong address, ulong size) { ulong endAddress = address + size; ulong currentSync = _context.SyncNumber; @@ -231,10 +356,23 @@ namespace Ryujinx.Graphics.Gpu.Memory // Range list must be consistent for this operation lock (_lock) { - rangeCount = FindOverlapsNonOverlapping(address, size, ref overlaps); + if (_migrationTarget != null) + { + rangeCount = -1; + } + else + { + rangeCount = FindOverlapsNonOverlapping(address, size, ref overlaps); + } } - if (rangeCount == 0) + if (rangeCount == -1) + { + _migrationTarget.Destination.WaitForAndFlushRanges(address, size); + + return; + } + else if (rangeCount == 0) { return; } @@ -264,47 +402,37 @@ namespace Ryujinx.Graphics.Gpu.Memory // Wait for the syncpoint. _context.Renderer.WaitSync(currentSync + (ulong)highestDiff); - // Flush and remove all regions with the older syncpoint. - lock (_lock) - { - for (int i = 0; i < rangeCount; i++) - { - BufferModifiedRange overlap = overlaps[i]; - - long diff = (long)(overlap.SyncNumber - currentSync); - - if (diff <= highestDiff) - { - ulong clampAddress = Math.Max(address, overlap.Address); - ulong clampEnd = Math.Min(endAddress, overlap.EndAddress); - - ClearPart(overlap, clampAddress, clampEnd); - - rangeAction(clampAddress, clampEnd - clampAddress); - } - } - } + RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress); } /// /// Inherit ranges from another modified range list. /// /// The range list to inherit from - /// The action to call for each modified range - public void InheritRanges(BufferModifiedRangeList ranges, Action rangeAction) + /// The action to call for each modified range + public void InheritRanges(BufferModifiedRangeList ranges, Action registerRangeAction) { BufferModifiedRange[] inheritRanges; lock (ranges._lock) { - inheritRanges = ranges.ToArray(); - } + BufferMigration migration = new(ranges._parent, ranges._flushAction, ranges, this, _context.SyncNumber); - lock (_lock) - { - foreach (BufferModifiedRange range in inheritRanges) + ranges._parent.IncrementReferenceCount(); + ranges._migrationTarget = migration; + + _context.RegisterBufferMigration(migration); + + inheritRanges = ranges.ToArray(); + + lock (_lock) { - Add(range); + (_sources ??= new List()).Add(migration); + + foreach (BufferModifiedRange range in inheritRanges) + { + Add(range); + } } } @@ -313,44 +441,20 @@ namespace Ryujinx.Graphics.Gpu.Memory { if (range.SyncNumber != currentSync) { - rangeAction(range.Address, range.Size); + registerRangeAction(range.Address, range.Size); } } } /// - /// Calls the given action for modified ranges that aren't from the current sync number. + /// Removes a source buffer migration, indicating its copy has completed. /// - /// The action to call for each modified range - public void ReregisterRanges(Action rangeAction) + /// The migration to remove + public void RemoveMigration(BufferMigration migration) { - ref var ranges = ref ThreadStaticArray.Get(); - int count; - - // Range list must be consistent for this operation. lock (_lock) { - count = Count; - if (ranges.Length < count) - { - Array.Resize(ref ranges, count); - } - - int i = 0; - foreach (BufferModifiedRange range in this) - { - ranges[i++] = range; - } - } - - ulong currentSync = _context.SyncNumber; - for (int i = 0; i < count; i++) - { - BufferModifiedRange range = ranges[i]; - if (range.SyncNumber != currentSync) - { - rangeAction(range.Address, range.Size); - } + _sources.Remove(migration); } } @@ -362,12 +466,12 @@ namespace Ryujinx.Graphics.Gpu.Memory if (overlap.Address < address) { - Add(new BufferModifiedRange(overlap.Address, address - overlap.Address, overlap.SyncNumber)); + Add(new BufferModifiedRange(overlap.Address, address - overlap.Address, overlap.SyncNumber, overlap.Parent)); } if (overlap.EndAddress > endAddress) { - Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber)); + Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent)); } } diff --git a/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs index e26fe6b6c..63c96fa23 100644 --- a/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs +++ b/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs @@ -238,6 +238,11 @@ namespace Ryujinx.Graphics.OpenGL _sync.Wait(id); } + public ulong GetCurrentSync() + { + return _sync.GetCurrent(); + } + public void Screenshot() { _window.ScreenCaptureRequested = true; diff --git a/Ryujinx.Graphics.OpenGL/Sync.cs b/Ryujinx.Graphics.OpenGL/Sync.cs index de94fd317..58818e6a7 100644 --- a/Ryujinx.Graphics.OpenGL/Sync.cs +++ b/Ryujinx.Graphics.OpenGL/Sync.cs @@ -40,6 +40,37 @@ namespace Ryujinx.Graphics.OpenGL } } + public ulong GetCurrent() + { + lock (_handles) + { + ulong lastHandle = _firstHandle; + + foreach (SyncHandle handle in _handles) + { + lock (handle) + { + if (handle.Handle == IntPtr.Zero) + { + continue; + } + + if (handle.ID > lastHandle) + { + WaitSyncStatus syncResult = GL.ClientWaitSync(handle.Handle, _syncFlags, 0); + + if (syncResult == WaitSyncStatus.AlreadySignaled) + { + lastHandle = handle.ID; + } + } + } + } + + return lastHandle; + } + } + public void Wait(ulong id) { SyncHandle result = null; diff --git a/Ryujinx.Graphics.Vulkan/SyncManager.cs b/Ryujinx.Graphics.Vulkan/SyncManager.cs index a39862d0c..35e3adf1e 100644 --- a/Ryujinx.Graphics.Vulkan/SyncManager.cs +++ b/Ryujinx.Graphics.Vulkan/SyncManager.cs @@ -11,6 +11,7 @@ namespace Ryujinx.Graphics.Vulkan { public ulong ID; public MultiFenceHolder Waitable; + public bool Signalled; } private ulong _firstHandle = 0; @@ -45,6 +46,37 @@ namespace Ryujinx.Graphics.Vulkan } } + public ulong GetCurrent() + { + lock (_handles) + { + ulong lastHandle = _firstHandle; + + foreach (SyncHandle handle in _handles) + { + lock (handle) + { + if (handle.Waitable == null) + { + continue; + } + + if (handle.ID > lastHandle) + { + bool signaled = handle.Signalled || handle.Waitable.WaitForFences(_gd.Api, _device, 0); + if (signaled) + { + lastHandle = handle.ID; + handle.Signalled = true; + } + } + } + } + + return lastHandle; + } + } + public void Wait(ulong id) { SyncHandle result = null; @@ -75,11 +107,15 @@ namespace Ryujinx.Graphics.Vulkan return; } - bool signaled = result.Waitable.WaitForFences(_gd.Api, _device, 1000000000); + bool signaled = result.Signalled || result.Waitable.WaitForFences(_gd.Api, _device, 1000000000); if (!signaled) { Logger.Error?.PrintMsg(LogClass.Gpu, $"VK Sync Object {result.ID} failed to signal within 1000ms. Continuing..."); } + else + { + result.Signalled = true; + } } } } diff --git a/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index 22e303294..5acbe841a 100644 --- a/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -565,6 +565,11 @@ namespace Ryujinx.Graphics.Vulkan _syncManager.Wait(id); } + public ulong GetCurrentSync() + { + return _syncManager.GetCurrent(); + } + public void Screenshot() { _window.ScreenCaptureRequested = true;