9f1cf6458c
* Initial implementation of migration between memory heaps - Missing OOM handling - Missing `_map` data safety when remapping - Copy may not have completed yet (needs some kind of fence) - Map may be unmapped before it is done being used. (needs scoped access) - SSBO accesses are all "writes" - maybe pass info in another way. - Missing keeping map type when resizing buffers (should this be done?) * Ensure migrated data is in place before flushing. * Fix issue where old waitable would be signalled. - There is a real issue where existing Auto<> references need to be replaced. * Swap bound Auto<> instances when swapping buffer backing * Fix conversion buffers * Don't try move buffers if the host has shared memory. * Make GPU methods return PinnedSpan with scope * Storage Hint * Fix stupidity * Fix rebase * Tweak rules Attempt to sidestep BOTW slowdown * Remove line * Migrate only when command buffers flush * Change backing swap log to debug * Address some feedback * Disallow backing swap when the flush lock is held by the current thread * Make PinnedSpan from ReadOnlySpan explicitly unsafe * Fix some small issues - Index buffer swap fixed - Allocate DeviceLocal buffers using a separate block list to images. * Remove alternative flags * Address feedback
314 lines
10 KiB
C#
314 lines
10 KiB
C#
using Ryujinx.Graphics.GAL;
|
|
using Ryujinx.Graphics.Vulkan.Queries;
|
|
using Silk.NET.Vulkan;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace Ryujinx.Graphics.Vulkan
|
|
{
|
|
class PipelineFull : PipelineBase, IPipeline
|
|
{
|
|
private const ulong MinByteWeightForFlush = 256 * 1024 * 1024; // MiB
|
|
|
|
private readonly List<(QueryPool, bool)> _activeQueries;
|
|
private CounterQueueEvent _activeConditionalRender;
|
|
|
|
private readonly List<BufferedQuery> _pendingQueryCopies;
|
|
|
|
private ulong _byteWeight;
|
|
|
|
private List<BufferHolder> _backingSwaps;
|
|
|
|
public PipelineFull(VulkanRenderer gd, Device device) : base(gd, device)
|
|
{
|
|
_activeQueries = new List<(QueryPool, bool)>();
|
|
_pendingQueryCopies = new();
|
|
_backingSwaps = new();
|
|
|
|
CommandBuffer = (Cbs = gd.CommandBufferPool.Rent()).CommandBuffer;
|
|
}
|
|
|
|
private void CopyPendingQuery()
|
|
{
|
|
foreach (var query in _pendingQueryCopies)
|
|
{
|
|
query.PoolCopy(Cbs);
|
|
}
|
|
|
|
_pendingQueryCopies.Clear();
|
|
}
|
|
|
|
public void ClearRenderTargetColor(int index, int layer, int layerCount, uint componentMask, ColorF color)
|
|
{
|
|
if (FramebufferParams == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (componentMask != 0xf)
|
|
{
|
|
// We can't use CmdClearAttachments if not writing all components,
|
|
// because on Vulkan, the pipeline state does not affect clears.
|
|
var dstTexture = FramebufferParams.GetAttachment(index);
|
|
if (dstTexture == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Span<float> clearColor = stackalloc float[4];
|
|
clearColor[0] = color.Red;
|
|
clearColor[1] = color.Green;
|
|
clearColor[2] = color.Blue;
|
|
clearColor[3] = color.Alpha;
|
|
|
|
// TODO: Clear only the specified layer.
|
|
Gd.HelperShader.Clear(
|
|
Gd,
|
|
dstTexture,
|
|
clearColor,
|
|
componentMask,
|
|
(int)FramebufferParams.Width,
|
|
(int)FramebufferParams.Height,
|
|
FramebufferParams.AttachmentFormats[index],
|
|
FramebufferParams.GetAttachmentComponentType(index),
|
|
ClearScissor);
|
|
}
|
|
else
|
|
{
|
|
ClearRenderTargetColor(index, layer, layerCount, color);
|
|
}
|
|
}
|
|
|
|
public void EndHostConditionalRendering()
|
|
{
|
|
if (Gd.Capabilities.SupportsConditionalRendering)
|
|
{
|
|
// Gd.ConditionalRenderingApi.CmdEndConditionalRendering(CommandBuffer);
|
|
}
|
|
else
|
|
{
|
|
// throw new NotSupportedException();
|
|
}
|
|
|
|
_activeConditionalRender?.ReleaseHostAccess();
|
|
_activeConditionalRender = null;
|
|
}
|
|
|
|
public bool TryHostConditionalRendering(ICounterEvent value, ulong compare, bool isEqual)
|
|
{
|
|
// Compare an event and a constant value.
|
|
if (value is CounterQueueEvent evt)
|
|
{
|
|
// Easy host conditional rendering when the check matches what GL can do:
|
|
// - Event is of type samples passed.
|
|
// - Result is not a combination of multiple queries.
|
|
// - Comparing against 0.
|
|
// - Event has not already been flushed.
|
|
|
|
if (compare == 0 && evt.Type == CounterType.SamplesPassed && evt.ClearCounter)
|
|
{
|
|
if (!value.ReserveForHostAccess())
|
|
{
|
|
// If the event has been flushed, then just use the values on the CPU.
|
|
// The query object may already be repurposed for another draw (eg. begin + end).
|
|
return false;
|
|
}
|
|
|
|
if (Gd.Capabilities.SupportsConditionalRendering)
|
|
{
|
|
var buffer = evt.GetBuffer().Get(Cbs, 0, sizeof(long)).Value;
|
|
var flags = isEqual ? ConditionalRenderingFlagsEXT.InvertedBitExt : 0;
|
|
|
|
var conditionalRenderingBeginInfo = new ConditionalRenderingBeginInfoEXT()
|
|
{
|
|
SType = StructureType.ConditionalRenderingBeginInfoExt,
|
|
Buffer = buffer,
|
|
Flags = flags
|
|
};
|
|
|
|
// Gd.ConditionalRenderingApi.CmdBeginConditionalRendering(CommandBuffer, conditionalRenderingBeginInfo);
|
|
}
|
|
|
|
_activeConditionalRender = evt;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// The GPU will flush the queries to CPU and evaluate the condition there instead.
|
|
|
|
FlushPendingQuery(); // The thread will be stalled manually flushing the counter, so flush commands now.
|
|
return false;
|
|
}
|
|
|
|
public bool TryHostConditionalRendering(ICounterEvent value, ICounterEvent compare, bool isEqual)
|
|
{
|
|
FlushPendingQuery(); // The thread will be stalled manually flushing the counter, so flush commands now.
|
|
return false;
|
|
}
|
|
|
|
private void FlushPendingQuery()
|
|
{
|
|
if (AutoFlush.ShouldFlushQuery())
|
|
{
|
|
FlushCommandsImpl();
|
|
}
|
|
}
|
|
|
|
public CommandBufferScoped GetPreloadCommandBuffer()
|
|
{
|
|
if (PreloadCbs == null)
|
|
{
|
|
PreloadCbs = Gd.CommandBufferPool.Rent();
|
|
}
|
|
|
|
return PreloadCbs.Value;
|
|
}
|
|
|
|
public void FlushCommandsIfWeightExceeding(IAuto disposedResource, ulong byteWeight)
|
|
{
|
|
bool usedByCurrentCb = disposedResource.HasCommandBufferDependency(Cbs);
|
|
|
|
if (PreloadCbs != null && !usedByCurrentCb)
|
|
{
|
|
usedByCurrentCb = disposedResource.HasCommandBufferDependency(PreloadCbs.Value);
|
|
}
|
|
|
|
if (usedByCurrentCb)
|
|
{
|
|
// Since we can only free memory after the command buffer that uses a given resource was executed,
|
|
// keeping the command buffer might cause a high amount of memory to be in use.
|
|
// To prevent that, we force submit command buffers if the memory usage by resources
|
|
// in use by the current command buffer is above a given limit, and those resources were disposed.
|
|
_byteWeight += byteWeight;
|
|
|
|
if (_byteWeight >= MinByteWeightForFlush)
|
|
{
|
|
FlushCommandsImpl();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void TryBackingSwaps()
|
|
{
|
|
CommandBufferScoped? cbs = null;
|
|
|
|
_backingSwaps.RemoveAll((holder) => holder.TryBackingSwap(ref cbs));
|
|
|
|
cbs?.Dispose();
|
|
}
|
|
|
|
public void AddBackingSwap(BufferHolder holder)
|
|
{
|
|
_backingSwaps.Add(holder);
|
|
}
|
|
|
|
public void Restore()
|
|
{
|
|
if (Pipeline != null)
|
|
{
|
|
Gd.Api.CmdBindPipeline(CommandBuffer, Pbp, Pipeline.Get(Cbs).Value);
|
|
}
|
|
|
|
SignalCommandBufferChange();
|
|
|
|
DynamicState.ReplayIfDirty(Gd.Api, CommandBuffer);
|
|
}
|
|
|
|
public void FlushCommandsImpl()
|
|
{
|
|
AutoFlush.RegisterFlush(DrawCount);
|
|
EndRenderPass();
|
|
|
|
foreach ((var queryPool, _) in _activeQueries)
|
|
{
|
|
Gd.Api.CmdEndQuery(CommandBuffer, queryPool, 0);
|
|
}
|
|
|
|
_byteWeight = 0;
|
|
|
|
if (PreloadCbs != null)
|
|
{
|
|
PreloadCbs.Value.Dispose();
|
|
PreloadCbs = null;
|
|
}
|
|
|
|
CommandBuffer = (Cbs = Gd.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer;
|
|
Gd.RegisterFlush();
|
|
|
|
// Restore per-command buffer state.
|
|
|
|
foreach ((var queryPool, var isOcclusion) in _activeQueries)
|
|
{
|
|
bool isPrecise = Gd.Capabilities.SupportsPreciseOcclusionQueries && isOcclusion;
|
|
|
|
Gd.Api.CmdResetQueryPool(CommandBuffer, queryPool, 0, 1);
|
|
Gd.Api.CmdBeginQuery(CommandBuffer, queryPool, 0, isPrecise ? QueryControlFlags.PreciseBit : 0);
|
|
}
|
|
|
|
Gd.ResetCounterPool();
|
|
|
|
TryBackingSwaps();
|
|
|
|
Restore();
|
|
}
|
|
|
|
public void BeginQuery(BufferedQuery query, QueryPool pool, bool needsReset, bool isOcclusion, bool fromSamplePool)
|
|
{
|
|
if (needsReset)
|
|
{
|
|
EndRenderPass();
|
|
|
|
Gd.Api.CmdResetQueryPool(CommandBuffer, pool, 0, 1);
|
|
|
|
if (fromSamplePool)
|
|
{
|
|
// Try reset some additional queries in advance.
|
|
|
|
Gd.ResetFutureCounters(CommandBuffer, AutoFlush.GetRemainingQueries());
|
|
}
|
|
}
|
|
|
|
bool isPrecise = Gd.Capabilities.SupportsPreciseOcclusionQueries && isOcclusion;
|
|
Gd.Api.CmdBeginQuery(CommandBuffer, pool, 0, isPrecise ? QueryControlFlags.PreciseBit : 0);
|
|
|
|
_activeQueries.Add((pool, isOcclusion));
|
|
}
|
|
|
|
public void EndQuery(QueryPool pool)
|
|
{
|
|
Gd.Api.CmdEndQuery(CommandBuffer, pool, 0);
|
|
|
|
for (int i = 0; i < _activeQueries.Count; i++)
|
|
{
|
|
if (_activeQueries[i].Item1.Handle == pool.Handle)
|
|
{
|
|
_activeQueries.RemoveAt(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void CopyQueryResults(BufferedQuery query)
|
|
{
|
|
_pendingQueryCopies.Add(query);
|
|
|
|
if (AutoFlush.RegisterPendingQuery())
|
|
{
|
|
FlushCommandsImpl();
|
|
}
|
|
}
|
|
|
|
protected override void SignalAttachmentChange()
|
|
{
|
|
if (AutoFlush.ShouldFlushAttachmentChange(DrawCount))
|
|
{
|
|
FlushCommandsImpl();
|
|
}
|
|
}
|
|
|
|
protected override void SignalRenderPassEnd()
|
|
{
|
|
CopyPendingQuery();
|
|
}
|
|
}
|
|
}
|