GPU: Pre-emptively flush textures that are flushed often (to imported memory when available) (#4711)
* WIP texture pre-flush Improve performance of TextureView GetData to buffer Fix copy/sync ordering Fix minor bug Make this actually work WIP host mapping stuff * Fix usage flags * message * Cleanup 1 * Fix rebase * Fix * Improve pre-flush rules * Fix pre-flush * A lot of cleanup * Use the host memory bits * Select the correct memory type * Cleanup TextureGroupHandle * Missing comment * Remove debugging logs * Revert BufferHandle _value access modifier * One interrupt action at a time. * Support D32S8 to D24S8 conversion, safeguards * Interrupt cannot happen in sync handle's lock Waitable needs to be checked twice now, but this should stop it from deadlocking. * Remove unused using * Address some feedback * Address feedback * Address more feedback * Address more feedback * Improve sync rules Should allow for faster sync in some cases.
This commit is contained in:
parent
36f10df775
commit
e18d258fa0
40 changed files with 1328 additions and 79 deletions
188
src/Ryujinx.Graphics.Vulkan/HostMemoryAllocator.cs
Normal file
188
src/Ryujinx.Graphics.Vulkan/HostMemoryAllocator.cs
Normal file
|
@ -0,0 +1,188 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Collections;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Silk.NET.Vulkan;
|
||||
using Silk.NET.Vulkan.Extensions.EXT;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
internal class HostMemoryAllocator
|
||||
{
|
||||
private struct HostMemoryAllocation
|
||||
{
|
||||
public readonly Auto<MemoryAllocation> Allocation;
|
||||
public readonly IntPtr Pointer;
|
||||
public readonly ulong Size;
|
||||
|
||||
public ulong Start => (ulong)Pointer;
|
||||
public ulong End => (ulong)Pointer + Size;
|
||||
|
||||
public HostMemoryAllocation(Auto<MemoryAllocation> allocation, IntPtr pointer, ulong size)
|
||||
{
|
||||
Allocation = allocation;
|
||||
Pointer = pointer;
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly MemoryAllocator _allocator;
|
||||
private readonly Vk _api;
|
||||
private readonly ExtExternalMemoryHost _hostMemoryApi;
|
||||
private readonly Device _device;
|
||||
private readonly object _lock = new();
|
||||
|
||||
private List<HostMemoryAllocation> _allocations;
|
||||
private IntervalTree<ulong, HostMemoryAllocation> _allocationTree;
|
||||
|
||||
public HostMemoryAllocator(MemoryAllocator allocator, Vk api, ExtExternalMemoryHost hostMemoryApi, Device device)
|
||||
{
|
||||
_allocator = allocator;
|
||||
_api = api;
|
||||
_hostMemoryApi = hostMemoryApi;
|
||||
_device = device;
|
||||
|
||||
_allocations = new List<HostMemoryAllocation>();
|
||||
_allocationTree = new IntervalTree<ulong, HostMemoryAllocation>();
|
||||
}
|
||||
|
||||
public unsafe bool TryImport(
|
||||
MemoryRequirements requirements,
|
||||
MemoryPropertyFlags flags,
|
||||
IntPtr pointer,
|
||||
ulong size)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
// Does a compatible allocation exist in the tree?
|
||||
var allocations = new HostMemoryAllocation[10];
|
||||
|
||||
ulong start = (ulong)pointer;
|
||||
ulong end = start + size;
|
||||
|
||||
int count = _allocationTree.Get(start, end, ref allocations);
|
||||
|
||||
// A compatible range is one that where the start and end completely cover the requested range.
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
HostMemoryAllocation existing = allocations[i];
|
||||
|
||||
if (start >= existing.Start && end <= existing.End)
|
||||
{
|
||||
try
|
||||
{
|
||||
existing.Allocation.IncrementReferenceCount();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Can throw if the allocation has been disposed.
|
||||
// Just continue the search if this happens.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nint pageAlignedPointer = BitUtils.AlignDown(pointer, Environment.SystemPageSize);
|
||||
nint pageAlignedEnd = BitUtils.AlignUp((nint)((ulong)pointer + size), Environment.SystemPageSize);
|
||||
ulong pageAlignedSize = (ulong)(pageAlignedEnd - pageAlignedPointer);
|
||||
|
||||
Result getResult = _hostMemoryApi.GetMemoryHostPointerProperties(_device, ExternalMemoryHandleTypeFlags.HostAllocationBitExt, (void*)pageAlignedPointer, out MemoryHostPointerPropertiesEXT properties);
|
||||
if (getResult < Result.Success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int memoryTypeIndex = _allocator.FindSuitableMemoryTypeIndex(properties.MemoryTypeBits & requirements.MemoryTypeBits, flags);
|
||||
if (memoryTypeIndex < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ImportMemoryHostPointerInfoEXT importInfo = new ImportMemoryHostPointerInfoEXT()
|
||||
{
|
||||
SType = StructureType.ImportMemoryHostPointerInfoExt,
|
||||
HandleType = ExternalMemoryHandleTypeFlags.HostAllocationBitExt,
|
||||
PHostPointer = (void*)pageAlignedPointer
|
||||
};
|
||||
|
||||
var memoryAllocateInfo = new MemoryAllocateInfo()
|
||||
{
|
||||
SType = StructureType.MemoryAllocateInfo,
|
||||
AllocationSize = pageAlignedSize,
|
||||
MemoryTypeIndex = (uint)memoryTypeIndex,
|
||||
PNext = &importInfo
|
||||
};
|
||||
|
||||
Result result = _api.AllocateMemory(_device, memoryAllocateInfo, null, out var deviceMemory);
|
||||
|
||||
if (result < Result.Success)
|
||||
{
|
||||
Logger.Debug?.PrintMsg(LogClass.Gpu, $"Host mapping import 0x{pageAlignedPointer:x16} 0x{pageAlignedSize:x8} failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var allocation = new MemoryAllocation(this, deviceMemory, pageAlignedPointer, 0, pageAlignedSize);
|
||||
var allocAuto = new Auto<MemoryAllocation>(allocation);
|
||||
var hostAlloc = new HostMemoryAllocation(allocAuto, pageAlignedPointer, pageAlignedSize);
|
||||
|
||||
allocAuto.IncrementReferenceCount();
|
||||
allocAuto.Dispose(); // Kept alive by ref count only.
|
||||
|
||||
// Register this mapping for future use.
|
||||
|
||||
_allocationTree.Add(hostAlloc.Start, hostAlloc.End, hostAlloc);
|
||||
_allocations.Add(hostAlloc);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public (Auto<MemoryAllocation>, ulong) GetExistingAllocation(IntPtr pointer, ulong size)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
// Does a compatible allocation exist in the tree?
|
||||
var allocations = new HostMemoryAllocation[10];
|
||||
|
||||
ulong start = (ulong)pointer;
|
||||
ulong end = start + size;
|
||||
|
||||
int count = _allocationTree.Get(start, end, ref allocations);
|
||||
|
||||
// A compatible range is one that where the start and end completely cover the requested range.
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
HostMemoryAllocation existing = allocations[i];
|
||||
|
||||
if (start >= existing.Start && end <= existing.End)
|
||||
{
|
||||
return (existing.Allocation, start - existing.Start);
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"No host allocation was prepared for requested range 0x{pointer:x16}:0x{size:x16}.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Free(DeviceMemory memory, ulong offset, ulong size)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_allocations.RemoveAll(allocation =>
|
||||
{
|
||||
if (allocation.Allocation.GetUnsafe().Memory.Handle == memory.Handle)
|
||||
{
|
||||
_allocationTree.Remove(allocation.Start, allocation);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
_api.FreeMemory(_device, memory, ReadOnlySpan<AllocationCallbacks>.Empty);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue