Support for resources on non-contiguous GPU memory regions (#1905)

* Support for resources on non-contiguous GPU memory regions

* Implement MultiRange physical addresses, only used with a single range for now

* Actually use non-contiguous ranges

* GetPhysicalRegions fixes

* Documentation and remove Address property from TextureInfo

* Finish implementing GetWritableRegion

* Fix typo
This commit is contained in:
gdkchan 2021-01-17 15:44:34 -03:00 committed by GitHub
parent 3bad321d2b
commit c4f56c5704
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 1141 additions and 167 deletions

View file

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

View file

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

View file

@ -303,7 +303,7 @@ namespace Ryujinx.Graphics.Gpu.Image
// Ensure that the buffer texture is using the correct buffer as storage.
// Buffers are frequently re-created to accomodate larger data, so we need to re-bind
// to ensure we're not using a old buffer that was already deleted.
_context.Methods.BufferManager.SetBufferTextureStorage(hostTexture, texture.Address, texture.Size, _isCompute);
_context.Methods.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, _isCompute);
}
Sampler sampler = _samplerPool.Get(samplerId);
@ -354,7 +354,7 @@ namespace Ryujinx.Graphics.Gpu.Image
// Ensure that the buffer texture is using the correct buffer as storage.
// Buffers are frequently re-created to accomodate larger data, so we need to re-bind
// to ensure we're not using a old buffer that was already deleted.
_context.Methods.BufferManager.SetBufferTextureStorage(hostTexture, texture.Address, texture.Size, _isCompute);
_context.Methods.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, _isCompute);
}
if (_imageState[stageIndex][index].Texture != hostTexture || _rebind)

View file

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

View file

