diff options
author | gdkchan <gab.dark.100@gmail.com> | 2021-01-17 15:44:34 -0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-01-17 19:44:34 +0100 |
commit | c4f56c570494a6186792439e3c0e74458cc82b5c (patch) | |
tree | 40be96f5f3f24b711e088bf2ca681d94aac68c15 | |
parent | 3bad321d2b0994cd19129bc18ed98bb3ab81c3b0 (diff) |
Support for resources on non-contiguous GPU memory regions (#1905)
* Support for resources on non-contiguous GPU memory regions
* Implement MultiRange physical addresses, only used with a single range for now
* Actually use non-contiguous ranges
* GetPhysicalRegions fixes
* Documentation and remove Address property from TextureInfo
* Finish implementing GetWritableRegion
* Fix typo
-rw-r--r-- | Ryujinx.Cpu/MemoryManager.cs | 4 | ||||
-rw-r--r-- | Ryujinx.Graphics.Gpu/Image/Texture.cs | 84 | ||||
-rw-r--r-- | Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs | 4 | ||||
-rw-r--r-- | Ryujinx.Graphics.Gpu/Image/TextureInfo.cs | 10 | ||||
-rw-r--r-- | Ryujinx.Graphics.Gpu/Image/TextureManager.cs | 100 | ||||
-rw-r--r-- | Ryujinx.Graphics.Gpu/Image/TexturePool.cs | 25 | ||||
-rw-r--r-- | Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs | 60 | ||||
-rw-r--r-- | Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs | 286 | ||||
-rw-r--r-- | Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs | 99 | ||||
-rw-r--r-- | Ryujinx.Graphics.Gpu/Window.cs | 31 | ||||
-rw-r--r-- | Ryujinx.Graphics.Texture/SizeInfo.cs | 2 | ||||
-rw-r--r-- | Ryujinx.Memory/AddressSpaceManager.cs | 5 | ||||
-rw-r--r-- | Ryujinx.Memory/IWritableBlock.cs | 9 | ||||
-rw-r--r-- | Ryujinx.Memory/Range/IMultiRangeItem.cs | 9 | ||||
-rw-r--r-- | Ryujinx.Memory/Range/MemoryRange.cs | 71 | ||||
-rw-r--r-- | Ryujinx.Memory/Range/MultiRange.cs | 295 | ||||
-rw-r--r-- | Ryujinx.Memory/Range/MultiRangeList.cs | 204 | ||||
-rw-r--r-- | Ryujinx.Memory/WritableRegion.cs | 10 |
18 files changed, 1141 insertions, 167 deletions
diff --git a/Ryujinx.Cpu/MemoryManager.cs b/Ryujinx.Cpu/MemoryManager.cs index 5a778e64..348ca2bd 100644 --- a/Ryujinx.Cpu/MemoryManager.cs +++ b/Ryujinx.Cpu/MemoryManager.cs @@ -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 diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs index ae7fa6cf..1c558f56 100644 --- a/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -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; } @@ -1046,17 +1047,6 @@ namespace Ryujinx.Graphics.Gpu.Image } /// <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> /// <param name="texture">The texture to check against</param> @@ -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); } diff --git a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs index 9601b832..173340b3 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs @@ -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) diff --git a/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs b/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs index d27194b8..3137f8b8 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs @@ -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; diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs index 5bad3952..30137d06 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs @@ -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, diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs index 333ebaed..065844cb 100644 --- a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs +++ b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs @@ -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, diff --git a/Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs b/Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs new file mode 100644 index 00000000..d2a05495 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs @@ -0,0 +1,60 @@ +using Ryujinx.Cpu.Tracking; +using Ryujinx.Memory.Tracking; +using System; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + class GpuRegionHandle : IRegionHandle + { + private readonly CpuRegionHandle[] _cpuRegionHandles; + + public bool Dirty + { + get + { + foreach (var regionHandle in _cpuRegionHandles) + { + if (regionHandle.Dirty) + { + return true; + } + } + + return false; + } + } + + public ulong Address => throw new NotSupportedException(); + public ulong Size => throw new NotSupportedException(); + public ulong EndAddress => throw new NotSupportedException(); + + public GpuRegionHandle(CpuRegionHandle[] cpuRegionHandles) + { + _cpuRegionHandles = cpuRegionHandles; + } + + public void Dispose() + { + foreach (var regionHandle in _cpuRegionHandles) + { + regionHandle.Dispose(); + } + } + + public void RegisterAction(RegionSignal action) + { + foreach (var regionHandle in _cpuRegionHandles) + { + regionHandle.RegisterAction(action); + } + } + + public void Reprotect() + { + foreach (var regionHandle in _cpuRegionHandles) + { + regionHandle.Reprotect(); + } + } + } +} diff --git a/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs index cf49fd93..3da22b22 100644 --- a/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs +++ b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs @@ -1,5 +1,7 @@ using Ryujinx.Memory; +using Ryujinx.Memory.Range; using System; +using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -8,7 +10,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// <summary> /// GPU memory manager. /// </summary> - public class MemoryManager + public class MemoryManager : IWritableBlock { private const int PtLvl0Bits = 14; private const int PtLvl1Bits = 14; @@ -24,6 +26,7 @@ namespace Ryujinx.Graphics.Gpu.Memory private const int PtLvl0Bit = PtPageBits + PtLvl1Bits; private const int PtLvl1Bit = PtPageBits; + private const int AddressSpaceBits = PtPageBits + PtLvl1Bits + PtLvl0Bits; public const ulong PteUnmapped = 0xffffffff_ffffffff; @@ -46,26 +49,69 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Reads data from GPU mapped memory. /// </summary> /// <typeparam name="T">Type of the data</typeparam> - /// <param name="gpuVa">GPU virtual address where the data is located</param> + /// <param name="va">GPU virtual address where the data is located</param> /// <returns>The data at the specified memory location</returns> - public T Read<T>(ulong gpuVa) where T : unmanaged + public T Read<T>(ulong va) where T : unmanaged { - ulong processVa = Translate(gpuVa); - - return MemoryMarshal.Cast<byte, T>(_context.PhysicalMemory.GetSpan(processVa, Unsafe.SizeOf<T>()))[0]; + return MemoryMarshal.Cast<byte, T>(GetSpan(va, Unsafe.SizeOf<T>()))[0]; } /// <summary> /// Gets a read-only span of data from GPU mapped memory. /// </summary> - /// <param name="gpuVa">GPU virtual address where the data is located</param> + /// <param name="va">GPU virtual address where the data is located</param> /// <param name="size">Size of the data</param> /// <returns>The span of the data at the specified memory location</returns> - public ReadOnlySpan<byte> GetSpan(ulong gpuVa, int size) + public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false) { - ulong processVa = Translate(gpuVa); + if (IsContiguous(va, size)) + { + return _context.PhysicalMemory.GetSpan(Translate(va), size, tracked); + } + else + { + Span<byte> data = new byte[size]; - return _context.PhysicalMemory.GetSpan(processVa, size); + ReadImpl(va, data, tracked); + + return data; + } + } + + /// <summary> + /// Reads data from a possibly non-contiguous region of GPU mapped memory. + /// </summary> + /// <param name="va">GPU virtual address of the data</param> + /// <param name="data">Span to write the read data into</param> + /// <param name="tracked">True to enable write tracking on read, false otherwise</param> + private void ReadImpl(ulong va, Span<byte> data, bool tracked) + { + if (data.Length == 0) + { + return; + } + + int offset = 0, size; + + if ((va & PageMask) != 0) + { + ulong pa = Translate(va); + + size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask)); + + _context.PhysicalMemory.GetSpan(pa, size, tracked).CopyTo(data.Slice(0, size)); + + offset += size; + } + + for (; offset < data.Length; offset += size) + { + ulong pa = Translate(va + (ulong)offset); + + size = Math.Min(data.Length - offset, (int)PageSize); + + _context.PhysicalMemory.GetSpan(pa, size, tracked).CopyTo(data.Slice(offset, size)); + } } /// <summary> @@ -74,36 +120,91 @@ namespace Ryujinx.Graphics.Gpu.Memory /// <param name="address">Start address of the range</param> /// <param name="size">Size in bytes to be range</param> /// <returns>A writable region with the data at the specified memory location</returns> - public WritableRegion GetWritableRegion(ulong gpuVa, int size) + public WritableRegion GetWritableRegion(ulong va, int size) { - ulong processVa = Translate(gpuVa); + if (IsContiguous(va, size)) + { + return _context.PhysicalMemory.GetWritableRegion(Translate(va), size); + } + else + { + Memory<byte> memory = new byte[size]; + + GetSpan(va, size).CopyTo(memory.Span); - return _context.PhysicalMemory.GetWritableRegion(processVa, size); + return new WritableRegion(this, va, memory); + } } /// <summary> /// Writes data to GPU mapped memory. /// </summary> /// <typeparam name="T">Type of the data</typeparam> - /// <param name="gpuVa">GPU virtual address to write the value into</param> + /// <param name="va">GPU virtual address to write the value into</param> /// <param name="value">The value to be written</param> - public void Write<T>(ulong gpuVa, T value) where T : unmanaged + public void Write<T>(ulong va, T value) where T : unmanaged { - ulong processVa = Translate(gpuVa); - - _context.PhysicalMemory.Write(processVa, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1))); + Write(va, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1))); } /// <summary> /// Writes data to GPU mapped memory. /// </summary> - /// <param name="gpuVa">GPU virtual address to write the data into</param> + /// <param name="va">GPU virtual address to write the data into</param> + /// <param name="data">The data to be written</param> + public void Write(ulong va, ReadOnlySpan<byte> data) + { + WriteImpl(va, data, _context.PhysicalMemory.Write); + } + + /// <summary> + /// Writes data to GPU mapped memory without write tracking. + /// </summary> + /// <param name="va">GPU virtual address to write the data into</param> /// <param name="data">The data to be written</param> - public void Write(ulong gpuVa, ReadOnlySpan<byte> data) + public void WriteUntracked(ulong va, ReadOnlySpan<byte> data) + { + WriteImpl(va, data, _context.PhysicalMemory.WriteUntracked); + } + + private delegate void WriteCallback(ulong address, ReadOnlySpan<byte> data); + + /// <summary> + /// Writes data to possibly non-contiguous GPU mapped memory. + /// </summary> + /// <param name="va">GPU virtual address of the region to write into</param> + /// <param name="data">Data to be written</param> + /// <param name="writeCallback">Write callback</param> + private void WriteImpl(ulong va, ReadOnlySpan<byte> data, WriteCallback writeCallback) { - ulong processVa = Translate(gpuVa); + if (IsContiguous(va, data.Length)) + { + writeCallback(Translate(va), data); + } + else + { + int offset = 0, size; + + if ((va & PageMask) != 0) + { + ulong pa = Translate(va); + + size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask)); + + writeCallback(pa, data.Slice(0, size)); - _context.PhysicalMemory.Write(processVa, data); + offset += size; + } + + for (; offset < data.Length; offset += size) + { + ulong pa = Translate(va + (ulong)offset); + + size = Math.Min(data.Length - offset, (int)PageSize); + + writeCallback(pa, data.Slice(offset, size)); + } + } } /// <summary> @@ -148,41 +249,150 @@ namespace Ryujinx.Graphics.Gpu.Memory } /// <summary> + /// Checks if a region of GPU mapped memory is contiguous. + /// </summary> + /// <param name="va">GPU virtual address of the region</param> + /// <param name="size">Size of the region</param> + /// <returns>True if the region is contiguous, false otherwise</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsContiguous(ulong va, int size) + { + if (!ValidateAddress(va) || GetPte(va) == PteUnmapped) + { + return false; + } + + ulong endVa = (va + (ulong)size + PageMask) & ~PageMask; + + va &= ~PageMask; + + int pages = (int)((endVa - va) / PageSize); + + for (int page = 0; page < pages - 1; page++) + { + if (!ValidateAddress(va + PageSize) || GetPte(va + PageSize) == PteUnmapped) + { + return false; + } + + if (Translate(va) + PageSize != Translate(va + PageSize)) + { + return false; + } + + va += PageSize; + } + + return true; + } + + /// <summary> + /// Gets the physical regions that make up the given virtual address region. + /// </summary> + /// <param name="va">Virtual address of the range</param> + /// <param name="size">Size of the range</param> + /// <returns>Multi-range with the physical regions</returns> + /// <exception cref="InvalidMemoryRegionException">The memory region specified by <paramref name="va"/> and <paramref name="size"/> is not fully mapped</exception> + public MultiRange GetPhysicalRegions(ulong va, ulong size) + { + if (IsContiguous(va, (int)size)) + { + return new MultiRange(Translate(va), size); + } + + if (!IsMapped(va)) + { + throw new InvalidMemoryRegionException($"The specified GPU virtual address 0x{va:X} is not mapped."); + } + + ulong regionStart = Translate(va); + ulong regionSize = Math.Min(size, PageSize - (va & PageMask)); + + ulong endVa = va + size; + ulong endVaRounded = (endVa + PageMask) & ~PageMask; + + va &= ~PageMask; + + int pages = (int)((endVaRounded - va) / PageSize); + + var regions = new List<MemoryRange>(); + + for (int page = 0; page < pages - 1; page++) + { + if (!IsMapped(va + PageSize)) + { + throw new InvalidMemoryRegionException($"The specified GPU virtual memory range 0x{va:X}..0x{(va + size):X} is not fully mapped."); + } + + ulong newPa = Translate(va + PageSize); + + if (Translate(va) + PageSize != newPa) + { + regions.Add(new MemoryRange(regionStart, regionSize)); + regionStart = newPa; + regionSize = 0; + } + + va += PageSize; + regionSize += Math.Min(endVa - va, PageSize); + } + + regions.Add(new MemoryRange(regionStart, regionSize)); + + return new MultiRange(regions.ToArray()); + } + + /// <summary> + /// Validates a GPU virtual address. + /// </summary> + /// <param name="va">Address to validate</param> + /// <returns>True if the address is valid, false otherwise</returns> + private static bool ValidateAddress(ulong va) + { + return va < (1UL << AddressSpaceBits); + } + + /// <summary> /// Checks if a given page is mapped. /// </summary> - /// <param name="gpuVa">GPU virtual address of the page to check</param> + /// <param name="va">GPU virtual address of the page to check</param> /// <returns>True if the page is mapped, false otherwise</returns> - public bool IsMapped(ulong gpuVa) + public bool IsMapped(ulong va) { - return Translate(gpuVa) != PteUnmapped; + return Translate(va) != PteUnmapped; } /// <summary> /// Translates a GPU virtual address to a CPU virtual address. /// </summary> - /// <param name="gpuVa">GPU virtual address to be translated</param> - /// <returns>CPU virtual address</returns> - public ulong Translate(ulong gpuVa) + /// <param name="va">GPU virtual address to be translated</param> + /// <returns>CPU virtual address, or <see cref="PteUnmapped"/> if unmapped</returns> + public ulong Translate(ulong va) { - ulong baseAddress = GetPte(gpuVa); + if (!ValidateAddress(va)) + { + return PteUnmapped; + } + + ulong baseAddress = GetPte(va); if (baseAddress == PteUnmapped) { return PteUnmapped; } - return baseAddress + (gpuVa & PageMask); + return baseAddress + (va & PageMask); } /// <summary> /// Gets the Page Table entry for a given GPU virtual address. /// </summary> - /// <param name="gpuVa">GPU virtual address</param> + /// <param name="va">GPU virtual address</param> /// <returns>Page table entry (CPU virtual address)</returns> - private ulong GetPte(ulong gpuVa) + private ulong GetPte(ulong va) { - ulong l0 = (gpuVa >> PtLvl0Bit) & PtLvl0Mask; - ulong l1 = (gpuVa >> PtLvl1Bit) & PtLvl1Mask; + ulong l0 = (va >> PtLvl0Bit) & PtLvl0Mask; + ulong l1 = (va >> PtLvl1Bit) & PtLvl1Mask; if (_pageTable[l0] == null) { @@ -195,12 +405,12 @@ namespace Ryujinx.Graphics.Gpu.Memory /// <summary> /// Sets a Page Table entry at a given GPU virtual address. /// </summary> - /// <param name="gpuVa">GPU virtual address</param> + /// <param name="va">GPU virtual address</param> /// <param name="pte">Page table entry (CPU virtual address)</param> - private void SetPte(ulong gpuVa, ulong pte) + private void SetPte(ulong va, ulong pte) { - ulong l0 = (gpuVa >> PtLvl0Bit) & PtLvl0Mask; - ulong l1 = (gpuVa >> PtLvl1Bit) & PtLvl1Mask; + ulong l0 = (va >> PtLvl0Bit) & PtLvl0Mask; + ulong l1 = (va >> PtLvl1Bit) & PtLvl1Mask; if (_pageTable[l0] == null) { diff --git a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs index de41fb9a..8b2401c7 100644 --- a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs +++ b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs @@ -1,6 +1,7 @@ using Ryujinx.Cpu; using Ryujinx.Cpu.Tracking; using Ryujinx.Memory; +using Ryujinx.Memory.Range; using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -39,6 +40,37 @@ namespace Ryujinx.Graphics.Gpu.Memory } /// <summary> + /// Gets a span of data from the application process. + /// </summary> + /// <param name="range">Ranges of physical memory where the data is located</param> + /// <param name="tracked">True if read tracking is triggered on the span</param> + /// <returns>A read only span of the data at the specified memory location</returns> + public ReadOnlySpan<byte> GetSpan(MultiRange range, bool tracked = false) + { + if (range.Count == 1) + { + var singleRange = range.GetSubRange(0); + return _cpuMemory.GetSpan(singleRange.Address, (int)singleRange.Size, tracked); + } + else + { + Span<byte> data = new byte[range.GetSize()]; + + int offset = 0; + + for (int i = 0; i < range.Count; i++) + { + var currentRange = range.GetSubRange(i); + int size = (int)currentRange.Size; + _cpuMemory.GetSpan(currentRange.Address, size, tracked).CopyTo(data.Slice(offset, size)); + offset += size; + } + + return data; + } + } + + /// <summary> /// Gets a writable region from the application process. /// </summary> /// <param name="address">Start address of the range</param> @@ -71,6 +103,16 @@ namespace Ryujinx.Graphics.Gpu.Memory } /// <summary> + /// Writes data to the application process. + /// </summary> + /// <param name="range">Ranges of physical memory where the data is located</param> + /// <param name="data">Data to be written</param> + public void Write(MultiRange range, ReadOnlySpan<byte> data) + { + WriteImpl(range, data, _cpuMemory.Write); + } + + /// <summary> /// Writes data to the application process, without any tracking. /// </summary> /// <param name="address">Address to write into</param> @@ -81,6 +123,45 @@ namespace Ryujinx.Graphics.Gpu.Memory } /// <summary> + /// Writes data to the application process, without any tracking. + /// </summary> + /// <param name="range">Ranges of physical memory where the data is located</param> + /// <param name="data">Data to be written</param> + public void WriteUntracked(MultiRange range, ReadOnlySpan<byte> data) + { + WriteImpl(range, data, _cpuMemory.WriteUntracked); + } + + private delegate void WriteCallback(ulong address, ReadOnlySpan<byte> data); + + /// <summary> + /// Writes data to the application process, using the supplied callback method. + /// </summary> + /// <param name="range">Ranges of physical memory where the data is located</param> + /// <param name="data">Data to be written</param> + /// <param name="writeCallback">Callback method that will perform the write</param> + private void WriteImpl(MultiRange range, ReadOnlySpan<byte> data, WriteCallback writeCallback) + { + if (range.Count == 1) + { + var singleRange = range.GetSubRange(0); + writeCallback(singleRange.Address, data); + } + else + { + int offset = 0; + + for (int i = 0; i < range.Count; i++) + { + var currentRange = range.GetSubRange(i); + int size = (int)currentRange.Size; + writeCallback(currentRange.Address, data.Slice(offset, size)); + offset += size; + } + } + } + + /// <summary> /// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with. /// </summary> /// <param name="address">CPU virtual address of the region</param> @@ -92,6 +173,24 @@ namespace Ryujinx.Graphics.Gpu.Memory } /// <summary> + /// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with. + /// </summary> + /// <param name="range">Ranges of physical memory where the data is located</param> + /// <returns>The memory tracking handle</returns> + public GpuRegionHandle BeginTracking(MultiRange range) + { + var cpuRegionHandles = new CpuRegionHandle[range.Count]; + + for (int i = 0; i < range.Count; i++) + { + var currentRange = range.GetSubRange(i); + cpuRegionHandles[i] = _cpuMemory.BeginTracking(currentRange.Address, currentRange.Size); + } + + return new GpuRegionHandle(cpuRegionHandles); + } + + /// <summary> /// Obtains a memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with. /// </summary> /// <param name="address">CPU virtual address of the region</param> diff --git a/Ryujinx.Graphics.Gpu/Window.cs b/Ryujinx.Graphics.Gpu/Window.cs index 9d269356..ad70adfd 100644 --- a/Ryujinx.Graphics.Gpu/Window.cs +++ b/Ryujinx.Graphics.Gpu/Window.cs @@ -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; @@ -26,6 +28,11 @@ namespace Ryujinx.Graphics.Gpu public TextureInfo Info { get; } /// <summary> + /// Physical memory locations where the texture data is located. + /// </summary> + public MultiRange Range { get; } + + /// <summary> /// Texture crop region. /// </summary> public ImageCrop Crop { get; } @@ -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(); diff --git a/Ryujinx.Graphics.Texture/SizeInfo.cs b/Ryujinx.Graphics.Texture/SizeInfo.cs index b6085e0c..55b22e3a 100644 --- a/Ryujinx.Graphics.Texture/SizeInfo.cs +++ b/Ryujinx.Graphics.Texture/SizeInfo.cs @@ -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); diff --git a/Ryujinx.Memory/AddressSpaceManager.cs b/Ryujinx.Memory/AddressSpaceManager.cs index 50c7adad..09977bbd 100644 --- a/Ryujinx.Memory/AddressSpaceManager.cs +++ b/Ryujinx.Memory/AddressSpaceManager.cs @@ -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; diff --git a/Ryujinx.Memory/IWritableBlock.cs b/Ryujinx.Memory/IWritableBlock.cs new file mode 100644 index 00000000..c95b754d --- /dev/null +++ b/Ryujinx.Memory/IWritableBlock.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.Memory +{ + public interface IWritableBlock + { + void Write(ulong va, ReadOnlySpan<byte> data); + } +} diff --git a/Ryujinx.Memory/Range/IMultiRangeItem.cs b/Ryujinx.Memory/Range/IMultiRangeItem.cs new file mode 100644 index 00000000..e95a69fc --- /dev/null +++ b/Ryujinx.Memory/Range/IMultiRangeItem.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Memory.Range +{ + public interface IMultiRangeItem + { + MultiRange Range { get; } + + ulong BaseAddress => Range.GetSubRange(0).Address; + } +} diff --git a/Ryujinx.Memory/Range/MemoryRange.cs b/Ryujinx.Memory/Range/MemoryRange.cs new file mode 100644 index 00000000..ba12bae5 --- /dev/null +++ b/Ryujinx.Memory/Range/MemoryRange.cs @@ -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); + } + } +} diff --git a/Ryujinx.Memory/Range/MultiRange.cs b/Ryujinx.Memory/Range/MultiRange.cs new file mode 100644 index 00000000..b40daa8a --- /dev/null +++ b/Ryujinx.Memory/Range/MultiRange.cs @@ -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(); + } + } +} diff --git a/Ryujinx.Memory/Range/MultiRangeList.cs b/Ryujinx.Memory/Range/MultiRangeList.cs new file mode 100644 index 00000000..1d5439a0 --- /dev/null +++ b/Ryujinx.Memory/Range/MultiRangeList.cs @@ -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(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Memory/WritableRegion.cs b/Ryujinx.Memory/WritableRegion.cs index e8ea310f..467ee7f7 100644 --- a/Ryujinx.Memory/WritableRegion.cs +++ b/Ryujinx.Memory/WritableRegion.cs @@ -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); } } } |