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
18 changed files with 1141 additions and 167 deletions
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
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;
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
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
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue