aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics.Gpu/Memory
diff options
context:
space:
mode:
authorgdkchan <gab.dark.100@gmail.com>2021-01-17 15:44:34 -0300
committerGitHub <noreply@github.com>2021-01-17 19:44:34 +0100
commitc4f56c570494a6186792439e3c0e74458cc82b5c (patch)
tree40be96f5f3f24b711e088bf2ca681d94aac68c15 /Ryujinx.Graphics.Gpu/Memory
parent3bad321d2b0994cd19129bc18ed98bb3ab81c3b0 (diff)
Support for resources on non-contiguous GPU memory regions (#1905)
* Support for resources on non-contiguous GPU memory regions * Implement MultiRange physical addresses, only used with a single range for now * Actually use non-contiguous ranges * GetPhysicalRegions fixes * Documentation and remove Address property from TextureInfo * Finish implementing GetWritableRegion * Fix typo
Diffstat (limited to 'Ryujinx.Graphics.Gpu/Memory')
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs60
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs286
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs99
3 files changed, 407 insertions, 38 deletions
diff --git a/Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs b/Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs
new file mode 100644
index 00000000..d2a05495
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs
@@ -0,0 +1,60 @@
+using Ryujinx.Cpu.Tracking;
+using Ryujinx.Memory.Tracking;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ class GpuRegionHandle : IRegionHandle
+ {
+ private readonly CpuRegionHandle[] _cpuRegionHandles;
+
+ public bool Dirty
+ {
+ get
+ {
+ foreach (var regionHandle in _cpuRegionHandles)
+ {
+ if (regionHandle.Dirty)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ public ulong Address => throw new NotSupportedException();
+ public ulong Size => throw new NotSupportedException();
+ public ulong EndAddress => throw new NotSupportedException();
+
+ public GpuRegionHandle(CpuRegionHandle[] cpuRegionHandles)
+ {
+ _cpuRegionHandles = cpuRegionHandles;
+ }
+
+ public void Dispose()
+ {
+ foreach (var regionHandle in _cpuRegionHandles)
+ {
+ regionHandle.Dispose();
+ }
+ }
+
+ public void RegisterAction(RegionSignal action)
+ {
+ foreach (var regionHandle in _cpuRegionHandles)
+ {
+ regionHandle.RegisterAction(action);
+ }
+ }
+
+ public void Reprotect()
+ {
+ foreach (var regionHandle in _cpuRegionHandles)
+ {
+ regionHandle.Reprotect();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
index cf49fd93..3da22b22 100644
--- a/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
@@ -1,5 +1,7 @@
using Ryujinx.Memory;
+using Ryujinx.Memory.Range;
using System;
+using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -8,7 +10,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary>
/// GPU memory manager.
/// </summary>
- public class MemoryManager
+ public class MemoryManager : IWritableBlock
{
private const int PtLvl0Bits = 14;
private const int PtLvl1Bits = 14;
@@ -24,6 +26,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
private const int PtLvl0Bit = PtPageBits + PtLvl1Bits;
private const int PtLvl1Bit = PtPageBits;
+ private const int AddressSpaceBits = PtPageBits + PtLvl1Bits + PtLvl0Bits;
public const ulong PteUnmapped = 0xffffffff_ffffffff;
@@ -46,26 +49,69 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Reads data from GPU mapped memory.
/// </summary>
/// <typeparam name="T">Type of the data</typeparam>
- /// <param name="gpuVa">GPU virtual address where the data is located</param>
+ /// <param name="va">GPU virtual address where the data is located</param>
/// <returns>The data at the specified memory location</returns>
- public T Read<T>(ulong gpuVa) where T : unmanaged
+ public T Read<T>(ulong va) where T : unmanaged
{
- ulong processVa = Translate(gpuVa);
-
- return MemoryMarshal.Cast<byte, T>(_context.PhysicalMemory.GetSpan(processVa, Unsafe.SizeOf<T>()))[0];
+ return MemoryMarshal.Cast<byte, T>(GetSpan(va, Unsafe.SizeOf<T>()))[0];
}
/// <summary>
/// Gets a read-only span of data from GPU mapped memory.
/// </summary>
- /// <param name="gpuVa">GPU virtual address where the data is located</param>
+ /// <param name="va">GPU virtual address where the data is located</param>
/// <param name="size">Size of the data</param>
/// <returns>The span of the data at the specified memory location</returns>
- public ReadOnlySpan<byte> GetSpan(ulong gpuVa, int size)
+ public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
{
- ulong processVa = Translate(gpuVa);
+ if (IsContiguous(va, size))
+ {
+ return _context.PhysicalMemory.GetSpan(Translate(va), size, tracked);
+ }
+ else
+ {
+ Span<byte> data = new byte[size];
- return _context.PhysicalMemory.GetSpan(processVa, size);
+ ReadImpl(va, data, tracked);
+
+ return data;
+ }
+ }
+
+ /// <summary>
+ /// Reads data from a possibly non-contiguous region of GPU mapped memory.
+ /// </summary>
+ /// <param name="va">GPU virtual address of the data</param>
+ /// <param name="data">Span to write the read data into</param>
+ /// <param name="tracked">True to enable write tracking on read, false otherwise</param>
+ private void ReadImpl(ulong va, Span<byte> data, bool tracked)
+ {
+ if (data.Length == 0)
+ {
+ return;
+ }
+
+ int offset = 0, size;
+
+ if ((va & PageMask) != 0)
+ {
+ ulong pa = Translate(va);
+
+ size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask));
+
+ _context.PhysicalMemory.GetSpan(pa, size, tracked).CopyTo(data.Slice(0, size));
+
+ offset += size;
+ }
+
+ for (; offset < data.Length; offset += size)
+ {
+ ulong pa = Translate(va + (ulong)offset);
+
+ size = Math.Min(data.Length - offset, (int)PageSize);
+
+ _context.PhysicalMemory.GetSpan(pa, size, tracked).CopyTo(data.Slice(offset, size));
+ }
}
/// <summary>
@@ -74,36 +120,91 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="address">Start address of the range</param>
/// <param name="size">Size in bytes to be range</param>
/// <returns>A writable region with the data at the specified memory location</returns>
- public WritableRegion GetWritableRegion(ulong gpuVa, int size)
+ public WritableRegion GetWritableRegion(ulong va, int size)
{
- ulong processVa = Translate(gpuVa);
+ if (IsContiguous(va, size))
+ {
+ return _context.PhysicalMemory.GetWritableRegion(Translate(va), size);
+ }
+ else
+ {
+ Memory<byte> memory = new byte[size];
+
+ GetSpan(va, size).CopyTo(memory.Span);
- return _context.PhysicalMemory.GetWritableRegion(processVa, size);
+ return new WritableRegion(this, va, memory);
+ }
}
/// <summary>
/// Writes data to GPU mapped memory.
/// </summary>
/// <typeparam name="T">Type of the data</typeparam>
- /// <param name="gpuVa">GPU virtual address to write the value into</param>
+ /// <param name="va">GPU virtual address to write the value into</param>
/// <param name="value">The value to be written</param>
- public void Write<T>(ulong gpuVa, T value) where T : unmanaged
+ public void Write<T>(ulong va, T value) where T : unmanaged
{
- ulong processVa = Translate(gpuVa);
-
- _context.PhysicalMemory.Write(processVa, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
+ Write(va, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
}
/// <summary>
/// Writes data to GPU mapped memory.
/// </summary>
- /// <param name="gpuVa">GPU virtual address to write the data into</param>
+ /// <param name="va">GPU virtual address to write the data into</param>
+ /// <param name="data">The data to be written</param>
+ public void Write(ulong va, ReadOnlySpan<byte> data)
+ {
+ WriteImpl(va, data, _context.PhysicalMemory.Write);
+ }
+
+ /// <summary>
+ /// Writes data to GPU mapped memory without write tracking.
+ /// </summary>
+ /// <param name="va">GPU virtual address to write the data into</param>
/// <param name="data">The data to be written</param>
- public void Write(ulong gpuVa, ReadOnlySpan<byte> data)
+ public void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
+ {
+ WriteImpl(va, data, _context.PhysicalMemory.WriteUntracked);
+ }
+
+ private delegate void WriteCallback(ulong address, ReadOnlySpan<byte> data);
+
+ /// <summary>
+ /// Writes data to possibly non-contiguous GPU mapped memory.
+ /// </summary>
+ /// <param name="va">GPU virtual address of the region to write into</param>
+ /// <param name="data">Data to be written</param>
+ /// <param name="writeCallback">Write callback</param>
+ private void WriteImpl(ulong va, ReadOnlySpan<byte> data, WriteCallback writeCallback)
{
- ulong processVa = Translate(gpuVa);
+ if (IsContiguous(va, data.Length))
+ {
+ writeCallback(Translate(va), data);
+ }
+ else
+ {
+ int offset = 0, size;
+
+ if ((va & PageMask) != 0)
+ {
+ ulong pa = Translate(va);
+
+ size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask));
+
+ writeCallback(pa, data.Slice(0, size));
- _context.PhysicalMemory.Write(processVa, data);
+ offset += size;
+ }
+
+ for (; offset < data.Length; offset += size)
+ {
+ ulong pa = Translate(va + (ulong)offset);
+
+ size = Math.Min(data.Length - offset, (int)PageSize);
+
+ writeCallback(pa, data.Slice(offset, size));
+ }
+ }
}
/// <summary>
@@ -148,41 +249,150 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
/// <summary>
+ /// Checks if a region of GPU mapped memory is contiguous.
+ /// </summary>
+ /// <param name="va">GPU virtual address of the region</param>
+ /// <param name="size">Size of the region</param>
+ /// <returns>True if the region is contiguous, false otherwise</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private bool IsContiguous(ulong va, int size)
+ {
+ if (!ValidateAddress(va) || GetPte(va) == PteUnmapped)
+ {
+ return false;
+ }
+
+ ulong endVa = (va + (ulong)size + PageMask) & ~PageMask;
+
+ va &= ~PageMask;
+
+ int pages = (int)((endVa - va) / PageSize);
+
+ for (int page = 0; page < pages - 1; page++)
+ {
+ if (!ValidateAddress(va + PageSize) || GetPte(va + PageSize) == PteUnmapped)
+ {
+ return false;
+ }
+
+ if (Translate(va) + PageSize != Translate(va + PageSize))
+ {
+ return false;
+ }
+
+ va += PageSize;
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Gets the physical regions that make up the given virtual address region.
+ /// </summary>
+ /// <param name="va">Virtual address of the range</param>
+ /// <param name="size">Size of the range</param>
+ /// <returns>Multi-range with the physical regions</returns>
+ /// <exception cref="InvalidMemoryRegionException">The memory region specified by <paramref name="va"/> and <paramref name="size"/> is not fully mapped</exception>
+ public MultiRange GetPhysicalRegions(ulong va, ulong size)
+ {
+ if (IsContiguous(va, (int)size))
+ {
+ return new MultiRange(Translate(va), size);
+ }
+
+ if (!IsMapped(va))
+ {
+ throw new InvalidMemoryRegionException($"The specified GPU virtual address 0x{va:X} is not mapped.");
+ }
+
+ ulong regionStart = Translate(va);
+ ulong regionSize = Math.Min(size, PageSize - (va & PageMask));
+
+ ulong endVa = va + size;
+ ulong endVaRounded = (endVa + PageMask) & ~PageMask;
+
+ va &= ~PageMask;
+
+ int pages = (int)((endVaRounded - va) / PageSize);
+
+ var regions = new List<MemoryRange>();
+
+ for (int page = 0; page < pages - 1; page++)
+ {
+ if (!IsMapped(va + PageSize))
+ {
+ throw new InvalidMemoryRegionException($"The specified GPU virtual memory range 0x{va:X}..0x{(va + size):X} is not fully mapped.");
+ }
+
+ ulong newPa = Translate(va + PageSize);
+
+ if (Translate(va) + PageSize != newPa)
+ {
+ regions.Add(new MemoryRange(regionStart, regionSize));
+ regionStart = newPa;
+ regionSize = 0;
+ }
+
+ va += PageSize;
+ regionSize += Math.Min(endVa - va, PageSize);
+ }
+
+ regions.Add(new MemoryRange(regionStart, regionSize));
+
+ return new MultiRange(regions.ToArray());
+ }
+
+ /// <summary>
+ /// Validates a GPU virtual address.
+ /// </summary>
+ /// <param name="va">Address to validate</param>
+ /// <returns>True if the address is valid, false otherwise</returns>
+ private static bool ValidateAddress(ulong va)
+ {
+ return va < (1UL << AddressSpaceBits);
+ }
+
+ /// <summary>
/// Checks if a given page is mapped.
/// </summary>
- /// <param name="gpuVa">GPU virtual address of the page to check</param>
+ /// <param name="va">GPU virtual address of the page to check</param>
/// <returns>True if the page is mapped, false otherwise</returns>
- public bool IsMapped(ulong gpuVa)
+ public bool IsMapped(ulong va)
{
- return Translate(gpuVa) != PteUnmapped;
+ return Translate(va) != PteUnmapped;
}
/// <summary>
/// Translates a GPU virtual address to a CPU virtual address.
/// </summary>
- /// <param name="gpuVa">GPU virtual address to be translated</param>
- /// <returns>CPU virtual address</returns>
- public ulong Translate(ulong gpuVa)
+ /// <param name="va">GPU virtual address to be translated</param>
+ /// <returns>CPU virtual address, or <see cref="PteUnmapped"/> if unmapped</returns>
+ public ulong Translate(ulong va)
{
- ulong baseAddress = GetPte(gpuVa);
+ if (!ValidateAddress(va))
+ {
+ return PteUnmapped;
+ }
+
+ ulong baseAddress = GetPte(va);
if (baseAddress == PteUnmapped)
{
return PteUnmapped;
}
- return baseAddress + (gpuVa & PageMask);
+ return baseAddress + (va & PageMask);
}
/// <summary>
/// Gets the Page Table entry for a given GPU virtual address.
/// </summary>
- /// <param name="gpuVa">GPU virtual address</param>
+ /// <param name="va">GPU virtual address</param>
/// <returns>Page table entry (CPU virtual address)</returns>
- private ulong GetPte(ulong gpuVa)
+ private ulong GetPte(ulong va)
{
- ulong l0 = (gpuVa >> PtLvl0Bit) & PtLvl0Mask;
- ulong l1 = (gpuVa >> PtLvl1Bit) & PtLvl1Mask;
+ ulong l0 = (va >> PtLvl0Bit) & PtLvl0Mask;
+ ulong l1 = (va >> PtLvl1Bit) & PtLvl1Mask;
if (_pageTable[l0] == null)
{
@@ -195,12 +405,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary>
/// Sets a Page Table entry at a given GPU virtual address.
/// </summary>
- /// <param name="gpuVa">GPU virtual address</param>
+ /// <param name="va">GPU virtual address</param>
/// <param name="pte">Page table entry (CPU virtual address)</param>
- private void SetPte(ulong gpuVa, ulong pte)
+ private void SetPte(ulong va, ulong pte)
{
- ulong l0 = (gpuVa >> PtLvl0Bit) & PtLvl0Mask;
- ulong l1 = (gpuVa >> PtLvl1Bit) & PtLvl1Mask;
+ ulong l0 = (va >> PtLvl0Bit) & PtLvl0Mask;
+ ulong l1 = (va >> PtLvl1Bit) & PtLvl1Mask;
if (_pageTable[l0] == null)
{
diff --git a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
index de41fb9a..8b2401c7 100644
--- a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
@@ -1,6 +1,7 @@
using Ryujinx.Cpu;
using Ryujinx.Cpu.Tracking;
using Ryujinx.Memory;
+using Ryujinx.Memory.Range;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -39,6 +40,37 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
/// <summary>
+ /// Gets a span of data from the application process.
+ /// </summary>
+ /// <param name="range">Ranges of physical memory where the data is located</param>
+ /// <param name="tracked">True if read tracking is triggered on the span</param>
+ /// <returns>A read only span of the data at the specified memory location</returns>
+ public ReadOnlySpan<byte> GetSpan(MultiRange range, bool tracked = false)
+ {
+ if (range.Count == 1)
+ {
+ var singleRange = range.GetSubRange(0);
+ return _cpuMemory.GetSpan(singleRange.Address, (int)singleRange.Size, tracked);
+ }
+ else
+ {
+ Span<byte> data = new byte[range.GetSize()];
+
+ int offset = 0;
+
+ for (int i = 0; i < range.Count; i++)
+ {
+ var currentRange = range.GetSubRange(i);
+ int size = (int)currentRange.Size;
+ _cpuMemory.GetSpan(currentRange.Address, size, tracked).CopyTo(data.Slice(offset, size));
+ offset += size;
+ }
+
+ return data;
+ }
+ }
+
+ /// <summary>
/// Gets a writable region from the application process.
/// </summary>
/// <param name="address">Start address of the range</param>
@@ -71,6 +103,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
/// <summary>
+ /// Writes data to the application process.
+ /// </summary>
+ /// <param name="range">Ranges of physical memory where the data is located</param>
+ /// <param name="data">Data to be written</param>
+ public void Write(MultiRange range, ReadOnlySpan<byte> data)
+ {
+ WriteImpl(range, data, _cpuMemory.Write);
+ }
+
+ /// <summary>
/// Writes data to the application process, without any tracking.
/// </summary>
/// <param name="address">Address to write into</param>
@@ -81,6 +123,45 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
/// <summary>
+ /// Writes data to the application process, without any tracking.
+ /// </summary>
+ /// <param name="range">Ranges of physical memory where the data is located</param>
+ /// <param name="data">Data to be written</param>
+ public void WriteUntracked(MultiRange range, ReadOnlySpan<byte> data)
+ {
+ WriteImpl(range, data, _cpuMemory.WriteUntracked);
+ }
+
+ private delegate void WriteCallback(ulong address, ReadOnlySpan<byte> data);
+
+ /// <summary>
+ /// Writes data to the application process, using the supplied callback method.
+ /// </summary>
+ /// <param name="range">Ranges of physical memory where the data is located</param>
+ /// <param name="data">Data to be written</param>
+ /// <param name="writeCallback">Callback method that will perform the write</param>
+ private void WriteImpl(MultiRange range, ReadOnlySpan<byte> data, WriteCallback writeCallback)
+ {
+ if (range.Count == 1)
+ {
+ var singleRange = range.GetSubRange(0);
+ writeCallback(singleRange.Address, data);
+ }
+ else
+ {
+ int offset = 0;
+
+ for (int i = 0; i < range.Count; i++)
+ {
+ var currentRange = range.GetSubRange(i);
+ int size = (int)currentRange.Size;
+ writeCallback(currentRange.Address, data.Slice(offset, size));
+ offset += size;
+ }
+ }
+ }
+
+ /// <summary>
/// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with.
/// </summary>
/// <param name="address">CPU virtual address of the region</param>
@@ -92,6 +173,24 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
/// <summary>
+ /// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with.
+ /// </summary>
+ /// <param name="range">Ranges of physical memory where the data is located</param>
+ /// <returns>The memory tracking handle</returns>
+ public GpuRegionHandle BeginTracking(MultiRange range)
+ {
+ var cpuRegionHandles = new CpuRegionHandle[range.Count];
+
+ for (int i = 0; i < range.Count; i++)
+ {
+ var currentRange = range.GetSubRange(i);
+ cpuRegionHandles[i] = _cpuMemory.BeginTracking(currentRange.Address, currentRange.Size);
+ }
+
+ return new GpuRegionHandle(cpuRegionHandles);
+ }
+
+ /// <summary>
/// Obtains a memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with.
/// </summary>
/// <param name="address">CPU virtual address of the region</param>