[GPU] Merge consecutive memory barrier requests in a recording buffer

This commit is contained in:
ParantezTech 2026-10-11 01:35:59 +03:00
parent 0de84e34df
commit c622b64ace
3 changed files with 104 additions and 1 deletions

View File

@ -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);
}

View File

@ -84,6 +84,8 @@ public sealed class SubmissionScheduler : IGpuTickScheduler, IDisposable
public ulong CurrentTick => _timeline.CurrentTick;
public Action<nint, MemoryBarrierRequest>? MemoryBarrierRecorder { get; set; }
public TickTimeline Timeline => _timeline;
internal bool PriorityWorkerAlive => _priorityThread.IsAlive;

View File

@ -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;