mirror of
https://github.com/ryujinx-mirror/ryujinx.git
synced 2024-11-28 01:50:50 +01:00
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
This commit is contained in:
parent
3bad321d2b
commit
c4f56c5704
@ -13,7 +13,7 @@ namespace Ryujinx.Cpu
|
||||
/// <summary>
|
||||
/// Represents a CPU memory manager.
|
||||
/// </summary>
|
||||
public sealed class MemoryManager : IMemoryManager, IVirtualMemoryManager, IDisposable
|
||||
public sealed class MemoryManager : IMemoryManager, IVirtualMemoryManager, IWritableBlock, IDisposable
|
||||
{
|
||||
public const int PageBits = 12;
|
||||
public const int PageSize = 1 << PageBits;
|
||||
@ -202,12 +202,12 @@ namespace Ryujinx.Cpu
|
||||
WriteImpl(va, data);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
/// <summary>
|
||||
/// Writes data to CPU mapped memory.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address to write the data into</param>
|
||||
/// <param name="data">Data to be written</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void WriteImpl(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
try
|
||||
|
@ -1,7 +1,7 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Cpu.Tracking;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using Ryujinx.Graphics.Texture.Astc;
|
||||
using Ryujinx.Memory.Range;
|
||||
@ -14,7 +14,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <summary>
|
||||
/// Represents a cached GPU texture.
|
||||
/// </summary>
|
||||
class Texture : IRange, IDisposable
|
||||
class Texture : IMultiRangeItem, IDisposable
|
||||
{
|
||||
// How many updates we need before switching to the byte-by-byte comparison
|
||||
// modification check method.
|
||||
@ -95,21 +95,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
public event Action<Texture> Disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Start address of the texture in guest memory.
|
||||
/// Physical memory ranges where the texture data is located.
|
||||
/// </summary>
|
||||
public ulong Address => Info.Address;
|
||||
|
||||
/// <summary>
|
||||
/// End address of the texture in guest memory.
|
||||
/// </summary>
|
||||
public ulong EndAddress => Info.Address + Size;
|
||||
public MultiRange Range { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Texture size in bytes.
|
||||
/// </summary>
|
||||
public ulong Size => (ulong)_sizeInfo.TotalSize;
|
||||
|
||||
private CpuRegionHandle _memoryTracking;
|
||||
private GpuRegionHandle _memoryTracking;
|
||||
|
||||
private int _referenceCount;
|
||||
|
||||
@ -119,6 +114,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="context">GPU context that the texture belongs to</param>
|
||||
/// <param name="info">Texture information</param>
|
||||
/// <param name="sizeInfo">Size information of the texture</param>
|
||||
/// <param name="range">Physical memory ranges where the texture data is located</param>
|
||||
/// <param name="firstLayer">The first layer of the texture, or 0 if the texture has no parent</param>
|
||||
/// <param name="firstLevel">The first mipmap level of the texture, or 0 if the texture has no parent</param>
|
||||
/// <param name="scaleFactor">The floating point scale factor to initialize with</param>
|
||||
@ -127,12 +123,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
GpuContext context,
|
||||
TextureInfo info,
|
||||
SizeInfo sizeInfo,
|
||||
MultiRange range,
|
||||
int firstLayer,
|
||||
int firstLevel,
|
||||
float scaleFactor,
|
||||
TextureScaleMode scaleMode)
|
||||
{
|
||||
InitializeTexture(context, info, sizeInfo);
|
||||
InitializeTexture(context, info, sizeInfo, range);
|
||||
|
||||
_firstLayer = firstLayer;
|
||||
_firstLevel = firstLevel;
|
||||
@ -149,13 +146,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="context">GPU context that the texture belongs to</param>
|
||||
/// <param name="info">Texture information</param>
|
||||
/// <param name="sizeInfo">Size information of the texture</param>
|
||||
/// <param name="range">Physical memory ranges where the texture data is located</param>
|
||||
/// <param name="scaleMode">The scale mode to initialize with. If scaled, the texture's data is loaded immediately and scaled up</param>
|
||||
public Texture(GpuContext context, TextureInfo info, SizeInfo sizeInfo, TextureScaleMode scaleMode)
|
||||
public Texture(GpuContext context, TextureInfo info, SizeInfo sizeInfo, MultiRange range, TextureScaleMode scaleMode)
|
||||
{
|
||||
ScaleFactor = 1f; // Texture is first loaded at scale 1x.
|
||||
ScaleMode = scaleMode;
|
||||
|
||||
InitializeTexture(context, info, sizeInfo);
|
||||
InitializeTexture(context, info, sizeInfo, range);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -166,10 +164,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="context">GPU context that the texture belongs to</param>
|
||||
/// <param name="info">Texture information</param>
|
||||
/// <param name="sizeInfo">Size information of the texture</param>
|
||||
private void InitializeTexture(GpuContext context, TextureInfo info, SizeInfo sizeInfo)
|
||||
/// <param name="range">Physical memory ranges where the texture data is located</param>
|
||||
private void InitializeTexture(GpuContext context, TextureInfo info, SizeInfo sizeInfo, MultiRange range)
|
||||
{
|
||||
_context = context;
|
||||
_sizeInfo = sizeInfo;
|
||||
Range = range;
|
||||
|
||||
SetInfo(info);
|
||||
|
||||
@ -186,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="withData">True if the texture is to be initialized with data</param>
|
||||
public void InitializeData(bool isView, bool withData = false)
|
||||
{
|
||||
_memoryTracking = _context.PhysicalMemory.BeginTracking(Address, Size);
|
||||
_memoryTracking = _context.PhysicalMemory.BeginTracking(Range);
|
||||
|
||||
if (withData)
|
||||
{
|
||||
@ -229,15 +229,17 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
/// <param name="info">Child texture information</param>
|
||||
/// <param name="sizeInfo">Child texture size information</param>
|
||||
/// <param name="range">Physical memory ranges where the texture data is located</param>
|
||||
/// <param name="firstLayer">Start layer of the child texture on the parent texture</param>
|
||||
/// <param name="firstLevel">Start mipmap level of the child texture on the parent texture</param>
|
||||
/// <returns>The child texture</returns>
|
||||
public Texture CreateView(TextureInfo info, SizeInfo sizeInfo, int firstLayer, int firstLevel)
|
||||
public Texture CreateView(TextureInfo info, SizeInfo sizeInfo, MultiRange range, int firstLayer, int firstLevel)
|
||||
{
|
||||
Texture texture = new Texture(
|
||||
_context,
|
||||
info,
|
||||
sizeInfo,
|
||||
range,
|
||||
_firstLayer + firstLayer,
|
||||
_firstLevel + firstLevel,
|
||||
ScaleFactor,
|
||||
@ -367,7 +369,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
ChangedSize = true;
|
||||
|
||||
SetInfo(new TextureInfo(
|
||||
Info.Address,
|
||||
Info.GpuAddress,
|
||||
width,
|
||||
height,
|
||||
depthOrLayers,
|
||||
@ -554,7 +556,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
_memoryTracking?.Reprotect();
|
||||
|
||||
ReadOnlySpan<byte> data = _context.PhysicalMemory.GetSpan(Address, (int)Size);
|
||||
ReadOnlySpan<byte> data = _context.PhysicalMemory.GetSpan(Range);
|
||||
|
||||
IsModified = false;
|
||||
|
||||
@ -586,6 +588,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
_hasData = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uploads new texture data to the host GPU.
|
||||
/// </summary>
|
||||
/// <param name="data">New data</param>
|
||||
public void SetData(ReadOnlySpan<byte> data)
|
||||
{
|
||||
BlacklistScale();
|
||||
@ -653,7 +659,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}";
|
||||
|
||||
Logger.Debug?.Print(LogClass.Gpu, $"Invalid ASTC texture at 0x{Info.Address:X} ({texInfo}).");
|
||||
Logger.Debug?.Print(LogClass.Gpu, $"Invalid ASTC texture at 0x{Info.GpuAddress:X} ({texInfo}).");
|
||||
}
|
||||
|
||||
data = decoded;
|
||||
@ -689,15 +695,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
if (tracked)
|
||||
{
|
||||
_context.PhysicalMemory.Write(Address, GetTextureDataFromGpu(tracked));
|
||||
_context.PhysicalMemory.Write(Range, GetTextureDataFromGpu(tracked));
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.PhysicalMemory.WriteUntracked(Address, GetTextureDataFromGpu(tracked));
|
||||
_context.PhysicalMemory.WriteUntracked(Range, GetTextureDataFromGpu(tracked));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Flushes the texture data, to be called from an external thread.
|
||||
/// The host backend must ensure that we have shared access to the resource from this thread.
|
||||
@ -725,7 +730,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
texture = _flushHostTexture = GetScaledHostTexture(1f, _flushHostTexture);
|
||||
}
|
||||
|
||||
_context.PhysicalMemory.WriteUntracked(Address, GetTextureDataFromGpu(false, texture));
|
||||
_context.PhysicalMemory.WriteUntracked(Range, GetTextureDataFromGpu(false, texture));
|
||||
});
|
||||
}
|
||||
|
||||
@ -847,25 +852,23 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return TextureMatchQuality.NoMatch;
|
||||
}
|
||||
|
||||
return Info.Address == info.Address && Info.Levels == info.Levels ? matchQuality : TextureMatchQuality.NoMatch;
|
||||
return Info.Levels == info.Levels ? matchQuality : TextureMatchQuality.NoMatch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if it's possible to create a view, with the given parameters, from this texture.
|
||||
/// </summary>
|
||||
/// <param name="info">Texture view information</param>
|
||||
/// <param name="size">Texture view size</param>
|
||||
/// <param name="range">Texture view physical memory ranges</param>
|
||||
/// <param name="firstLayer">Texture view initial layer on this texture</param>
|
||||
/// <param name="firstLevel">Texture view first mipmap level on this texture</param>
|
||||
/// <returns>The level of compatiblilty a view with the given parameters created from this texture has</returns>
|
||||
public TextureViewCompatibility IsViewCompatible(
|
||||
TextureInfo info,
|
||||
ulong size,
|
||||
out int firstLayer,
|
||||
out int firstLevel)
|
||||
public TextureViewCompatibility IsViewCompatible(TextureInfo info, MultiRange range, out int firstLayer, out int firstLevel)
|
||||
{
|
||||
int offset = Range.FindOffset(range);
|
||||
|
||||
// Out of range.
|
||||
if (info.Address < Address || info.Address + size > EndAddress)
|
||||
if (offset < 0)
|
||||
{
|
||||
firstLayer = 0;
|
||||
firstLevel = 0;
|
||||
@ -873,9 +876,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
|
||||
int offset = (int)(info.Address - Address);
|
||||
|
||||
if (!_sizeInfo.FindView(offset, (int)size, out firstLayer, out firstLevel))
|
||||
if (!_sizeInfo.FindView(offset, out firstLayer, out firstLevel))
|
||||
{
|
||||
return TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
@ -1045,17 +1046,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
HostTexture = hostTexture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the texture overlaps with a memory range.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="size">Size of the range</param>
|
||||
/// <returns>True if the texture overlaps with the range, false otherwise</returns>
|
||||
public bool OverlapsWith(ulong address, ulong size)
|
||||
{
|
||||
return Address < address + size && address < EndAddress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if any of our child textures are compaible as views of the given texture.
|
||||
/// </summary>
|
||||
@ -1070,7 +1060,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
foreach (Texture view in _views)
|
||||
{
|
||||
if (texture.IsViewCompatible(view.Info, view.Size, out int _, out int _) != TextureViewCompatibility.Incompatible)
|
||||
if (texture.IsViewCompatible(view.Info, view.Range, out _, out _) != TextureViewCompatibility.Incompatible)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -1153,7 +1143,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
IsModified = false; // We shouldn't flush this texture, as its memory is no longer mapped.
|
||||
|
||||
CpuRegionHandle tracking = _memoryTracking;
|
||||
var tracking = _memoryTracking;
|
||||
tracking?.Reprotect();
|
||||
tracking?.RegisterAction(null);
|
||||
}
|
||||
|
@ -303,7 +303,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
// Ensure that the buffer texture is using the correct buffer as storage.
|
||||
// Buffers are frequently re-created to accomodate larger data, so we need to re-bind
|
||||
// to ensure we're not using a old buffer that was already deleted.
|
||||
_context.Methods.BufferManager.SetBufferTextureStorage(hostTexture, texture.Address, texture.Size, _isCompute);
|
||||
_context.Methods.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, _isCompute);
|
||||
}
|
||||
|
||||
Sampler sampler = _samplerPool.Get(samplerId);
|
||||
@ -354,7 +354,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
// Ensure that the buffer texture is using the correct buffer as storage.
|
||||
// Buffers are frequently re-created to accomodate larger data, so we need to re-bind
|
||||
// to ensure we're not using a old buffer that was already deleted.
|
||||
_context.Methods.BufferManager.SetBufferTextureStorage(hostTexture, texture.Address, texture.Size, _isCompute);
|
||||
_context.Methods.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, _isCompute);
|
||||
}
|
||||
|
||||
if (_imageState[stageIndex][index].Texture != hostTexture || _rebind)
|
||||
|
@ -9,9 +9,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
struct TextureInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Address of the texture in guest memory.
|
||||
/// Address of the texture in GPU mapped memory.
|
||||
/// </summary>
|
||||
public ulong Address { get; }
|
||||
public ulong GpuAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The width of the texture.
|
||||
@ -112,7 +112,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <summary>
|
||||
/// Constructs the texture information structure.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of the texture</param>
|
||||
/// <param name="gpuAddress">The GPU address of the texture</param>
|
||||
/// <param name="width">The width of the texture</param>
|
||||
/// <param name="height">The height or the texture</param>
|
||||
/// <param name="depthOrLayers">The depth or layers count of the texture</param>
|
||||
@ -132,7 +132,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="swizzleB">Swizzle for the blue color channel</param>
|
||||
/// <param name="swizzleA">Swizzle for the alpha color channel</param>
|
||||
public TextureInfo(
|
||||
ulong address,
|
||||
ulong gpuAddress,
|
||||
int width,
|
||||
int height,
|
||||
int depthOrLayers,
|
||||
@ -152,7 +152,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
SwizzleComponent swizzleB = SwizzleComponent.Blue,
|
||||
SwizzleComponent swizzleA = SwizzleComponent.Alpha)
|
||||
{
|
||||
Address = address;
|
||||
GpuAddress = gpuAddress;
|
||||
Width = width;
|
||||
Height = height;
|
||||
DepthOrLayers = depthOrLayers;
|
||||
|
@ -37,14 +37,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
private readonly TextureBindingsManager _gpBindingsManager;
|
||||
|
||||
private readonly Texture[] _rtColors;
|
||||
|
||||
private Texture _rtDepthStencil;
|
||||
|
||||
private readonly ITexture[] _rtHostColors;
|
||||
|
||||
private Texture _rtDepthStencil;
|
||||
private ITexture _rtHostDs;
|
||||
|
||||
private readonly RangeList<Texture> _textures;
|
||||
private readonly MultiRangeList<Texture> _textures;
|
||||
|
||||
private Texture[] _textureOverlaps;
|
||||
private OverlapInfo[] _overlapInfo;
|
||||
@ -70,10 +67,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
_gpBindingsManager = new TextureBindingsManager(context, texturePoolCache, isCompute: false);
|
||||
|
||||
_rtColors = new Texture[Constants.TotalRenderTargets];
|
||||
|
||||
_rtHostColors = new ITexture[Constants.TotalRenderTargets];
|
||||
|
||||
_textures = new RangeList<Texture>();
|
||||
_textures = new MultiRangeList<Texture>();
|
||||
|
||||
_textureOverlaps = new Texture[OverlapsBufferInitialCapacity];
|
||||
_overlapInfo = new OverlapInfo[OverlapsBufferInitialCapacity];
|
||||
@ -470,13 +466,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <returns>The texture</returns>
|
||||
public Texture FindOrCreateTexture(CopyTexture copyTexture, FormatInfo formatInfo, bool preferScaling = true, Size? sizeHint = null)
|
||||
{
|
||||
ulong address = _context.MemoryManager.Translate(copyTexture.Address.Pack());
|
||||
|
||||
if (address == MemoryManager.PteUnmapped)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int gobBlocksInY = copyTexture.MemoryLayout.UnpackGobBlocksInY();
|
||||
int gobBlocksInZ = copyTexture.MemoryLayout.UnpackGobBlocksInZ();
|
||||
|
||||
@ -492,7 +481,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
TextureInfo info = new TextureInfo(
|
||||
address,
|
||||
copyTexture.Address.Pack(),
|
||||
width,
|
||||
copyTexture.Height,
|
||||
copyTexture.Depth,
|
||||
@ -514,9 +503,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
flags |= TextureSearchFlags.WithUpscale;
|
||||
}
|
||||
|
||||
Texture texture = FindOrCreateTexture(info, flags, 0, sizeHint);
|
||||
Texture texture = FindOrCreateTexture(flags, info, 0, sizeHint);
|
||||
|
||||
texture.SynchronizeMemory();
|
||||
texture?.SynchronizeMemory();
|
||||
|
||||
return texture;
|
||||
}
|
||||
@ -531,13 +520,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <returns>The texture</returns>
|
||||
public Texture FindOrCreateTexture(RtColorState colorState, int samplesInX, int samplesInY, Size sizeHint)
|
||||
{
|
||||
ulong address = _context.MemoryManager.Translate(colorState.Address.Pack());
|
||||
|
||||
if (address == MemoryManager.PteUnmapped)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
bool isLinear = colorState.MemoryLayout.UnpackIsLinear();
|
||||
|
||||
int gobBlocksInY = colorState.MemoryLayout.UnpackGobBlocksInY();
|
||||
@ -583,7 +565,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
TextureInfo info = new TextureInfo(
|
||||
address,
|
||||
colorState.Address.Pack(),
|
||||
width,
|
||||
colorState.Height,
|
||||
colorState.Depth,
|
||||
@ -600,9 +582,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
int layerSize = !isLinear ? colorState.LayerSize * 4 : 0;
|
||||
|
||||
Texture texture = FindOrCreateTexture(info, TextureSearchFlags.WithUpscale, layerSize, sizeHint);
|
||||
Texture texture = FindOrCreateTexture(TextureSearchFlags.WithUpscale, info, layerSize, sizeHint);
|
||||
|
||||
texture.SynchronizeMemory();
|
||||
texture?.SynchronizeMemory();
|
||||
|
||||
return texture;
|
||||
}
|
||||
@ -618,13 +600,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <returns>The texture</returns>
|
||||
public Texture FindOrCreateTexture(RtDepthStencilState dsState, Size3D size, int samplesInX, int samplesInY, Size sizeHint)
|
||||
{
|
||||
ulong address = _context.MemoryManager.Translate(dsState.Address.Pack());
|
||||
|
||||
if (address == MemoryManager.PteUnmapped)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY();
|
||||
int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ();
|
||||
|
||||
@ -635,7 +610,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
FormatInfo formatInfo = dsState.Format.Convert();
|
||||
|
||||
TextureInfo info = new TextureInfo(
|
||||
address,
|
||||
dsState.Address.Pack(),
|
||||
size.Width,
|
||||
size.Height,
|
||||
size.Depth,
|
||||
@ -650,9 +625,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
target,
|
||||
formatInfo);
|
||||
|
||||
Texture texture = FindOrCreateTexture(info, TextureSearchFlags.WithUpscale, dsState.LayerSize * 4, sizeHint);
|
||||
Texture texture = FindOrCreateTexture(TextureSearchFlags.WithUpscale, info, dsState.LayerSize * 4, sizeHint);
|
||||
|
||||
texture.SynchronizeMemory();
|
||||
texture?.SynchronizeMemory();
|
||||
|
||||
return texture;
|
||||
}
|
||||
@ -660,12 +635,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <summary>
|
||||
/// Tries to find an existing texture, or create a new one if not found.
|
||||
/// </summary>
|
||||
/// <param name="info">Texture information of the texture to be found or created</param>
|
||||
/// <param name="flags">The texture search flags, defines texture comparison rules</param>
|
||||
/// <param name="info">Texture information of the texture to be found or created</param>
|
||||
/// <param name="layerSize">Size in bytes of a single texture layer</param>
|
||||
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
|
||||
/// <param name="range">Optional ranges of physical memory where the texture data is located</param>
|
||||
/// <returns>The texture</returns>
|
||||
public Texture FindOrCreateTexture(TextureInfo info, TextureSearchFlags flags = TextureSearchFlags.None, int layerSize = 0, Size? sizeHint = null)
|
||||
public Texture FindOrCreateTexture(TextureSearchFlags flags, TextureInfo info, int layerSize = 0, Size? sizeHint = null, MultiRange? range = null)
|
||||
{
|
||||
bool isSamplerTexture = (flags & TextureSearchFlags.ForSampler) != 0;
|
||||
|
||||
@ -677,12 +653,28 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
scaleMode = (flags & TextureSearchFlags.WithUpscale) != 0 ? TextureScaleMode.Scaled : TextureScaleMode.Eligible;
|
||||
}
|
||||
|
||||
ulong address;
|
||||
|
||||
if (range != null)
|
||||
{
|
||||
address = range.Value.GetSubRange(0).Address;
|
||||
}
|
||||
else
|
||||
{
|
||||
address = _context.MemoryManager.Translate(info.GpuAddress);
|
||||
|
||||
if (address == MemoryManager.PteUnmapped)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
int sameAddressOverlapsCount;
|
||||
|
||||
lock (_textures)
|
||||
{
|
||||
// Try to find a perfect texture match, with the same address and parameters.
|
||||
sameAddressOverlapsCount = _textures.FindOverlaps(info.Address, ref _textureOverlaps);
|
||||
sameAddressOverlapsCount = _textures.FindOverlaps(address, ref _textureOverlaps);
|
||||
}
|
||||
|
||||
Texture texture = null;
|
||||
@ -693,6 +685,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
Texture overlap = _textureOverlaps[index];
|
||||
|
||||
bool rangeMatches = range != null ? overlap.Range.Equals(range.Value) : overlap.Info.GpuAddress == info.GpuAddress;
|
||||
if (!rangeMatches)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
TextureMatchQuality matchQuality = overlap.IsExactMatch(info, flags);
|
||||
|
||||
if (matchQuality == TextureMatchQuality.Perfect)
|
||||
@ -727,19 +725,25 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
// Calculate texture sizes, used to find all overlapping textures.
|
||||
SizeInfo sizeInfo = info.CalculateSizeInfo(layerSize);
|
||||
|
||||
// Find view compatible matches.
|
||||
ulong size = (ulong)sizeInfo.TotalSize;
|
||||
|
||||
if (range == null)
|
||||
{
|
||||
range = _context.MemoryManager.GetPhysicalRegions(info.GpuAddress, size);
|
||||
}
|
||||
|
||||
// Find view compatible matches.
|
||||
int overlapsCount;
|
||||
|
||||
lock (_textures)
|
||||
{
|
||||
overlapsCount = _textures.FindOverlaps(info.Address, size, ref _textureOverlaps);
|
||||
overlapsCount = _textures.FindOverlaps(range.Value, ref _textureOverlaps);
|
||||
}
|
||||
|
||||
for (int index = 0; index < overlapsCount; index++)
|
||||
{
|
||||
Texture overlap = _textureOverlaps[index];
|
||||
TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(info, size, out int firstLayer, out int firstLevel);
|
||||
TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(info, range.Value, out int firstLayer, out int firstLevel);
|
||||
|
||||
if (overlapCompatibility == TextureViewCompatibility.Full)
|
||||
{
|
||||
@ -750,7 +754,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
info = oInfo;
|
||||
}
|
||||
|
||||
texture = overlap.CreateView(oInfo, sizeInfo, firstLayer, firstLevel);
|
||||
texture = overlap.CreateView(oInfo, sizeInfo, range.Value, firstLayer, firstLevel);
|
||||
|
||||
if (overlap.IsModified)
|
||||
{
|
||||
@ -771,7 +775,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
// No match, create a new texture.
|
||||
if (texture == null)
|
||||
{
|
||||
texture = new Texture(_context, info, sizeInfo, scaleMode);
|
||||
texture = new Texture(_context, info, sizeInfo, range.Value, scaleMode);
|
||||
|
||||
// Step 1: Find textures that are view compatible with the new texture.
|
||||
// Any textures that are incompatible will contain garbage data, so they should be removed where possible.
|
||||
@ -784,7 +788,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
Texture overlap = _textureOverlaps[index];
|
||||
bool overlapInCache = overlap.CacheNode != null;
|
||||
|
||||
TextureViewCompatibility compatibility = texture.IsViewCompatible(overlap.Info, overlap.Size, out int firstLayer, out int firstLevel);
|
||||
TextureViewCompatibility compatibility = texture.IsViewCompatible(overlap.Info, overlap.Range, out int firstLayer, out int firstLevel);
|
||||
|
||||
if (compatibility != TextureViewCompatibility.Incompatible)
|
||||
{
|
||||
@ -812,7 +816,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
// If the data has been modified by the CPU, then it also shouldn't be flushed.
|
||||
bool modified = overlap.ConsumeModified();
|
||||
|
||||
bool flush = overlapInCache && !modified && (overlap.Address < texture.Address || overlap.EndAddress > texture.EndAddress) && overlap.HasViewCompatibleChild(texture);
|
||||
bool flush = overlapInCache && !modified && !texture.Range.Contains(overlap.Range) && overlap.HasViewCompatibleChild(texture);
|
||||
|
||||
setData |= modified || flush;
|
||||
|
||||
@ -1070,7 +1074,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
return new TextureInfo(
|
||||
info.Address,
|
||||
info.GpuAddress,
|
||||
width,
|
||||
height,
|
||||
depthOrLayers,
|
||||
|
@ -54,15 +54,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
TextureInfo info = GetInfo(descriptor, out int layerSize);
|
||||
|
||||
// Bad address. We can't add a texture with a invalid address
|
||||
// to the cache.
|
||||
if (info.Address == MemoryManager.PteUnmapped)
|
||||
texture = Context.Methods.TextureManager.FindOrCreateTexture(TextureSearchFlags.ForSampler, info, layerSize);
|
||||
|
||||
// If this happens, then the texture address is invalid, we can't add it to the cache.
|
||||
if (texture == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
texture = Context.Methods.TextureManager.FindOrCreateTexture(info, TextureSearchFlags.ForSampler, layerSize);
|
||||
|
||||
texture.IncrementReferenceCount();
|
||||
|
||||
Items[id] = texture;
|
||||
@ -123,7 +122,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
// If the descriptors are the same, the texture is the same,
|
||||
// we don't need to remove as it was not modified. Just continue.
|
||||
if (texture.IsExactMatch(GetInfo(descriptor, out _), TextureSearchFlags.Strict) != TextureMatchQuality.NoMatch)
|
||||
if (texture.Info.GpuAddress == descriptor.UnpackAddress() &&
|
||||
texture.IsExactMatch(GetInfo(descriptor, out _), TextureSearchFlags.Strict) != TextureMatchQuality.NoMatch)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -143,9 +143,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <returns>The texture information</returns>
|
||||
private TextureInfo GetInfo(TextureDescriptor descriptor, out int layerSize)
|
||||
{
|
||||
ulong address = Context.MemoryManager.Translate(descriptor.UnpackAddress());
|
||||
bool addressIsValid = address != MemoryManager.PteUnmapped;
|
||||
|
||||
int width = descriptor.UnpackWidth();
|
||||
int height = descriptor.UnpackHeight();
|
||||
int depthOrLayers = descriptor.UnpackDepth();
|
||||
@ -183,9 +180,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
uint format = descriptor.UnpackFormat();
|
||||
bool srgb = descriptor.UnpackSrgb();
|
||||
|
||||
ulong gpuVa = descriptor.UnpackAddress();
|
||||
|
||||
if (!FormatTable.TryGetTextureFormat(format, srgb, out FormatInfo formatInfo))
|
||||
{
|
||||
if (addressIsValid && (int)format > 0)
|
||||
if (Context.MemoryManager.IsMapped(gpuVa) && (int)format > 0)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Invalid texture format 0x{format:X} (sRGB: {srgb}).");
|
||||
}
|
||||
@ -204,7 +203,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
int maxLod = descriptor.UnpackMaxLevelInclusive();
|
||||
|
||||
// Linear textures don't support mipmaps, so we don't handle this case here.
|
||||
if ((minLod != 0 || maxLod + 1 != levels) && target != Target.TextureBuffer && !isLinear && addressIsValid)
|
||||
if ((minLod != 0 || maxLod + 1 != levels) && target != Target.TextureBuffer && !isLinear)
|
||||
{
|
||||
int depth = TextureInfo.GetDepth(target, depthOrLayers);
|
||||
int layers = TextureInfo.GetLayers(target, depthOrLayers);
|
||||
@ -229,7 +228,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
// If the base level is not zero, we additionally add the mip level offset
|
||||
// to the address, this allows the texture manager to find the base level from the
|
||||
// address if there is a overlapping texture on the cache that can contain the new texture.
|
||||
address += (ulong)sizeInfo.GetMipOffset(minLod);
|
||||
gpuVa += (ulong)sizeInfo.GetMipOffset(minLod);
|
||||
|
||||
width = Math.Max(1, width >> minLod);
|
||||
height = Math.Max(1, height >> minLod);
|
||||
@ -274,7 +273,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
return new TextureInfo(
|
||||
address,
|
||||
gpuVa,
|
||||
width,
|
||||
height,
|
||||
depthOrLayers,
|
||||
|
60
Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs
Normal file
60
Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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];
|
||||
|
||||
return _context.PhysicalMemory.GetWritableRegion(processVa, size);
|
||||
GetSpan(va, size).CopyTo(memory.Span);
|
||||
|
||||
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 gpuVa, ReadOnlySpan<byte> data)
|
||||
public void Write(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
ulong processVa = Translate(gpuVa);
|
||||
WriteImpl(va, data, _context.PhysicalMemory.Write);
|
||||
}
|
||||
|
||||
_context.PhysicalMemory.Write(processVa, data);
|
||||
/// <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 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)
|
||||
{
|
||||
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));
|
||||
|
||||
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>
|
||||
@ -147,42 +248,151 @@ 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)
|
||||
{
|
||||
|
@ -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;
|
||||
@ -38,6 +39,37 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return _cpuMemory.GetSpan(address, size, tracked);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
@ -70,6 +102,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
_cpuMemory.Write(address, data);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
@ -80,6 +122,45 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
_cpuMemory.WriteUntracked(address, data);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
@ -91,6 +172,24 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return _cpuMemory.BeginTracking(address, size);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
|
@ -1,5 +1,7 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
@ -25,6 +27,11 @@ namespace Ryujinx.Graphics.Gpu
|
||||
/// </summary>
|
||||
public TextureInfo Info { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Physical memory locations where the texture data is located.
|
||||
/// </summary>
|
||||
public MultiRange Range { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Texture crop region.
|
||||
/// </summary>
|
||||
@ -49,18 +56,21 @@ namespace Ryujinx.Graphics.Gpu
|
||||
/// Creates a new instance of the presentation texture.
|
||||
/// </summary>
|
||||
/// <param name="info">Information of the texture to be presented</param>
|
||||
/// <param name="range">Physical memory locations where the texture data is located</param>
|
||||
/// <param name="crop">Texture crop region</param>
|
||||
/// <param name="acquireCallback">Texture acquire callback</param>
|
||||
/// <param name="releaseCallback">Texture release callback</param>
|
||||
/// <param name="userObj">User defined object passed to the release callback, can be used to identify the texture</param>
|
||||
public PresentationTexture(
|
||||
TextureInfo info,
|
||||
MultiRange range,
|
||||
ImageCrop crop,
|
||||
Action<GpuContext, object> acquireCallback,
|
||||
Action<object> releaseCallback,
|
||||
object userObj)
|
||||
{
|
||||
Info = info;
|
||||
Range = range;
|
||||
Crop = crop;
|
||||
AcquireCallback = acquireCallback;
|
||||
ReleaseCallback = releaseCallback;
|
||||
@ -118,7 +128,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||
FormatInfo formatInfo = new FormatInfo(format, 1, 1, bytesPerPixel, 4);
|
||||
|
||||
TextureInfo info = new TextureInfo(
|
||||
address,
|
||||
0UL,
|
||||
width,
|
||||
height,
|
||||
1,
|
||||
@ -133,7 +143,22 @@ namespace Ryujinx.Graphics.Gpu
|
||||
Target.Texture2D,
|
||||
formatInfo);
|
||||
|
||||
_frameQueue.Enqueue(new PresentationTexture(info, crop, acquireCallback, releaseCallback, userObj));
|
||||
int size = SizeCalculator.GetBlockLinearTextureSize(
|
||||
width,
|
||||
height,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
bytesPerPixel,
|
||||
gobBlocksInY,
|
||||
1,
|
||||
1).TotalSize;
|
||||
|
||||
MultiRange range = new MultiRange(address, (ulong)size);
|
||||
|
||||
_frameQueue.Enqueue(new PresentationTexture(info, range, crop, acquireCallback, releaseCallback, userObj));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -149,7 +174,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||
{
|
||||
pt.AcquireCallback(_context, pt.UserObj);
|
||||
|
||||
Texture texture = _context.Methods.TextureManager.FindOrCreateTexture(pt.Info, TextureSearchFlags.WithUpscale);
|
||||
Texture texture = _context.Methods.TextureManager.FindOrCreateTexture(TextureSearchFlags.WithUpscale, pt.Info, 0, null, pt.Range);
|
||||
|
||||
texture.SynchronizeMemory();
|
||||
|
||||
|
@ -45,7 +45,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
return _mipOffsets[level];
|
||||
}
|
||||
|
||||
public bool FindView(int offset, int size, out int firstLayer, out int firstLevel)
|
||||
public bool FindView(int offset, out int firstLayer, out int firstLevel)
|
||||
{
|
||||
int index = Array.BinarySearch(_allOffsets, offset);
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
using Ryujinx.Common;
|
||||
using System;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@ -9,7 +8,7 @@ namespace Ryujinx.Memory
|
||||
/// Represents a address space manager.
|
||||
/// Supports virtual memory region mapping, address translation and read/write access to mapped regions.
|
||||
/// </summary>
|
||||
public sealed class AddressSpaceManager : IVirtualMemoryManager
|
||||
public sealed class AddressSpaceManager : IVirtualMemoryManager, IWritableBlock
|
||||
{
|
||||
public const int PageBits = 12;
|
||||
public const int PageSize = 1 << PageBits;
|
||||
|
9
Ryujinx.Memory/IWritableBlock.cs
Normal file
9
Ryujinx.Memory/IWritableBlock.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Memory
|
||||
{
|
||||
public interface IWritableBlock
|
||||
{
|
||||
void Write(ulong va, ReadOnlySpan<byte> data);
|
||||
}
|
||||
}
|
9
Ryujinx.Memory/Range/IMultiRangeItem.cs
Normal file
9
Ryujinx.Memory/Range/IMultiRangeItem.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Ryujinx.Memory.Range
|
||||
{
|
||||
public interface IMultiRangeItem
|
||||
{
|
||||
MultiRange Range { get; }
|
||||
|
||||
ulong BaseAddress => Range.GetSubRange(0).Address;
|
||||
}
|
||||
}
|
71
Ryujinx.Memory/Range/MemoryRange.cs
Normal file
71
Ryujinx.Memory/Range/MemoryRange.cs
Normal file
@ -0,0 +1,71 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Memory.Range
|
||||
{
|
||||
/// <summary>
|
||||
/// Range of memory composed of an address and size.
|
||||
/// </summary>
|
||||
public struct MemoryRange : IEquatable<MemoryRange>
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty memory range, with a null address and zero size.
|
||||
/// </summary>
|
||||
public static MemoryRange Empty => new MemoryRange(0UL, 0);
|
||||
|
||||
/// <summary>
|
||||
/// Start address of the range.
|
||||
/// </summary>
|
||||
public ulong Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Size of the range in bytes.
|
||||
/// </summary>
|
||||
public ulong Size { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Address where the range ends (exclusive).
|
||||
/// </summary>
|
||||
public ulong EndAddress => Address + Size;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new memory range with the specified address and size.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address</param>
|
||||
/// <param name="size">Size in bytes</param>
|
||||
public MemoryRange(ulong address, ulong size)
|
||||
{
|
||||
Address = address;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the range overlaps with another.
|
||||
/// </summary>
|
||||
/// <param name="other">The other range to check for overlap</param>
|
||||
/// <returns>True if the ranges overlap, false otherwise</returns>
|
||||
public bool OverlapsWith(MemoryRange other)
|
||||
{
|
||||
ulong thisAddress = Address;
|
||||
ulong thisEndAddress = EndAddress;
|
||||
ulong otherAddress = other.Address;
|
||||
ulong otherEndAddress = other.EndAddress;
|
||||
|
||||
return thisAddress < otherEndAddress && otherAddress < thisEndAddress;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is MemoryRange other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals(MemoryRange other)
|
||||
{
|
||||
return Address == other.Address && Size == other.Size;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Address, Size);
|
||||
}
|
||||
}
|
||||
}
|
295
Ryujinx.Memory/Range/MultiRange.cs
Normal file
295
Ryujinx.Memory/Range/MultiRange.cs
Normal file
@ -0,0 +1,295 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Memory.Range
|
||||
{
|
||||
/// <summary>
|
||||
/// Sequence of physical memory regions that a single non-contiguous virtual memory region maps to.
|
||||
/// </summary>
|
||||
public struct MultiRange : IEquatable<MultiRange>
|
||||
{
|
||||
private readonly MemoryRange _singleRange;
|
||||
private readonly MemoryRange[] _ranges;
|
||||
|
||||
private bool HasSingleRange => _ranges == null;
|
||||
|
||||
/// <summary>
|
||||
/// Total of physical sub-ranges on the virtual memory region.
|
||||
/// </summary>
|
||||
public int Count => HasSingleRange ? 1 : _ranges.Length;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum start address of all sub-ranges.
|
||||
/// </summary>
|
||||
public ulong MinAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum end address of all sub-ranges.
|
||||
/// </summary>
|
||||
public ulong MaxAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new multi-range with a single physical region.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the region</param>
|
||||
/// <param name="size">Size of the region in bytes</param>
|
||||
public MultiRange(ulong address, ulong size)
|
||||
{
|
||||
_singleRange = new MemoryRange(address, size);
|
||||
_ranges = null;
|
||||
MinAddress = address;
|
||||
MaxAddress = address + size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new multi-range with multiple physical regions.
|
||||
/// </summary>
|
||||
/// <param name="ranges">Array of physical regions</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="ranges"/> is null</exception>
|
||||
public MultiRange(MemoryRange[] ranges)
|
||||
{
|
||||
_singleRange = MemoryRange.Empty;
|
||||
_ranges = ranges ?? throw new ArgumentNullException(nameof(ranges));
|
||||
|
||||
if (ranges.Length != 0)
|
||||
{
|
||||
MinAddress = ulong.MaxValue;
|
||||
MaxAddress = 0UL;
|
||||
|
||||
foreach (MemoryRange range in ranges)
|
||||
{
|
||||
if (MinAddress > range.Address)
|
||||
{
|
||||
MinAddress = range.Address;
|
||||
}
|
||||
|
||||
if (MaxAddress < range.EndAddress)
|
||||
{
|
||||
MaxAddress = range.EndAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MinAddress = 0UL;
|
||||
MaxAddress = 0UL;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the physical region at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the physical region</param>
|
||||
/// <returns>Region at the index specified</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is invalid</exception>
|
||||
public MemoryRange GetSubRange(int index)
|
||||
{
|
||||
if (HasSingleRange)
|
||||
{
|
||||
if (index != 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
|
||||
return _singleRange;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((uint)index >= _ranges.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
|
||||
return _ranges[index];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the physical region at the specified index, without explicit bounds checking.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the physical region</param>
|
||||
/// <returns>Region at the index specified</returns>
|
||||
private MemoryRange GetSubRangeUnchecked(int index)
|
||||
{
|
||||
return HasSingleRange ? _singleRange : _ranges[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if two multi-ranges overlap with each other.
|
||||
/// </summary>
|
||||
/// <param name="other">Other multi-range to check for overlap</param>
|
||||
/// <returns>True if any sub-range overlaps, false otherwise</returns>
|
||||
public bool OverlapsWith(MultiRange other)
|
||||
{
|
||||
if (HasSingleRange && other.HasSingleRange)
|
||||
{
|
||||
return _singleRange.OverlapsWith(other._singleRange);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
MemoryRange currentRange = GetSubRangeUnchecked(i);
|
||||
|
||||
for (int j = 0; j < other.Count; j++)
|
||||
{
|
||||
if (currentRange.OverlapsWith(other.GetSubRangeUnchecked(j)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given multi-range is fully contained inside another.
|
||||
/// </summary>
|
||||
/// <param name="other">Multi-range to be checked</param>
|
||||
/// <returns>True if all the sub-ranges on <paramref name="other"/> are contained inside the multi-range, with the same order, false otherwise</returns>
|
||||
public bool Contains(MultiRange other)
|
||||
{
|
||||
return FindOffset(other) >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the offset of a given multi-range inside another, when the multi-range is fully contained
|
||||
/// inside the other multi-range, otherwise returns -1.
|
||||
/// </summary>
|
||||
/// <param name="other">Multi-range that should be fully contained inside this one</param>
|
||||
/// <returns>Offset in bytes if fully contained, otherwise -1</returns>
|
||||
public int FindOffset(MultiRange other)
|
||||
{
|
||||
int thisCount = Count;
|
||||
int otherCount = other.Count;
|
||||
|
||||
if (thisCount == 1 && otherCount == 1)
|
||||
{
|
||||
MemoryRange otherFirstRange = other.GetSubRangeUnchecked(0);
|
||||
MemoryRange currentFirstRange = GetSubRangeUnchecked(0);
|
||||
|
||||
if (otherFirstRange.Address >= currentFirstRange.Address &&
|
||||
otherFirstRange.EndAddress <= currentFirstRange.EndAddress)
|
||||
{
|
||||
return (int)(otherFirstRange.Address - currentFirstRange.Address);
|
||||
}
|
||||
}
|
||||
else if (thisCount >= otherCount)
|
||||
{
|
||||
ulong baseOffset = 0;
|
||||
|
||||
MemoryRange otherFirstRange = other.GetSubRangeUnchecked(0);
|
||||
MemoryRange otherLastRange = other.GetSubRangeUnchecked(otherCount - 1);
|
||||
|
||||
for (int i = 0; i < (thisCount - otherCount) + 1; baseOffset += GetSubRangeUnchecked(i).Size, i++)
|
||||
{
|
||||
MemoryRange currentFirstRange = GetSubRangeUnchecked(i);
|
||||
MemoryRange currentLastRange = GetSubRangeUnchecked(i + otherCount - 1);
|
||||
|
||||
if (otherCount > 1)
|
||||
{
|
||||
if (otherFirstRange.Address < currentFirstRange.Address ||
|
||||
otherFirstRange.EndAddress != currentFirstRange.EndAddress)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (otherLastRange.Address != currentLastRange.Address ||
|
||||
otherLastRange.EndAddress > currentLastRange.EndAddress)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool fullMatch = true;
|
||||
|
||||
for (int j = 1; j < otherCount - 1; j++)
|
||||
{
|
||||
if (!GetSubRangeUnchecked(i + j).Equals(other.GetSubRangeUnchecked(j)))
|
||||
{
|
||||
fullMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fullMatch)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (currentFirstRange.Address > otherFirstRange.Address ||
|
||||
currentFirstRange.EndAddress < otherFirstRange.EndAddress)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return (int)(baseOffset + (otherFirstRange.Address - currentFirstRange.Address));
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total size of all sub-ranges in bytes.
|
||||
/// </summary>
|
||||
/// <returns>Total size in bytes</returns>
|
||||
public ulong GetSize()
|
||||
{
|
||||
ulong sum = 0;
|
||||
|
||||
foreach (MemoryRange range in _ranges)
|
||||
{
|
||||
sum += range.Size;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is MultiRange other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals(MultiRange other)
|
||||
{
|
||||
if (HasSingleRange && other.HasSingleRange)
|
||||
{
|
||||
return _singleRange.Equals(other._singleRange);
|
||||
}
|
||||
|
||||
int thisCount = Count;
|
||||
if (thisCount != other.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < thisCount; i++)
|
||||
{
|
||||
if (!GetSubRangeUnchecked(i).Equals(other.GetSubRangeUnchecked(i)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
if (HasSingleRange)
|
||||
{
|
||||
return _singleRange.GetHashCode();
|
||||
}
|
||||
|
||||
HashCode hash = new HashCode();
|
||||
|
||||
foreach (MemoryRange range in _ranges)
|
||||
{
|
||||
hash.Add(range);
|
||||
}
|
||||
|
||||
return hash.ToHashCode();
|
||||
}
|
||||
}
|
||||
}
|
204
Ryujinx.Memory/Range/MultiRangeList.cs
Normal file
204
Ryujinx.Memory/Range/MultiRangeList.cs
Normal file
@ -0,0 +1,204 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Memory.Range
|
||||
{
|
||||
/// <summary>
|
||||
/// Sorted list of ranges that supports binary search.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the range.</typeparam>
|
||||
public class MultiRangeList<T> : IEnumerable<T> where T : IMultiRangeItem
|
||||
{
|
||||
private const int ArrayGrowthSize = 32;
|
||||
|
||||
private readonly List<T> _items;
|
||||
|
||||
public int Count => _items.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new range list.
|
||||
/// </summary>
|
||||
public MultiRangeList()
|
||||
{
|
||||
_items = new List<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new item to the list.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to be added</param>
|
||||
public void Add(T item)
|
||||
{
|
||||
int index = BinarySearch(item.BaseAddress);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index;
|
||||
}
|
||||
|
||||
_items.Insert(index, item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an item from the list.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to be removed</param>
|
||||
/// <returns>True if the item was removed, or false if it was not found</returns>
|
||||
public bool Remove(T item)
|
||||
{
|
||||
int index = BinarySearch(item.BaseAddress);
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
while (index > 0 && _items[index - 1].BaseAddress == item.BaseAddress)
|
||||
{
|
||||
index--;
|
||||
}
|
||||
|
||||
while (index < _items.Count)
|
||||
{
|
||||
if (_items[index].Equals(item))
|
||||
{
|
||||
_items.RemoveAt(index);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_items[index].BaseAddress > item.BaseAddress)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all items on the list overlapping the specified memory range.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
/// <param name="output">Output array where matches will be written. It is automatically resized to fit the results</param>
|
||||
/// <returns>The number of overlapping items found</returns>
|
||||
public int FindOverlaps(ulong address, ulong size, ref T[] output)
|
||||
{
|
||||
return FindOverlaps(new MultiRange(address, size), ref output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all items on the list overlapping the specified memory ranges.
|
||||
/// </summary>
|
||||
/// <param name="range">Ranges of memory being searched</param>
|
||||
/// <param name="output">Output array where matches will be written. It is automatically resized to fit the results</param>
|
||||
/// <returns>The number of overlapping items found</returns>
|
||||
public int FindOverlaps(MultiRange range, ref T[] output)
|
||||
{
|
||||
int outputIndex = 0;
|
||||
|
||||
foreach (T item in _items)
|
||||
{
|
||||
if (item.Range.OverlapsWith(range))
|
||||
{
|
||||
if (outputIndex == output.Length)
|
||||
{
|
||||
Array.Resize(ref output, outputIndex + ArrayGrowthSize);
|
||||
}
|
||||
|
||||
output[outputIndex++] = item;
|
||||
}
|
||||
}
|
||||
|
||||
return outputIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all items on the list starting at the specified memory address.
|
||||
/// </summary>
|
||||
/// <param name="baseAddress">Base address to find</param>
|
||||
/// <param name="output">Output array where matches will be written. It is automatically resized to fit the results</param>
|
||||
/// <returns>The number of matches found</returns>
|
||||
public int FindOverlaps(ulong baseAddress, ref T[] output)
|
||||
{
|
||||
int index = BinarySearch(baseAddress);
|
||||
|
||||
int outputIndex = 0;
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
while (index > 0 && _items[index - 1].BaseAddress == baseAddress)
|
||||
{
|
||||
index--;
|
||||
}
|
||||
|
||||
while (index < _items.Count)
|
||||
{
|
||||
T overlap = _items[index++];
|
||||
|
||||
if (overlap.BaseAddress != baseAddress)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (outputIndex == output.Length)
|
||||
{
|
||||
Array.Resize(ref output, outputIndex + ArrayGrowthSize);
|
||||
}
|
||||
|
||||
output[outputIndex++] = overlap;
|
||||
}
|
||||
}
|
||||
|
||||
return outputIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs binary search on the internal list of items.
|
||||
/// </summary>
|
||||
/// <param name="address">Address to find</param>
|
||||
/// <returns>List index of the item, or complement index of nearest item with lower value on the list</returns>
|
||||
private int BinarySearch(ulong address)
|
||||
{
|
||||
int left = 0;
|
||||
int right = _items.Count - 1;
|
||||
|
||||
while (left <= right)
|
||||
{
|
||||
int range = right - left;
|
||||
|
||||
int middle = left + (range >> 1);
|
||||
|
||||
T item = _items[middle];
|
||||
|
||||
if (item.BaseAddress == address)
|
||||
{
|
||||
return middle;
|
||||
}
|
||||
|
||||
if (address < item.BaseAddress)
|
||||
{
|
||||
right = middle - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
left = middle + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ~left;
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return _items.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _items.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
@ -4,16 +4,16 @@ namespace Ryujinx.Memory
|
||||
{
|
||||
public sealed class WritableRegion : IDisposable
|
||||
{
|
||||
private readonly IVirtualMemoryManager _mm;
|
||||
private readonly IWritableBlock _block;
|
||||
private readonly ulong _va;
|
||||
|
||||
private bool NeedsWriteback => _mm != null;
|
||||
private bool NeedsWriteback => _block != null;
|
||||
|
||||
public Memory<byte> Memory { get; }
|
||||
|
||||
public WritableRegion(IVirtualMemoryManager mm, ulong va, Memory<byte> memory)
|
||||
public WritableRegion(IWritableBlock block, ulong va, Memory<byte> memory)
|
||||
{
|
||||
_mm = mm;
|
||||
_block = block;
|
||||
_va = va;
|
||||
Memory = memory;
|
||||
}
|
||||
@ -22,7 +22,7 @@ namespace Ryujinx.Memory
|
||||
{
|
||||
if (NeedsWriteback)
|
||||
{
|
||||
_mm.Write(_va, Memory.Span);
|
||||
_block.Write(_va, Memory.Span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user