From c622b64ace469cd3def86bcb5e62618fc76fa1dc Mon Sep 17 00:00:00 2001 From: ParantezTech <12572227+par274@users.noreply.github.com> Date: Sun, 11 Oct 2026 01:35:59 +0300 Subject: [PATCH] [GPU] Merge consecutive memory barrier requests in a recording buffer --- .../Gpu/Scheduling/RecordingBuffer.cs | 81 ++++++++++++++++++- .../Gpu/Scheduling/SubmissionScheduler.cs | 2 + .../VulkanVideoPresenter.Scheduler.cs | 22 +++++ 3 files changed, 104 insertions(+), 1 deletion(-) diff --git a/src/SharpEmu.Libs/Gpu/Scheduling/RecordingBuffer.cs b/src/SharpEmu.Libs/Gpu/Scheduling/RecordingBuffer.cs index ea228a5..6f791f2 100644 --- a/src/SharpEmu.Libs/Gpu/Scheduling/RecordingBuffer.cs +++ b/src/SharpEmu.Libs/Gpu/Scheduling/RecordingBuffer.cs @@ -1,8 +1,23 @@ // Copyright (C) 2026 SharpEmu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +using Silk.NET.Vulkan; + namespace SharpEmu.Libs.Gpu.Scheduling; +public readonly record struct MemoryBarrierRequest( + PipelineStageFlags SourceStages, + PipelineStageFlags DestinationStages, + AccessFlags SourceAccess, + AccessFlags DestinationAccess) +{ + public MemoryBarrierRequest Union(in MemoryBarrierRequest other) => new( + SourceStages | other.SourceStages, + DestinationStages | other.DestinationStages, + SourceAccess | other.SourceAccess, + DestinationAccess | other.DestinationAccess); +} + public enum RecordedOperation : uint { DispatchDirect, @@ -23,6 +38,8 @@ public sealed class RecordingBuffer private readonly IRenderingState _rendering; private SubmissionContext? _context; private bool _handleWasRequested; + private MemoryBarrierRequest _pendingBarrier; + private bool _hasPendingBarrier; internal nint Buffer; @@ -51,7 +68,11 @@ public sealed class RecordingBuffer public bool IsInvalid => Buffer == 0; - internal bool HasPendingCommands => _handleWasRequested || _rendering.IsRendering; + internal bool HasPendingCommands => _handleWasRequested || _hasPendingBarrier || _rendering.IsRendering; + + public long MergedBarriers { get; private set; } + + public long RecordedBarriers { get; private set; } // The native handle stays valid only until the next submission. public nint Handle @@ -64,11 +85,68 @@ public sealed class RecordingBuffer } // A caller can record native commands after it obtains the handle. + FlushPendingBarrier(); _handleWasRequested = true; return Buffer; } } + public nint StateHandle + { + get + { + if (IsInvalid) + { + throw SubmissionScheduler.Fatal("The command buffer is not recording."); + } + + _handleWasRequested = true; + return Buffer; + } + } + + public void RequestMemoryBarrier(in MemoryBarrierRequest request) + { + if (IsInvalid || _rendering.IsRendering) + { + throw SubmissionScheduler.Fatal("A memory barrier needs a recording command buffer outside rendering."); + } + + if (_hasPendingBarrier) + { + _pendingBarrier = _pendingBarrier.Union(request); + MergedBarriers++; + if (Vulkan.GpuSyncProfile.Enabled) + { + Vulkan.GpuSyncProfile.CountMergedRequest(); + } + + return; + } + + _pendingBarrier = request; + _hasPendingBarrier = true; + } + + private void FlushPendingBarrier() + { + if (!_hasPendingBarrier) + { + return; + } + + _hasPendingBarrier = false; + if (_rendering.IsRendering) + { + throw SubmissionScheduler.Fatal("A pending memory barrier reached a rendering scope."); + } + + var recorder = Owner.MemoryBarrierRecorder ?? + throw SubmissionScheduler.Fatal("A memory barrier was requested without a recorder."); + recorder(Buffer, _pendingBarrier); + RecordedBarriers++; + } + public SubmissionContext Context => _context ?? throw SubmissionScheduler.Fatal("The command buffer has no submission context."); public void SetDebugInfo(uint op, ulong submitId, uint arg0 = 0, uint arg1 = 0, uint arg2 = 0, uint arg3 = 0, ulong arg4 = 0) @@ -101,6 +179,7 @@ public sealed class RecordingBuffer _device.BeginBuffer(Buffer); _handleWasRequested = false; + _hasPendingBarrier = false; SetDebugInfo((uint)RecordedOperation.Unknown, 0); } diff --git a/src/SharpEmu.Libs/Gpu/Scheduling/SubmissionScheduler.cs b/src/SharpEmu.Libs/Gpu/Scheduling/SubmissionScheduler.cs index 5535658..2a5b2f9 100644 --- a/src/SharpEmu.Libs/Gpu/Scheduling/SubmissionScheduler.cs +++ b/src/SharpEmu.Libs/Gpu/Scheduling/SubmissionScheduler.cs @@ -84,6 +84,8 @@ public sealed class SubmissionScheduler : IGpuTickScheduler, IDisposable public ulong CurrentTick => _timeline.CurrentTick; + public Action? MemoryBarrierRecorder { get; set; } + public TickTimeline Timeline => _timeline; internal bool PriorityWorkerAlive => _priorityThread.IsAlive; diff --git a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Scheduler.cs b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Scheduler.cs index 1a7e29f..a974b1f 100644 --- a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Scheduler.cs +++ b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Scheduler.cs @@ -74,6 +74,18 @@ internal static unsafe partial class VulkanVideoPresenter _ => WakeRenderThread()); } + private void RecordRequestedMemoryBarrier(nint handle, MemoryBarrierRequest request) + { + var barrier = new MemoryBarrier2 + { + SType = StructureType.MemoryBarrier2, + SrcAccessMask = VulkanSynchronization.Access(request.SourceAccess), + DstAccessMask = VulkanSynchronization.Access(request.DestinationAccess), + }; + VulkanSynchronization.PipelineBarrier(_vk, new CommandBuffer(handle), request.SourceStages, request.DestinationStages, + 0, 1, &barrier, 0, null, 0, null); + } + // Both stores share the manager's page guard and read guest memory through its address space. private static (GuestGpuMemory Memory, ICpuMemory Guest, IGuestBackedSpace Backing) RequireGuestMemory(string store) { @@ -167,6 +179,16 @@ internal static unsafe partial class VulkanVideoPresenter return new CommandBuffer(_scheduler.Current.Handle); } + private RecordingBuffer CurrentRecording() + { + if (!_scheduler.Active) + { + _scheduler.Begin(_submissionContext); + } + + return _scheduler.Current; + } + private void BindSubmissionContext(VulkanGuestQueueIdentity queue) { _submissionContext.QueueName = queue.Name;