mirror of
https://github.com/par274/sharpemu.git
synced 2026-09-02 14:56:49 +08:00
[video] stabilize guest resources
This commit is contained in:
@@ -36,6 +36,7 @@ public static class PerfOverlay
|
|||||||
private static long _presentedInWindow;
|
private static long _presentedInWindow;
|
||||||
private static long _submittedInWindow;
|
private static long _submittedInWindow;
|
||||||
private static long _drawsInWindow;
|
private static long _drawsInWindow;
|
||||||
|
private static long _guestBufferCacheBytes;
|
||||||
|
|
||||||
// Refreshed once per second so per-frame fills never allocate.
|
// Refreshed once per second so per-frame fills never allocate.
|
||||||
private static long _statsWindowStart = Stopwatch.GetTimestamp();
|
private static long _statsWindowStart = Stopwatch.GetTimestamp();
|
||||||
@@ -74,11 +75,8 @@ public static class PerfOverlay
|
|||||||
if (last != 0)
|
if (last != 0)
|
||||||
{
|
{
|
||||||
var milliseconds = (now - last) * 1000.0 / Stopwatch.Frequency;
|
var milliseconds = (now - last) * 1000.0 / Stopwatch.Frequency;
|
||||||
if (milliseconds < 1000.0)
|
_frameMilliseconds[_frameHistoryIndex] = milliseconds;
|
||||||
{
|
_frameHistoryIndex = (_frameHistoryIndex + 1) % FrameHistorySize;
|
||||||
_frameMilliseconds[_frameHistoryIndex] = milliseconds;
|
|
||||||
_frameHistoryIndex = (_frameHistoryIndex + 1) % FrameHistorySize;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +86,9 @@ public static class PerfOverlay
|
|||||||
/// <summary>Called per translated draw/dispatch executed.</summary>
|
/// <summary>Called per translated draw/dispatch executed.</summary>
|
||||||
public static void RecordDraw() => Interlocked.Increment(ref _drawsInWindow);
|
public static void RecordDraw() => Interlocked.Increment(ref _drawsInWindow);
|
||||||
|
|
||||||
|
public static void SetGuestBufferCacheBytes(ulong bytes) =>
|
||||||
|
Interlocked.Exchange(ref _guestBufferCacheBytes, checked((long)bytes));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Rasterizes the panel into a BGRA byte span of PanelWidth x PanelHeight.
|
/// Rasterizes the panel into a BGRA byte span of PanelWidth x PanelHeight.
|
||||||
/// Runs on the render thread.
|
/// Runs on the render thread.
|
||||||
@@ -165,7 +166,7 @@ public static class PerfOverlay
|
|||||||
Environment.ProcessorCount;
|
Environment.ProcessorCount;
|
||||||
_lastCpuTime = cpuTime;
|
_lastCpuTime = cpuTime;
|
||||||
|
|
||||||
var drawsPerFrame = _fps > 0.5 ? _drawsPerSecond / _fps : 0;
|
var drawsPerFrame = _fps > 0 ? _drawsPerSecond / _fps : 0;
|
||||||
var sessionStart = Interlocked.Read(ref _sessionStartTimestamp);
|
var sessionStart = Interlocked.Read(ref _sessionStartTimestamp);
|
||||||
var elapsedSeconds = sessionStart == 0
|
var elapsedSeconds = sessionStart == 0
|
||||||
? 0L
|
? 0L
|
||||||
@@ -176,7 +177,9 @@ public static class PerfOverlay
|
|||||||
_line1 = $"FPS {_fps:0.0} FLIP {_submittedFps:0.0} {_averageFrameMs:0.0} MS";
|
_line1 = $"FPS {_fps:0.0} FLIP {_submittedFps:0.0} {_averageFrameMs:0.0} MS";
|
||||||
_line2 = $"DRAWS {_drawsPerSecond:0}/S {drawsPerFrame:0}/F Q {pendingWork}+{inFlightSubmissions}";
|
_line2 = $"DRAWS {_drawsPerSecond:0}/S {drawsPerFrame:0}/F Q {pendingWork}+{inFlightSubmissions}";
|
||||||
_line3 = $"ALLOC {_allocatedMbPerSecond:0.0} MB/S GC {_gen0PerWindow}/{_gen1PerWindow}/{_gen2PerWindow}";
|
_line3 = $"ALLOC {_allocatedMbPerSecond:0.0} MB/S GC {_gen0PerWindow}/{_gen1PerWindow}/{_gen2PerWindow}";
|
||||||
_line4 = $"CPU {_cpuPercent:0}% HEAP {GC.GetTotalMemory(false) / (1024 * 1024)} MB F1 HIDE";
|
var heapMb = GC.GetTotalMemory(false) / (1024 * 1024);
|
||||||
|
var guestBufferMb = Interlocked.Read(ref _guestBufferCacheBytes) / (1024 * 1024);
|
||||||
|
_line4 = $"MEM {heapMb}M BUF {guestBufferMb}M CPU {_cpuPercent:0}%";
|
||||||
_line5 = $"TIME {elapsedHours:00}:{elapsedMinutes:00}:{elapsedRemainingSeconds:00}";
|
_line5 = $"TIME {elapsedHours:00}:{elapsedMinutes:00}:{elapsedRemainingSeconds:00}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2412,7 +2412,9 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryTakeGuestWork(out PendingGuestWork work)
|
private static bool TryTakeGuestWork(
|
||||||
|
out PendingGuestWork work,
|
||||||
|
HashSet<string>? excludedQueues = null)
|
||||||
{
|
{
|
||||||
lock (_gate)
|
lock (_gate)
|
||||||
{
|
{
|
||||||
@@ -2425,6 +2427,14 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
}
|
}
|
||||||
|
|
||||||
var queueName = _pendingGuestQueueSchedule[_pendingGuestQueueCursor];
|
var queueName = _pendingGuestQueueSchedule[_pendingGuestQueueCursor];
|
||||||
|
if (excludedQueues?.Contains(queueName) == true)
|
||||||
|
{
|
||||||
|
_pendingGuestQueueCursor =
|
||||||
|
(_pendingGuestQueueCursor + 1) % _pendingGuestQueueSchedule.Count;
|
||||||
|
queuesToProbe--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!_pendingGuestWorkByQueue.TryGetValue(queueName, out var queue) ||
|
if (!_pendingGuestWorkByQueue.TryGetValue(queueName, out var queue) ||
|
||||||
queue.First is not { } first)
|
queue.First is not { } first)
|
||||||
{
|
{
|
||||||
@@ -2466,6 +2476,32 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool RequeueGuestWorkFront(in PendingGuestWork work)
|
||||||
|
{
|
||||||
|
lock (_gate)
|
||||||
|
{
|
||||||
|
if (_closed)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_pendingGuestWorkByQueue.TryGetValue(work.Queue.Name, out var queue))
|
||||||
|
{
|
||||||
|
queue = new LinkedList<PendingGuestWork>();
|
||||||
|
_pendingGuestWorkByQueue.Add(work.Queue.Name, queue);
|
||||||
|
_pendingGuestQueueSchedule.Add(work.Queue.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryTakeGuestWork removes only the item count. Payload ownership
|
||||||
|
// remains live until CompleteGuestWork, so requeueing must not add
|
||||||
|
// the retained-byte total a second time.
|
||||||
|
queue.AddFirst(work);
|
||||||
|
_pendingGuestWorkCount++;
|
||||||
|
System.Threading.Monitor.PulseAll(_gate);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void CompleteGuestWork(in PendingGuestWork pending)
|
private static void CompleteGuestWork(in PendingGuestWork pending)
|
||||||
{
|
{
|
||||||
SharpEmu.HLE.GuestImageWriteTracker.FlushPendingDiagnostics();
|
SharpEmu.HLE.GuestImageWriteTracker.FlushPendingDiagnostics();
|
||||||
@@ -2875,11 +2911,14 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
DescriptorSetLayout DescriptorSetLayout,
|
DescriptorSetLayout DescriptorSetLayout,
|
||||||
PipelineLayout PipelineLayout);
|
PipelineLayout PipelineLayout);
|
||||||
|
|
||||||
private readonly record struct DirtyGuestBufferRange(ulong Offset, ulong Length);
|
private readonly record struct DirtyGuestBufferRange(
|
||||||
|
ulong Offset,
|
||||||
|
ulong Length,
|
||||||
|
string QueueName,
|
||||||
|
ulong Timeline);
|
||||||
|
|
||||||
private sealed class GuestBufferAllocation
|
private sealed class GuestBufferAllocation
|
||||||
{
|
{
|
||||||
public string QueueName = VulkanGuestQueueIdentity.Default.Name;
|
|
||||||
public ulong BaseAddress;
|
public ulong BaseAddress;
|
||||||
public ulong Size;
|
public ulong Size;
|
||||||
public VkBuffer Buffer;
|
public VkBuffer Buffer;
|
||||||
@@ -2890,8 +2929,6 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
public List<DirtyGuestBufferRange> DirtyRanges { get; } = [];
|
public List<DirtyGuestBufferRange> DirtyRanges { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string SharedReadOnlyGuestBufferQueue = "shared.readonly";
|
|
||||||
|
|
||||||
private sealed class TranslatedDrawResources
|
private sealed class TranslatedDrawResources
|
||||||
{
|
{
|
||||||
public string DebugName = "SharpEmu translated";
|
public string DebugName = "SharpEmu translated";
|
||||||
@@ -4795,7 +4832,9 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
MarkGuestBufferDirty(
|
MarkGuestBufferDirty(
|
||||||
allocation,
|
allocation,
|
||||||
globalBuffer.GuestOffset,
|
globalBuffer.GuestOffset,
|
||||||
globalBuffer.GuestSize);
|
globalBuffer.GuestSize,
|
||||||
|
_activeGuestQueue.Name,
|
||||||
|
_submitTimeline);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4991,7 +5030,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WaitForActiveGuestQueueSubmissionsForCpuVisibility()
|
private bool TryMakeActiveGuestQueueSubmissionsCpuVisible()
|
||||||
{
|
{
|
||||||
FlushBatchedGuestCommands();
|
FlushBatchedGuestCommands();
|
||||||
if (!_lastSubmittedTimelineByGuestQueue.TryGetValue(
|
if (!_lastSubmittedTimelineByGuestQueue.TryGetValue(
|
||||||
@@ -4999,7 +5038,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
out var targetTimeline) ||
|
out var targetTimeline) ||
|
||||||
targetTimeline <= _completedTimeline)
|
targetTimeline <= _completedTimeline)
|
||||||
{
|
{
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
PendingGuestSubmission? target = null;
|
PendingGuestSubmission? target = null;
|
||||||
@@ -5019,27 +5058,77 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
$"{targetTimeline} (completed={_completedTimeline}).");
|
$"{targetTimeline} (completed={_completedTimeline}).");
|
||||||
}
|
}
|
||||||
|
|
||||||
var waitStart = System.Diagnostics.Stopwatch.GetTimestamp();
|
|
||||||
var fence = target.Fence;
|
var fence = target.Fence;
|
||||||
Check(
|
var status = _vk.GetFenceStatus(_device, fence);
|
||||||
_vk.WaitForFences(_device, 1, &fence, true, ulong.MaxValue),
|
if (status == Result.NotReady)
|
||||||
$"vkWaitForFences(queue visibility: {_activeGuestQueue.Name})");
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == Result.ErrorDeviceLost)
|
||||||
|
{
|
||||||
|
_deviceLost = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Check(status, $"vkGetFenceStatus(queue visibility: {_activeGuestQueue.Name})");
|
||||||
CollectCompletedGuestSubmissions(waitForOldest: false);
|
CollectCompletedGuestSubmissions(waitForOldest: false);
|
||||||
var waitedMs = (System.Diagnostics.Stopwatch.GetTimestamp() - waitStart) *
|
|
||||||
1000.0 / System.Diagnostics.Stopwatch.Frequency;
|
|
||||||
if (_traceVulkanShaderEnabled)
|
if (_traceVulkanShaderEnabled)
|
||||||
{
|
{
|
||||||
TraceVulkanShader(
|
TraceVulkanShader(
|
||||||
$"vk.queue_visibility queue={_activeGuestQueue.Name} " +
|
$"vk.queue_visibility queue={_activeGuestQueue.Name} " +
|
||||||
$"submission={_activeGuestQueue.SubmissionId} " +
|
$"submission={_activeGuestQueue.SubmissionId} " +
|
||||||
$"target_timeline={targetTimeline} completed_timeline={_completedTimeline} " +
|
$"target_timeline={targetTimeline} completed_timeline={_completedTimeline}");
|
||||||
$"waited_ms={waitedMs:F3}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExecuteOrderedGuestAction(VulkanOrderedGuestAction work)
|
private void WaitForGuestBufferAllocationForCpuVisibility(
|
||||||
|
GuestBufferAllocation allocation)
|
||||||
{
|
{
|
||||||
WaitForActiveGuestQueueSubmissionsForCpuVisibility();
|
if (IsGuestBufferAllocationReferencedByOpenBatch(allocation))
|
||||||
|
{
|
||||||
|
FlushBatchedGuestCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetTimeline = allocation.LastUseTimeline;
|
||||||
|
if (targetTimeline <= _completedTimeline)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingGuestSubmission? target = null;
|
||||||
|
foreach (var submission in _pendingGuestSubmissions)
|
||||||
|
{
|
||||||
|
if (submission.Timeline == targetTimeline)
|
||||||
|
{
|
||||||
|
target = submission;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target is null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Guest buffer 0x{allocation.BaseAddress:X16} lost pending timeline " +
|
||||||
|
$"{targetTimeline} (completed={_completedTimeline}).");
|
||||||
|
}
|
||||||
|
|
||||||
|
var fence = target.Fence;
|
||||||
|
Check(
|
||||||
|
_vk.WaitForFences(_device, 1, &fence, true, ulong.MaxValue),
|
||||||
|
$"vkWaitForFences(buffer visibility: 0x{allocation.BaseAddress:X16})");
|
||||||
|
CollectCompletedGuestSubmissions(waitForOldest: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryExecuteOrderedGuestAction(VulkanOrderedGuestAction work)
|
||||||
|
{
|
||||||
|
if (!TryMakeActiveGuestQueueSubmissionsCpuVisible())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
WriteBackAllDirtyGuestBuffers(_activeGuestQueue.Name);
|
WriteBackAllDirtyGuestBuffers(_activeGuestQueue.Name);
|
||||||
work.Action();
|
work.Action();
|
||||||
if (_traceVulkanShaderEnabled)
|
if (_traceVulkanShaderEnabled)
|
||||||
@@ -5049,6 +5138,8 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
$"submission={_activeGuestQueue.SubmissionId} " +
|
$"submission={_activeGuestQueue.SubmissionId} " +
|
||||||
$"work_sequence={_activeGuestWorkSequence} name='{work.DebugName}'");
|
$"work_sequence={_activeGuestWorkSequence} name='{work.DebugName}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExecuteOrderedGuestFlip(VulkanOrderedGuestFlip work)
|
private void ExecuteOrderedGuestFlip(VulkanOrderedGuestFlip work)
|
||||||
@@ -8186,9 +8277,6 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
var size = (ulong)Math.Max(guestBuffer.Length, sizeof(uint));
|
var size = (ulong)Math.Max(guestBuffer.Length, sizeof(uint));
|
||||||
var endAddress = checked(guestBuffer.BaseAddress + size);
|
var endAddress = checked(guestBuffer.BaseAddress + size);
|
||||||
GuestBufferAllocation? allocation = null;
|
GuestBufferAllocation? allocation = null;
|
||||||
var allocationPriority = -1;
|
|
||||||
// This runs for every bound global buffer. Preserve the previous
|
|
||||||
// stable queue preference without allocating LINQ sort state.
|
|
||||||
foreach (var candidate in _guestBufferAllocations)
|
foreach (var candidate in _guestBufferAllocations)
|
||||||
{
|
{
|
||||||
if (candidate.BaseAddress > guestBuffer.BaseAddress ||
|
if (candidate.BaseAddress > guestBuffer.BaseAddress ||
|
||||||
@@ -8197,22 +8285,8 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var candidatePriority = string.Equals(
|
allocation = candidate;
|
||||||
candidate.QueueName,
|
break;
|
||||||
_activeGuestQueue.Name,
|
|
||||||
StringComparison.Ordinal)
|
|
||||||
? 2
|
|
||||||
: string.Equals(
|
|
||||||
candidate.QueueName,
|
|
||||||
SharedReadOnlyGuestBufferQueue,
|
|
||||||
StringComparison.Ordinal)
|
|
||||||
? 1
|
|
||||||
: 0;
|
|
||||||
if (candidatePriority > allocationPriority)
|
|
||||||
{
|
|
||||||
allocation = candidate;
|
|
||||||
allocationPriority = candidatePriority;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allocation is null)
|
if (allocation is null)
|
||||||
@@ -8247,11 +8321,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
var shadow = allocation.Shadow.AsSpan(checked((int)guestOffset), guestBuffer.Length);
|
var shadow = allocation.Shadow.AsSpan(checked((int)guestOffset), guestBuffer.Length);
|
||||||
if (!source.SequenceEqual(shadow))
|
if (!source.SequenceEqual(shadow))
|
||||||
{
|
{
|
||||||
var sharedReadOnly = string.Equals(
|
if (!guestBuffer.Writable &&
|
||||||
allocation.QueueName,
|
|
||||||
SharedReadOnlyGuestBufferQueue,
|
|
||||||
StringComparison.Ordinal);
|
|
||||||
if (sharedReadOnly &&
|
|
||||||
(allocation.LastUseTimeline > _completedTimeline ||
|
(allocation.LastUseTimeline > _completedTimeline ||
|
||||||
IsGuestBufferAllocationReferencedByOpenBatch(allocation)))
|
IsGuestBufferAllocationReferencedByOpenBatch(allocation)))
|
||||||
{
|
{
|
||||||
@@ -8265,14 +8335,8 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
// an in-flight shader access. Retire prior users, publish their
|
// an in-flight shader access. Retire prior users, publish their
|
||||||
// dirty ranges to guest memory, then upload the current guest
|
// dirty ranges to guest memory, then upload the current guest
|
||||||
// bytes (which may be newer than the parser's captured array).
|
// bytes (which may be newer than the parser's captured array).
|
||||||
if (!sharedReadOnly)
|
WaitForGuestBufferAllocationForCpuVisibility(allocation);
|
||||||
{
|
WriteBackAllDirtyGuestBuffers();
|
||||||
// Writable aliases are private to one logical guest queue.
|
|
||||||
// Retiring unrelated queues here recreates the global FIFO
|
|
||||||
// and turns routine buffer refreshes into queue-wide stalls.
|
|
||||||
WaitForActiveGuestQueueSubmissionsForCpuVisibility();
|
|
||||||
WriteBackAllDirtyGuestBuffers(_activeGuestQueue.Name);
|
|
||||||
}
|
|
||||||
// Populate the cached shadow copy first and write it out to the
|
// Populate the cached shadow copy first and write it out to the
|
||||||
// mapped allocation in one pass. The mapped memory is
|
// mapped allocation in one pass. The mapped memory is
|
||||||
// HOST_VISIBLE|HOST_COHERENT (write-combined on most drivers),
|
// HOST_VISIBLE|HOST_COHERENT (write-combined on most drivers),
|
||||||
@@ -8418,7 +8482,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ranges = new List<(ulong Start, ulong End, bool Writable)>(buffers.Count);
|
var ranges = new List<(ulong Start, ulong End)>(buffers.Count);
|
||||||
foreach (var buffer in buffers)
|
foreach (var buffer in buffers)
|
||||||
{
|
{
|
||||||
if (buffer.BaseAddress == 0)
|
if (buffer.BaseAddress == 0)
|
||||||
@@ -8432,8 +8496,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
var paddedEnd = checked(buffer.BaseAddress + size + 3) & ~3UL;
|
var paddedEnd = checked(buffer.BaseAddress + size + 3) & ~3UL;
|
||||||
ranges.Add((
|
ranges.Add((
|
||||||
alignedStart,
|
alignedStart,
|
||||||
paddedEnd,
|
paddedEnd));
|
||||||
buffer.Writable && buffer.WriteBackToGuest));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ranges.Count == 0)
|
if (ranges.Count == 0)
|
||||||
@@ -8442,7 +8505,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
}
|
}
|
||||||
|
|
||||||
ranges.Sort(static (left, right) => left.Start.CompareTo(right.Start));
|
ranges.Sort(static (left, right) => left.Start.CompareTo(right.Start));
|
||||||
var merged = new List<(ulong Start, ulong End, bool Writable)>(ranges.Count);
|
var merged = new List<(ulong Start, ulong End)>(ranges.Count);
|
||||||
foreach (var range in ranges)
|
foreach (var range in ranges)
|
||||||
{
|
{
|
||||||
if (merged.Count == 0 || range.Start > merged[^1].End)
|
if (merged.Count == 0 || range.Start > merged[^1].End)
|
||||||
@@ -8454,25 +8517,18 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
var previous = merged[^1];
|
var previous = merged[^1];
|
||||||
merged[^1] = (
|
merged[^1] = (
|
||||||
previous.Start,
|
previous.Start,
|
||||||
Math.Max(previous.End, range.End),
|
Math.Max(previous.End, range.End));
|
||||||
previous.Writable || range.Writable);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var range in merged)
|
foreach (var range in merged)
|
||||||
{
|
{
|
||||||
EnsureGuestBufferAllocation(
|
EnsureGuestBufferAllocation(range.Start, range.End);
|
||||||
range.Start,
|
|
||||||
range.End,
|
|
||||||
range.Writable
|
|
||||||
? _activeGuestQueue.Name
|
|
||||||
: SharedReadOnlyGuestBufferQueue);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsureGuestBufferAllocation(
|
private void EnsureGuestBufferAllocation(
|
||||||
ulong requestedStart,
|
ulong requestedStart,
|
||||||
ulong requestedEnd,
|
ulong requestedEnd)
|
||||||
string queueName)
|
|
||||||
{
|
{
|
||||||
var start = requestedStart;
|
var start = requestedStart;
|
||||||
var end = requestedEnd;
|
var end = requestedEnd;
|
||||||
@@ -8481,10 +8537,6 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
{
|
{
|
||||||
overlaps = _guestBufferAllocations
|
overlaps = _guestBufferAllocations
|
||||||
.Where(allocation =>
|
.Where(allocation =>
|
||||||
string.Equals(
|
|
||||||
allocation.QueueName,
|
|
||||||
queueName,
|
|
||||||
StringComparison.Ordinal) &&
|
|
||||||
allocation.BaseAddress < end &&
|
allocation.BaseAddress < end &&
|
||||||
start < allocation.BaseAddress + allocation.Size)
|
start < allocation.BaseAddress + allocation.Size)
|
||||||
.ToList();
|
.ToList();
|
||||||
@@ -8521,7 +8573,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
WriteBackAllDirtyGuestBuffers();
|
WriteBackAllDirtyGuestBuffers();
|
||||||
}
|
}
|
||||||
|
|
||||||
var replacement = CreateGuestBufferAllocation(start, end, queueName);
|
var replacement = CreateGuestBufferAllocation(start, end);
|
||||||
foreach (var overlap in overlaps)
|
foreach (var overlap in overlaps)
|
||||||
{
|
{
|
||||||
_guestBufferAllocations.Remove(overlap);
|
_guestBufferAllocations.Remove(overlap);
|
||||||
@@ -8530,22 +8582,27 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
|
|
||||||
_guestBufferAllocations.Add(replacement);
|
_guestBufferAllocations.Add(replacement);
|
||||||
_guestBufferAllocations.Sort(static (left, right) =>
|
_guestBufferAllocations.Sort(static (left, right) =>
|
||||||
{
|
left.BaseAddress.CompareTo(right.BaseAddress));
|
||||||
var queueOrder = string.CompareOrdinal(left.QueueName, right.QueueName);
|
UpdateGuestBufferCacheMetric();
|
||||||
return queueOrder != 0
|
|
||||||
? queueOrder
|
|
||||||
: left.BaseAddress.CompareTo(right.BaseAddress);
|
|
||||||
});
|
|
||||||
TraceVulkanShader(
|
TraceVulkanShader(
|
||||||
$"vk.guest_buffer_allocation queue={queueName} " +
|
$"vk.guest_buffer_allocation base=0x{start:X16} bytes={replacement.Size} " +
|
||||||
$"base=0x{start:X16} bytes={replacement.Size} " +
|
|
||||||
$"merged={overlaps.Count}");
|
$"merged={overlaps.Count}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateGuestBufferCacheMetric()
|
||||||
|
{
|
||||||
|
var bytes = 0UL;
|
||||||
|
foreach (var allocation in _guestBufferAllocations)
|
||||||
|
{
|
||||||
|
bytes = checked(bytes + allocation.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
PerfOverlay.SetGuestBufferCacheBytes(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
private GuestBufferAllocation CreateGuestBufferAllocation(
|
private GuestBufferAllocation CreateGuestBufferAllocation(
|
||||||
ulong start,
|
ulong start,
|
||||||
ulong end,
|
ulong end)
|
||||||
string queueName)
|
|
||||||
{
|
{
|
||||||
var size = checked(end - start);
|
var size = checked(end - start);
|
||||||
if (size == 0 || size > int.MaxValue)
|
if (size == 0 || size > int.MaxValue)
|
||||||
@@ -8571,7 +8628,6 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
$"SharpEmu guest VA 0x{start:X16}-0x{end:X16}");
|
$"SharpEmu guest VA 0x{start:X16}-0x{end:X16}");
|
||||||
return new GuestBufferAllocation
|
return new GuestBufferAllocation
|
||||||
{
|
{
|
||||||
QueueName = queueName,
|
|
||||||
BaseAddress = start,
|
BaseAddress = start,
|
||||||
Size = size,
|
Size = size,
|
||||||
Buffer = buffer,
|
Buffer = buffer,
|
||||||
@@ -9577,15 +9633,6 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
|
|
||||||
MarkSampledImagesInitialized(resources);
|
MarkSampledImagesInitialized(resources);
|
||||||
MarkStorageImagesInitialized(resources, traceContents: false);
|
MarkStorageImagesInitialized(resources, traceContents: false);
|
||||||
if (work.WritesGlobalMemory)
|
|
||||||
{
|
|
||||||
// The CPU submit thread may immediately consume an indirect
|
|
||||||
// argument written by this dispatch. Wait for the specific
|
|
||||||
// guest fences and publish only dirty ranges; a queue-wide
|
|
||||||
// idle unnecessarily serialized presentation work too.
|
|
||||||
WaitForActiveGuestQueueSubmissionsForCpuVisibility();
|
|
||||||
WriteBackAllDirtyGuestBuffers(_activeGuestQueue.Name);
|
|
||||||
}
|
|
||||||
TraceVulkanShader(
|
TraceVulkanShader(
|
||||||
$"vk.compute_dispatch groups={work.GroupCountX}x" +
|
$"vk.compute_dispatch groups={work.GroupCountX}x" +
|
||||||
$"{work.GroupCountY}x{work.GroupCountZ} " +
|
$"{work.GroupCountY}x{work.GroupCountZ} " +
|
||||||
@@ -9815,7 +9862,9 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
private static void MarkGuestBufferDirty(
|
private static void MarkGuestBufferDirty(
|
||||||
GuestBufferAllocation allocation,
|
GuestBufferAllocation allocation,
|
||||||
ulong offset,
|
ulong offset,
|
||||||
ulong length)
|
ulong length,
|
||||||
|
string queueName,
|
||||||
|
ulong timeline)
|
||||||
{
|
{
|
||||||
if (length == 0)
|
if (length == 0)
|
||||||
{
|
{
|
||||||
@@ -9827,6 +9876,11 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
for (var index = allocation.DirtyRanges.Count - 1; index >= 0; index--)
|
for (var index = allocation.DirtyRanges.Count - 1; index >= 0; index--)
|
||||||
{
|
{
|
||||||
var existing = allocation.DirtyRanges[index];
|
var existing = allocation.DirtyRanges[index];
|
||||||
|
if (!string.Equals(existing.QueueName, queueName, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var existingEnd = existing.Offset + existing.Length;
|
var existingEnd = existing.Offset + existing.Length;
|
||||||
if (end < existing.Offset || existingEnd < start)
|
if (end < existing.Offset || existingEnd < start)
|
||||||
{
|
{
|
||||||
@@ -9835,10 +9889,12 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
|
|
||||||
start = Math.Min(start, existing.Offset);
|
start = Math.Min(start, existing.Offset);
|
||||||
end = Math.Max(end, existingEnd);
|
end = Math.Max(end, existingEnd);
|
||||||
|
timeline = Math.Max(timeline, existing.Timeline);
|
||||||
allocation.DirtyRanges.RemoveAt(index);
|
allocation.DirtyRanges.RemoveAt(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
allocation.DirtyRanges.Add(new DirtyGuestBufferRange(start, end - start));
|
allocation.DirtyRanges.Add(
|
||||||
|
new DirtyGuestBufferRange(start, end - start, queueName, timeline));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteBackAllDirtyGuestBuffers(string? queueName = null)
|
private void WriteBackAllDirtyGuestBuffers(string? queueName = null)
|
||||||
@@ -9851,33 +9907,51 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
|
|
||||||
foreach (var allocation in _guestBufferAllocations)
|
foreach (var allocation in _guestBufferAllocations)
|
||||||
{
|
{
|
||||||
if (queueName is not null &&
|
|
||||||
!string.Equals(allocation.QueueName, queueName, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allocation.DirtyRanges.Count != 0 &&
|
|
||||||
allocation.LastUseTimeline > _completedTimeline)
|
|
||||||
{
|
|
||||||
// A mapped HOST_COHERENT allocation still cannot be read
|
|
||||||
// by the CPU while a shader may be writing it. Callers
|
|
||||||
// normally retire the relevant fences first; keep this
|
|
||||||
// helper fail-closed if a future path forgets to do so.
|
|
||||||
TraceVulkanShader(
|
|
||||||
$"vk.global_writeback_deferred base=0x{allocation.BaseAddress:X16} " +
|
|
||||||
$"last_use={allocation.LastUseTimeline} completed={_completedTimeline}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var index = allocation.DirtyRanges.Count - 1; index >= 0; index--)
|
for (var index = allocation.DirtyRanges.Count - 1; index >= 0; index--)
|
||||||
{
|
{
|
||||||
var range = allocation.DirtyRanges[index];
|
var range = allocation.DirtyRanges[index];
|
||||||
|
if ((queueName is not null &&
|
||||||
|
!string.Equals(range.QueueName, queueName, StringComparison.Ordinal)) ||
|
||||||
|
range.Timeline > _completedTimeline)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (range.Length == 0 || range.Length > int.MaxValue)
|
if (range.Length == 0 || range.Length > int.MaxValue)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var rangeEnd = checked(range.Offset + range.Length);
|
||||||
|
var overlapsInFlightWrite = false;
|
||||||
|
for (var otherIndex = 0;
|
||||||
|
otherIndex < allocation.DirtyRanges.Count;
|
||||||
|
otherIndex++)
|
||||||
|
{
|
||||||
|
if (otherIndex == index)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var other = allocation.DirtyRanges[otherIndex];
|
||||||
|
if (other.Timeline <= _completedTimeline)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var otherEnd = checked(other.Offset + other.Length);
|
||||||
|
if (range.Offset < otherEnd && other.Offset < rangeEnd)
|
||||||
|
{
|
||||||
|
overlapsInFlightWrite = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overlapsInFlightWrite)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var mappedBytes = new ReadOnlySpan<byte>(
|
var mappedBytes = new ReadOnlySpan<byte>(
|
||||||
(void*)(allocation.Mapped + checked((nint)range.Offset)),
|
(void*)(allocation.Mapped + checked((nint)range.Offset)),
|
||||||
checked((int)range.Length));
|
checked((int)range.Length));
|
||||||
@@ -9907,6 +9981,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
const int pageSize = 4096;
|
const int pageSize = 4096;
|
||||||
const int unreadableMergeGap = 16;
|
const int unreadableMergeGap = 16;
|
||||||
var livePageBuffer = GuestDataPool.Shared.Rent(pageSize);
|
var livePageBuffer = GuestDataPool.Shared.Rent(pageSize);
|
||||||
|
var mappedPageBuffer = GuestDataPool.Shared.Rent(pageSize);
|
||||||
var pageRuns = new List<(int Start, int Length)>(64);
|
var pageRuns = new List<(int Start, int Length)>(64);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -9915,35 +9990,49 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
pageStart += pageSize)
|
pageStart += pageSize)
|
||||||
{
|
{
|
||||||
var pageEnd = Math.Min(pageStart + pageSize, mappedBytes.Length);
|
var pageEnd = Math.Min(pageStart + pageSize, mappedBytes.Length);
|
||||||
pageRuns.Clear();
|
var pageLength = pageEnd - pageStart;
|
||||||
var cursor = pageStart;
|
var mappedPageSource = mappedBytes.Slice(pageStart, pageLength);
|
||||||
while (cursor < pageEnd)
|
var shadowPage = shadowBytes.Slice(pageStart, pageLength);
|
||||||
|
if (mappedPageSource.SequenceEqual(shadowPage))
|
||||||
{
|
{
|
||||||
while (cursor < pageEnd &&
|
continue;
|
||||||
mappedBytes[cursor] == shadowBytes[cursor])
|
}
|
||||||
|
|
||||||
|
// HOST_COHERENT mappings are commonly uncached or
|
||||||
|
// write-combined on the CPU. Read each changed page
|
||||||
|
// once with a bulk copy, then perform the byte-level
|
||||||
|
// merge against ordinary cached memory.
|
||||||
|
var mappedPage = mappedPageBuffer.AsSpan(0, pageLength);
|
||||||
|
mappedPageSource.CopyTo(mappedPage);
|
||||||
|
pageRuns.Clear();
|
||||||
|
var cursor = 0;
|
||||||
|
while (cursor < pageLength)
|
||||||
|
{
|
||||||
|
while (cursor < pageLength &&
|
||||||
|
mappedPage[cursor] == shadowPage[cursor])
|
||||||
{
|
{
|
||||||
cursor++;
|
cursor++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cursor == pageEnd)
|
if (cursor == pageLength)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var runStart = cursor;
|
var runStart = cursor;
|
||||||
while (cursor < pageEnd &&
|
while (cursor < pageLength &&
|
||||||
mappedBytes[cursor] != shadowBytes[cursor])
|
mappedPage[cursor] != shadowPage[cursor])
|
||||||
{
|
{
|
||||||
cursor++;
|
cursor++;
|
||||||
}
|
}
|
||||||
|
|
||||||
var runLength = cursor - runStart;
|
var runLength = cursor - runStart;
|
||||||
pageRuns.Add((runStart, runLength));
|
pageRuns.Add((pageStart + runStart, runLength));
|
||||||
changedRuns++;
|
changedRuns++;
|
||||||
changedBytes += (ulong)runLength;
|
changedBytes += (ulong)runLength;
|
||||||
if (firstChangedOffset < 0)
|
if (firstChangedOffset < 0)
|
||||||
{
|
{
|
||||||
firstChangedOffset = runStart;
|
firstChangedOffset = pageStart + runStart;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9953,13 +10042,12 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
}
|
}
|
||||||
|
|
||||||
changedPages++;
|
changedPages++;
|
||||||
var pageLength = pageEnd - pageStart;
|
|
||||||
var livePage = livePageBuffer.AsSpan(0, pageLength);
|
var livePage = livePageBuffer.AsSpan(0, pageLength);
|
||||||
if (memory.TryRead(guestAddress + (ulong)pageStart, livePage))
|
if (memory.TryRead(guestAddress + (ulong)pageStart, livePage))
|
||||||
{
|
{
|
||||||
foreach (var run in pageRuns)
|
foreach (var run in pageRuns)
|
||||||
{
|
{
|
||||||
mappedBytes.Slice(run.Start, run.Length).CopyTo(
|
mappedPage.Slice(run.Start - pageStart, run.Length).CopyTo(
|
||||||
livePage.Slice(run.Start - pageStart, run.Length));
|
livePage.Slice(run.Start - pageStart, run.Length));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9967,7 +10055,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
{
|
{
|
||||||
foreach (var run in pageRuns)
|
foreach (var run in pageRuns)
|
||||||
{
|
{
|
||||||
mappedBytes.Slice(run.Start, run.Length).CopyTo(
|
mappedPage.Slice(run.Start - pageStart, run.Length).CopyTo(
|
||||||
shadowBytes.Slice(run.Start, run.Length));
|
shadowBytes.Slice(run.Start, run.Length));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9982,7 +10070,9 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
MarkGuestBufferDirty(
|
MarkGuestBufferDirty(
|
||||||
allocation,
|
allocation,
|
||||||
range.Offset + (ulong)run.Start,
|
range.Offset + (ulong)run.Start,
|
||||||
(ulong)run.Length);
|
(ulong)run.Length,
|
||||||
|
range.QueueName,
|
||||||
|
range.Timeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -10018,7 +10108,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
overlayIndex++)
|
overlayIndex++)
|
||||||
{
|
{
|
||||||
var run = pageRuns[overlayIndex];
|
var run = pageRuns[overlayIndex];
|
||||||
mappedBytes.Slice(run.Start, run.Length).CopyTo(
|
mappedPage.Slice(run.Start - pageStart, run.Length).CopyTo(
|
||||||
mergedLive.Slice(
|
mergedLive.Slice(
|
||||||
run.Start - mergedStart,
|
run.Start - mergedStart,
|
||||||
run.Length));
|
run.Length));
|
||||||
@@ -10034,7 +10124,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
overlayIndex++)
|
overlayIndex++)
|
||||||
{
|
{
|
||||||
var run = pageRuns[overlayIndex];
|
var run = pageRuns[overlayIndex];
|
||||||
mappedBytes.Slice(run.Start, run.Length).CopyTo(
|
mappedPage.Slice(run.Start - pageStart, run.Length).CopyTo(
|
||||||
shadowBytes.Slice(run.Start, run.Length));
|
shadowBytes.Slice(run.Start, run.Length));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10051,7 +10141,9 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
exactIndex++)
|
exactIndex++)
|
||||||
{
|
{
|
||||||
var run = pageRuns[exactIndex];
|
var run = pageRuns[exactIndex];
|
||||||
var changed = mappedBytes.Slice(run.Start, run.Length);
|
var changed = mappedPage.Slice(
|
||||||
|
run.Start - pageStart,
|
||||||
|
run.Length);
|
||||||
fallbackWrites++;
|
fallbackWrites++;
|
||||||
if (memory.TryWrite(
|
if (memory.TryWrite(
|
||||||
guestAddress + (ulong)run.Start,
|
guestAddress + (ulong)run.Start,
|
||||||
@@ -10068,7 +10160,9 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
MarkGuestBufferDirty(
|
MarkGuestBufferDirty(
|
||||||
allocation,
|
allocation,
|
||||||
range.Offset + (ulong)run.Start,
|
range.Offset + (ulong)run.Start,
|
||||||
(ulong)run.Length);
|
(ulong)run.Length,
|
||||||
|
range.QueueName,
|
||||||
|
range.Timeline);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10077,6 +10171,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
GuestDataPool.Shared.Return(livePageBuffer);
|
GuestDataPool.Shared.Return(livePageBuffer);
|
||||||
|
GuestDataPool.Shared.Return(mappedPageBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
var probe = mappedBytes[..Math.Min(mappedBytes.Length, 256)];
|
var probe = mappedBytes[..Math.Min(mappedBytes.Length, 256)];
|
||||||
@@ -10346,6 +10441,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
}
|
}
|
||||||
GuestDepthResource? depth = null;
|
GuestDepthResource? depth = null;
|
||||||
DepthFramebufferResource? depthFramebuffer = null;
|
DepthFramebufferResource? depthFramebuffer = null;
|
||||||
|
var clearDepthSeparately = false;
|
||||||
if (ShouldAttachGuestDepth(
|
if (ShouldAttachGuestDepth(
|
||||||
work.DepthTarget,
|
work.DepthTarget,
|
||||||
draw.RenderState.Depth) &&
|
draw.RenderState.Depth) &&
|
||||||
@@ -10373,12 +10469,26 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
depth.GuestClearDepth = effectiveDepthTarget.ClearDepth;
|
depth.GuestClearDepth = effectiveDepthTarget.ClearDepth;
|
||||||
depth.ClearDepth = effectiveDepthTarget.ClearDepth;
|
depth.ClearDepth = effectiveDepthTarget.ClearDepth;
|
||||||
}
|
}
|
||||||
if (targets.Length == 1)
|
clearDepthSeparately = clearDepthForDraw &&
|
||||||
|
(depth.Width < firstTarget.Width ||
|
||||||
|
depth.Height < firstTarget.Height);
|
||||||
|
if (targets.Length == 1 && !clearDepthSeparately)
|
||||||
{
|
{
|
||||||
depthFramebuffer = GetOrCreateDepthFramebuffer(firstTarget, depth);
|
depthFramebuffer = GetOrCreateDepthFramebuffer(firstTarget, depth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (depth is not null && !clearDepthSeparately)
|
||||||
|
{
|
||||||
|
// Guest color images may be allocated at their maximum
|
||||||
|
// resolution while the active viewport and DB surface use
|
||||||
|
// a smaller dynamic-rendering extent. Vulkan requires the
|
||||||
|
// framebuffer extent to fit every attachment.
|
||||||
|
extent = new Extent2D(
|
||||||
|
Math.Min(firstTarget.Width, depth.Width),
|
||||||
|
Math.Min(firstTarget.Height, depth.Height));
|
||||||
|
}
|
||||||
|
|
||||||
if (clearDepthForDraw)
|
if (clearDepthForDraw)
|
||||||
{
|
{
|
||||||
// DB_RENDER_CONTROL.DEPTH_CLEAR_ENABLE makes this a DB
|
// DB_RENDER_CONTROL.DEPTH_CLEAR_ENABLE makes this a DB
|
||||||
@@ -10412,17 +10522,18 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
var framebuffer = depthFramebuffer?.Framebuffer ?? firstTarget.Framebuffer;
|
var framebuffer = depthFramebuffer?.Framebuffer ?? firstTarget.Framebuffer;
|
||||||
if (targets.Length > 1)
|
if (targets.Length > 1)
|
||||||
{
|
{
|
||||||
|
var attachedDepth = clearDepthSeparately ? null : depth;
|
||||||
(renderPass, framebuffer) = CreateRenderPassAndFramebuffer(
|
(renderPass, framebuffer) = CreateRenderPassAndFramebuffer(
|
||||||
formats,
|
formats,
|
||||||
targets.Select(target => target.MipViews.Length > 0
|
targets.Select(target => target.MipViews.Length > 0
|
||||||
? target.MipViews[0]
|
? target.MipViews[0]
|
||||||
: target.View).ToArray(),
|
: target.View).ToArray(),
|
||||||
firstTarget.Width,
|
extent.Width,
|
||||||
firstTarget.Height,
|
extent.Height,
|
||||||
targets.Select(target =>
|
targets.Select(target =>
|
||||||
target.Initialized || target.InitialUploadPending).ToArray(),
|
target.Initialized || target.InitialUploadPending).ToArray(),
|
||||||
depth,
|
attachedDepth,
|
||||||
depth?.Initialized == true && !clearDepthForDraw);
|
attachedDepth?.Initialized == true && !clearDepthForDraw);
|
||||||
transientRenderPass = renderPass;
|
transientRenderPass = renderPass;
|
||||||
transientFramebuffer = framebuffer;
|
transientFramebuffer = framebuffer;
|
||||||
}
|
}
|
||||||
@@ -10433,8 +10544,8 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
formats,
|
formats,
|
||||||
extent,
|
extent,
|
||||||
targets,
|
targets,
|
||||||
hasDepthAttachment: depth is not null,
|
hasDepthAttachment: depth is not null && !clearDepthSeparately,
|
||||||
feedbackDepth: depth);
|
feedbackDepth: clearDepthSeparately ? null : depth);
|
||||||
resources.TransientRenderPass = transientRenderPass;
|
resources.TransientRenderPass = transientRenderPass;
|
||||||
resources.TransientFramebuffer = transientFramebuffer;
|
resources.TransientFramebuffer = transientFramebuffer;
|
||||||
transientRenderPass = default;
|
transientRenderPass = default;
|
||||||
@@ -10454,6 +10565,10 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
submitted = true;
|
submitted = true;
|
||||||
|
|
||||||
BeginDebugLabel(_commandBuffer, resources.DebugName);
|
BeginDebugLabel(_commandBuffer, resources.DebugName);
|
||||||
|
if (clearDepthSeparately && depth is not null)
|
||||||
|
{
|
||||||
|
RecordStandaloneGuestDepthClear(depth);
|
||||||
|
}
|
||||||
var hasStorageImages = false;
|
var hasStorageImages = false;
|
||||||
foreach (var texture in resources.Textures)
|
foreach (var texture in resources.Textures)
|
||||||
{
|
{
|
||||||
@@ -10517,6 +10632,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
toColorAttachments);
|
toColorAttachments);
|
||||||
|
|
||||||
if (depth is not null &&
|
if (depth is not null &&
|
||||||
|
!clearDepthSeparately &&
|
||||||
depth.Layout == ImageLayout.ShaderReadOnlyOptimal)
|
depth.Layout == ImageLayout.ShaderReadOnlyOptimal)
|
||||||
{
|
{
|
||||||
var toDepthAttachment = new ImageMemoryBarrier
|
var toDepthAttachment = new ImageMemoryBarrier
|
||||||
@@ -10554,7 +10670,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
framebuffer,
|
framebuffer,
|
||||||
extent,
|
extent,
|
||||||
colorAttachmentCount: targets.Length,
|
colorAttachmentCount: targets.Length,
|
||||||
hasDepthAttachment: depth is not null,
|
hasDepthAttachment: depth is not null && !clearDepthSeparately,
|
||||||
clearDepth: depth?.ClearDepth ?? 1f);
|
clearDepth: depth?.ClearDepth ?? 1f);
|
||||||
RecordTranslatedDrawInPass(resources, extent);
|
RecordTranslatedDrawInPass(resources, extent);
|
||||||
_vk.CmdEndRenderPass(_commandBuffer);
|
_vk.CmdEndRenderPass(_commandBuffer);
|
||||||
@@ -10610,7 +10726,10 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
if (depth is not null)
|
if (depth is not null)
|
||||||
{
|
{
|
||||||
depth.Initialized = true;
|
depth.Initialized = true;
|
||||||
depth.Layout = ImageLayout.DepthStencilAttachmentOptimal;
|
if (!clearDepthSeparately)
|
||||||
|
{
|
||||||
|
depth.Layout = ImageLayout.DepthStencilAttachmentOptimal;
|
||||||
|
}
|
||||||
if (clearDepthForDraw)
|
if (clearDepthForDraw)
|
||||||
{
|
{
|
||||||
depth.InitializationSource = "guest-depth-clear";
|
depth.InitializationSource = "guest-depth-clear";
|
||||||
@@ -11624,14 +11743,6 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (depth.Width < color.Width || depth.Height < color.Height)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"guest depth 0x{depth.Address:X16} extent {depth.Width}x{depth.Height} " +
|
|
||||||
$"is smaller than color target 0x{color.Address:X16} " +
|
|
||||||
$"{color.Width}x{color.Height}");
|
|
||||||
}
|
|
||||||
|
|
||||||
var attachmentView = color.MipViews.Length > 0 ? color.MipViews[0] : color.View;
|
var attachmentView = color.MipViews.Length > 0 ? color.MipViews[0] : color.View;
|
||||||
var loadRenderPass = CreateDepthRenderPass(
|
var loadRenderPass = CreateDepthRenderPass(
|
||||||
color.Format,
|
color.Format,
|
||||||
@@ -11658,8 +11769,8 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
RenderPass = loadRenderPass,
|
RenderPass = loadRenderPass,
|
||||||
AttachmentCount = 2,
|
AttachmentCount = 2,
|
||||||
PAttachments = attachments,
|
PAttachments = attachments,
|
||||||
Width = color.Width,
|
Width = Math.Min(color.Width, depth.Width),
|
||||||
Height = color.Height,
|
Height = Math.Min(color.Height, depth.Height),
|
||||||
Layers = 1,
|
Layers = 1,
|
||||||
};
|
};
|
||||||
Check(
|
Check(
|
||||||
@@ -12155,6 +12266,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
|
|
||||||
EvictDirtyCachedTextures();
|
EvictDirtyCachedTextures();
|
||||||
var completedWork = 0;
|
var completedWork = 0;
|
||||||
|
HashSet<string>? deferredOrderedQueues = null;
|
||||||
var renderWorkDeadline = _renderWorkBudgetTicks > 0
|
var renderWorkDeadline = _renderWorkBudgetTicks > 0
|
||||||
? System.Diagnostics.Stopwatch.GetTimestamp() + _renderWorkBudgetTicks
|
? System.Diagnostics.Stopwatch.GetTimestamp() + _renderWorkBudgetTicks
|
||||||
: long.MaxValue;
|
: long.MaxValue;
|
||||||
@@ -12172,7 +12284,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryTakeGuestWork(out var pendingGuestWork))
|
if (!TryTakeGuestWork(out var pendingGuestWork, deferredOrderedQueues))
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -12199,6 +12311,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
_enqueueAsImmediateQueueFollowup = true;
|
_enqueueAsImmediateQueueFollowup = true;
|
||||||
_immediateFollowupTail = null;
|
_immediateFollowupTail = null;
|
||||||
var work = pendingGuestWork.Work;
|
var work = pendingGuestWork.Work;
|
||||||
|
var deferGuestWork = false;
|
||||||
|
|
||||||
var traceWork = ShouldTracePresentedGuestImageContentsForDiagnostics();
|
var traceWork = ShouldTracePresentedGuestImageContentsForDiagnostics();
|
||||||
var workStart = traceWork ? System.Diagnostics.Stopwatch.GetTimestamp() : 0L;
|
var workStart = traceWork ? System.Diagnostics.Stopwatch.GetTimestamp() : 0L;
|
||||||
@@ -12226,7 +12339,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
ExecuteGuestImageWrite(guestImageWrite);
|
ExecuteGuestImageWrite(guestImageWrite);
|
||||||
break;
|
break;
|
||||||
case VulkanOrderedGuestAction orderedAction:
|
case VulkanOrderedGuestAction orderedAction:
|
||||||
ExecuteOrderedGuestAction(orderedAction);
|
deferGuestWork = !TryExecuteOrderedGuestAction(orderedAction);
|
||||||
break;
|
break;
|
||||||
case VulkanOrderedGuestFlip orderedFlip:
|
case VulkanOrderedGuestFlip orderedFlip:
|
||||||
ExecuteOrderedGuestFlip(orderedFlip);
|
ExecuteOrderedGuestFlip(orderedFlip);
|
||||||
@@ -12238,12 +12351,21 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
CompleteGuestWork(pendingGuestWork);
|
if (!deferGuestWork || !RequeueGuestWorkFront(pendingGuestWork))
|
||||||
|
{
|
||||||
|
CompleteGuestWork(pendingGuestWork);
|
||||||
|
}
|
||||||
_enqueueAsImmediateQueueFollowup = false;
|
_enqueueAsImmediateQueueFollowup = false;
|
||||||
_immediateFollowupTail = null;
|
_immediateFollowupTail = null;
|
||||||
Volatile.Write(ref _executingGuestWorkSequence, 0);
|
Volatile.Write(ref _executingGuestWorkSequence, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (deferGuestWork)
|
||||||
|
{
|
||||||
|
deferredOrderedQueues ??= new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
deferredOrderedQueues.Add(pendingGuestWork.Queue.Name);
|
||||||
|
}
|
||||||
|
|
||||||
if (workStart != 0)
|
if (workStart != 0)
|
||||||
{
|
{
|
||||||
var elapsedMs = (System.Diagnostics.Stopwatch.GetTimestamp() - workStart)
|
var elapsedMs = (System.Diagnostics.Stopwatch.GetTimestamp() - workStart)
|
||||||
@@ -13200,6 +13322,79 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
depth.Layout = ImageLayout.ShaderReadOnlyOptimal;
|
depth.Layout = ImageLayout.ShaderReadOnlyOptimal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RecordStandaloneGuestDepthClear(GuestDepthResource depth)
|
||||||
|
{
|
||||||
|
var depthRange = new ImageSubresourceRange(
|
||||||
|
ImageAspectFlags.DepthBit,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
1);
|
||||||
|
var sourceStage = PipelineStageFlags.TopOfPipeBit;
|
||||||
|
var sourceAccess = AccessFlags.None;
|
||||||
|
switch (depth.Layout)
|
||||||
|
{
|
||||||
|
case ImageLayout.ShaderReadOnlyOptimal:
|
||||||
|
sourceStage =
|
||||||
|
PipelineStageFlags.VertexShaderBit |
|
||||||
|
PipelineStageFlags.FragmentShaderBit |
|
||||||
|
PipelineStageFlags.ComputeShaderBit;
|
||||||
|
sourceAccess = AccessFlags.ShaderReadBit;
|
||||||
|
break;
|
||||||
|
case ImageLayout.DepthStencilAttachmentOptimal:
|
||||||
|
sourceStage =
|
||||||
|
PipelineStageFlags.EarlyFragmentTestsBit |
|
||||||
|
PipelineStageFlags.LateFragmentTestsBit;
|
||||||
|
sourceAccess =
|
||||||
|
AccessFlags.DepthStencilAttachmentReadBit |
|
||||||
|
AccessFlags.DepthStencilAttachmentWriteBit;
|
||||||
|
break;
|
||||||
|
case ImageLayout.TransferDstOptimal:
|
||||||
|
sourceStage = PipelineStageFlags.TransferBit;
|
||||||
|
sourceAccess = AccessFlags.TransferWriteBit;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (depth.Layout != ImageLayout.TransferDstOptimal)
|
||||||
|
{
|
||||||
|
var toTransfer = new ImageMemoryBarrier
|
||||||
|
{
|
||||||
|
SType = StructureType.ImageMemoryBarrier,
|
||||||
|
SrcAccessMask = sourceAccess,
|
||||||
|
DstAccessMask = AccessFlags.TransferWriteBit,
|
||||||
|
OldLayout = depth.Layout,
|
||||||
|
NewLayout = ImageLayout.TransferDstOptimal,
|
||||||
|
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||||
|
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||||
|
Image = depth.Image,
|
||||||
|
SubresourceRange = depthRange,
|
||||||
|
};
|
||||||
|
_vk.CmdPipelineBarrier(
|
||||||
|
_commandBuffer,
|
||||||
|
sourceStage,
|
||||||
|
PipelineStageFlags.TransferBit,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
1,
|
||||||
|
&toTransfer);
|
||||||
|
}
|
||||||
|
|
||||||
|
var clearValue = new ClearDepthStencilValue(depth.ClearDepth, 0);
|
||||||
|
_vk.CmdClearDepthStencilImage(
|
||||||
|
_commandBuffer,
|
||||||
|
depth.Image,
|
||||||
|
ImageLayout.TransferDstOptimal,
|
||||||
|
&clearValue,
|
||||||
|
1,
|
||||||
|
&depthRange);
|
||||||
|
depth.Initialized = true;
|
||||||
|
depth.Layout = ImageLayout.TransferDstOptimal;
|
||||||
|
depth.InitializationSource = "guest-depth-clear";
|
||||||
|
}
|
||||||
|
|
||||||
private void RecordRenderTargetFeedbackSnapshots(
|
private void RecordRenderTargetFeedbackSnapshots(
|
||||||
TranslatedDrawResources resources,
|
TranslatedDrawResources resources,
|
||||||
PipelineStageFlags shaderStage)
|
PipelineStageFlags shaderStage)
|
||||||
@@ -14965,6 +15160,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
DestroyGuestBufferAllocation(allocation);
|
DestroyGuestBufferAllocation(allocation);
|
||||||
}
|
}
|
||||||
_guestBufferAllocations.Clear();
|
_guestBufferAllocations.Clear();
|
||||||
|
PerfOverlay.SetGuestBufferCacheBytes(0);
|
||||||
_hostBufferPool.Dispose();
|
_hostBufferPool.Dispose();
|
||||||
foreach (var guestImage in _guestImages.Values)
|
foreach (var guestImage in _guestImages.Values)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user