diff --git a/Ryujinx.Cpu/MemoryManager.cs b/Ryujinx.Cpu/MemoryManager.cs
index 5a778e6411..348ca2bd20 100644
--- a/Ryujinx.Cpu/MemoryManager.cs
+++ b/Ryujinx.Cpu/MemoryManager.cs
@@ -13,7 +13,7 @@ namespace Ryujinx.Cpu
///
/// Represents a CPU memory manager.
///
- 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)]
///
/// Writes data to CPU mapped memory.
///
/// Virtual address to write the data into
/// Data to be written
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteImpl(ulong va, ReadOnlySpan data)
{
try
diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs
index ae7fa6cf55..1c558f567d 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
///
/// Represents a cached GPU texture.
///
- 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 Disposed;
///
- /// Start address of the texture in guest memory.
+ /// Physical memory ranges where the texture data is located.
///
- public ulong Address => Info.Address;
-
- ///
- /// End address of the texture in guest memory.
- ///
- public ulong EndAddress => Info.Address + Size;
+ public MultiRange Range { get; private set; }
///
/// Texture size in bytes.
///
public ulong Size => (ulong)_sizeInfo.TotalSize;
- private CpuRegionHandle _memoryTracking;
+ private GpuRegionHandle _memoryTracking;
private int _referenceCount;
@@ -119,6 +114,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// GPU context that the texture belongs to
/// Texture information
/// Size information of the texture
+ /// Physical memory ranges where the texture data is located
/// The first layer of the texture, or 0 if the texture has no parent
/// The first mipmap level of the texture, or 0 if the texture has no parent
/// The floating point scale factor to initialize with
@@ -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
/// GPU context that the texture belongs to
/// Texture information
/// Size information of the texture
+ /// Physical memory ranges where the texture data is located
/// The scale mode to initialize with. If scaled, the texture's data is loaded immediately and scaled up
- 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);
}
///
@@ -166,10 +164,12 @@ namespace Ryujinx.Graphics.Gpu.Image
/// GPU context that the texture belongs to
/// Texture information
/// Size information of the texture
- private void InitializeTexture(GpuContext context, TextureInfo info, SizeInfo sizeInfo)
+ /// Physical memory ranges where the texture data is located
+ 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
/// True if the texture is to be initialized with data
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
///
/// Child texture information
/// Child texture size information
+ /// Physical memory ranges where the texture data is located
/// Start layer of the child texture on the parent texture
/// Start mipmap level of the child texture on the parent texture
/// The child texture
- 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 data = _context.PhysicalMemory.GetSpan(Address, (int)Size);
+ ReadOnlySpan data = _context.PhysicalMemory.GetSpan(Range);
IsModified = false;
@@ -586,6 +588,10 @@ namespace Ryujinx.Graphics.Gpu.Image
_hasData = true;
}
+ ///
+ /// Uploads new texture data to the host GPU.
+ ///
+ /// New data
public void SetData(ReadOnlySpan 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));
}
}
-
///
/// 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;
}
///
/// Check if it's possible to create a view, with the given parameters, from this texture.
///
/// Texture view information
- /// Texture view size
+ /// Texture view physical memory ranges
/// Texture view initial layer on this texture
/// Texture view first mipmap level on this texture
/// The level of compatiblilty a view with the given parameters created from this texture has
- 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;
}
- ///
- /// Checks if the texture overlaps with a memory range.
- ///
- /// Start address of the range
- /// Size of the range
- /// True if the texture overlaps with the range, false otherwise
- public bool OverlapsWith(ulong address, ulong size)
- {
- return Address < address + size && address < EndAddress;
- }
-
///
/// Determine if any of our child textures are compaible as views of the given texture.
///
@@ -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 9601b83255..173340b3ba 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 d27194b85b..3137f8b8cd 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
{
///
- /// Address of the texture in guest memory.
+ /// Address of the texture in GPU mapped memory.
///
- public ulong Address { get; }
+ public ulong GpuAddress { get; }
///
/// The width of the texture.
@@ -112,7 +112,7 @@ namespace Ryujinx.Graphics.Gpu.Image
///
/// Constructs the texture information structure.
///
- /// The address of the texture
+ /// The GPU address of the texture
/// The width of the texture
/// The height or the texture
/// The depth or layers count of the texture
@@ -132,7 +132,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Swizzle for the blue color channel
/// Swizzle for the alpha color channel
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 5bad3952d4..30137d0646 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 _textures;
+ private readonly MultiRangeList _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();
+ _textures = new MultiRangeList();
_textureOverlaps = new Texture[OverlapsBufferInitialCapacity];
_overlapInfo = new OverlapInfo[OverlapsBufferInitialCapacity];
@@ -470,13 +466,6 @@ namespace Ryujinx.Graphics.Gpu.Image
/// The texture
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
/// The texture
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
/// The texture
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
///
/// Tries to find an existing texture, or create a new one if not found.
///
- /// Texture information of the texture to be found or created
/// The texture search flags, defines texture comparison rules
+ /// Texture information of the texture to be found or created
/// Size in bytes of a single texture layer
/// A hint indicating the minimum used size for the texture
+ /// Optional ranges of physical memory where the texture data is located
/// The texture
- 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 333ebaedc0..065844cb02 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
/// The texture information
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 0000000000..d2a0549533
--- /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 cf49fd93f0..3da22b22f5 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
///
/// GPU memory manager.
///
- 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.
///
/// Type of the data
- /// GPU virtual address where the data is located
+ /// GPU virtual address where the data is located
/// The data at the specified memory location
- public T Read(ulong gpuVa) where T : unmanaged
+ public T Read(ulong va) where T : unmanaged
{
- ulong processVa = Translate(gpuVa);
-
- return MemoryMarshal.Cast(_context.PhysicalMemory.GetSpan(processVa, Unsafe.SizeOf()))[0];
+ return MemoryMarshal.Cast(GetSpan(va, Unsafe.SizeOf()))[0];
}
///
/// Gets a read-only span of data from GPU mapped memory.
///
- /// GPU virtual address where the data is located
+ /// GPU virtual address where the data is located
/// Size of the data
/// The span of the data at the specified memory location
- public ReadOnlySpan GetSpan(ulong gpuVa, int size)
+ public ReadOnlySpan 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 data = new byte[size];
- return _context.PhysicalMemory.GetSpan(processVa, size);
+ ReadImpl(va, data, tracked);
+
+ return data;
+ }
+ }
+
+ ///
+ /// Reads data from a possibly non-contiguous region of GPU mapped memory.
+ ///
+ /// GPU virtual address of the data
+ /// Span to write the read data into
+ /// True to enable write tracking on read, false otherwise
+ private void ReadImpl(ulong va, Span 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));
+ }
}
///
@@ -74,36 +120,91 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Start address of the range
/// Size in bytes to be range
/// A writable region with the data at the specified memory location
- 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 memory = new byte[size];
- return _context.PhysicalMemory.GetWritableRegion(processVa, size);
+ GetSpan(va, size).CopyTo(memory.Span);
+
+ return new WritableRegion(this, va, memory);
+ }
}
///
/// Writes data to GPU mapped memory.
///
/// Type of the data
- /// GPU virtual address to write the value into
+ /// GPU virtual address to write the value into
/// The value to be written
- public void Write(ulong gpuVa, T value) where T : unmanaged
+ public void Write(ulong va, T value) where T : unmanaged
{
- ulong processVa = Translate(gpuVa);
-
- _context.PhysicalMemory.Write(processVa, MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1)));
+ Write(va, MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1)));
}
///
/// Writes data to GPU mapped memory.
///
- /// GPU virtual address to write the data into
+ /// GPU virtual address to write the data into
/// The data to be written
- public void Write(ulong gpuVa, ReadOnlySpan data)
+ public void Write(ulong va, ReadOnlySpan data)
{
- ulong processVa = Translate(gpuVa);
+ WriteImpl(va, data, _context.PhysicalMemory.Write);
+ }
- _context.PhysicalMemory.Write(processVa, data);
+ ///
+ /// Writes data to GPU mapped memory without write tracking.
+ ///
+ /// GPU virtual address to write the data into
+ /// The data to be written
+ public void WriteUntracked(ulong va, ReadOnlySpan data)
+ {
+ WriteImpl(va, data, _context.PhysicalMemory.WriteUntracked);
+ }
+
+ private delegate void WriteCallback(ulong address, ReadOnlySpan data);
+
+ ///
+ /// Writes data to possibly non-contiguous GPU mapped memory.
+ ///
+ /// GPU virtual address of the region to write into
+ /// Data to be written
+ /// Write callback
+ private void WriteImpl(ulong va, ReadOnlySpan 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));
+ }
+ }
}
///
@@ -147,42 +248,151 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
+ ///
+ /// Checks if a region of GPU mapped memory is contiguous.
+ ///
+ /// GPU virtual address of the region
+ /// Size of the region
+ /// True if the region is contiguous, false otherwise
+ [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;
+ }
+
+ ///
+ /// Gets the physical regions that make up the given virtual address region.
+ ///
+ /// Virtual address of the range
+ /// Size of the range
+ /// Multi-range with the physical regions
+ /// The memory region specified by and is not fully mapped
+ 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();
+
+ 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());
+ }
+
+ ///
+ /// Validates a GPU virtual address.
+ ///
+ /// Address to validate
+ /// True if the address is valid, false otherwise
+ private static bool ValidateAddress(ulong va)
+ {
+ return va < (1UL << AddressSpaceBits);
+ }
+
///
/// Checks if a given page is mapped.
///
- /// GPU virtual address of the page to check
+ /// GPU virtual address of the page to check
/// True if the page is mapped, false otherwise
- public bool IsMapped(ulong gpuVa)
+ public bool IsMapped(ulong va)
{
- return Translate(gpuVa) != PteUnmapped;
+ return Translate(va) != PteUnmapped;
}
///
/// Translates a GPU virtual address to a CPU virtual address.
///
- /// GPU virtual address to be translated
- /// CPU virtual address
- public ulong Translate(ulong gpuVa)
+ /// GPU virtual address to be translated
+ /// CPU virtual address, or if unmapped
+ 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);
}
///
/// Gets the Page Table entry for a given GPU virtual address.
///
- /// GPU virtual address
+ /// GPU virtual address
/// Page table entry (CPU virtual address)
- 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
///
/// Sets a Page Table entry at a given GPU virtual address.
///
- /// GPU virtual address
+ /// GPU virtual address
/// Page table entry (CPU virtual address)
- 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 de41fb9aba..8b2401c743 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;
@@ -38,6 +39,37 @@ namespace Ryujinx.Graphics.Gpu.Memory
return _cpuMemory.GetSpan(address, size, tracked);
}
+ ///
+ /// Gets a span of data from the application process.
+ ///
+ /// Ranges of physical memory where the data is located
+ /// True if read tracking is triggered on the span
+ /// A read only span of the data at the specified memory location
+ public ReadOnlySpan 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 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;
+ }
+ }
+
///
/// Gets a writable region from the application process.
///
@@ -70,6 +102,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
_cpuMemory.Write(address, data);
}
+ ///
+ /// Writes data to the application process.
+ ///
+ /// Ranges of physical memory where the data is located
+ /// Data to be written
+ public void Write(MultiRange range, ReadOnlySpan data)
+ {
+ WriteImpl(range, data, _cpuMemory.Write);
+ }
+
///
/// Writes data to the application process, without any tracking.
///
@@ -80,6 +122,45 @@ namespace Ryujinx.Graphics.Gpu.Memory
_cpuMemory.WriteUntracked(address, data);
}
+ ///
+ /// Writes data to the application process, without any tracking.
+ ///
+ /// Ranges of physical memory where the data is located
+ /// Data to be written
+ public void WriteUntracked(MultiRange range, ReadOnlySpan data)
+ {
+ WriteImpl(range, data, _cpuMemory.WriteUntracked);
+ }
+
+ private delegate void WriteCallback(ulong address, ReadOnlySpan data);
+
+ ///
+ /// Writes data to the application process, using the supplied callback method.
+ ///
+ /// Ranges of physical memory where the data is located
+ /// Data to be written
+ /// Callback method that will perform the write
+ private void WriteImpl(MultiRange range, ReadOnlySpan 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;
+ }
+ }
+ }
+
///
/// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with.
///
@@ -91,6 +172,24 @@ namespace Ryujinx.Graphics.Gpu.Memory
return _cpuMemory.BeginTracking(address, size);
}
+ ///
+ /// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with.
+ ///
+ /// Ranges of physical memory where the data is located
+ /// The memory tracking handle
+ 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);
+ }
+
///
/// Obtains a memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with.
///
diff --git a/Ryujinx.Graphics.Gpu/Window.cs b/Ryujinx.Graphics.Gpu/Window.cs
index 9d26935695..ad70adfd04 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;
@@ -25,6 +27,11 @@ namespace Ryujinx.Graphics.Gpu
///
public TextureInfo Info { get; }
+ ///
+ /// Physical memory locations where the texture data is located.
+ ///
+ public MultiRange Range { get; }
+
///
/// Texture crop region.
///
@@ -49,18 +56,21 @@ namespace Ryujinx.Graphics.Gpu
/// Creates a new instance of the presentation texture.
///
/// Information of the texture to be presented
+ /// Physical memory locations where the texture data is located
/// Texture crop region
/// Texture acquire callback
/// Texture release callback
/// User defined object passed to the release callback, can be used to identify the texture
public PresentationTexture(
TextureInfo info,
+ MultiRange range,
ImageCrop crop,
Action acquireCallback,
Action