@ -37,14 +37,11 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly TextureBindingsManager _gpBindingsManager;
private readonly Texture[] _rtColors;
private Texture _rtDepthStencil;
private readonly ITexture[] _rtHostColors;
private Texture _rtDepthStencil;
private ITexture _rtHostDs;
private readonly RangeList<Texture> _textures;
private readonly MultiRangeList<Texture> _textures;
private Texture[] _textureOverlaps;
private OverlapInfo[] _overlapInfo;
@ -70,10 +67,9 @@ namespace Ryujinx.Graphics.Gpu.Image
_gpBindingsManager = new TextureBindingsManager(context, texturePoolCache, isCompute: false);
_rtColors = new Texture[Constants.TotalRenderTargets];
_rtHostColors = new ITexture[Constants.TotalRenderTargets];
_textures = new RangeList<Texture>();
_textures = new MultiRangeList<Texture>();
_textureOverlaps = new Texture[OverlapsBufferInitialCapacity];
_overlapInfo = new OverlapInfo[OverlapsBufferInitialCapacity];
@ -470,13 +466,6 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <returns>The texture</returns>
public Texture FindOrCreateTexture(CopyTexture copyTexture, FormatInfo formatInfo, bool preferScaling = true, Size? sizeHint = null)
{
ulong address = _context.MemoryManager.Translate(copyTexture.Address.Pack());
if (address == MemoryManager.PteUnmapped)
{
return null;
}
int gobBlocksInY = copyTexture.MemoryLayout.UnpackGobBlocksInY();
int gobBlocksInZ = copyTexture.MemoryLayout.UnpackGobBlocksInZ();
@ -492,7 +481,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
TextureInfo info = new TextureInfo(
address,
copyTexture.Address.Pack(),
width,
copyTexture.Height,
copyTexture.Depth,
@ -514,9 +503,9 @@ namespace Ryujinx.Graphics.Gpu.Image
flags |= TextureSearchFlags.WithUpscale;
}
Texture texture = FindOrCreateTexture(info, flags, 0, sizeHint);
Texture texture = FindOrCreateTexture(flags, info, 0, sizeHint);
texture.SynchronizeMemory();
texture?.SynchronizeMemory();
return texture;
}
@ -531,13 +520,6 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <returns>The texture</returns>
public Texture FindOrCreateTexture(RtColorState colorState, int samplesInX, int samplesInY, Size sizeHint)
{
ulong address = _context.MemoryManager.Translate(colorState.Address.Pack());
if (address == MemoryManager.PteUnmapped)
{
return null;
}
bool isLinear = colorState.MemoryLayout.UnpackIsLinear();
int gobBlocksInY = colorState.MemoryLayout.UnpackGobBlocksInY();
@ -583,7 +565,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
TextureInfo info = new TextureInfo(
address,
colorState.Address.Pack(),
width,
colorState.Height,
colorState.Depth,
@ -600,9 +582,9 @@ namespace Ryujinx.Graphics.Gpu.Image
int layerSize = !isLinear ? colorState.LayerSize * 4 : 0;
Texture texture = FindOrCreateTexture(info, TextureSearchFlags.WithUpscale, layerSize, sizeHint);
Texture texture = FindOrCreateTexture(TextureSearchFlags.WithUpscale, info, layerSize, sizeHint);
texture.SynchronizeMemory();
texture?.SynchronizeMemory();
return texture;
}
@ -618,13 +600,6 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <returns>The texture</returns>
public Texture FindOrCreateTexture(RtDepthStencilState dsState, Size3D size, int samplesInX, int samplesInY, Size sizeHint)
{
ulong address = _context.MemoryManager.Translate(dsState.Address.Pack());
if (address == MemoryManager.PteUnmapped)
{
return null;
}
int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY();
int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ();
@ -635,7 +610,7 @@ namespace Ryujinx.Graphics.Gpu.Image
FormatInfo formatInfo = dsState.Format.Convert();
TextureInfo info = new TextureInfo(
address,
dsState.Address.Pack(),
size.Width,
size.Height,
size.Depth,
@ -650,9 +625,9 @@ namespace Ryujinx.Graphics.Gpu.Image
target,
formatInfo);
Texture texture = FindOrCreateTexture(info, TextureSearchFlags.WithUpscale, dsState.LayerSize * 4, sizeHint);
Texture texture = FindOrCreateTexture(TextureSearchFlags.WithUpscale, info, dsState.LayerSize * 4, sizeHint);
texture.SynchronizeMemory();
texture?.SynchronizeMemory();
return texture;
}
@ -660,12 +635,13 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary>
/// Tries to find an existing texture, or create a new one if not found.
/// </summary>
/// <param name="info">Texture information of the texture to be found or created</param>
/// <param name="flags">The texture search flags, defines texture comparison rules</param>
/// <param name="info">Texture information of the texture to be found or created</param>
/// <param name="layerSize">Size in bytes of a single texture layer</param>
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
/// <param name="range">Optional ranges of physical memory where the texture data is located</param>
/// <returns>The texture</returns>
public Texture FindOrCreateTexture(TextureInfo info, TextureSearchFlags flags = TextureSearchFlags.None, int layerSize = 0, Size? sizeHint = null)
public Texture FindOrCreateTexture(TextureSearchFlags flags, TextureInfo info, int layerSize = 0, Size? sizeHint = null, MultiRange? range = null)
{
bool isSamplerTexture = (flags & TextureSearchFlags.ForSampler) != 0;
@ -677,12 +653,28 @@ namespace Ryujinx.Graphics.Gpu.Image
scaleMode = (flags & TextureSearchFlags.WithUpscale) != 0 ? TextureScaleMode.Scaled : TextureScaleMode.Eligible;
}
ulong address;
if (range != null)
{
address = range.Value.GetSubRange(0).Address;
}
else
{
address = _context.MemoryManager.Translate(info.GpuAddress);
if (address == MemoryManager.PteUnmapped)
{
return null;
}
}
int sameAddressOverlapsCount;
lock (_textures)
{
// Try to find a perfect texture match, with the same address and parameters.
sameAddressOverlapsCount = _textures.FindOverlaps(info.Address, ref _textureOverlaps);
sameAddressOverlapsCount = _textures.FindOverlaps(address, ref _textureOverlaps);
}
Texture texture = null;
@ -693,6 +685,12 @@ namespace Ryujinx.Graphics.Gpu.Image
{
Texture overlap = _textureOverlaps[index];
bool rangeMatches = range != null ? overlap.Range.Equals(range.Value) : overlap.Info.GpuAddress == info.GpuAddress;
if (!rangeMatches)
{
continue;
}
TextureMatchQuality matchQuality = overlap.IsExactMatch(info, flags);
if (matchQuality == TextureMatchQuality.Perfect)
@ -727,19 +725,25 @@ namespace Ryujinx.Graphics.Gpu.Image
// Calculate texture sizes, used to find all overlapping textures.
SizeInfo sizeInfo = info.CalculateSizeInfo(layerSize);
// Find view compatible matches.
ulong size = (ulong)sizeInfo.TotalSize;
if (range == null)
{
range = _context.MemoryManager.GetPhysicalRegions(info.GpuAddress, size);
}
// Find view compatible matches.
int overlapsCount;
lock (_textures)
{
overlapsCount = _textures.FindOverlaps(info.Address, size, ref _textureOverlaps);
overlapsCount = _textures.FindOverlaps(range.Value, ref _textureOverlaps);
}
for (int index = 0; index < overlapsCount; index++)
{
Texture overlap = _textureOverlaps[index];
TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(info, size, out int firstLayer, out int firstLevel);
TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(info, range.Value, out int firstLayer, out int firstLevel);
if (overlapCompatibility == TextureViewCompatibility.Full)
{
@ -750,7 +754,7 @@ namespace Ryujinx.Graphics.Gpu.Image
info = oInfo;
}
texture = overlap.CreateView(oInfo, sizeInfo, firstLayer, firstLevel);
texture = overlap.CreateView(oInfo, sizeInfo, range.Value, firstLayer, firstLevel);
if (overlap.IsModified)
{
@ -771,7 +775,7 @@ namespace Ryujinx.Graphics.Gpu.Image
// No match, create a new texture.
if (texture == null)
{
texture = new Texture(_context, info, sizeInfo, scaleMode);
texture = new Texture(_context, info, sizeInfo, range.Value, scaleMode);
// Step 1: Find textures that are view compatible with the new texture.
// Any textures that are incompatible will contain garbage data, so they should be removed where possible.
@ -784,7 +788,7 @@ namespace Ryujinx.Graphics.Gpu.Image
Texture overlap = _textureOverlaps[index];
bool overlapInCache = overlap.CacheNode != null;
TextureViewCompatibility compatibility = texture.IsViewCompatible(overlap.Info, overlap.Size, out int firstLayer, out int firstLevel);
TextureViewCompatibility compatibility = texture.IsViewCompatible(overlap.Info, overlap.Range, out int firstLayer, out int firstLevel);
if (compatibility != TextureViewCompatibility.Incompatible)
{
@ -812,7 +816,7 @@ namespace Ryujinx.Graphics.Gpu.Image
// If the data has been modified by the CPU, then it also shouldn't be flushed.
bool modified = overlap.ConsumeModified();
bool flush = overlapInCache && !modified && (overlap.Address < texture.Address || overlap.EndAddress > texture.EndAddress) && overlap.HasViewCompatibleChild(texture);
bool flush = overlapInCache && !modified && !texture.Range.Contains(overlap.Range) && overlap.HasViewCompatibleChild(texture);
setData |= modified || flush;
@ -1070,7 +1074,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
return new TextureInfo(
info.Address,
info.GpuAddress,
width,
height,
depthOrLayers,

View file

@ -54,15 +54,14 @@ namespace Ryujinx.Graphics.Gpu.Image
TextureInfo info = GetInfo(descriptor, out int layerSize);
// Bad address. We can't add a texture with a invalid address
// to the cache.
if (info.Address == MemoryManager.PteUnmapped)
texture = Context.Methods.TextureManager.FindOrCreateTexture(TextureSearchFlags.ForSampler, info, layerSize);
// If this happens, then the texture address is invalid, we can't add it to the cache.
if (texture == null)
{
return null;
}
texture = Context.Methods.TextureManager.FindOrCreateTexture(info, TextureSearchFlags.ForSampler, layerSize);
texture.IncrementReferenceCount();
Items[id] = texture;
@ -123,7 +122,8 @@ namespace Ryujinx.Graphics.Gpu.Image
// If the descriptors are the same, the texture is the same,
// we don't need to remove as it was not modified. Just continue.
if (texture.IsExactMatch(GetInfo(descriptor, out _), TextureSearchFlags.Strict) != TextureMatchQuality.NoMatch)
if (texture.Info.GpuAddress == descriptor.UnpackAddress() &&
texture.IsExactMatch(GetInfo(descriptor, out _), TextureSearchFlags.Strict) != TextureMatchQuality.NoMatch)
{
continue;
}
@ -143,9 +143,6 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <returns>The texture information</returns>
private TextureInfo GetInfo(TextureDescriptor descriptor, out int layerSize)
{
ulong address = Context.MemoryManager.Translate(descriptor.UnpackAddress());
bool addressIsValid = address != MemoryManager.PteUnmapped;
int width = descriptor.UnpackWidth();
int height = descriptor.UnpackHeight();
int depthOrLayers = descriptor.UnpackDepth();
@ -183,9 +180,11 @@ namespace Ryujinx.Graphics.Gpu.Image
uint format = descriptor.UnpackFormat();
bool srgb = descriptor.UnpackSrgb();
ulong gpuVa = descriptor.UnpackAddress();
if (!FormatTable.TryGetTextureFormat(format, srgb, out FormatInfo formatInfo))
{
if (addressIsValid && (int)format > 0)
if (Context.MemoryManager.IsMapped(gpuVa) && (int)format > 0)
{
Logger.Error?.Print(LogClass.Gpu, $"Invalid texture format 0x{format:X} (sRGB: {srgb}).");
}
@ -204,7 +203,7 @@ namespace Ryujinx.Graphics.Gpu.Image
int maxLod = descriptor.UnpackMaxLevelInclusive();
// Linear textures don't support mipmaps, so we don't handle this case here.
if ((minLod != 0 || maxLod + 1 != levels) && target != Target.TextureBuffer && !isLinear && addressIsValid)
if ((minLod != 0 || maxLod + 1 != levels) && target != Target.TextureBuffer && !isLinear)
{
int depth = TextureInfo.GetDepth(target, depthOrLayers);
int layers = TextureInfo.GetLayers(target, depthOrLayers);
@ -229,7 +228,7 @@ namespace Ryujinx.Graphics.Gpu.Image
// If the base level is not zero, we additionally add the mip level offset
// to the address, this allows the texture manager to find the base level from the
// address if there is a overlapping texture on the cache that can contain the new texture.
address += (ulong)sizeInfo.GetMipOffset(minLod);
gpuVa += (ulong)sizeInfo.GetMipOffset(minLod);
width = Math.Max(1, width >> minLod);
height = Math.Max(1, height >> minLod);
@ -274,7 +273,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
return new TextureInfo(
address,
gpuVa,
width,
height,
depthOrLayers,

View file

@ -0,0 +1,60 @@
using Ryujinx.Cpu.Tracking;
using Ryujinx.Memory.Tracking;
using System;
namespace Ryujinx.Graphics.Gpu.Memory
{
class GpuRegionHandle : IRegionHandle
{
private readonly CpuRegionHandle[] _cpuRegionHandles;
public bool Dirty
{
get
{
foreach (var regionHandle in _cpuRegionHandles)
{
if (regionHandle.Dirty)
{
return true;
}
}
return false;
}
}
public ulong Address => throw new NotSupportedException();
public ulong Size => throw new NotSupportedException();
public ulong EndAddress => throw new NotSupportedException();
public GpuRegionHandle(CpuRegionHandle[] cpuRegionHandles)
{
_cpuRegionHandles = cpuRegionHandles;
}
public void Dispose()
{
foreach (var regionHandle in _cpuRegionHandles)
{
regionHandle.Dispose();
}
}
public void RegisterAction(RegionSignal action)
{
foreach (var regionHandle in _cpuRegionHandles)
{
regionHandle.RegisterAction(action);
}
}
public void Reprotect()
{
foreach (var regionHandle in _cpuRegionHandles)
{
regionHandle.Reprotect();
}
}
}
}

View file

@ -1,5 +1,7 @@
using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -8,7 +10,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary>
/// GPU memory manager.
/// </summary>
public class MemoryManager
public class MemoryManager : IWritableBlock
{
private const int PtLvl0Bits = 14;
private const int PtLvl1Bits = 14;
@ -24,6 +26,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
private const int PtLvl0Bit = PtPageBits + PtLvl1Bits;
private const int PtLvl1Bit = PtPageBits;
private const int AddressSpaceBits = PtPageBits + PtLvl1Bits + PtLvl0Bits;
public const ulong PteUnmapped = 0xffffffff_ffffffff;
@ -46,26 +49,69 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Reads data from GPU mapped memory.
/// </summary>
/// <typeparam name="T">Type of the data</typeparam>
/// <param name="gpuVa">GPU virtual address where the data is located</param>
/// <param name="va">GPU virtual address where the data is located</param>
/// <returns>The data at the specified memory location</returns>
public T Read<T>(ulong gpuVa) where T : unmanaged
public T Read<T>(ulong va) where T : unmanaged
{
ulong processVa = Translate(gpuVa);
return MemoryMarshal.Cast<byte, T>(_context.PhysicalMemory.GetSpan(processVa, Unsafe.SizeOf<T>()))[0];
return MemoryMarshal.Cast<byte, T>(GetSpan(va, Unsafe.SizeOf<T>()))[0];
}
/// <summary>
/// Gets a read-only span of data from GPU mapped memory.
/// </summary>
/// <param name="gpuVa">GPU virtual address where the data is located</param>
/// <param name="va">GPU virtual address where the data is located</param>
/// <param name="size">Size of the data</param>
/// <returns>The span of the data at the specified memory location</returns>
public ReadOnlySpan<byte> GetSpan(ulong gpuVa, int size)
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
{
ulong processVa = Translate(gpuVa);
if (IsContiguous(va, size))
{
return _context.PhysicalMemory.GetSpan(Translate(va), size, tracked);
}
else
{
Span<byte> data = new byte[size];
return _context.PhysicalMemory.GetSpan(processVa, size);
ReadImpl(va, data, tracked);
return data;
}
}
/// <summary>
/// Reads data from a possibly non-contiguous region of GPU mapped memory.
/// </summary>
/// <param name="va">GPU virtual address of the data</param>
/// <param name="data">Span to write the read data into</param>
/// <param name="tracked">True to enable write tracking on read, false otherwise</param>
private void ReadImpl(ulong va, Span<byte> data, bool tracked)
{
if (data.Length == 0)
{
return;
}
int offset = 0, size;
if ((va & PageMask) != 0)
{
ulong pa = Translate(va);
size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask));
_context.PhysicalMemory.GetSpan(pa, size, tracked).CopyTo(data.Slice(0, size));
offset += size;
}
for (; offset < data.Length; offset += size)
{
ulong pa = Translate(va + (ulong)offset);
size = Math.Min(data.Length - offset, (int)PageSize);
_context.PhysicalMemory.GetSpan(pa, size, tracked).CopyTo(data.Slice(offset, size));
}
}
/// <summary>
@ -74,36 +120,91 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="address">Start address of the range</param>
/// <param name="size">Size in bytes to be range</param>
/// <returns>A writable region with the data at the specified memory location</returns>
public WritableRegion GetWritableRegion(ulong gpuVa, int size)
public WritableRegion GetWritableRegion(ulong va, int size)
{
ulong processVa = Translate(gpuVa);
if (IsContiguous(va, size))
{
return _context.PhysicalMemory.GetWritableRegion(Translate(va), size);
}
else
{
Memory<byte> memory = new byte[size];
return _context.PhysicalMemory.GetWritableRegion(processVa, size);
GetSpan(va, size).CopyTo(memory.Span);
return new WritableRegion(this, va, memory);
}
}
/// <summary>
/// Writes data to GPU mapped memory.
/// </summary>
/// <typeparam name="T">Type of the data</typeparam>
/// <param name="gpuVa">GPU virtual address to write the value into</param>
/// <param name="va">GPU virtual address to write the value into</param>
/// <param name="value">The value to be written</param>
public void Write<T>(ulong gpuVa, T value) where T : unmanaged
public void Write<T>(ulong va, T value) where T : unmanaged
{
ulong processVa = Translate(gpuVa);
_context.PhysicalMemory.Write(processVa, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
Write(va, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
}
/// <summary>
/// Writes data to GPU mapped memory.
/// </summary>
/// <param name="gpuVa">GPU virtual address to write the data into</param>
/// <param name="va">GPU virtual address to write the data into</param>
/// <param name="data">The data to be written</param>
public void Write(ulong gpuVa, ReadOnlySpan<byte> data)
public void Write(ulong va, ReadOnlySpan<byte> data)
{
ulong processVa = Translate(gpuVa);
WriteImpl(va, data, _context.PhysicalMemory.Write);
}
_context.PhysicalMemory.Write(processVa, data);
/// <summary>
/// Writes data to GPU mapped memory without write tracking.
/// </summary>
/// <param name="va">GPU virtual address to write the data into</param>
/// <param name="data">The data to be written</param>
public void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
{
WriteImpl(va, data, _context.PhysicalMemory.WriteUntracked);
}
private delegate void WriteCallback(ulong address, ReadOnlySpan<byte> data);
/// <summary>
/// Writes data to possibly non-contiguous GPU mapped memory.
/// </summary>
/// <param name="va">GPU virtual address of the region to write into</param>
/// <param name="data">Data to be written</param>
/// <param name="writeCallback">Write callback</param>
private void WriteImpl(ulong va, ReadOnlySpan<byte> data, WriteCallback writeCallback)
{
if (IsContiguous(va, data.Length))
{
writeCallback(Translate(va), data);
}
else
{
int offset = 0, size;
if ((va & PageMask) != 0)
{
ulong pa = Translate(va);
size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask));
writeCallback(pa, data.Slice(0, size));
offset += size;
}
for (; offset < data.Length; offset += size)
{
ulong pa = Translate(va + (ulong)offset);
size = Math.Min(data.Length - offset, (int)PageSize);
writeCallback(pa, data.Slice(offset, size));
}
}
}
/// <summary>
@ -147,42 +248,151 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
/// <summary>
/// Checks if a region of GPU mapped memory is contiguous.
/// </summary>
/// <param name="va">GPU virtual address of the region</param>
/// <param name="size">Size of the region</param>
/// <returns>True if the region is contiguous, false otherwise</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsContiguous(ulong va, int size)
{
if (!ValidateAddress(va) || GetPte(va) == PteUnmapped)
{
return false;
}
ulong endVa = (va + (ulong)size + PageMask) & ~PageMask;
va &= ~PageMask;
int pages = (int)((endVa - va) / PageSize);
for (int page = 0; page < pages - 1; page++)
{
if (!ValidateAddress(va + PageSize) || GetPte(va + PageSize) == PteUnmapped)
{
return false;
}
if (Translate(va) + PageSize != Translate(va + PageSize))
{
return false;
}
va += PageSize;
}
return true;
}
/// <summary>
/// Gets the physical regions that make up the given virtual address region.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range</param>
/// <returns>Multi-range with the physical regions</returns>
/// <exception cref="InvalidMemoryRegionException">The memory region specified by <paramref name="va"/> and <paramref name="size"/> is not fully mapped</exception>
public MultiRange GetPhysicalRegions(ulong va, ulong size)
{
if (IsContiguous(va, (int)size))
{
return new MultiRange(Translate(va), size);
}
if (!IsMapped(va))
{
throw new InvalidMemoryRegionException($"The specified GPU virtual address 0x{va:X} is not mapped.");
}
ulong regionStart = Translate(va);
ulong regionSize = Math.Min(size, PageSize - (va & PageMask));
ulong endVa = va + size;
ulong endVaRounded = (endVa + PageMask) & ~PageMask;
va &= ~PageMask;
int pages = (int)((endVaRounded - va) / PageSize);
var regions = new List<MemoryRange>();
for (int page = 0; page < pages - 1; page++)
{
if (!IsMapped(va + PageSize))
{
throw new InvalidMemoryRegionException($"The specified GPU virtual memory range 0x{va:X}..0x{(va + size):X} is not fully mapped.");
}
ulong newPa = Translate(va + PageSize);
if (Translate(va) + PageSize != newPa)
{
regions.Add(new MemoryRange(regionStart, regionSize));
regionStart = newPa;
regionSize = 0;
}
va += PageSize;
regionSize += Math.Min(endVa - va, PageSize);
}
regions.Add(new MemoryRange(regionStart, regionSize));
return new MultiRange(regions.ToArray());
}
/// <summary>
/// Validates a GPU virtual address.
/// </summary>
/// <param name="va">Address to validate</param>
/// <returns>True if the address is valid, false otherwise</returns>
private static bool ValidateAddress(ulong va)
{
return va < (1UL << AddressSpaceBits);
}
/// <summary>
/// Checks if a given page is mapped.
/// </summary>
/// <param name="gpuVa">GPU virtual address of the page to check</param>
/// <param name="va">GPU virtual address of the page to check</param>
/// <returns>True if the page is mapped, false otherwise</returns>
public bool IsMapped(ulong gpuVa)
public bool IsMapped(ulong va)
{
return Translate(gpuVa) != PteUnmapped;
return Translate(va) != PteUnmapped;
}
/// <summary>
/// Translates a GPU virtual address to a CPU virtual address.
/// </summary>
/// <param name="gpuVa">GPU virtual address to be translated</param>
/// <returns>CPU virtual address</returns>
public ulong Translate(ulong gpuVa)
/// <param name="va">GPU virtual address to be translated</param>
/// <returns>CPU virtual address, or <see cref="PteUnmapped"/> if unmapped</returns>
public ulong Translate(ulong va)
{
ulong baseAddress = GetPte(gpuVa);
if (!ValidateAddress(va))
{
return PteUnmapped;
}
ulong baseAddress = GetPte(va);
if (baseAddress == PteUnmapped)
{
return PteUnmapped;
}
return baseAddress + (gpuVa & PageMask);
return baseAddress + (va & PageMask);
}
/// <summary>
/// Gets the Page Table entry for a given GPU virtual address.
/// </summary>
/// <param name="gpuVa">GPU virtual address</param>
/// <param name="va">GPU virtual address</param>
/// <returns>Page table entry (CPU virtual address)</returns>
private ulong GetPte(ulong gpuVa)
private ulong GetPte(ulong va)
{
ulong l0 = (gpuVa >> PtLvl0Bit) & PtLvl0Mask;
ulong l1 = (gpuVa >> PtLvl1Bit) & PtLvl1Mask;
ulong l0 = (va >> PtLvl0Bit) & PtLvl0Mask;
ulong l1 = (va >> PtLvl1Bit) & PtLvl1Mask;
if (_pageTable[l0] == null)
{
@ -195,12 +405,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary>
/// Sets a Page Table entry at a given GPU virtual address.
/// </summary>
/// <param name="gpuVa">GPU virtual address</param>
/// <param name="va">GPU virtual address</param>
/// <param name="pte">Page table entry (CPU virtual address)</param>
private void SetPte(ulong gpuVa, ulong pte)
private void SetPte(ulong va, ulong pte)
{
ulong l0 = (gpuVa >> PtLvl0Bit) & PtLvl0Mask;
ulong l1 = (gpuVa >> PtLvl1Bit) & PtLvl1Mask;
ulong l0 = (va >> PtLvl0Bit) & PtLvl0Mask;
ulong l1 = (va >> PtLvl1Bit) & PtLvl1Mask;
if (_pageTable[l0] == null)
{

View file

@ -1,6 +1,7 @@
using Ryujinx.Cpu;
using Ryujinx.Cpu.Tracking;
using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -38,6 +39,37 @@ namespace Ryujinx.Graphics.Gpu.Memory
return _cpuMemory.GetSpan(address, size, tracked);
}
/// <summary>
/// Gets a span of data from the application process.
/// </summary>
/// <param name="range">Ranges of physical memory where the data is located</param>
/// <param name="tracked">True if read tracking is triggered on the span</param>
/// <returns>A read only span of the data at the specified memory location</returns>
public ReadOnlySpan<byte> GetSpan(MultiRange range, bool tracked = false)
{
if (range.Count == 1)
{
var singleRange = range.GetSubRange(0);
return _cpuMemory.GetSpan(singleRange.Address, (int)singleRange.Size, tracked);
}
else
{
Span<byte> data = new byte[range.GetSize()];
int offset = 0;
for (int i = 0; i < range.Count; i++)
{
var currentRange = range.GetSubRange(i);
int size = (int)currentRange.Size;
_cpuMemory.GetSpan(currentRange.Address, size, tracked).CopyTo(data.Slice(offset, size));
offset += size;
}
return data;
}
}
/// <summary>
/// Gets a writable region from the application process.
/// </summary>
@ -70,6 +102,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
_cpuMemory.Write(address, data);
}
/// <summary>
/// Writes data to the application process.
/// </summary>
/// <param name="range">Ranges of physical memory where the data is located</param>
/// <param name="data">Data to be written</param>
public void Write(MultiRange range, ReadOnlySpan<byte> data)
{
WriteImpl(range, data, _cpuMemory.Write);
}
/// <summary>
/// Writes data to the application process, without any tracking.
/// </summary>
@ -80,6 +122,45 @@ namespace Ryujinx.Graphics.Gpu.Memory
_cpuMemory.WriteUntracked(address, data);
}
/// <summary>
/// Writes data to the application process, without any tracking.
/// </summary>
/// <param name="range">Ranges of physical memory where the data is located</param>
/// <param name="data">Data to be written</param>
public void WriteUntracked(MultiRange range, ReadOnlySpan<byte> data)
{
WriteImpl(range, data, _cpuMemory.WriteUntracked);
}
private delegate void WriteCallback(ulong address, ReadOnlySpan<byte> data);
/// <summary>
/// Writes data to the application process, using the supplied callback method.
/// </summary>
/// <param name="range">Ranges of physical memory where the data is located</param>
/// <param name="data">Data to be written</param>
/// <param name="writeCallback">Callback method that will perform the write</param>
private void WriteImpl(MultiRange range, ReadOnlySpan<byte> data, WriteCallback writeCallback)
{
if (range.Count == 1)
{
var singleRange = range.GetSubRange(0);
writeCallback(singleRange.Address, data);
}
else
{
int offset = 0;
for (int i = 0; i < range.Count; i++)
{
var currentRange = range.GetSubRange(i);
int size = (int)currentRange.Size;
writeCallback(currentRange.Address, data.Slice(offset, size));
offset += size;
}
}
}
/// <summary>
/// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with.
/// </summary>
@ -91,6 +172,24 @@ namespace Ryujinx.Graphics.Gpu.Memory
return _cpuMemory.BeginTracking(address, size);
}
/// <summary>
/// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with.
/// </summary>
/// <param name="range">Ranges of physical memory where the data is located</param>
/// <returns>The memory tracking handle</returns>
public GpuRegionHandle BeginTracking(MultiRange range)
{
var cpuRegionHandles = new CpuRegionHandle[range.Count];
for (int i = 0; i < range.Count; i++)
{
var currentRange = range.GetSubRange(i);
cpuRegionHandles[i] = _cpuMemory.BeginTracking(currentRange.Address, currentRange.Size);
}
return new GpuRegionHandle(cpuRegionHandles);
}
/// <summary>
/// Obtains a memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with.
/// </summary>

View file

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

View file

@ -45,7 +45,7 @@ namespace Ryujinx.Graphics.Texture
return _mipOffsets[level];
}
public bool FindView(int offset, int size, out int firstLayer, out int firstLevel)
public bool FindView(int offset, out int firstLayer, out int firstLevel)
{
int index = Array.BinarySearch(_allOffsets, offset);

View file

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

View file

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

View file

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

View file

@ -0,0 +1,71 @@
using System;
namespace Ryujinx.Memory.Range
{
/// <summary>
/// Range of memory composed of an address and size.
/// </summary>
public struct MemoryRange : IEquatable<MemoryRange>
{
/// <summary>
/// An empty memory range, with a null address and zero size.
/// </summary>
public static MemoryRange Empty => new MemoryRange(0UL, 0);
/// <summary>
/// Start address of the range.
/// </summary>
public ulong Address { get; }
/// <summary>
/// Size of the range in bytes.
/// </summary>
public ulong Size { get; }
/// <summary>
/// Address where the range ends (exclusive).
/// </summary>
public ulong EndAddress => Address + Size;
/// <summary>
/// Creates a new memory range with the specified address and size.
/// </summary>
/// <param name="address">Start address</param>
/// <param name="size">Size in bytes</param>
public MemoryRange(ulong address, ulong size)
{
Address = address;
Size = size;
}
/// <summary>
/// Checks if the range overlaps with another.
/// </summary>
/// <param name="other">The other range to check for overlap</param>
/// <returns>True if the ranges overlap, false otherwise</returns>
public bool OverlapsWith(MemoryRange other)
{
ulong thisAddress = Address;
ulong thisEndAddress = EndAddress;
ulong otherAddress = other.Address;
ulong otherEndAddress = other.EndAddress;
return thisAddress < otherEndAddress && otherAddress < thisEndAddress;
}
public override bool Equals(object obj)
{
return obj is MemoryRange other && Equals(other);
}
public bool Equals(MemoryRange other)
{
return Address == other.Address && Size == other.Size;
}
public override int GetHashCode()
{
return HashCode.Combine(Address, Size);
}
}
}

View file

@ -0,0 +1,295 @@
using System;
namespace Ryujinx.Memory.Range
{
/// <summary>
/// Sequence of physical memory regions that a single non-contiguous virtual memory region maps to.
/// </summary>
public struct MultiRange : IEquatable<MultiRange>
{
private readonly MemoryRange _singleRange;
private readonly MemoryRange[] _ranges;
private bool HasSingleRange => _ranges == null;
/// <summary>
/// Total of physical sub-ranges on the virtual memory region.
/// </summary>
public int Count => HasSingleRange ? 1 : _ranges.Length;
/// <summary>
/// Minimum start address of all sub-ranges.
/// </summary>
public ulong MinAddress { get; }
/// <summary>
/// Maximum end address of all sub-ranges.
/// </summary>
public ulong MaxAddress { get; }
/// <summary>
/// Creates a new multi-range with a single physical region.
/// </summary>
/// <param name="address">Start address of the region</param>
/// <param name="size">Size of the region in bytes</param>
public MultiRange(ulong address, ulong size)
{
_singleRange = new MemoryRange(address, size);
_ranges = null;
MinAddress = address;
MaxAddress = address + size;
}
/// <summary>
/// Creates a new multi-range with multiple physical regions.
/// </summary>
/// <param name="ranges">Array of physical regions</param>
/// <exception cref="ArgumentNullException"><paramref name="ranges"/> is null</exception>
public MultiRange(MemoryRange[] ranges)
{
_singleRange = MemoryRange.Empty;
_ranges = ranges ?? throw new ArgumentNullException(nameof(ranges));
if (ranges.Length != 0)
{
MinAddress = ulong.MaxValue;
MaxAddress = 0UL;
foreach (MemoryRange range in ranges)
{
if (MinAddress > range.Address)
{
MinAddress = range.Address;
}
if (MaxAddress < range.EndAddress)
{
MaxAddress = range.EndAddress;
}
}
}
else
{
MinAddress = 0UL;
MaxAddress = 0UL;
}
}
/// <summary>
/// Gets the physical region at the specified index.
/// </summary>
/// <param name="index">Index of the physical region</param>
/// <returns>Region at the index specified</returns>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is invalid</exception>
public MemoryRange GetSubRange(int index)
{
if (HasSingleRange)
{
if (index != 0)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
return _singleRange;
}
else
{
if ((uint)index >= _ranges.Length)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
return _ranges[index];
}
}
/// <summary>
/// Gets the physical region at the specified index, without explicit bounds checking.
/// </summary>
/// <param name="index">Index of the physical region</param>
/// <returns>Region at the index specified</returns>
private MemoryRange GetSubRangeUnchecked(int index)
{
return HasSingleRange ? _singleRange : _ranges[index];
}
/// <summary>
/// Check if two multi-ranges overlap with each other.
/// </summary>
/// <param name="other">Other multi-range to check for overlap</param>
/// <returns>True if any sub-range overlaps, false otherwise</returns>
public bool OverlapsWith(MultiRange other)
{
if (HasSingleRange && other.HasSingleRange)
{
return _singleRange.OverlapsWith(other._singleRange);
}
else
{
for (int i = 0; i < Count; i++)
{
MemoryRange currentRange = GetSubRangeUnchecked(i);
for (int j = 0; j < other.Count; j++)
{
if (currentRange.OverlapsWith(other.GetSubRangeUnchecked(j)))
{
return true;
}
}
}
}
return false;
}
/// <summary>
/// Checks if a given multi-range is fully contained inside another.
/// </summary>
/// <param name="other">Multi-range to be checked</param>
/// <returns>True if all the sub-ranges on <paramref name="other"/> are contained inside the multi-range, with the same order, false otherwise</returns>
public bool Contains(MultiRange other)
{
return FindOffset(other) >= 0;
}
/// <summary>
/// Calculates the offset of a given multi-range inside another, when the multi-range is fully contained
/// inside the other multi-range, otherwise returns -1.
/// </summary>
/// <param name="other">Multi-range that should be fully contained inside this one</param>
/// <returns>Offset in bytes if fully contained, otherwise -1</returns>
public int FindOffset(MultiRange other)
{
int thisCount = Count;
int otherCount = other.Count;
if (thisCount == 1 && otherCount == 1)
{
MemoryRange otherFirstRange = other.GetSubRangeUnchecked(0);
MemoryRange currentFirstRange = GetSubRangeUnchecked(0);
if (otherFirstRange.Address >= currentFirstRange.Address &&
otherFirstRange.EndAddress <= currentFirstRange.EndAddress)
{
return (int)(otherFirstRange.Address - currentFirstRange.Address);
}
}
else if (thisCount >= otherCount)
{
ulong baseOffset = 0;
MemoryRange otherFirstRange = other.GetSubRangeUnchecked(0);
MemoryRange otherLastRange = other.GetSubRangeUnchecked(otherCount - 1);
for (int i = 0; i < (thisCount - otherCount) + 1; baseOffset += GetSubRangeUnchecked(i).Size, i++)
{
MemoryRange currentFirstRange = GetSubRangeUnchecked(i);
MemoryRange currentLastRange = GetSubRangeUnchecked(i + otherCount - 1);
if (otherCount > 1)
{
if (otherFirstRange.Address < currentFirstRange.Address ||
otherFirstRange.EndAddress != currentFirstRange.EndAddress)
{
continue;
}
if (otherLastRange.Address != currentLastRange.Address ||
otherLastRange.EndAddress > currentLastRange.EndAddress)
{
continue;
}
bool fullMatch = true;
for (int j = 1; j < otherCount - 1; j++)
{
if (!GetSubRangeUnchecked(i + j).Equals(other.GetSubRangeUnchecked(j)))
{
fullMatch = false;
break;
}
}
if (!fullMatch)
{
continue;
}
}
else if (currentFirstRange.Address > otherFirstRange.Address ||
currentFirstRange.EndAddress < otherFirstRange.EndAddress)
{
continue;
}
return (int)(baseOffset + (otherFirstRange.Address - currentFirstRange.Address));
}
}
return -1;
}
/// <summary>
/// Gets the total size of all sub-ranges in bytes.
/// </summary>
/// <returns>Total size in bytes</returns>
public ulong GetSize()
{
ulong sum = 0;
foreach (MemoryRange range in _ranges)
{
sum += range.Size;
}
return sum;
}
public override bool Equals(object obj)
{
return obj is MultiRange other && Equals(other);
}
public bool Equals(MultiRange other)
{
if (HasSingleRange && other.HasSingleRange)
{
return _singleRange.Equals(other._singleRange);
}
int thisCount = Count;
if (thisCount != other.Count)
{
return false;
}
for (int i = 0; i < thisCount; i++)
{
if (!GetSubRangeUnchecked(i).Equals(other.GetSubRangeUnchecked(i)))
{
return false;
}
}
return true;
}
public override int GetHashCode()
{
if (HasSingleRange)
{
return _singleRange.GetHashCode();
}
HashCode hash = new HashCode();
foreach (MemoryRange range in _ranges)
{
hash.Add(range);
}
return hash.ToHashCode();
}
}
}

View file

@ -0,0 +1,204 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Ryujinx.Memory.Range
{
/// <summary>
/// Sorted list of ranges that supports binary search.
/// </summary>
/// <typeparam name="T">Type of the range.</typeparam>
public class MultiRangeList<T> : IEnumerable<T> where T : IMultiRangeItem
{
private const int ArrayGrowthSize = 32;
private readonly List<T> _items;
public int Count => _items.Count;
/// <summary>
/// Creates a new range list.
/// </summary>
public MultiRangeList()
{
_items = new List<T>();
}
/// <summary>
/// Adds a new item to the list.
/// </summary>
/// <param name="item">The item to be added</param>
public void Add(T item)
{
int index = BinarySearch(item.BaseAddress);
if (index < 0)
{
index = ~index;
}
_items.Insert(index, item);
}
/// <summary>
/// Removes an item from the list.
/// </summary>
/// <param name="item">The item to be removed</param>
/// <returns>True if the item was removed, or false if it was not found</returns>
public bool Remove(T item)
{
int index = BinarySearch(item.BaseAddress);
if (index >= 0)
{
while (index > 0 && _items[index - 1].BaseAddress == item.BaseAddress)
{
index--;
}
while (index < _items.Count)
{
if (_items[index].Equals(item))
{
_items.RemoveAt(index);
return true;
}
if (_items[index].BaseAddress > item.BaseAddress)
{
break;
}
index++;
}
}
return false;
}
/// <summary>
/// Gets all items on the list overlapping the specified memory range.
/// </summary>
/// <param name="address">Start address of the range</param>
/// <param name="size">Size in bytes of the range</param>
/// <param name="output">Output array where matches will be written. It is automatically resized to fit the results</param>
/// <returns>The number of overlapping items found</returns>
public int FindOverlaps(ulong address, ulong size, ref T[] output)
{
return FindOverlaps(new MultiRange(address, size), ref output);
}
/// <summary>
/// Gets all items on the list overlapping the specified memory ranges.
/// </summary>
/// <param name="range">Ranges of memory being searched</param>
/// <param name="output">Output array where matches will be written. It is automatically resized to fit the results</param>
/// <returns>The number of overlapping items found</returns>
public int FindOverlaps(MultiRange range, ref T[] output)
{
int outputIndex = 0;
foreach (T item in _items)
{
if (item.Range.OverlapsWith(range))
{
if (outputIndex == output.Length)
{
Array.Resize(ref output, outputIndex + ArrayGrowthSize);
}
output[outputIndex++] = item;
}
}
return outputIndex;
}
/// <summary>
/// Gets all items on the list starting at the specified memory address.
/// </summary>
/// <param name="baseAddress">Base address to find</param>
/// <param name="output">Output array where matches will be written. It is automatically resized to fit the results</param>
/// <returns>The number of matches found</returns>
public int FindOverlaps(ulong baseAddress, ref T[] output)
{
int index = BinarySearch(baseAddress);
int outputIndex = 0;
if (index >= 0)
{
while (index > 0 && _items[index - 1].BaseAddress == baseAddress)
{
index--;
}
while (index < _items.Count)
{
T overlap = _items[index++];
if (overlap.BaseAddress != baseAddress)
{
break;
}
if (outputIndex == output.Length)
{
Array.Resize(ref output, outputIndex + ArrayGrowthSize);
}
output[outputIndex++] = overlap;
}
}
return outputIndex;
}
/// <summary>
/// Performs binary search on the internal list of items.
/// </summary>
/// <param name="address">Address to find</param>
/// <returns>List index of the item, or complement index of nearest item with lower value on the list</returns>
private int BinarySearch(ulong address)
{
int left = 0;
int right = _items.Count - 1;
while (left <= right)
{
int range = right - left;
int middle = left + (range >> 1);
T item = _items[middle];
if (item.BaseAddress == address)
{
return middle;
}
if (address < item.BaseAddress)
{
right = middle - 1;
}
else
{
left = middle + 1;
}
}
return ~left;
}
public IEnumerator<T> GetEnumerator()
{
return _items.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _items.GetEnumerator();
}
}
}

View file

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