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:
gdkchan 2021-01-17 15:44:34 -03:00 committed by GitHub
parent 3bad321d2b
commit c4f56c5704
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 1141 additions and 167 deletions

View file

@ -13,7 +13,7 @@ namespace Ryujinx.Cpu
/// <summary> /// <summary>
/// Represents a CPU memory manager. /// Represents a CPU memory manager.
/// </summary> /// </summary>
public sealed class MemoryManager : IMemoryManager, IVirtualMemoryManager, IDisposable public sealed class MemoryManager : IMemoryManager, IVirtualMemoryManager, IWritableBlock, IDisposable
{ {
public const int PageBits = 12; public const int PageBits = 12;
public const int PageSize = 1 << PageBits; public const int PageSize = 1 << PageBits;
@ -202,12 +202,12 @@ namespace Ryujinx.Cpu
WriteImpl(va, data); WriteImpl(va, data);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
/// <summary> /// <summary>
/// Writes data to CPU mapped memory. /// Writes data to CPU mapped memory.
/// </summary> /// </summary>
/// <param name="va">Virtual address to write the data into</param> /// <param name="va">Virtual address to write the data into</param>
/// <param name="data">Data to be written</param> /// <param name="data">Data to be written</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteImpl(ulong va, ReadOnlySpan<byte> data) private void WriteImpl(ulong va, ReadOnlySpan<byte> data)
{ {
try try

View file

@ -1,7 +1,7 @@
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Cpu.Tracking;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Texture; using Ryujinx.Graphics.Texture;
using Ryujinx.Graphics.Texture.Astc; using Ryujinx.Graphics.Texture.Astc;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
@ -14,7 +14,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary> /// <summary>
/// Represents a cached GPU texture. /// Represents a cached GPU texture.
/// </summary> /// </summary>
class Texture : IRange, IDisposable class Texture : IMultiRangeItem, IDisposable
{ {
// How many updates we need before switching to the byte-by-byte comparison // How many updates we need before switching to the byte-by-byte comparison
// modification check method. // modification check method.
@ -95,21 +95,16 @@ namespace Ryujinx.Graphics.Gpu.Image
public event Action<Texture> Disposed; public event Action<Texture> Disposed;
/// <summary> /// <summary>
/// Start address of the texture in guest memory. /// Physical memory ranges where the texture data is located.
/// </summary> /// </summary>
public ulong Address => Info.Address; public MultiRange Range { get; private set; }
/// <summary>
/// End address of the texture in guest memory.
/// </summary>
public ulong EndAddress => Info.Address + Size;
/// <summary> /// <summary>
/// Texture size in bytes. /// Texture size in bytes.
/// </summary> /// </summary>
public ulong Size => (ulong)_sizeInfo.TotalSize; public ulong Size => (ulong)_sizeInfo.TotalSize;
private CpuRegionHandle _memoryTracking; private GpuRegionHandle _memoryTracking;
private int _referenceCount; 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="context">GPU context that the texture belongs to</param>
/// <param name="info">Texture information</param> /// <param name="info">Texture information</param>
/// <param name="sizeInfo">Size information of the texture</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="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="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> /// <param name="scaleFactor">The floating point scale factor to initialize with</param>
@ -127,12 +123,13 @@ namespace Ryujinx.Graphics.Gpu.Image
GpuContext context, GpuContext context,
TextureInfo info, TextureInfo info,
SizeInfo sizeInfo, SizeInfo sizeInfo,
MultiRange range,
int firstLayer, int firstLayer,
int firstLevel, int firstLevel,
float scaleFactor, float scaleFactor,
TextureScaleMode scaleMode) TextureScaleMode scaleMode)
{ {
InitializeTexture(context, info, sizeInfo); InitializeTexture(context, info, sizeInfo, range);
_firstLayer = firstLayer; _firstLayer = firstLayer;
_firstLevel = firstLevel; _firstLevel = firstLevel;
@ -149,13 +146,14 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="context">GPU context that the texture belongs to</param> /// <param name="context">GPU context that the texture belongs to</param>
/// <param name="info">Texture information</param> /// <param name="info">Texture information</param>
/// <param name="sizeInfo">Size information of the texture</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> /// <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. ScaleFactor = 1f; // Texture is first loaded at scale 1x.
ScaleMode = scaleMode; ScaleMode = scaleMode;
InitializeTexture(context, info, sizeInfo); InitializeTexture(context, info, sizeInfo, range);
} }
/// <summary> /// <summary>
@ -166,10 +164,12 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="context">GPU context that the texture belongs to</param> /// <param name="context">GPU context that the texture belongs to</param>
/// <param name="info">Texture information</param> /// <param name="info">Texture information</param>
/// <param name="sizeInfo">Size information of the texture</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; _context = context;
_sizeInfo = sizeInfo; _sizeInfo = sizeInfo;
Range = range;
SetInfo(info); 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> /// <param name="withData">True if the texture is to be initialized with data</param>
public void InitializeData(bool isView, bool withData = false) public void InitializeData(bool isView, bool withData = false)
{ {
_memoryTracking = _context.PhysicalMemory.BeginTracking(Address, Size); _memoryTracking = _context.PhysicalMemory.BeginTracking(Range);
if (withData) if (withData)
{ {
@ -229,15 +229,17 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
/// <param name="info">Child texture information</param> /// <param name="info">Child texture information</param>
/// <param name="sizeInfo">Child texture size 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="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> /// <param name="firstLevel">Start mipmap level of the child texture on the parent texture</param>
/// <returns>The child texture</returns> /// <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( Texture texture = new Texture(
_context, _context,
info, info,
sizeInfo, sizeInfo,
range,
_firstLayer + firstLayer, _firstLayer + firstLayer,
_firstLevel + firstLevel, _firstLevel + firstLevel,
ScaleFactor, ScaleFactor,
@ -367,7 +369,7 @@ namespace Ryujinx.Graphics.Gpu.Image
ChangedSize = true; ChangedSize = true;
SetInfo(new TextureInfo( SetInfo(new TextureInfo(
Info.Address, Info.GpuAddress,
width, width,
height, height,
depthOrLayers, depthOrLayers,
@ -554,7 +556,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_memoryTracking?.Reprotect(); _memoryTracking?.Reprotect();
ReadOnlySpan<byte> data = _context.PhysicalMemory.GetSpan(Address, (int)Size); ReadOnlySpan<byte> data = _context.PhysicalMemory.GetSpan(Range);
IsModified = false; IsModified = false;
@ -586,6 +588,10 @@ namespace Ryujinx.Graphics.Gpu.Image
_hasData = true; _hasData = true;
} }
/// <summary>
/// Uploads new texture data to the host GPU.
/// </summary>
/// <param name="data">New data</param>
public void SetData(ReadOnlySpan<byte> data) public void SetData(ReadOnlySpan<byte> data)
{ {
BlacklistScale(); 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}"; 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; data = decoded;
@ -689,15 +695,14 @@ namespace Ryujinx.Graphics.Gpu.Image
if (tracked) if (tracked)
{ {
_context.PhysicalMemory.Write(Address, GetTextureDataFromGpu(tracked)); _context.PhysicalMemory.Write(Range, GetTextureDataFromGpu(tracked));
} }
else else
{ {
_context.PhysicalMemory.WriteUntracked(Address, GetTextureDataFromGpu(tracked)); _context.PhysicalMemory.WriteUntracked(Range, GetTextureDataFromGpu(tracked));
} }
} }
/// <summary> /// <summary>
/// Flushes the texture data, to be called from an external thread. /// 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. /// 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); 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 TextureMatchQuality.NoMatch;
} }
return Info.Address == info.Address && Info.Levels == info.Levels ? matchQuality : TextureMatchQuality.NoMatch; return Info.Levels == info.Levels ? matchQuality : TextureMatchQuality.NoMatch;
} }
/// <summary> /// <summary>
/// Check if it's possible to create a view, with the given parameters, from this texture. /// Check if it's possible to create a view, with the given parameters, from this texture.
/// </summary> /// </summary>
/// <param name="info">Texture view information</param> /// <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="firstLayer">Texture view initial layer on this texture</param>
/// <param name="firstLevel">Texture view first mipmap level 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> /// <returns>The level of compatiblilty a view with the given parameters created from this texture has</returns>
public TextureViewCompatibility IsViewCompatible( public TextureViewCompatibility IsViewCompatible(TextureInfo info, MultiRange range, out int firstLayer, out int firstLevel)
TextureInfo info,
ulong size,
out int firstLayer,
out int firstLevel)
{ {
int offset = Range.FindOffset(range);
// Out of range. // Out of range.
if (info.Address < Address || info.Address + size > EndAddress) if (offset < 0)
{ {
firstLayer = 0; firstLayer = 0;
firstLevel = 0; firstLevel = 0;
@ -873,9 +876,7 @@ namespace Ryujinx.Graphics.Gpu.Image
return TextureViewCompatibility.Incompatible; return TextureViewCompatibility.Incompatible;
} }
int offset = (int)(info.Address - Address); if (!_sizeInfo.FindView(offset, out firstLayer, out firstLevel))
if (!_sizeInfo.FindView(offset, (int)size, out firstLayer, out firstLevel))
{ {
return TextureViewCompatibility.Incompatible; return TextureViewCompatibility.Incompatible;
} }
@ -1045,17 +1046,6 @@ namespace Ryujinx.Graphics.Gpu.Image
HostTexture = hostTexture; 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> /// <summary>
/// Determine if any of our child textures are compaible as views of the given texture. /// Determine if any of our child textures are compaible as views of the given texture.
/// </summary> /// </summary>
@ -1070,7 +1060,7 @@ namespace Ryujinx.Graphics.Gpu.Image
foreach (Texture view in _views) 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; 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. IsModified = false; // We shouldn't flush this texture, as its memory is no longer mapped.
CpuRegionHandle tracking = _memoryTracking; var tracking = _memoryTracking;
tracking?.Reprotect(); tracking?.Reprotect();
tracking?.RegisterAction(null); tracking?.RegisterAction(null);
} }

View file

@ -303,7 +303,7 @@ namespace Ryujinx.Graphics.Gpu.Image
// Ensure that the buffer texture is using the correct buffer as storage. // 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 // 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. // 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); 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. // 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 // 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. // 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) if (_imageState[stageIndex][index].Texture != hostTexture || _rebind)

View file

@ -9,9 +9,9 @@ namespace Ryujinx.Graphics.Gpu.Image
struct TextureInfo struct TextureInfo
{ {
/// <summary> /// <summary>
/// Address of the texture in guest memory. /// Address of the texture in GPU mapped memory.
/// </summary> /// </summary>
public ulong Address { get; } public ulong GpuAddress { get; }
/// <summary> /// <summary>
/// The width of the texture. /// The width of the texture.
@ -112,7 +112,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary> /// <summary>
/// Constructs the texture information structure. /// Constructs the texture information structure.
/// </summary> /// </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="width">The width of the texture</param>
/// <param name="height">The height or the texture</param> /// <param name="height">The height or the texture</param>
/// <param name="depthOrLayers">The depth or layers count of 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="swizzleB">Swizzle for the blue color channel</param>
/// <param name="swizzleA">Swizzle for the alpha color channel</param> /// <param name="swizzleA">Swizzle for the alpha color channel</param>
public TextureInfo( public TextureInfo(
ulong address, ulong gpuAddress,
int width, int width,
int height, int height,
int depthOrLayers, int depthOrLayers,
@ -152,7 +152,7 @@ namespace Ryujinx.Graphics.Gpu.Image
SwizzleComponent swizzleB = SwizzleComponent.Blue, SwizzleComponent swizzleB = SwizzleComponent.Blue,
SwizzleComponent swizzleA = SwizzleComponent.Alpha) SwizzleComponent swizzleA = SwizzleComponent.Alpha)
{ {
Address = address; GpuAddress = gpuAddress;
Width = width; Width = width;
Height = height; Height = height;
DepthOrLayers = depthOrLayers; DepthOrLayers = depthOrLayers;

View file

@ -37,14 +37,11 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly TextureBindingsManager _gpBindingsManager; private readonly TextureBindingsManager _gpBindingsManager;
private readonly Texture[] _rtColors; private readonly Texture[] _rtColors;
private Texture _rtDepthStencil;
private readonly ITexture[] _rtHostColors; private readonly ITexture[] _rtHostColors;
private Texture _rtDepthStencil;
private ITexture _rtHostDs; private ITexture _rtHostDs;
private readonly RangeList<Texture> _textures; private readonly MultiRangeList<Texture> _textures;
private Texture[] _textureOverlaps; private Texture[] _textureOverlaps;
private OverlapInfo[] _overlapInfo; private OverlapInfo[] _overlapInfo;
@ -70,10 +67,9 @@ namespace Ryujinx.Graphics.Gpu.Image
_gpBindingsManager = new TextureBindingsManager(context, texturePoolCache, isCompute: false); _gpBindingsManager = new TextureBindingsManager(context, texturePoolCache, isCompute: false);
_rtColors = new Texture[Constants.TotalRenderTargets]; _rtColors = new Texture[Constants.TotalRenderTargets];
_rtHostColors = new ITexture[Constants.TotalRenderTargets]; _rtHostColors = new ITexture[Constants.TotalRenderTargets];
_textures = new RangeList<Texture>(); _textures = new MultiRangeList<Texture>();
_textureOverlaps = new Texture[OverlapsBufferInitialCapacity]; _textureOverlaps = new Texture[OverlapsBufferInitialCapacity];
_overlapInfo = new OverlapInfo[OverlapsBufferInitialCapacity]; _overlapInfo = new OverlapInfo[OverlapsBufferInitialCapacity];
@ -470,13 +466,6 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <returns>The texture</returns> /// <returns>The texture</returns>
public Texture FindOrCreateTexture(CopyTexture copyTexture, FormatInfo formatInfo, bool preferScaling = true, Size? sizeHint = null) 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 gobBlocksInY = copyTexture.MemoryLayout.UnpackGobBlocksInY();
int gobBlocksInZ = copyTexture.MemoryLayout.UnpackGobBlocksInZ(); int gobBlocksInZ = copyTexture.MemoryLayout.UnpackGobBlocksInZ();
@ -492,7 +481,7 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
TextureInfo info = new TextureInfo( TextureInfo info = new TextureInfo(
address, copyTexture.Address.Pack(),
width, width,
copyTexture.Height, copyTexture.Height,
copyTexture.Depth, copyTexture.Depth,
@ -514,9 +503,9 @@ namespace Ryujinx.Graphics.Gpu.Image
flags |= TextureSearchFlags.WithUpscale; flags |= TextureSearchFlags.WithUpscale;
} }
Texture texture = FindOrCreateTexture(info, flags, 0, sizeHint); Texture texture = FindOrCreateTexture(flags, info, 0, sizeHint);
texture.SynchronizeMemory(); texture?.SynchronizeMemory();
return texture; return texture;
} }
@ -531,13 +520,6 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <returns>The texture</returns> /// <returns>The texture</returns>
public Texture FindOrCreateTexture(RtColorState colorState, int samplesInX, int samplesInY, Size sizeHint) 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(); bool isLinear = colorState.MemoryLayout.UnpackIsLinear();
int gobBlocksInY = colorState.MemoryLayout.UnpackGobBlocksInY(); int gobBlocksInY = colorState.MemoryLayout.UnpackGobBlocksInY();
@ -583,7 +565,7 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
TextureInfo info = new TextureInfo( TextureInfo info = new TextureInfo(
address, colorState.Address.Pack(),
width, width,
colorState.Height, colorState.Height,
colorState.Depth, colorState.Depth,
@ -600,9 +582,9 @@ namespace Ryujinx.Graphics.Gpu.Image
int layerSize = !isLinear ? colorState.LayerSize * 4 : 0; 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; return texture;
} }
@ -618,13 +600,6 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <returns>The texture</returns> /// <returns>The texture</returns>
public Texture FindOrCreateTexture(RtDepthStencilState dsState, Size3D size, int samplesInX, int samplesInY, Size sizeHint) 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 gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY();
int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ(); int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ();
@ -635,7 +610,7 @@ namespace Ryujinx.Graphics.Gpu.Image
FormatInfo formatInfo = dsState.Format.Convert(); FormatInfo formatInfo = dsState.Format.Convert();
TextureInfo info = new TextureInfo( TextureInfo info = new TextureInfo(
address, dsState.Address.Pack(),
size.Width, size.Width,
size.Height, size.Height,
size.Depth, size.Depth,
@ -650,9 +625,9 @@ namespace Ryujinx.Graphics.Gpu.Image
target, target,
formatInfo); 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; return texture;
} }
@ -660,12 +635,13 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary> /// <summary>
/// Tries to find an existing texture, or create a new one if not found. /// Tries to find an existing texture, or create a new one if not found.
/// </summary> /// </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="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="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="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> /// <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; bool isSamplerTexture = (flags & TextureSearchFlags.ForSampler) != 0;
@ -677,12 +653,28 @@ namespace Ryujinx.Graphics.Gpu.Image
scaleMode = (flags & TextureSearchFlags.WithUpscale) != 0 ? TextureScaleMode.Scaled : TextureScaleMode.Eligible; 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; int sameAddressOverlapsCount;
lock (_textures) lock (_textures)
{ {
// Try to find a perfect texture match, with the same address and parameters. // 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; Texture texture = null;
@ -693,6 +685,12 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
Texture overlap = _textureOverlaps[index]; 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); TextureMatchQuality matchQuality = overlap.IsExactMatch(info, flags);
if (matchQuality == TextureMatchQuality.Perfect) if (matchQuality == TextureMatchQuality.Perfect)
@ -727,19 +725,25 @@ namespace Ryujinx.Graphics.Gpu.Image
// Calculate texture sizes, used to find all overlapping textures. // Calculate texture sizes, used to find all overlapping textures.
SizeInfo sizeInfo = info.CalculateSizeInfo(layerSize); SizeInfo sizeInfo = info.CalculateSizeInfo(layerSize);
// Find view compatible matches.
ulong size = (ulong)sizeInfo.TotalSize; ulong size = (ulong)sizeInfo.TotalSize;
if (range == null)
{
range = _context.MemoryManager.GetPhysicalRegions(info.GpuAddress, size);
}
// Find view compatible matches.
int overlapsCount; int overlapsCount;
lock (_textures) lock (_textures)
{ {
overlapsCount = _textures.FindOverlaps(info.Address, size, ref _textureOverlaps); overlapsCount = _textures.FindOverlaps(range.Value, ref _textureOverlaps);
} }
for (int index = 0; index < overlapsCount; index++) for (int index = 0; index < overlapsCount; index++)
{ {
Texture overlap = _textureOverlaps[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) if (overlapCompatibility == TextureViewCompatibility.Full)
{ {
@ -750,7 +754,7 @@ namespace Ryujinx.Graphics.Gpu.Image
info = oInfo; info = oInfo;
} }
texture = overlap.CreateView(oInfo, sizeInfo, firstLayer, firstLevel); texture = overlap.CreateView(oInfo, sizeInfo, range.Value, firstLayer, firstLevel);
if (overlap.IsModified) if (overlap.IsModified)
{ {
@ -771,7 +775,7 @@ namespace Ryujinx.Graphics.Gpu.Image
// No match, create a new texture. // No match, create a new texture.
if (texture == null) 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. // 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. // 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]; Texture overlap = _textureOverlaps[index];
bool overlapInCache = overlap.CacheNode != null; 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) 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. // If the data has been modified by the CPU, then it also shouldn't be flushed.
bool modified = overlap.ConsumeModified(); 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; setData |= modified || flush;
@ -1070,7 +1074,7 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
return new TextureInfo( return new TextureInfo(
info.Address, info.GpuAddress,
width, width,
height, height,
depthOrLayers, depthOrLayers,

View file

@ -54,15 +54,14 @@ namespace Ryujinx.Graphics.Gpu.Image
TextureInfo info = GetInfo(descriptor, out int layerSize); TextureInfo info = GetInfo(descriptor, out int layerSize);
// Bad address. We can't add a texture with a invalid address texture = Context.Methods.TextureManager.FindOrCreateTexture(TextureSearchFlags.ForSampler, info, layerSize);
// to the cache.
if (info.Address == MemoryManager.PteUnmapped) // If this happens, then the texture address is invalid, we can't add it to the cache.
if (texture == null)
{ {
return null; return null;
} }
texture = Context.Methods.TextureManager.FindOrCreateTexture(info, TextureSearchFlags.ForSampler, layerSize);
texture.IncrementReferenceCount(); texture.IncrementReferenceCount();
Items[id] = texture; Items[id] = texture;
@ -123,7 +122,8 @@ namespace Ryujinx.Graphics.Gpu.Image
// If the descriptors are the same, the texture is the same, // If the descriptors are the same, the texture is the same,
// we don't need to remove as it was not modified. Just continue. // 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; continue;
} }
@ -143,9 +143,6 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <returns>The texture information</returns> /// <returns>The texture information</returns>
private TextureInfo GetInfo(TextureDescriptor descriptor, out int layerSize) 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 width = descriptor.UnpackWidth();
int height = descriptor.UnpackHeight(); int height = descriptor.UnpackHeight();
int depthOrLayers = descriptor.UnpackDepth(); int depthOrLayers = descriptor.UnpackDepth();
@ -183,9 +180,11 @@ namespace Ryujinx.Graphics.Gpu.Image
uint format = descriptor.UnpackFormat(); uint format = descriptor.UnpackFormat();
bool srgb = descriptor.UnpackSrgb(); bool srgb = descriptor.UnpackSrgb();
ulong gpuVa = descriptor.UnpackAddress();
if (!FormatTable.TryGetTextureFormat(format, srgb, out FormatInfo formatInfo)) 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})."); 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(); int maxLod = descriptor.UnpackMaxLevelInclusive();
// Linear textures don't support mipmaps, so we don't handle this case here. // 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 depth = TextureInfo.GetDepth(target, depthOrLayers);
int layers = TextureInfo.GetLayers(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 // 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 // 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 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); width = Math.Max(1, width >> minLod);
height = Math.Max(1, height >> minLod); height = Math.Max(1, height >> minLod);
@ -274,7 +273,7 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
return new TextureInfo( return new TextureInfo(
address, gpuVa,
width, width,
height, height,
depthOrLayers, depthOrLayers,

View 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();
}
}
}
}

View file

@ -1,5 +1,7 @@
using Ryujinx.Memory; using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using System; using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -8,7 +10,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary> /// <summary>
/// GPU memory manager. /// GPU memory manager.
/// </summary> /// </summary>
public class MemoryManager public class MemoryManager : IWritableBlock
{ {
private const int PtLvl0Bits = 14; private const int PtLvl0Bits = 14;
private const int PtLvl1Bits = 14; private const int PtLvl1Bits = 14;
@ -24,6 +26,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
private const int PtLvl0Bit = PtPageBits + PtLvl1Bits; private const int PtLvl0Bit = PtPageBits + PtLvl1Bits;
private const int PtLvl1Bit = PtPageBits; private const int PtLvl1Bit = PtPageBits;
private const int AddressSpaceBits = PtPageBits + PtLvl1Bits + PtLvl0Bits;
public const ulong PteUnmapped = 0xffffffff_ffffffff; public const ulong PteUnmapped = 0xffffffff_ffffffff;
@ -46,26 +49,69 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Reads data from GPU mapped memory. /// Reads data from GPU mapped memory.
/// </summary> /// </summary>
/// <typeparam name="T">Type of the data</typeparam> /// <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> /// <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>(GetSpan(va, Unsafe.SizeOf<T>()))[0];
return MemoryMarshal.Cast<byte, T>(_context.PhysicalMemory.GetSpan(processVa, Unsafe.SizeOf<T>()))[0];
} }
/// <summary> /// <summary>
/// Gets a read-only span of data from GPU mapped memory. /// Gets a read-only span of data from GPU mapped memory.
/// </summary> /// </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> /// <param name="size">Size of the data</param>
/// <returns>The span of the data at the specified memory location</returns> /// <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> /// <summary>
@ -74,36 +120,91 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="address">Start address of the range</param> /// <param name="address">Start address of the range</param>
/// <param name="size">Size in bytes to be 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> /// <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> /// <summary>
/// Writes data to GPU mapped memory. /// Writes data to GPU mapped memory.
/// </summary> /// </summary>
/// <typeparam name="T">Type of the data</typeparam> /// <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> /// <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); Write(va, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
_context.PhysicalMemory.Write(processVa, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
} }
/// <summary> /// <summary>
/// Writes data to GPU mapped memory. /// Writes data to GPU mapped memory.
/// </summary> /// </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> /// <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> /// <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> /// <summary>
/// Checks if a given page is mapped. /// Checks if a given page is mapped.
/// </summary> /// </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> /// <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> /// <summary>
/// Translates a GPU virtual address to a CPU virtual address. /// Translates a GPU virtual address to a CPU virtual address.
/// </summary> /// </summary>
/// <param name="gpuVa">GPU virtual address to be translated</param> /// <param name="va">GPU virtual address to be translated</param>
/// <returns>CPU virtual address</returns> /// <returns>CPU virtual address, or <see cref="PteUnmapped"/> if unmapped</returns>
public ulong Translate(ulong gpuVa) public ulong Translate(ulong va)
{ {
ulong baseAddress = GetPte(gpuVa); if (!ValidateAddress(va))
{
return PteUnmapped;
}
ulong baseAddress = GetPte(va);
if (baseAddress == PteUnmapped) if (baseAddress == PteUnmapped)
{ {
return PteUnmapped; return PteUnmapped;
} }
return baseAddress + (gpuVa & PageMask); return baseAddress + (va & PageMask);
} }
/// <summary> /// <summary>
/// Gets the Page Table entry for a given GPU virtual address. /// Gets the Page Table entry for a given GPU virtual address.
/// </summary> /// </summary>
/// <param name="gpuVa">GPU virtual address</param> /// <param name="va">GPU virtual address</param>
/// <returns>Page table entry (CPU virtual address)</returns> /// <returns>Page table entry (CPU virtual address)</returns>
private ulong GetPte(ulong gpuVa) private ulong GetPte(ulong va)
{ {
ulong l0 = (gpuVa >> PtLvl0Bit) & PtLvl0Mask; ulong l0 = (va >> PtLvl0Bit) & PtLvl0Mask;
ulong l1 = (gpuVa >> PtLvl1Bit) & PtLvl1Mask; ulong l1 = (va >> PtLvl1Bit) & PtLvl1Mask;
if (_pageTable[l0] == null) if (_pageTable[l0] == null)
{ {
@ -195,12 +405,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary> /// <summary>
/// Sets a Page Table entry at a given GPU virtual address. /// Sets a Page Table entry at a given GPU virtual address.
/// </summary> /// </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> /// <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 l0 = (va >> PtLvl0Bit) & PtLvl0Mask;
ulong l1 = (gpuVa >> PtLvl1Bit) & PtLvl1Mask; ulong l1 = (va >> PtLvl1Bit) & PtLvl1Mask;
if (_pageTable[l0] == null) if (_pageTable[l0] == null)
{ {

View file

@ -1,6 +1,7 @@
using Ryujinx.Cpu; using Ryujinx.Cpu;
using Ryujinx.Cpu.Tracking; using Ryujinx.Cpu.Tracking;
using Ryujinx.Memory; using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -38,6 +39,37 @@ namespace Ryujinx.Graphics.Gpu.Memory
return _cpuMemory.GetSpan(address, size, tracked); 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> /// <summary>
/// Gets a writable region from the application process. /// Gets a writable region from the application process.
/// </summary> /// </summary>
@ -70,6 +102,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
_cpuMemory.Write(address, data); _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> /// <summary>
/// Writes data to the application process, without any tracking. /// Writes data to the application process, without any tracking.
/// </summary> /// </summary>
@ -80,6 +122,45 @@ namespace Ryujinx.Graphics.Gpu.Memory
_cpuMemory.WriteUntracked(address, data); _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> /// <summary>
/// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with. /// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with.
/// </summary> /// </summary>
@ -91,6 +172,24 @@ namespace Ryujinx.Graphics.Gpu.Memory
return _cpuMemory.BeginTracking(address, size); 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> /// <summary>
/// Obtains a memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with. /// Obtains a memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with.
/// </summary> /// </summary>

View file

@ -1,5 +1,7 @@
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Texture;
using Ryujinx.Memory.Range;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading; using System.Threading;
@ -25,6 +27,11 @@ namespace Ryujinx.Graphics.Gpu
/// </summary> /// </summary>
public TextureInfo Info { get; } public TextureInfo Info { get; }
/// <summary>
/// Physical memory locations where the texture data is located.
/// </summary>
public MultiRange Range { get; }
/// <summary> /// <summary>
/// Texture crop region. /// Texture crop region.
/// </summary> /// </summary>
@ -49,18 +56,21 @@ namespace Ryujinx.Graphics.Gpu
/// Creates a new instance of the presentation texture. /// Creates a new instance of the presentation texture.
/// </summary> /// </summary>
/// <param name="info">Information of the texture to be presented</param> /// <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="crop">Texture crop region</param>
/// <param name="acquireCallback">Texture acquire callback</param> /// <param name="acquireCallback">Texture acquire callback</param>
/// <param name="releaseCallback">Texture release 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> /// <param name="userObj">User defined object passed to the release callback, can be used to identify the texture</param>
public PresentationTexture( public PresentationTexture(
TextureInfo info, TextureInfo info,
MultiRange range,
ImageCrop crop, ImageCrop crop,
Action<GpuContext, object> acquireCallback, Action<GpuContext, object> acquireCallback,
Action<object> releaseCallback, Action<object> releaseCallback,
object userObj) object userObj)
{ {
Info = info; Info = info;
Range = range;
Crop = crop; Crop = crop;
AcquireCallback = acquireCallback; AcquireCallback = acquireCallback;
ReleaseCallback = releaseCallback; ReleaseCallback = releaseCallback;
@ -118,7 +128,7 @@ namespace Ryujinx.Graphics.Gpu
FormatInfo formatInfo = new FormatInfo(format, 1, 1, bytesPerPixel, 4); FormatInfo formatInfo = new FormatInfo(format, 1, 1, bytesPerPixel, 4);
TextureInfo info = new TextureInfo( TextureInfo info = new TextureInfo(
address, 0UL,
width, width,
height, height,
1, 1,
@ -133,7 +143,22 @@ namespace Ryujinx.Graphics.Gpu
Target.Texture2D, Target.Texture2D,
formatInfo); 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> /// <summary>
@ -149,7 +174,7 @@ namespace Ryujinx.Graphics.Gpu
{ {
pt.AcquireCallback(_context, pt.UserObj); 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(); texture.SynchronizeMemory();

View file

@ -45,7 +45,7 @@ namespace Ryujinx.Graphics.Texture
return _mipOffsets[level]; 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); int index = Array.BinarySearch(_allOffsets, offset);

View file

@ -1,5 +1,4 @@
using Ryujinx.Common; using System;
using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -9,7 +8,7 @@ namespace Ryujinx.Memory
/// Represents a address space manager. /// Represents a address space manager.
/// Supports virtual memory region mapping, address translation and read/write access to mapped regions. /// Supports virtual memory region mapping, address translation and read/write access to mapped regions.
/// </summary> /// </summary>
public sealed class AddressSpaceManager : IVirtualMemoryManager public sealed class AddressSpaceManager : IVirtualMemoryManager, IWritableBlock
{ {
public const int PageBits = 12; public const int PageBits = 12;
public const int PageSize = 1 << PageBits; public const int PageSize = 1 << PageBits;

View file

@ -0,0 +1,9 @@
using System;
namespace Ryujinx.Memory
{
public interface IWritableBlock
{
void Write(ulong va, ReadOnlySpan<byte> data);
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.Memory.Range
{
public interface IMultiRangeItem
{
MultiRange Range { get; }
ulong BaseAddress => Range.GetSubRange(0).Address;
}
}

View 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);
}
}
}

View 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();
}
}
}

View 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();
}
}
}

View file

@ -4,16 +4,16 @@ namespace Ryujinx.Memory
{ {
public sealed class WritableRegion : IDisposable public sealed class WritableRegion : IDisposable
{ {
private readonly IVirtualMemoryManager _mm; private readonly IWritableBlock _block;
private readonly ulong _va; private readonly ulong _va;
private bool NeedsWriteback => _mm != null; private bool NeedsWriteback => _block != null;
public Memory<byte> Memory { get; } 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; _va = va;
Memory = memory; Memory = memory;
} }
@ -22,7 +22,7 @@ namespace Ryujinx.Memory
{ {
if (NeedsWriteback) if (NeedsWriteback)
{ {
_mm.Write(_va, Memory.Span); _block.Write(_va, Memory.Span);
} }
} }
} }