Ryujinx/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs
sharmander 06aa8a7578
GPU - Improve Memory Allocation (#1722)
* Implement TreeMap from scratch.

Begin implementation of MemoryBlockManager

* Implement GetFreePosition using MemoryBlocks

* Implementation of Memory Management using a Tree.

Still some issues to work around, but promising thus far.

* Resolved invalid mapping issue.

Performance appears promising.

* Add tick metrics

* Use the logger instead

* Use debug loggin instead of info.

* Remove unnecessary code. Add descriptions of added functions.

* Improve memory allocation even further. As well as improve speed of position fetching.

* Add TreeDictionary to Ryujinx Commons

Removed Unnecessary  Usigns

* Add a Performance Profiler + Improve ReserveFixed

* Begin transition to allocation in nvdrv

* Create singleton nvmemallocator

* Moved Allocation into Nv Related Files

As requested by gdkchan, any allocation of memory has been moved into the driver files.

Mapping remains in the GPU MemoryManager.

* Remove unnecessary usings

* Add missing descriptions

* Correct descriptions

* Fix formatting.

* Remove unnecessary whitespace

* Formatting / Convention Updates

* Changes / Fixes

Made syntax and convention changes as requested by gdkchan.

Fixed an issue where IsRegionUsed would return the wrong boolean.

Fixed an issue where GetFreePosition was asked for an address instead of a size.

* Undo commenting of Assert in shader cache

* Update Ryujinx.Common/Collections/TreeDictionary.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Resolved many suggestions

* Implement Improved TreeDictionary

Based off of Pseudo code and custom implementations.

* Rename _set to _dictionary

* Remove unused code

* Remove unused code.

* Remove unnecessary MapLow function.

* Resolve data-structure based issues

* Make adjustments to memory management.

Deactive de-allocation for now, it causes more harm than good.

* Minor refactorings + Re-implement deallocation

Also cleaned up unnecessary code.

* Add Tests for TreeDictionary

* Update data structure to properly balance the tree

* Experimental Implementation:

1. Reduce Time to Next Node to O(1) Runtime
2. Reduce While Loop Ct To 2 (In Most Cases)

* Address issues w/ Deallocating Memory

* Final Build

+ Fully Implement Dictionary Interface for new Data Structure
+ Cover All Memory Allocation Edge Cases, particularly w/ Games that De-Allocate a lot.

* Minor Corrections

Give TreeDictionary its own count (do not depend on inner dictionary)

Properly remove adjacent allocations

* Add AsList

* Fix bug where internal dictionary wasn't being updated w/ new node for overwritten key.

* Address comments in review.

* Fix issue where block wouldn't break out (Fixes UE4 issues)

* Update descriptions

* Update descriptions

* Reduce Node visibility to protect TreeDictionary Integrity + Remove usage of struct.

* Update tests to use new TreeDictionary implementation.

* Remove usage of dictionary in TreeDictionary

* Refactoring / Renaming

* Remove unneeded memoryblock class.

* Add space for while

* Add space for if

* Formatting / descriptions

* Clarified some descriptions

* Reduce visibility of memory allocator

* Edit method names to make more sense as memory blocks are no longer in use.

* Make names consistent.

* Protect against npe when sucessorof is called against keys that don't exist. (Not in use by memory manager, this is for other prs that might use this data structure)

* Possible edge-case resolve

* Update Ryujinx.Common/Collections/TreeDictionary.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Update Ryujinx.HLE/HOS/Services/Nv/NvMemoryAllocator.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Reduce # of unnecessary duplicate variables / Reduce visibility of variables only internally used.

* Rename count to _count

* Update Description of Add method.

* Fix copypasta

* Address comments

* Address comments

* Remove whitespace

* Address comments, condense variables.

* Consolidate vars

* Fix whitespace.

* Nit

* Fix exception msg

* Fix arrayIndex check

* Fix arrayIndex check + indexer

* Remove whitespace from cast

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2020-12-09 19:26:05 -03:00

359 lines
13 KiB
C#

using Ryujinx.Common.Collections;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
using Ryujinx.Memory;
using System;
using System.Collections.Concurrent;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
{
class NvHostAsGpuDeviceFile : NvDeviceFile
{
private static ConcurrentDictionary<KProcess, AddressSpaceContext> _addressSpaceContextRegistry = new ConcurrentDictionary<KProcess, AddressSpaceContext>();
private NvMemoryAllocator _memoryAllocator;
public NvHostAsGpuDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, long owner) : base(context, owner)
{
_memoryAllocator = context.Device.MemoryAllocator;
}
public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments)
{
NvInternalResult result = NvInternalResult.NotImplemented;
if (command.Type == NvIoctl.NvGpuAsMagic)
{
switch (command.Number)
{
case 0x01:
result = CallIoctlMethod<BindChannelArguments>(BindChannel, arguments);
break;
case 0x02:
result = CallIoctlMethod<AllocSpaceArguments>(AllocSpace, arguments);
break;
case 0x03:
result = CallIoctlMethod<FreeSpaceArguments>(FreeSpace, arguments);
break;
case 0x05:
result = CallIoctlMethod<UnmapBufferArguments>(UnmapBuffer, arguments);
break;
case 0x06:
result = CallIoctlMethod<MapBufferExArguments>(MapBufferEx, arguments);
break;
case 0x08:
result = CallIoctlMethod<GetVaRegionsArguments>(GetVaRegions, arguments);
break;
case 0x09:
result = CallIoctlMethod<InitializeExArguments>(InitializeEx, arguments);
break;
case 0x14:
result = CallIoctlMethod<RemapArguments>(Remap, arguments);
break;
}
}
return result;
}
public override NvInternalResult Ioctl3(NvIoctl command, Span<byte> arguments, Span<byte> inlineOutBuffer)
{
NvInternalResult result = NvInternalResult.NotImplemented;
if (command.Type == NvIoctl.NvGpuAsMagic)
{
switch (command.Number)
{
case 0x08:
// This is the same as the one in ioctl as inlineOutBuffer is empty.
result = CallIoctlMethod<GetVaRegionsArguments>(GetVaRegions, arguments);
break;
}
}
return result;
}
private NvInternalResult BindChannel(ref BindChannelArguments arguments)
{
Logger.Stub?.PrintStub(LogClass.ServiceNv);
return NvInternalResult.Success;
}
private NvInternalResult AllocSpace(ref AllocSpaceArguments arguments)
{
AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Context);
ulong size = (ulong)arguments.Pages * (ulong)arguments.PageSize;
NvInternalResult result = NvInternalResult.Success;
lock (addressSpaceContext)
{
// Note: When the fixed offset flag is not set,
// the Offset field holds the alignment size instead.
if ((arguments.Flags & AddressSpaceFlags.FixedOffset) != 0)
{
bool regionInUse = _memoryAllocator.IsRegionInUse((ulong)arguments.Offset, size, out ulong freeAddressStartPosition);
ulong address;
if (!regionInUse)
{
_memoryAllocator.AllocateRange((ulong)arguments.Offset, size, freeAddressStartPosition);
address = freeAddressStartPosition;
}
else
{
address = NvMemoryAllocator.PteUnmapped;
}
arguments.Offset = (long)address;
}
else
{
ulong address = _memoryAllocator.GetFreeAddress((ulong)size, out ulong freeAddressStartPosition, (ulong)arguments.Offset);
if (address != NvMemoryAllocator.PteUnmapped)
{
_memoryAllocator.AllocateRange(address, (ulong)size, freeAddressStartPosition);
}
arguments.Offset = unchecked((long)address);
}
if (arguments.Offset < 0)
{
arguments.Offset = 0;
Logger.Warning?.Print(LogClass.ServiceNv, $"Failed to allocate size {size:x16}!");
result = NvInternalResult.OutOfMemory;
}
else
{
addressSpaceContext.AddReservation(arguments.Offset, (long)size);
}
}
return result;
}
private NvInternalResult FreeSpace(ref FreeSpaceArguments arguments)
{
AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Context);
NvInternalResult result = NvInternalResult.Success;
lock (addressSpaceContext)
{
ulong size = (ulong)arguments.Pages * (ulong)arguments.PageSize;
if (addressSpaceContext.RemoveReservation(arguments.Offset))
{
_memoryAllocator.DeallocateRange((ulong)arguments.Offset, size);
addressSpaceContext.Gmm.Free((ulong)arguments.Offset, size);
}
else
{
Logger.Warning?.Print(LogClass.ServiceNv,
$"Failed to free offset 0x{arguments.Offset:x16} size 0x{size:x16}!");
result = NvInternalResult.InvalidInput;
}
}
return result;
}
private NvInternalResult UnmapBuffer(ref UnmapBufferArguments arguments)
{
AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Context);
lock (addressSpaceContext)
{
if (addressSpaceContext.RemoveMap(arguments.Offset, out long size))
{
if (size != 0)
{
_memoryAllocator.DeallocateRange((ulong)arguments.Offset, (ulong)size);
addressSpaceContext.Gmm.Free((ulong)arguments.Offset, (ulong)size);
}
}
else
{
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid buffer offset {arguments.Offset:x16}!");
}
}
return NvInternalResult.Success;
}
private NvInternalResult MapBufferEx(ref MapBufferExArguments arguments)
{
const string mapErrorMsg = "Failed to map fixed buffer with offset 0x{0:x16}, size 0x{1:x16} and alignment 0x{2:x16}!";
AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Context);
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, arguments.NvMapHandle, true);
if (map == null)
{
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid NvMap handle 0x{arguments.NvMapHandle:x8}!");
return NvInternalResult.InvalidInput;
}
ulong pageSize = (ulong)arguments.PageSize;
if (pageSize == 0)
{
pageSize = (ulong)map.Align;
}
long physicalAddress;
if ((arguments.Flags & AddressSpaceFlags.RemapSubRange) != 0)
{
lock (addressSpaceContext)
{
if (addressSpaceContext.TryGetMapPhysicalAddress(arguments.Offset, out physicalAddress))
{
long virtualAddress = arguments.Offset + arguments.BufferOffset;
physicalAddress += arguments.BufferOffset;
if ((long)addressSpaceContext.Gmm.Map((ulong)physicalAddress, (ulong)virtualAddress, (ulong)arguments.MappingSize) < 0)
{
string message = string.Format(mapErrorMsg, virtualAddress, arguments.MappingSize, pageSize);
Logger.Warning?.Print(LogClass.ServiceNv, message);
return NvInternalResult.InvalidInput;
}
return NvInternalResult.Success;
}
else
{
Logger.Warning?.Print(LogClass.ServiceNv, $"Address 0x{arguments.Offset:x16} not mapped!");
return NvInternalResult.InvalidInput;
}
}
}
physicalAddress = map.Address + arguments.BufferOffset;
long size = arguments.MappingSize;
if (size == 0)
{
size = (uint)map.Size;
}
NvInternalResult result = NvInternalResult.Success;
lock (addressSpaceContext)
{
// Note: When the fixed offset flag is not set,
// the Offset field holds the alignment size instead.
bool virtualAddressAllocated = (arguments.Flags & AddressSpaceFlags.FixedOffset) == 0;
if (!virtualAddressAllocated)
{
if (addressSpaceContext.ValidateFixedBuffer(arguments.Offset, size, pageSize))
{
arguments.Offset = (long)addressSpaceContext.Gmm.Map((ulong)physicalAddress, (ulong)arguments.Offset, (ulong)size);
}
else
{
string message = string.Format(mapErrorMsg, arguments.Offset, size, pageSize);
Logger.Warning?.Print(LogClass.ServiceNv, message);
result = NvInternalResult.InvalidInput;
}
}
else
{
ulong va = _memoryAllocator.GetFreeAddress((ulong)size, out ulong freeAddressStartPosition, (ulong) pageSize);
if (va != NvMemoryAllocator.PteUnmapped)
{
_memoryAllocator.AllocateRange(va, (ulong)size, freeAddressStartPosition);
}
arguments.Offset = (long)addressSpaceContext.Gmm.Map((ulong)physicalAddress, va, (ulong)size);
}
if (arguments.Offset < 0)
{
arguments.Offset = 0;
Logger.Warning?.Print(LogClass.ServiceNv, $"Failed to map size 0x{size:x16}!");
result = NvInternalResult.InvalidInput;
}
else
{
addressSpaceContext.AddMap(arguments.Offset, size, physicalAddress, virtualAddressAllocated);
}
}
return result;
}
private NvInternalResult GetVaRegions(ref GetVaRegionsArguments arguments)
{
Logger.Stub?.PrintStub(LogClass.ServiceNv);
return NvInternalResult.Success;
}
private NvInternalResult InitializeEx(ref InitializeExArguments arguments)
{
Logger.Stub?.PrintStub(LogClass.ServiceNv);
return NvInternalResult.Success;
}
private NvInternalResult Remap(Span<RemapArguments> arguments)
{
for (int index = 0; index < arguments.Length; index++)
{
MemoryManager gmm = GetAddressSpaceContext(Context).Gmm;
NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, arguments[index].NvMapHandle, true);
if (map == null)
{
Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid NvMap handle 0x{arguments[index].NvMapHandle:x8}!");
return NvInternalResult.InvalidInput;
}
long result = (long)gmm.Map(
((ulong)arguments[index].MapOffset << 16) + (ulong)map.Address,
(ulong)arguments[index].GpuOffset << 16,
(ulong)arguments[index].Pages << 16);
if (result < 0)
{
Logger.Warning?.Print(LogClass.ServiceNv,
$"Page 0x{arguments[index].GpuOffset:x16} size 0x{arguments[index].Pages:x16} not allocated!");
return NvInternalResult.InvalidInput;
}
}
return NvInternalResult.Success;
}
public override void Close() { }
public static AddressSpaceContext GetAddressSpaceContext(ServiceCtx context)
{
return _addressSpaceContextRegistry.GetOrAdd(context.Process, (key) => new AddressSpaceContext(context));
}
}
}