aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorriperiperi <rhy3756547@hotmail.com>2021-03-02 22:30:54 +0000
committerGitHub <noreply@github.com>2021-03-02 19:30:54 -0300
commitb530f0e1104723b894695b2860cf0f568f24cc9a (patch)
tree04f6b7d76d334ddc5c0378f6aa00b98739bc793e
parent7a90abc03555f41ba7589bc8e1f714839f9e2fed (diff)
Texture Cache: "Texture Groups" and "Texture Dependencies" (#2001)
* Initial implementation (3d tex mips broken) This works rather well for most games, just need to fix 3d texture mips. * Cleanup * Address feedback * Copy Dependencies and various other fixes * Fix layer/level offset for copy from view<->view. * Remove dirty flag from dependency The dirty flag behaviour is not needed - DeferredCopy is all we need. * Fix tracking mip slices. * Propagate granularity (fix astral chain) * Address Feedback pt 1 * Save slice sizes as part of SizeInfo * Fix nits * Fix disposing multiple dependencies causing a crash This list is obviously modified when removing dependencies, so create a copy of it.
-rw-r--r--Ryujinx.Cpu/Tracking/CpuRegionHandle.cs6
-rw-r--r--Ryujinx.Graphics.GAL/ITexture.cs2
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/Methods.cs10
-rw-r--r--Ryujinx.Graphics.Gpu/Image/Texture.cs289
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs41
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureDependency.cs37
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureGroup.cs971
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs327
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureInfo.cs25
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureManager.cs195
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs4
-rw-r--r--Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs10
-rw-r--r--Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs41
-rw-r--r--Ryujinx.Graphics.OpenGL/Image/TextureView.cs65
-rw-r--r--Ryujinx.Graphics.Texture/SizeCalculator.cs29
-rw-r--r--Ryujinx.Graphics.Texture/SizeInfo.cs43
-rw-r--r--Ryujinx.Memory/Tracking/IRegionHandle.cs2
-rw-r--r--Ryujinx.Memory/Tracking/RegionHandle.cs30
18 files changed, 1911 insertions, 216 deletions
diff --git a/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs b/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs
index 9dbdbfcb..f4391aad 100644
--- a/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs
+++ b/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs
@@ -1,4 +1,5 @@
using Ryujinx.Memory.Tracking;
+using System;
namespace Ryujinx.Cpu.Tracking
{
@@ -18,6 +19,9 @@ namespace Ryujinx.Cpu.Tracking
public void Dispose() => _impl.Dispose();
public void RegisterAction(RegionSignal action) => _impl.RegisterAction(action);
- public void Reprotect() => _impl.Reprotect();
+ public void RegisterDirtyEvent(Action action) => _impl.RegisterDirtyEvent(action);
+ public void Reprotect(bool asDirty = false) => _impl.Reprotect(asDirty);
+
+ public bool OverlapsWith(ulong address, ulong size) => _impl.OverlapsWith(address, size);
}
}
diff --git a/Ryujinx.Graphics.GAL/ITexture.cs b/Ryujinx.Graphics.GAL/ITexture.cs
index 543f9de0..ad8fd297 100644
--- a/Ryujinx.Graphics.GAL/ITexture.cs
+++ b/Ryujinx.Graphics.GAL/ITexture.cs
@@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.GAL
float ScaleFactor { get; }
void CopyTo(ITexture destination, int firstLayer, int firstLevel);
+ void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel);
void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter);
ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel);
@@ -16,6 +17,7 @@ namespace Ryujinx.Graphics.GAL
byte[] GetData();
void SetData(ReadOnlySpan<byte> data);
+ void SetData(ReadOnlySpan<byte> data, int layer, int level);
void SetStorage(BufferRange buffer);
void Release();
}
diff --git a/Ryujinx.Graphics.Gpu/Engine/Methods.cs b/Ryujinx.Graphics.Gpu/Engine/Methods.cs
index a41fd541..0731f1c2 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Methods.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Methods.cs
@@ -377,11 +377,6 @@ namespace Ryujinx.Graphics.Gpu.Engine
Texture color = TextureManager.FindOrCreateTexture(colorState, samplesInX, samplesInY, sizeHint);
changedScale |= TextureManager.SetRenderTargetColor(index, color);
-
- if (color != null)
- {
- color.SignalModified();
- }
}
bool dsEnable = state.Get<Boolean32>(MethodOffset.RtDepthStencilEnable);
@@ -406,11 +401,6 @@ namespace Ryujinx.Graphics.Gpu.Engine
UpdateViewportTransform(state);
UpdateScissorState(state);
}
-
- if (depthStencil != null)
- {
- depthStencil.SignalModified();
- }
}
/// <summary>
diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs
index 6dfe4628..4d4091cb 100644
--- a/Ryujinx.Graphics.Gpu/Image/Texture.cs
+++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -51,6 +51,11 @@ namespace Ryujinx.Graphics.Gpu.Image
public TextureScaleMode ScaleMode { get; private set; }
/// <summary>
+ /// Group that this texture belongs to. Manages read/write memory tracking.
+ /// </summary>
+ public TextureGroup Group { get; private set; }
+
+ /// <summary>
/// Set when a texture has been modified by the Host GPU since it was last flushed.
/// </summary>
public bool IsModified { get; internal set; }
@@ -63,10 +68,11 @@ namespace Ryujinx.Graphics.Gpu.Image
private int _depth;
private int _layers;
- private int _firstLayer;
- private int _firstLevel;
+ public int FirstLayer { get; private set; }
+ public int FirstLevel { get; private set; }
private bool _hasData;
+ private bool _dirty = true;
private int _updateCount;
private byte[] _currentData;
@@ -100,11 +106,19 @@ namespace Ryujinx.Graphics.Gpu.Image
public MultiRange Range { get; private set; }
/// <summary>
+ /// Layer size in bytes.
+ /// </summary>
+ public int LayerSize => _sizeInfo.LayerSize;
+
+ /// <summary>
/// Texture size in bytes.
/// </summary>
public ulong Size => (ulong)_sizeInfo.TotalSize;
- private GpuRegionHandle _memoryTracking;
+ /// <summary>
+ /// Whether or not the texture belongs is a view.
+ /// </summary>
+ public bool IsView => _viewStorage != this;
private int _referenceCount;
@@ -131,8 +145,8 @@ namespace Ryujinx.Graphics.Gpu.Image
{
InitializeTexture(context, info, sizeInfo, range);
- _firstLayer = firstLayer;
- _firstLevel = firstLevel;
+ FirstLayer = firstLayer;
+ FirstLevel = firstLevel;
ScaleFactor = scaleFactor;
ScaleMode = scaleMode;
@@ -186,8 +200,6 @@ 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(Range);
-
if (withData)
{
Debug.Assert(!isView);
@@ -203,12 +215,13 @@ namespace Ryujinx.Graphics.Gpu.Image
}
else
{
- // Don't update this texture the next time we synchronize.
- ConsumeModified();
_hasData = true;
if (!isView)
{
+ // Don't update this texture the next time we synchronize.
+ ConsumeModified();
+
if (ScaleMode == TextureScaleMode.Scaled)
{
// Don't need to start at 1x as there is no data to scale, just go straight to the target scale.
@@ -222,6 +235,18 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
+ /// Initialize a new texture group with this texture as storage.
+ /// </summary>
+ /// <param name="hasLayerViews">True if the texture will have layer views</param>
+ /// <param name="hasMipViews">True if the texture will have mip views</param>
+ public void InitializeGroup(bool hasLayerViews, bool hasMipViews)
+ {
+ Group = new TextureGroup(_context, this);
+
+ Group.Initialize(ref _sizeInfo, hasLayerViews, hasMipViews);
+ }
+
+ /// <summary>
/// Create a texture view from this texture.
/// A texture view is defined as a child texture, from a sub-range of their parent texture.
/// For example, the initial layer and mipmap level of the view can be defined, so the texture
@@ -240,8 +265,8 @@ namespace Ryujinx.Graphics.Gpu.Image
info,
sizeInfo,
range,
- _firstLayer + firstLayer,
- _firstLevel + firstLevel,
+ FirstLayer + firstLayer,
+ FirstLevel + firstLevel,
ScaleFactor,
ScaleMode);
@@ -259,11 +284,26 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="texture">The child texture</param>
private void AddView(Texture texture)
{
- DisableMemoryTracking();
+ IncrementReferenceCount();
_views.Add(texture);
texture._viewStorage = this;
+
+ Group.UpdateViews(_views);
+
+ if (texture.Group != null && texture.Group != Group)
+ {
+ if (texture.Group.Storage == texture)
+ {
+ // This texture's group is no longer used.
+ Group.Inherit(texture.Group);
+
+ texture.Group.Dispose();
+ }
+ }
+
+ texture.Group = Group;
}
/// <summary>
@@ -276,7 +316,27 @@ namespace Ryujinx.Graphics.Gpu.Image
texture._viewStorage = texture;
- DeleteIfNotUsed();
+ DecrementReferenceCount();
+ }
+
+ /// <summary>
+ /// Create a copy dependency to a texture that is view compatible with this one.
+ /// When either texture is modified, the texture data will be copied to the other to keep them in sync.
+ /// This is essentially an emulated view, useful for handling multiple view parents or format incompatibility.
+ /// This also forces a copy on creation, to or from the given texture to get them in sync immediately.
+ /// </summary>
+ /// <param name="contained">The view compatible texture to create a dependency to</param>
+ /// <param name="layer">The base layer of the given texture relative to this one</param>
+ /// <param name="level">The base level of the given texture relative to this one</param>
+ /// <param name="copyTo">True if this texture is first copied to the given one, false for the opposite direction</param>
+ public void CreateCopyDependency(Texture contained, int layer, int level, bool copyTo)
+ {
+ if (contained.Group == Group)
+ {
+ return;
+ }
+
+ Group.CreateCopyDependency(contained, FirstLayer + layer, FirstLevel + level, copyTo);
}
/// <summary>
@@ -294,12 +354,12 @@ namespace Ryujinx.Graphics.Gpu.Image
int blockWidth = Info.FormatInfo.BlockWidth;
int blockHeight = Info.FormatInfo.BlockHeight;
- width <<= _firstLevel;
- height <<= _firstLevel;
+ width <<= FirstLevel;
+ height <<= FirstLevel;
if (Target == Target.Texture3D)
{
- depthOrLayers <<= _firstLevel;
+ depthOrLayers <<= FirstLevel;
}
else
{
@@ -310,14 +370,14 @@ namespace Ryujinx.Graphics.Gpu.Image
foreach (Texture view in _viewStorage._views)
{
- int viewWidth = Math.Max(1, width >> view._firstLevel);
- int viewHeight = Math.Max(1, height >> view._firstLevel);
+ int viewWidth = Math.Max(1, width >> view.FirstLevel);
+ int viewHeight = Math.Max(1, height >> view.FirstLevel);
int viewDepthOrLayers;
if (view.Info.Target == Target.Texture3D)
{
- viewDepthOrLayers = Math.Max(1, depthOrLayers >> view._firstLevel);
+ viewDepthOrLayers = Math.Max(1, depthOrLayers >> view.FirstLevel);
}
else
{
@@ -329,16 +389,6 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
- /// Disables memory tracking on this texture. Currently used for view containers, as we assume their views are covering all memory regions.
- /// Textures with disabled memory tracking also cannot flush in most circumstances.
- /// </summary>
- public void DisableMemoryTracking()
- {
- _memoryTracking?.Dispose();
- _memoryTracking = null;
- }
-
- /// <summary>
/// Recreates the texture storage (or view, in the case of child textures) of this texture.
/// This allows recreating the texture with a new size.
/// A copy is automatically performed from the old to the new texture.
@@ -393,7 +443,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (_viewStorage != this)
{
- ReplaceStorage(_viewStorage.HostTexture.CreateView(createInfo, _firstLayer, _firstLevel));
+ ReplaceStorage(_viewStorage.HostTexture.CreateView(createInfo, FirstLayer, FirstLevel));
}
else
{
@@ -495,7 +545,7 @@ namespace Ryujinx.Graphics.Gpu.Image
view.ScaleFactor = scale;
TextureCreateInfo viewCreateInfo = TextureManager.GetCreateInfo(view.Info, _context.Capabilities, scale);
- ITexture newView = HostTexture.CreateView(viewCreateInfo, view._firstLayer - _firstLayer, view._firstLevel - _firstLevel);
+ ITexture newView = HostTexture.CreateView(viewCreateInfo, view.FirstLayer - FirstLayer, view.FirstLevel - FirstLevel);
view.ReplaceStorage(newView);
view.ScaleMode = newScaleMode;
@@ -517,17 +567,10 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Checks if the memory for this texture was modified, and returns true if it was.
/// The modified flags are consumed as a result.
/// </summary>
- /// <remarks>
- /// If there is no memory tracking for this texture, it will always report as modified.
- /// </remarks>
/// <returns>True if the texture was modified, false otherwise.</returns>
public bool ConsumeModified()
{
- bool wasDirty = _memoryTracking?.Dirty ?? true;
-
- _memoryTracking?.Reprotect();
-
- return wasDirty;
+ return Group.ConsumeDirty(this);
}
/// <summary>
@@ -544,18 +587,43 @@ namespace Ryujinx.Graphics.Gpu.Image
return;
}
+ if (!_dirty)
+ {
+ return;
+ }
+
+ _dirty = false;
+
if (_hasData)
{
- if (_memoryTracking?.Dirty != true)
- {
- return;
- }
+ Group.SynchronizeMemory(this);
+ }
+ else
+ {
+ Group.ConsumeDirty(this);
+ SynchronizeFull();
+ }
+ }
+ /// <summary>
+ /// Signal that this texture is dirty, indicating that the texture group must be checked.
+ /// </summary>
+ public void SignalGroupDirty()
+ {
+ _dirty = true;
+ }
+
+ /// <summary>
+ /// Fully synchronizes guest and host memory.
+ /// This will replace the entire texture with the data present in guest memory.
+ /// </summary>
+ public void SynchronizeFull()
+ {
+ if (_hasData)
+ {
BlacklistScale();
}
- _memoryTracking?.Reprotect();
-
ReadOnlySpan<byte> data = _context.PhysicalMemory.GetSpan(Range);
IsModified = false;
@@ -596,7 +664,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
BlacklistScale();
- _memoryTracking?.Reprotect();
+ Group.ConsumeDirty(this);
IsModified = false;
@@ -606,17 +674,45 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
+ /// Uploads new texture data to the host GPU for a specific layer/level.
+ /// </summary>
+ /// <param name="data">New data</param>
+ /// <param name="layer">Target layer</param>
+ /// <param name="level">Target level</param>
+ public void SetData(ReadOnlySpan<byte> data, int layer, int level)
+ {
+ BlacklistScale();
+
+ HostTexture.SetData(data, layer, level);
+
+ _currentData = null;
+
+ _hasData = true;
+ }
+
+ /// <summary>
/// Converts texture data to a format and layout that is supported by the host GPU.
/// </summary>
/// <param name="data">Data to be converted</param>
/// <returns>Converted data</returns>
- private ReadOnlySpan<byte> ConvertToHostCompatibleFormat(ReadOnlySpan<byte> data)
+ public ReadOnlySpan<byte> ConvertToHostCompatibleFormat(ReadOnlySpan<byte> data, int level = 0, bool single = false)
{
+ int width = Info.Width;
+ int height = Info.Height;
+
+ int depth = single ? 1 : _depth;
+ int layers = single ? 1 : _layers;
+ int levels = single ? 1 : Info.Levels;
+
+ width = Math.Max(width >> level, 1);
+ height = Math.Max(height >> level, 1);
+ depth = Math.Max(depth >> level, 1);
+
if (Info.IsLinear)
{
data = LayoutConverter.ConvertLinearStridedToLinear(
- Info.Width,
- Info.Height,
+ width,
+ height,
Info.FormatInfo.BlockWidth,
Info.FormatInfo.BlockHeight,
Info.Stride,
@@ -626,11 +722,11 @@ namespace Ryujinx.Graphics.Gpu.Image
else
{
data = LayoutConverter.ConvertBlockLinearToLinear(
- Info.Width,
- Info.Height,
- _depth,
- Info.Levels,
- _layers,
+ width,
+ height,
+ depth,
+ levels,
+ layers,
Info.FormatInfo.BlockWidth,
Info.FormatInfo.BlockHeight,
Info.FormatInfo.BytesPerPixel,
@@ -650,11 +746,11 @@ namespace Ryujinx.Graphics.Gpu.Image
data.ToArray(),
Info.FormatInfo.BlockWidth,
Info.FormatInfo.BlockHeight,
- Info.Width,
- Info.Height,
- _depth,
- Info.Levels,
- _layers,
+ width,
+ height,
+ depth,
+ levels,
+ layers,
out Span<byte> decoded))
{
string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}";
@@ -666,11 +762,11 @@ namespace Ryujinx.Graphics.Gpu.Image
}
else if (Target == Target.Texture3D && Info.FormatInfo.Format.IsBc4())
{
- data = BCnDecoder.DecodeBC4(data, Info.Width, Info.Height, _depth, Info.Levels, _layers, Info.FormatInfo.Format == Format.Bc4Snorm);
+ data = BCnDecoder.DecodeBC4(data, width, height, depth, levels, layers, Info.FormatInfo.Format == Format.Bc4Snorm);
}
else if (Target == Target.Texture3D && Info.FormatInfo.Format.IsBc5())
{
- data = BCnDecoder.DecodeBC5(data, Info.Width, Info.Height, _depth, Info.Levels, _layers, Info.FormatInfo.Format == Format.Bc5Snorm);
+ data = BCnDecoder.DecodeBC5(data, width, height, depth, levels, layers, Info.FormatInfo.Format == Format.Bc5Snorm);
}
return data;
@@ -710,7 +806,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public void ExternalFlush(ulong address, ulong size)
{
- if (!IsModified || _memoryTracking == null)
+ if (!IsModified)
{
return;
}
@@ -869,7 +965,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <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, MultiRange range, out int firstLayer, out int firstLevel)
+ public TextureViewCompatibility IsViewCompatible(TextureInfo info, MultiRange range, int layerSize, out int firstLayer, out int firstLevel)
{
int offset = Range.FindOffset(range);
@@ -892,15 +988,17 @@ namespace Ryujinx.Graphics.Gpu.Image
return TextureViewCompatibility.Incompatible;
}
- if (!TextureCompatibility.ViewFormatCompatible(Info, info))
+ if (info.GetSlices() > 1 && LayerSize != layerSize)
{
return TextureViewCompatibility.Incompatible;
}
TextureViewCompatibility result = TextureViewCompatibility.Full;
+ result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewFormatCompatible(Info, info));
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewSizeMatches(Info, info, firstLevel));
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info));
+ result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewSubImagesInBounds(Info, info, firstLayer, firstLevel));
return (Info.SamplesInX == info.SamplesInX &&
Info.SamplesInY == info.SamplesInY) ? result : TextureViewCompatibility.Incompatible;
@@ -1003,14 +1101,37 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="firstLevel">The first level of the view</param>
public void ReplaceView(Texture parent, TextureInfo info, ITexture hostTexture, int firstLayer, int firstLevel)
{
+ IncrementReferenceCount();
parent._viewStorage.SynchronizeMemory();
+
+ // If this texture has views, they must be given to the new parent.
+ if (_views.Count > 0)
+ {
+ Texture[] viewCopy = _views.ToArray();
+
+ foreach (Texture view in viewCopy)
+ {
+ TextureCreateInfo createInfo = TextureManager.GetCreateInfo(view.Info, _context.Capabilities, ScaleFactor);
+
+ ITexture newView = parent.HostTexture.CreateView(createInfo, view.FirstLayer + firstLayer, view.FirstLevel + firstLevel);
+
+ view.ReplaceView(parent, view.Info, newView, view.FirstLayer + firstLayer, view.FirstLevel + firstLevel);
+ }
+ }
+
ReplaceStorage(hostTexture);
- _firstLayer = parent._firstLayer + firstLayer;
- _firstLevel = parent._firstLevel + firstLevel;
+ if (_viewStorage != this)
+ {
+ _viewStorage.RemoveView(this);
+ }
+
+ FirstLayer = parent.FirstLayer + firstLayer;
+ FirstLevel = parent.FirstLevel + firstLevel;
parent._viewStorage.AddView(this);
SetInfo(info);
+ DecrementReferenceCount();
}
/// <summary>
@@ -1031,14 +1152,28 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public void SignalModified()
{
- IsModified = true;
-
- if (_viewStorage != this)
+ bool wasModified = IsModified;
+ if (!wasModified || Group.HasCopyDependencies)
{
- _viewStorage.SignalModified();
+ IsModified = true;
+ Group.SignalModified(this, !wasModified);
}
+ }
+
+ /// <summary>
+ /// Signals that a texture has been bound, or has been unbound.
+ /// During this time, lazy copies will not clear the dirty flag.
+ /// </summary>
+ /// <param name="bound">True if the texture has been bound, false if it has been unbound</param>
+ public void SignalModifying(bool bound)
+ {
+ bool wasModified = IsModified;
- _memoryTracking?.RegisterAction(ExternalFlush);
+ if (!wasModified || Group.HasCopyDependencies)
+ {
+ IsModified = true;
+ Group.SignalModifying(this, bound, !wasModified);
+ }
}
/// <summary>
@@ -1066,7 +1201,7 @@ namespace Ryujinx.Graphics.Gpu.Image
foreach (Texture view in _views)
{
- if (texture.IsViewCompatible(view.Info, view.Range, out _, out _) != TextureViewCompatibility.Incompatible)
+ if (texture.IsViewCompatible(view.Info, view.Range, view.LayerSize, out _, out _) != TextureViewCompatibility.Incompatible)
{
return true;
}
@@ -1148,10 +1283,6 @@ namespace Ryujinx.Graphics.Gpu.Image
public void Unmapped()
{
IsModified = false; // We shouldn't flush this texture, as its memory is no longer mapped.
-
- var tracking = _memoryTracking;
- tracking?.Reprotect();
- tracking?.RegisterAction(null);
}
/// <summary>
@@ -1162,7 +1293,11 @@ namespace Ryujinx.Graphics.Gpu.Image
DisposeTextures();
Disposed?.Invoke(this);
- _memoryTracking?.Dispose();
+
+ if (Group.Storage == this)
+ {
+ Group.Dispose();
+ }
}
}
} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
index e3574be5..d613612f 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
@@ -215,7 +215,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="lhs">Texture information of the texture view</param>
/// <param name="rhs">Texture information of the texture view to match against</param>
/// <param name="level">Mipmap level of the texture view in relation to this texture</param>
- /// <returns>True if the sizes are compatible, false otherwise</returns>
+ /// <returns>The view compatibility level of the view sizes</returns>
public static TextureViewCompatibility ViewSizeMatches(TextureInfo lhs, TextureInfo rhs, int level)
{
Size size = GetAlignedSize(lhs, level);
@@ -236,6 +236,27 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
+ /// Checks if the potential child texture fits within the level and layer bounds of the parent.
+ /// </summary>
+ /// <param name="parent">Texture information for the parent</param>
+ /// <param name="child">Texture information for the child</param>
+ /// <param name="layer">Base layer of the child texture</param>
+ /// <param name="level">Base level of the child texture</param>
+ /// <returns>Full compatiblity if the child's layer and level count fit within the parent, incompatible otherwise</returns>
+ public static TextureViewCompatibility ViewSubImagesInBounds(TextureInfo parent, TextureInfo child, int layer, int level)
+ {
+ if (level + child.Levels <= parent.Levels &&
+ layer + child.GetSlices() <= parent.GetSlices())
+ {
+ return TextureViewCompatibility.Full;
+ }
+ else
+ {
+ return TextureViewCompatibility.Incompatible;
+ }
+ }
+
+ /// <summary>
/// Checks if the texture sizes of the supplied texture informations match.
/// </summary>
/// <param name="lhs">Texture information to compare</param>
@@ -382,10 +403,22 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
/// <param name="lhs">Texture information of the texture view</param>
/// <param name="rhs">Texture information of the texture view</param>
- /// <returns>True if the formats are compatible, false otherwise</returns>
- public static bool ViewFormatCompatible(TextureInfo lhs, TextureInfo rhs)
+ /// <returns>The view compatibility level of the texture formats</returns>
+ public static TextureViewCompatibility ViewFormatCompatible(TextureInfo lhs, TextureInfo rhs)
{
- return FormatCompatible(lhs.FormatInfo, rhs.FormatInfo);
+ if (FormatCompatible(lhs.FormatInfo, rhs.FormatInfo))
+ {
+ if (lhs.FormatInfo.IsCompressed != rhs.FormatInfo.IsCompressed)
+ {
+ return TextureViewCompatibility.CopyOnly;
+ }
+ else
+ {
+ return TextureViewCompatibility.Full;
+ }
+ }
+
+ return TextureViewCompatibility.Incompatible;
}
/// <summary>
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureDependency.cs b/Ryujinx.Graphics.Gpu/Image/TextureDependency.cs
new file mode 100644
index 00000000..269ddbd9
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureDependency.cs
@@ -0,0 +1,37 @@
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ /// <summary>
+ /// One side of a two-way dependency between one texture view and another.
+ /// Contains a reference to the handle owning the dependency, and the other dependency.
+ /// </summary>
+ class TextureDependency
+ {
+ /// <summary>
+ /// The handle that owns this dependency.
+ /// </summary>
+ public TextureGroupHandle Handle;
+
+ /// <summary>
+ /// The other dependency linked to this one, which belongs to another handle.
+ /// </summary>
+ public TextureDependency Other;
+
+ /// <summary>
+ /// Create a new texture dependency.
+ /// </summary>
+ /// <param name="handle">The handle that owns the dependency</param>
+ public TextureDependency(TextureGroupHandle handle)
+ {
+ Handle = handle;
+ }
+
+ /// <summary>
+ /// Signal that the owner of this dependency has been modified,
+ /// meaning that the other dependency's handle must defer a copy from it.
+ /// </summary>
+ public void SignalModified()
+ {
+ Other.Handle.DeferCopy(Handle);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
new file mode 100644
index 00000000..5d150559
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
@@ -0,0 +1,971 @@
+using Ryujinx.Common;
+using Ryujinx.Cpu.Tracking;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Texture;
+using Ryujinx.Memory.Range;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ /// <summary>
+ /// A texture group represents a group of textures that belong to the same storage.
+ /// When views are created, this class will track memory accesses for them separately.
+ /// The group iteratively adds more granular tracking as views of different kinds are added.
+ /// Note that a texture group can be absorbed into another when it becomes a view parent.
+ /// </summary>
+ class TextureGroup : IDisposable
+ {
+ private const int StrideAlignment = 32;
+ private const int GobAlignment = 64;
+
+ private delegate void HandlesCallbackDelegate(int baseHandle, int regionCount, bool split = false);
+
+ /// <summary>
+ /// The storage texture associated with this group.
+ /// </summary>
+ public Texture Storage { get; }
+
+ /// <summary>
+ /// Indicates if the texture has copy dependencies. If true, then all modifications
+ /// must be signalled to the group, rather than skipping ones still to be flushed.
+ /// </summary>
+ public bool HasCopyDependencies { get; set; }
+
+ private GpuContext _context;
+
+ private int[] _allOffsets;
+ private int[] _sliceSizes;
+ private bool _is3D;
+ private bool _hasMipViews;
+ private bool _hasLayerViews;
+ private int _layers;
+ private int _levels;
+
+ private MultiRange TextureRange => Storage.Range;
+
+ /// <summary>
+ /// The views list from the storage texture.
+ /// </summary>
+ private List<Texture> _views;
+ private TextureGroupHandle[] _handles;
+ private bool[] _loadNeeded;
+
+ /// <summary>
+ /// Create a new texture group.
+ /// </summary>
+ /// <param name="context">GPU context that the texture group belongs to</param>
+ /// <param name="storage">The storage texture for this group</param>
+ public TextureGroup(GpuContext context, Texture storage)
+ {
+ Storage = storage;
+ _context = context;
+
+ _is3D = storage.Info.Target == Target.Texture3D;
+ _layers = storage.Info.GetSlices();
+ _levels = storage.Info.Levels;
+ }
+
+ /// <summary>
+ /// Initialize a new texture group's dirty regions and offsets.
+ /// </summary>
+ /// <param name="size">Size info for the storage texture</param>
+ /// <param name="hasLayerViews">True if the storage will have layer views</param>
+ /// <param name="hasMipViews">True if the storage will have mip views</param>
+ public void Initialize(ref SizeInfo size, bool hasLayerViews, bool hasMipViews)
+ {
+ _allOffsets = size.AllOffsets;
+ _sliceSizes = size.SliceSizes;
+
+ (_hasLayerViews, _hasMipViews) = PropagateGranularity(hasLayerViews, hasMipViews);
+
+ RecalculateHandleRegions();
+ }
+
+ /// <summary>
+ /// Consume the dirty flags for a given texture. The state is shared between views of the same layers and levels.
+ /// </summary>
+ /// <param name="texture">The texture being used</param>
+ /// <returns>True if a flag was dirty, false otherwise</returns>
+ public bool ConsumeDirty(Texture texture)
+ {
+ bool dirty = false;
+
+ EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
+ {
+ for (int i = 0; i < regionCount; i++)
+ {
+ TextureGroupHandle group = _handles[baseHandle + i];
+
+ foreach (CpuRegionHandle handle in group.Handles)
+ {
+ if (handle.Dirty)
+ {
+ handle.Reprotect();
+ dirty = true;
+ }
+ }
+ }
+ });
+
+ return dirty;
+ }
+
+ /// <summary>
+ /// Synchronize memory for a given texture.
+ /// If overlapping tracking handles are dirty, fully or partially synchronize the texture data.
+ /// </summary>
+ /// <param name="texture">The texture being used</param>
+ public void SynchronizeMemory(Texture texture)
+ {
+ EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
+ {
+ bool dirty = false;
+ bool anyModified = false;
+
+ for (int i = 0; i < regionCount; i++)
+ {
+ TextureGroupHandle group = _handles[baseHandle + i];
+
+ bool modified = group.Modified;
+ bool handleDirty = false;
+ bool handleModified = false;
+
+ foreach (CpuRegionHandle handle in group.Handles)
+ {
+ if (handle.Dirty)
+ {
+ handle.Reprotect();
+ handleDirty = true;
+ }
+ else
+ {
+ handleModified |= modified;
+ }
+ }
+
+ // Evaluate if any copy dependencies need to be fulfilled. A few rules:
+ // If the copy handle needs to be synchronized, prefer our own state.
+ // If we need to be synchronized and there is a copy present, prefer the copy.
+
+ if (group.NeedsCopy && group.Copy())
+ {
+ anyModified |= true; // The copy target has been modified.
+ handleDirty = false;
+ }
+ else
+ {
+ anyModified |= handleModified;
+ dirty |= handleDirty;
+ }
+
+ if (group.NeedsCopy)
+ {
+ // The texture we copied from is still being written to. Copy from it again the next time this texture is used.
+ texture.SignalGroupDirty();
+ }
+
+ _loadNeeded[baseHandle + i] = handleDirty;
+ }
+
+ if (dirty)
+ {
+ if (_handles.Length > 1 && (anyModified || split))
+ {
+ // Partial texture invalidation. Only update the layers/levels with dirty flags of the storage.
+
+ SynchronizePartial(baseHandle, regionCount);
+ }
+ else
+ {
+ // Full texture invalidation.
+
+ texture.SynchronizeFull();
+ }
+ }
+ });
+ }
+
+ /// <summary>
+ /// Synchronize part of the storage texture, represented by a given range of handles.
+ /// Only handles marked by the _loadNeeded array will be synchronized.
+ /// </summary>
+ /// <param name="baseHandle">The base index of the range of handles</param>
+ /// <param name="regionCount">The number of handles to synchronize</param>
+ private void SynchronizePartial(int baseHandle, int regionCount)
+ {
+ ReadOnlySpan<byte> fullData = _context.PhysicalMemory.GetSpan(Storage.Range);
+
+ for (int i = 0; i < regionCount; i++)
+ {
+ if (_loadNeeded[baseHandle + i])
+ {
+ var info = GetHandleInformation(baseHandle + i);
+ int offsetIndex = info.Index;
+
+ // Only one of these will be greater than 1, as partial sync is only called when there are sub-image views.
+ for (int layer = 0; layer < info.Layers; layer++)
+ {
+ for (int level = 0; level < info.Levels; level++)
+ {
+ int offset = _allOffsets[offsetIndex];
+ int endOffset = (offsetIndex + 1 == _allOffsets.Length) ? (int)Storage.Size : _allOffsets[offsetIndex + 1];
+ int size = endOffset - offset;
+
+ ReadOnlySpan<byte> data = fullData.Slice(offset, size);
+
+ data = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel, true);
+
+ Storage.SetData(data, info.BaseLayer, info.BaseLevel);
+
+ offsetIndex++;
+ }
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Signal that a texture in the group has been modified by the GPU.
+ /// </summary>
+ /// <param name="texture">The texture that has been modified</param>
+ /// <param name="registerAction">True if the flushing read action should be registered, false otherwise</param>
+ public void SignalModified(Texture texture, bool registerAction)
+ {
+ EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
+ {
+ for (int i = 0; i < regionCount; i++)
+ {
+ TextureGroupHandle group = _handles[baseHandle + i];
+
+ group.SignalModified();
+
+ if (registerAction)
+ {
+ RegisterAction(group);
+ }
+ }
+ });
+ }
+
+ /// <summary>
+ /// Signal that a texture in the group is actively bound, or has been unbound by the GPU.
+ /// </summary>
+ /// <param name="texture">The texture that has been modified</param>
+ /// <param name="bound">True if this texture is being bound, false if unbound</param>
+ /// <param name="registerAction">True if the flushing read action should be registered, false otherwise</param>
+ public void SignalModifying(Texture texture, bool bound, bool registerAction)
+ {
+ EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
+ {
+ for (int i = 0; i < regionCount; i++)
+ {
+ TextureGroupHandle group = _handles[baseHandle + i];
+
+ group.SignalModifying(bound);
+
+ if (registerAction)
+ {
+ RegisterAction(group);
+ }
+ }
+ });
+ }
+
+ /// <summary>
+ /// Register a read/write action to flush for a texture group.
+ /// </summary>
+ /// <param name="group">The group to register an action for</param>
+ public void RegisterAction(TextureGroupHandle group)
+ {
+ foreach (CpuRegionHandle handle in group.Handles)
+ {
+ handle.RegisterAction((address, size) => FlushAction(group, address, size));
+ }
+ }
+
+ /// <summary>
+ /// Propagates the mip/layer view flags depending on the texture type.
+ /// When the most granular type of subresource has views, the other type of subresource must be segmented granularly too.
+ /// </summary>
+ /// <param name="hasLayerViews">True if the storage has layer views</param>
+ /// <param name="hasMipViews">True if the storage has mip views</param>
+ /// <returns>The input values after propagation</returns>
+ private (bool HasLayerViews, bool HasMipViews) PropagateGranularity(bool hasLayerViews, bool hasMipViews)
+ {
+ if (_is3D)
+ {
+ hasMipViews |= hasLayerViews;
+ }
+ else
+ {
+ hasLayerViews |= hasMipViews;
+ }
+
+ return (hasLayerViews, hasMipViews);
+ }
+
+ /// <summary>
+ /// Evaluate the range of tracking handles which a view texture overlaps with.
+ /// </summary>
+ /// <param name="texture">The texture to get handles for</param>
+ /// <param name="callback">
+ /// A function to be called with the base index of the range of handles for the given texture, and the number of handles it covers.
+ /// This can be called for multiple disjoint ranges, if required.
+ /// </param>
+ private void EvaluateRelevantHandles(Texture texture, HandlesCallbackDelegate callback)
+ {
+ if (texture == Storage || !(_hasMipViews || _hasLayerViews))
+ {
+ callback(0, _handles.Length);
+
+ return;
+ }
+
+ EvaluateRelevantHandles(texture.FirstLayer, texture.FirstLevel, texture.Info.GetSlices(), texture.Info.Levels, callback);
+ }
+
+ /// <summary>
+ /// Evaluate the range of tracking handles which a view texture overlaps with,
+ /// using the view's position and slice/level counts.
+ /// </summary>
+ /// <param name="firstLayer">The first layer of the texture</param>
+ /// <param name="firstLevel">The first level of the texture</param>
+ /// <param name="slices">The slice count of the texture</param>
+ /// <param name="levels">The level count of the texture</param>
+ /// <param name="callback">
+ /// A function to be called with the base index of the range of handles for the given texture, and the number of handles it covers.
+ /// This can be called for multiple disjoint ranges, if required.
+ /// </param>
+ private void EvaluateRelevantHandles(int firstLayer, int firstLevel, int slices, int levels, HandlesCallbackDelegate callback)
+ {
+ int targetLayerHandles = _hasLayerViews ? slices : 1;
+ int targetLevelHandles = _hasMipViews ? levels : 1;
+
+ if (_is3D)
+ {
+ // Future mip levels come after all layers of the last mip level. Each mipmap has less layers (depth) than the last.
+
+ if (!_hasLayerViews)
+ {
+ // When there are no layer views, the mips are at a consistent offset.
+
+ callback(firstLevel, targetLevelHandles);
+ }
+ else
+ {
+ (int levelIndex, int layerCount) = Get3DLevelRange(firstLevel);
+
+ if (levels > 1 && slices < _layers)
+ {
+ // The given texture only covers some of the depth of multiple mips. (a "depth slice")
+ // Callback with each mip's range separately.
+ // Can assume that the group is fully subdivided (both slices and levels > 1 for storage)
+
+ while (levels-- > 1)
+ {
+ callback(firstLayer + levelIndex, slices);
+
+ levelIndex += layerCount;
+ layerCount = Math.Max(layerCount >> 1, 1);
+ slices = Math.Max(layerCount >> 1, 1);
+ }
+ }
+ else
+ {
+ int totalSize = Math.Min(layerCount, slices);
+
+ while (levels-- > 1)
+ {
+ layerCount = Math.Max(layerCount >> 1, 1);
+ totalSize += layerCount;
+ }
+
+ callback(firstLayer + levelIndex, totalSize);
+ }
+ }
+ }
+ else
+ {
+ // Future layers come after all mipmaps of the last.
+ int levelHandles = _hasMipViews ? _levels : 1;
+
+ if (slices > 1 && levels < _levels)
+ {
+ // The given texture only covers some of the mipmaps of multiple slices. (a "mip slice")
+ // Callback with each layer's range separately.
+ // Can assume that the group is fully subdivided (both slices and levels > 1 for storage)
+
+ for (int i = 0; i < slices; i++)
+ {
+ callback(firstLevel + (firstLayer + i) * levelHandles, targetLevelHandles, true);
+ }
+ }
+ else
+ {
+ callback(firstLevel + firstLayer * levelHandles, targetLevelHandles + (targetLayerHandles - 1) * levelHandles);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Get the range of offsets for a given mip level of a 3D texture.
+ /// </summary>
+ /// <param name="level">The level to return</param>
+ /// <returns>Start index and count of offsets for the given level</returns>
+ private (int Index, int Count) Get3DLevelRange(int level)
+ {
+ int index = 0;
+ int count = _layers; // Depth. Halves with each mip level.
+
+ while (level-- > 0)
+ {
+ index += count;
+ count = Math.Max(count >> 1, 1);
+ }
+
+ return (index, count);
+ }
+
+ /// <summary>
+ /// Get view information for a single tracking handle.
+ /// </summary>
+ /// <param name="handleIndex">The index of the handle</param>
+ /// <returns>The layers and levels that the handle covers, and its index in the offsets array</returns>
+ private (int BaseLayer, int BaseLevel, int Levels, int Layers, int Index) GetHandleInformation(int handleIndex)
+ {
+ int baseLayer;
+ int baseLevel;
+ int levels = _hasMipViews ? 1 : _levels;
+ int layers = _hasLayerViews ? 1 : _layers;
+ int index;
+
+ if (_is3D)
+ {
+ if (_hasLayerViews)
+ {
+ // NOTE: Will also have mip views, or only one level in storage.
+
+ index = handleIndex;
+ baseLevel = 0;
+
+ int layerLevels = _levels;
+
+ while (handleIndex >= layerLevels)
+ {
+ handleIndex -= layerLevels;
+ baseLevel++;
+ layerLevels = Math.Max(layerLevels >> 1, 1);
+ }
+
+ baseLayer = handleIndex;
+ }
+ else
+ {
+ baseLayer = 0;
+ baseLevel = handleIndex;
+
+ (index, _) = Get3DLevelRange(baseLevel);
+ }
+ }
+ else
+ {
+ baseLevel = _hasMipViews ? handleIndex % _levels : 0;
+ baseLayer = _hasMipViews ? handleIndex / _levels : handleIndex;
+ index = baseLevel + baseLayer * _levels;
+ }
+
+ return (baseLayer, baseLevel, levels, layers, index);
+ }
+
+ /// <summary>
+ /// Gets the layer and level for a given view.
+ /// </summary>
+ /// <param name="index">The index of the view</param>
+ /// <returns>The layer and level of the specified view</returns>
+ private (int BaseLayer, int BaseLevel) GetLayerLevelForView(int index)
+ {
+ if (_is3D)
+ {
+ int baseLevel = 0;
+
+ int layerLevels = _layers;
+
+ while (index >= layerLevels)
+ {
+ index -= layerLevels;
+ baseLevel++;
+ layerLevels = Math.Max(layerLevels >> 1, 1);
+ }
+
+ return (index, baseLevel);
+ }
+ else
+ {
+ return (index / _levels, index % _levels);
+ }
+ }
+
+ /// <summary>
+ /// Find the byte offset of a given texture relative to the storage.
+ /// </summary>
+ /// <param name="texture">The texture to locate</param>
+ /// <returns>The offset of the texture in bytes</returns>
+ public int FindOffset(Texture texture)
+ {
+ return _allOffsets[GetOffsetIndex(texture.FirstLayer, texture.FirstLevel)];
+ }
+
+ /// <summary>
+ /// Find the offset index of a given layer and level.
+ /// </summary>
+ /// <param name="layer">The view layer</param>
+ /// <param name="level">The view level</param>
+ /// <returns>The offset index of the given layer and level</returns>
+ public int GetOffsetIndex(int layer, int level)
+ {
+ if (_is3D)
+ {
+ return layer + Get3DLevelRange(level).Index;
+ }
+ else
+ {
+ return level + layer * _levels;
+ }
+ }
+
+ /// <summary>
+ /// The action to perform when a memory tracking handle is flipped to dirty.
+ /// This notifies overlapping textures that the memory needs to be synchronized.
+ /// </summary>
+ /// <param name="groupHandle">The handle that a dirty flag was set on</param>
+ private void DirtyAction(TextureGroupHandle groupHandle)
+ {
+ // Notify all textures that belong to this handle.
+
+ Storage.SignalGroupDirty();
+
+ lock (groupHandle.Overlaps)
+ {
+ foreach (Texture overlap in groupHandle.Overlaps)
+ {
+ overlap.SignalGroupDirty();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Generate a CpuRegionHandle for a given address and size range in CPU VA.
+ /// </summary>
+ /// <param name="address">The start address of the tracked region</param>
+ /// <param name="size">The size of the tracked region</param>
+ /// <returns>A CpuRegionHandle covering the given range</returns>
+ private CpuRegionHandle GenerateHandle(ulong address, ulong size)
+ {
+ return _context.PhysicalMemory.BeginTracking(address, size);
+ }
+
+ /// <summary>
+ /// Generate a TextureGroupHandle covering a specified range of views.
+ /// </summary>
+ /// <param name="viewStart">The start view of the handle</param>
+ /// <param name="views">The number of views to cover</param>
+ /// <returns>A TextureGroupHandle covering the given views</returns>
+ private TextureGroupHandle GenerateHandles(int viewStart, int views)
+ {
+ int offset = _allOffsets[viewStart];
+ int endOffset = (viewStart + views == _allOffsets.Length) ? (int)Storage.Size : _allOffsets[viewStart + views];
+ int size = endOffset - offset;
+
+ var result = new List<CpuRegionHandle>();
+
+ for (int i = 0; i < TextureRange.Count; i++)
+ {
+ MemoryRange item = TextureRange.GetSubRange(i);
+ int subRangeSize = (int)item.Size;
+
+ int sliceStart = Math.Clamp(offset, 0, subRangeSize);
+ int sliceEnd = Math.Clamp(endOffset, 0, subRangeSize);
+
+ if (sliceStart != sliceEnd)
+ {
+ result.Add(GenerateHandle(item.Address + (ulong)sliceStart, (ulong)(sliceEnd - sliceStart)));
+ }
+
+ offset -= subRangeSize;
+ endOffset -= subRangeSize;
+
+ if (endOffset <= 0)
+ {
+ break;
+ }
+ }
+
+ (int firstLayer, int firstLevel) = GetLayerLevelForView(viewStart);
+
+ if (_hasLayerViews && _hasMipViews)
+ {
+ size = _sliceSizes[firstLevel];
+ }
+
+ var groupHandle = new TextureGroupHandle(this, _allOffsets[viewStart], (ulong)size, _views, firstLayer, firstLevel, result.ToArray());
+
+ foreach (CpuRegionHandle handle in result)
+ {
+ handle.RegisterDirtyEvent(() => DirtyAction(groupHandle));
+ }
+
+ return groupHandle;
+ }
+
+ /// <summary>
+ /// Update the views in this texture group, rebuilding the memory tracking if required.
+ /// </summary>
+ /// <param name="views">The views list of the storage texture</param>
+ public void UpdateViews(List<Texture> views)
+ {
+ // This is saved to calculate overlapping views for each handle.
+ _views = views;
+
+ bool layerViews = _hasLayerViews;
+ bool mipViews = _hasMipViews;
+ bool regionsRebuilt = false;
+
+ if (!(layerViews && mipViews))
+ {
+ foreach (Texture view in views)
+ {
+ if (view.Info.GetSlices() < _layers)
+ {
+ layerViews = true;
+ }
+
+ if (view.Info.Levels < _levels)
+ {
+ mipViews = true;
+ }
+ }
+
+ (layerViews, mipViews) = PropagateGranularity(layerViews, mipViews);
+
+ if (layerViews != _hasLayerViews || mipViews != _hasMipViews)
+ {
+ _hasLayerViews = layerViews;
+ _hasMipViews = mipViews;
+
+ RecalculateHandleRegions();
+ regionsRebuilt = true;
+ }
+ }
+
+ if (!regionsRebuilt)
+ {
+ // Must update the overlapping views on all handles, but only if they were not just recreated.
+
+ foreach (TextureGroupHandle handle in _handles)
+ {
+ handle.RecalculateOverlaps(this, views);
+ }
+ }
+
+ Storage.SignalGroupDirty();
+ foreach (Texture texture in views)
+ {
+ texture.SignalGroupDirty();
+ }
+ }
+
+ /// <summary>
+ /// Inherit handle state from an old set of handles, such as modified and dirty flags.
+ /// </summary>
+ /// <param name="oldHandles">The set of handles to inherit state from</param>
+ /// <param name="handles">The set of handles inheriting the state</param>
+ private void InheritHandles(TextureGroupHandle[] oldHandles, TextureGroupHandle[] handles)
+ {
+ foreach (var group in handles)
+ {
+ foreach (var handle in group.Handles)
+ {
+ bool dirty = false;
+
+ foreach (var oldGroup in oldHandles)
+ {
+ if (group.OverlapsWith(oldGroup.Offset, oldGroup.Size))
+ {
+ foreach (var oldHandle in oldGroup.Handles)
+ {
+ if (handle.OverlapsWith(oldHandle.Address, oldHandle.Size))
+ {
+ dirty |= oldHandle.Dirty;
+ }
+ }
+
+ group.Inherit(oldGroup);
+ }
+ }
+
+ if (dirty && !handle.Dirty)
+ {
+ handle.Reprotect(true);
+ }
+
+ if (group.Modified)
+ {
+ handle.RegisterAction((address, size) => FlushAction(group, address, size));
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Inherit state from another texture group.
+ /// </summary>
+ /// <param name="other">The texture group to inherit from</param>
+ public void Inherit(TextureGroup other)
+ {
+ bool layerViews = _hasLayerViews || other._hasLayerViews;
+ bool mipViews = _hasMipViews || other._hasMipViews;
+
+ if (layerViews != _hasLayerViews || mipViews != _hasMipViews)
+ {
+ _hasLayerViews = layerViews;
+ _hasMipViews = mipViews;
+
+ RecalculateHandleRegions();
+ }
+
+ InheritHandles(other._handles, _handles);
+ }
+
+ /// <summary>
+ /// Replace the current handles with the new handles. It is assumed that the new handles start dirty.
+ /// The dirty flags from the previous handles will be kept.
+ /// </summary>
+ /// <param name="handles">The handles to replace the current handles with</param>
+ private void ReplaceHandles(TextureGroupHandle[] handles)
+ {
+ if (_handles != null)
+ {
+ // When replacing handles, they should start as non-dirty.
+
+ foreach (TextureGroupHandle groupHandle in handles)
+ {
+ foreach (CpuRegionHandle handle in groupHandle.Handles)
+ {
+ handle.Reprotect();
+ }
+ }
+
+ InheritHandles(_handles, handles);
+
+ foreach (var oldGroup in _handles)
+ {
+ foreach (var oldHandle in oldGroup.Handles)
+ {
+ oldHandle.Dispose();
+ }
+ }
+ }
+
+ _handles = handles;
+ _loadNeeded = new bool[_handles.Length];
+ }
+
+ /// <summary>
+ /// Recalculate handle regions for this texture group, and inherit existing state into the new handles.
+ /// </summary>
+ private void RecalculateHandleRegions()
+ {
+ TextureGroupHandle[] handles;
+
+ if (!(_hasMipViews || _hasLayerViews))
+ {
+ // Single dirty region.
+ var cpuRegionHandles = new CpuRegionHandle[TextureRange.Count];
+
+ for (int i = 0; i < TextureRange.Count; i++)
+ {
+ var currentRange = TextureRange.GetSubRange(i);
+ cpuRegionHandles[i] = GenerateHandle(currentRange.Address, currentRange.Size);
+ }
+
+ var groupHandle = new TextureGroupHandle(this, 0, Storage.Size, _views, 0, 0, cpuRegionHandles);
+
+ foreach (CpuRegionHandle handle in cpuRegionHandles)
+ {
+ handle.RegisterDirtyEvent(() => DirtyAction(groupHandle));
+ }
+
+ handles = new TextureGroupHandle[] { groupHandle };
+ }
+ else
+ {
+ // Get views for the host texture.
+ // It's worth noting that either the texture has layer views or mip views when getting to this point, which simplifies the logic a little.
+ // Depending on if the texture is 3d, either the mip views imply that layer views are present (2d) or the other way around (3d).
+ // This is enforced by the way the texture matched as a view, so we don't need to check.
+
+ int layerHandles = _hasLayerViews ? _layers : 1;
+ int levelHandles = _hasMipViews ? _levels : 1;
+
+ int handleIndex = 0;
+
+ if (_is3D)
+ {
+ var handlesList = new List<TextureGroupHandle>();
+
+ for (int i = 0; i < levelHandles; i++)
+ {
+ for (int j = 0; j < layerHandles; j++)
+ {
+ (int viewStart, int views) = Get3DLevelRange(i);
+ viewStart += j;
+ views = _hasLayerViews ? 1 : views; // A layer view is also a mip view.
+
+ handlesList.Add(GenerateHandles(viewStart, views));
+ }
+
+ layerHandles = Math.Max(1, layerHandles >> 1);
+ }
+
+ handles = handlesList.ToArray();
+ }
+ else
+ {
+ handles = new TextureGroupHandle[layerHandles * levelHandles];
+
+ for (int i = 0; i < layerHandles; i++)
+ {
+ for (int j = 0; j < levelHandles; j++)
+ {
+ int viewStart = j + i * _levels;
+ int views = _hasMipViews ? 1 : _levels; // A mip view is also a layer view.
+
+ handles[handleIndex++] = GenerateHandles(viewStart, views);
+ }
+ }
+ }
+ }
+
+ ReplaceHandles(handles);
+ }
+
+ /// <summary>
+ /// Ensure that there is a handle for each potential texture view. Required for copy dependencies to work.
+ /// </summary>
+ private void EnsureFullSubdivision()
+ {
+ if (!(_hasLayerViews && _hasMipViews))
+ {
+ _hasLayerViews = true;
+ _hasMipViews = true;
+
+ RecalculateHandleRegions();
+ }
+ }
+
+ /// <summary>
+ /// Create a copy dependency between this texture group, and a texture at a given layer/level offset.
+ /// </summary>
+ /// <param name="other">The view compatible texture to create a dependency to</param>
+ /// <param name="firstLayer">The base layer of the given texture relative to the storage</param>
+ /// <param name="firstLevel">The base level of the given texture relative to the storage</param>
+ /// <param name="copyTo">True if this texture is first copied to the given one, false for the opposite direction</param>
+ public void CreateCopyDependency(Texture other, int firstLayer, int firstLevel, bool copyTo)
+ {
+ TextureGroup otherGroup = other.Group;
+
+ EnsureFullSubdivision();
+ otherGroup.EnsureFullSubdivision();
+
+ // Get the location of each texture within its storage, so we can find the handles to apply the dependency to.
+ // This can consist of multiple disjoint regions, for example if this is a mip slice of an array texture.
+
+ var targetRange = new List<(int BaseHandle, int RegionCount)>();
+ var otherRange = new List<(int BaseHandle, int RegionCount)>();
+
+ EvaluateRelevantHandles(firstLayer, firstLevel, other.Info.GetSlices(), other.Info.Levels, (baseHandle, regionCount, split) => targetRange.Add((baseHandle, regionCount)));
+ otherGroup.EvaluateRelevantHandles(other, (baseHandle, regionCount, split) => otherRange.Add((baseHandle, regionCount)));
+
+ int targetIndex = 0;
+ int otherIndex = 0;
+ (int Handle, int RegionCount) targetRegion = (0, 0);
+ (int Handle, int RegionCount) otherRegion = (0, 0);
+
+ while (true)
+ {
+ if (targetRegion.RegionCount == 0)
+ {
+ if (targetIndex >= targetRange.Count)
+ {
+ break;
+ }
+
+ targetRegion = targetRange[targetIndex++];
+ }
+
+ if (otherRegion.RegionCount == 0)
+ {
+ if (otherIndex >= otherRange.Count)
+ {
+ break;
+ }
+
+ otherRegion = otherRange[otherIndex++];
+ }
+
+ TextureGroupHandle handle = _handles[targetRegion.Handle++];
+ TextureGroupHandle otherHandle = other.Group._handles[otherRegion.Handle++];
+
+ targetRegion.RegionCount--;
+ otherRegion.RegionCount--;
+
+ handle.CreateCopyDependency(otherHandle, copyTo);
+
+ // If "copyTo" is true, this texture must copy to the other.
+ // Otherwise, it must copy to this texture.
+
+ if (copyTo)
+ {
+ otherHandle.Copy(handle);
+ }
+ else
+ {
+ handle.Copy(otherHandle);
+ }
+ }
+ }
+
+ /// <summary>
+ /// A flush has been requested on a tracked region. Find an appropriate view to flush.
+ /// </summary>
+ /// <param name="handle">The handle this flush action is for</param>
+ /// <param name="address">The address of the flushing memory access</param>
+ /// <param name="size">The size of the flushing memory access</param>
+ public void FlushAction(TextureGroupHandle handle, ulong address, ulong size)
+ {
+ Storage.ExternalFlush(address, size);
+
+ lock (handle.Overlaps)
+ {
+ foreach (Texture overlap in handle.Overlaps)
+ {
+ overlap.ExternalFlush(address, size);
+ }
+ }
+
+ handle.Modified = false;
+ }
+
+ /// <summary>
+ /// Dispose this texture group, disposing all related memory tracking handles.
+ /// </summary>
+ public void Dispose()
+ {
+ foreach (TextureGroupHandle group in _handles)
+ {
+ group.Dispose();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs b/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs
new file mode 100644
index 00000000..27ee1e49
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs
@@ -0,0 +1,327 @@
+using Ryujinx.Cpu.Tracking;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ /// <summary>
+ /// A tracking handle for a texture group, which represents a range of views in a storage texture.
+ /// Retains a list of overlapping texture views, a modified flag, and tracking for each
+ /// CPU VA range that the views cover.
+ /// Also tracks copy dependencies for the handle - references to other handles that must be kept
+ /// in sync with this one before use.
+ /// </summary>
+ class TextureGroupHandle : IDisposable
+ {
+ private TextureGroup _group;
+ private int _bindCount;
+ private int _firstLevel;
+ private int _firstLayer;
+
+ /// <summary>
+ /// The byte offset from the start of the storage of this handle.
+ /// </summary>
+ public int Offset { get; }
+
+ /// <summary>
+ /// The size in bytes covered by this handle.
+ /// </summary>
+ public int Size { get; }
+
+ /// <summary>
+ /// The textures which this handle overlaps with.
+ /// </summary>
+ public List<Texture> Overlaps { get; }
+
+ /// <summary>
+ /// The CPU memory tracking handles that cover this handle.
+ /// </summary>
+ public CpuRegionHandle[] Handles { get; }
+
+ /// <summary>
+ /// True if a texture overlapping this handle has been modified. Is set false when the flush action is called.
+ /// </summary>
+ public bool Modified { get; set; }
+
+ /// <summary>
+ /// Dependencies to handles from other texture groups.
+ /// </summary>
+ public List<TextureDependency> Dependencies { get; }
+
+ /// <summary>
+ /// A flag indicating that a copy is required from one of the dependencies.
+ /// </summary>
+ public bool NeedsCopy => DeferredCopy != null;
+
+ /// <summary>
+ /// A data copy that must be acknowledged the next time this handle is used.
+ /// </summary>
+ public TextureGroupHandle DeferredCopy { get; set; }
+
+ /// <summary>
+ /// Create a new texture group handle, representing a range of views in a storage texture.
+ /// </summary>
+ /// <param name="group">The TextureGroup that the handle belongs to</param>
+ /// <param name="offset">The byte offset from the start of the storage of the handle</param>
+ /// <param name="size">The size in bytes covered by the handle</param>
+ /// <param name="views">All views of the storage texture, used to calculate overlaps</param>
+ /// <param name="firstLayer">The first layer of this handle in the storage texture</param>
+ /// <param name="firstLevel">The first level of this handle in the storage texture</param>
+ /// <param name="handles">The memory tracking handles that cover this handle</param>
+ public TextureGroupHandle(TextureGroup group, int offset, ulong size, List<Texture> views, int firstLayer, int firstLevel, CpuRegionHandle[] handles)
+ {
+ _group = group;
+ _firstLayer = firstLayer;
+ _firstLevel = firstLevel;
+
+ Offset = offset;
+ Size = (int)size;
+ Overlaps = new List<Texture>();
+ Dependencies = new List<TextureDependency>();
+
+ if (views != null)
+ {
+ RecalculateOverlaps(group, views);
+ }
+
+ Handles = handles;
+ }
+
+ /// <summary>
+ /// Calculate a list of which views overlap this handle.
+ /// </summary>
+ /// <param name="group">The parent texture group, used to find a view's base CPU VA offset</param>
+ /// <param name="views">The list of views to search for overlaps</param>
+ public void RecalculateOverlaps(TextureGroup group, List<Texture> views)
+ {
+ // Overlaps can be accessed from the memory tracking signal handler, so access must be atomic.
+ lock (Overlaps)
+ {
+ int endOffset = Offset + Size;
+
+ Overlaps.Clear();
+
+ foreach (Texture view in views)
+ {
+ int viewOffset = group.FindOffset(view);
+ if (viewOffset < endOffset && Offset < viewOffset + (int)view.Size)
+ {
+ Overlaps.Add(view);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Signal that this handle has been modified to any existing dependencies, and set the modified flag.
+ /// </summary>
+ public void SignalModified()
+ {
+ Modified = true;
+
+ // If this handle has any copy dependencies, notify the other handle that a copy needs to be performed.
+
+ foreach (TextureDependency dependency in Dependencies)
+ {
+ dependency.SignalModified();
+ }
+ }
+
+ /// <summary>
+ /// Signal that this handle has either started or ended being modified.
+ /// </summary>
+ /// <param name="bound">True if this handle is being bound, false if unbound</param>
+ public void SignalModifying(bool bound)
+ {
+ SignalModified();
+
+ // Note: Bind count currently resets to 0 on inherit for safety, as the handle <-> view relationship can change.
+ _bindCount = Math.Max(0, _bindCount + (bound ? 1 : -1));
+ }
+
+ /// <summary>
+ /// Signal that a copy dependent texture has been modified, and must have its data copied to this one.
+ /// </summary>
+ /// <param name="copyFrom">The texture handle that must defer a copy to this one</param>
+ public void DeferCopy(TextureGroupHandle copyFrom)
+ {
+ DeferredCopy = copyFrom;
+
+ _group.Storage.SignalGroupDirty();
+
+ foreach (Texture overlap in Overlaps)
+ {
+ overlap.SignalGroupDirty();
+ }
+ }
+
+ /// <summary>
+ /// Create a copy dependency between this handle, and another.
+ /// </summary>
+ /// <param name="other">The handle to create a copy dependency to</param>
+ /// <param name="copyToOther">True if a copy should be deferred to all of the other handle's dependencies</param>
+ public void CreateCopyDependency(TextureGroupHandle other, bool copyToOther = false)
+ {
+ // Does this dependency already exist?
+ foreach (TextureDependency existing in Dependencies)
+ {
+ if (existing.Other.Handle == other)
+ {
+ // Do not need to create it again. May need to set the dirty flag.
+ return;
+ }
+ }
+
+ _group.HasCopyDependencies = true;
+ other._group.HasCopyDependencies = true;
+
+ TextureDependency dependency = new TextureDependency(this);
+ TextureDependency otherDependency = new TextureDependency(other);
+
+ dependency.Other = otherDependency;
+ otherDependency.Other = dependency;
+
+ Dependencies.Add(dependency);
+ other.Dependencies.Add(otherDependency);
+
+ // Recursively create dependency:
+ // All of this handle's dependencies must depend on the other.
+ foreach (TextureDependency existing in Dependencies.ToArray())
+ {
+ if (existing != dependency && existing.Other.Handle != other)
+ {
+ existing.Other.Handle.CreateCopyDependency(other);
+ }
+ }
+
+ // All of the other handle's dependencies must depend on this.
+ foreach (TextureDependency existing in other.Dependencies.ToArray())
+ {
+ if (existing != otherDependency && existing.Other.Handle != this)
+ {
+ existing.Other.Handle.CreateCopyDependency(this);
+
+ if (copyToOther)
+ {
+ existing.Other.Handle.DeferCopy(this);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Remove a dependency from this handle's dependency list.
+ /// </summary>
+ /// <param name="dependency">The dependency to remove</param>
+ public void RemoveDependency(TextureDependency dependency)
+ {
+ Dependencies.Remove(dependency);
+ }
+
+ /// <summary>
+ /// Check if any of this handle's memory tracking handles are dirty.
+ /// </summary>
+ /// <returns>True if at least one of the handles is dirty</returns>
+ private bool CheckDirty()
+ {
+ return Handles.Any(handle => handle.Dirty);
+ }
+
+ /// <summary>
+ /// Perform a copy from the provided handle to this one, or perform a deferred copy if none is provided.
+ /// </summary>
+ /// <param name="fromHandle">The handle to copy from. If not provided, this method will copy from and clear the deferred copy instead</param>
+ /// <returns>True if the copy was performed, false otherwise</returns>
+ public bool Copy(TextureGroupHandle fromHandle = null)
+ {
+ bool result = false;
+
+ if (fromHandle == null)
+ {
+ fromHandle = DeferredCopy;
+
+ if (fromHandle != null && fromHandle._bindCount == 0)
+ {
+ // Repeat the copy in future if the bind count is greater than 0.
+ DeferredCopy = null;
+ }
+ }
+
+ if (fromHandle != null)
+ {
+ // If the copy texture is dirty, do not copy. Its data no longer matters, and this handle should also be dirty.
+ if (!fromHandle.CheckDirty())
+ {
+ Texture from = fromHandle._group.Storage;
+ Texture to = _group.Storage;
+
+ if (from.ScaleFactor != to.ScaleFactor)
+ {
+ to.PropagateScale(from);
+ }
+
+ from.HostTexture.CopyTo(
+ to.HostTexture,
+ fromHandle._firstLayer,
+ _firstLayer,
+ fromHandle._firstLevel,
+ _firstLevel);
+
+ Modified = true;
+
+ _group.RegisterAction(this);
+
+ result = true;
+ }
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Inherit modified flags and dependencies from another texture handle.
+ /// </summary>
+ /// <param name="old">The texture handle to inherit from</param>
+ public void Inherit(TextureGroupHandle old)
+ {
+ Modified |= old.Modified;
+
+ foreach (TextureDependency dependency in old.Dependencies.ToArray())
+ {
+ CreateCopyDependency(dependency.Other.Handle);
+
+ if (dependency.Other.Handle.DeferredCopy == old)
+ {
+ dependency.Other.Handle.DeferredCopy = this;
+ }
+ }
+
+ DeferredCopy = old.DeferredCopy;
+ }
+
+ /// <summary>
+ /// Check if this region overlaps with another.
+ /// </summary>
+ /// <param name="address">Base address</param>
+ /// <param name="size">Size of the region</param>
+ /// <returns>True if overlapping, false otherwise</returns>
+ public bool OverlapsWith(int offset, int size)
+ {
+ return Offset < offset + size && offset < Offset + Size;
+ }
+
+ public void Dispose()
+ {
+ foreach (CpuRegionHandle handle in Handles)
+ {
+ handle.Dispose();
+ }
+
+ foreach (TextureDependency dependency in Dependencies.ToArray())
+ {
+ dependency.Other.Handle.RemoveDependency(dependency.Other);
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs b/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs
index 3137f8b8..571f440e 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs
@@ -233,6 +233,31 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
+ /// Gets the number of 2D slices of the texture.
+ /// Returns 6 for cubemap textures, layer faces for cubemap array textures, and DepthOrLayers for everything else.
+ /// </summary>
+ /// <returns>The number of texture slices</returns>
+ public int GetSlices()
+ {
+ if (Target == Target.Texture3D || Target == Target.Texture2DArray || Target == Target.Texture2DMultisampleArray)
+ {
+ return DepthOrLayers;
+ }
+ else if (Target == Target.CubemapArray)
+ {
+ return DepthOrLayers * 6;
+ }
+ else if (Target == Target.Cubemap)
+ {
+ return 6;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+
+ /// <summary>
/// Calculates the size information from the texture information.
/// </summary>
/// <param name="layerSize">Optional size of each texture layer in bytes</param>
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
index 2646a75b..f13c3443 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
@@ -185,7 +185,14 @@ namespace Ryujinx.Graphics.Gpu.Image
{
bool hasValue = color != null;
bool changesScale = (hasValue != (_rtColors[index] != null)) || (hasValue && RenderTargetScale != color.ScaleFactor);
- _rtColors[index] = color;
+
+ if (_rtColors[index] != color)
+ {
+ _rtColors[index]?.SignalModifying(false);
+ color?.SignalModifying(true);
+
+ _rtColors[index] = color;
+ }
return changesScale || (hasValue && color.ScaleMode != TextureScaleMode.Blacklisted && color.ScaleFactor != GraphicsConfig.ResScale);
}
@@ -292,7 +299,14 @@ namespace Ryujinx.Graphics.Gpu.Image
{
bool hasValue = depthStencil != null;
bool changesScale = (hasValue != (_rtDepthStencil != null)) || (hasValue && RenderTargetScale != depthStencil.ScaleFactor);
- _rtDepthStencil = depthStencil;
+
+ if (_rtDepthStencil != depthStencil)
+ {
+ _rtDepthStencil?.SignalModifying(false);
+ depthStencil?.SignalModifying(true);
+
+ _rtDepthStencil = depthStencil;
+ }
return changesScale || (hasValue && depthStencil.ScaleMode != TextureScaleMode.Blacklisted && depthStencil.ScaleFactor != GraphicsConfig.ResScale);
}
@@ -754,38 +768,97 @@ namespace Ryujinx.Graphics.Gpu.Image
overlapsCount = _textures.FindOverlaps(range.Value, ref _textureOverlaps);
}
+ if (_overlapInfo.Length != _textureOverlaps.Length)
+ {
+ Array.Resize(ref _overlapInfo, _textureOverlaps.Length);
+ }
+
+ // =============== Find Texture View of Existing Texture ===============
+
+ int fullyCompatible = 0;
+
+ // Evaluate compatibility of overlaps
+
for (int index = 0; index < overlapsCount; index++)
{
Texture overlap = _textureOverlaps[index];
- TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(info, range.Value, out int firstLayer, out int firstLevel);
+ TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(info, range.Value, sizeInfo.LayerSize, out int firstLayer, out int firstLevel);
if (overlapCompatibility == TextureViewCompatibility.Full)
{
- TextureInfo oInfo = AdjustSizes(overlap, info, firstLevel);
-
- if (!isSamplerTexture)
+ if (overlap.IsView)
+ {
+ overlapCompatibility = TextureViewCompatibility.CopyOnly;
+ }
+ else
{
- info = oInfo;
+ fullyCompatible++;
}
+ }
+
+ _overlapInfo[index] = new OverlapInfo(overlapCompatibility, firstLayer, firstLevel);
+ }
+
+ // Search through the overlaps to find a compatible view and establish any copy dependencies.
+
+ for (int index = 0; index < overlapsCount; index++)
+ {
+ Texture overlap = _textureOverlaps[index];
+ OverlapInfo oInfo = _overlapInfo[index];
- texture = overlap.CreateView(oInfo, sizeInfo, range.Value, firstLayer, firstLevel);
+ if (oInfo.Compatibility == TextureViewCompatibility.Full)
+ {
+ TextureInfo adjInfo = AdjustSizes(overlap, info, oInfo.FirstLevel);
- if (overlap.IsModified)
+ if (!isSamplerTexture)
{
- texture.SignalModified();
+ info = adjInfo;
}
+ texture = overlap.CreateView(adjInfo, sizeInfo, range.Value, oInfo.FirstLayer, oInfo.FirstLevel);
+
ChangeSizeIfNeeded(info, texture, isSamplerTexture, sizeHint);
+ texture.SynchronizeMemory();
break;
}
- else if (overlapCompatibility == TextureViewCompatibility.CopyOnly)
+ else if (oInfo.Compatibility == TextureViewCompatibility.CopyOnly && fullyCompatible == 0)
{
- // TODO: Copy rules for targets created after the container texture. See below.
- overlap.DisableMemoryTracking();
+ // Only copy compatible. If there's another choice for a FULLY compatible texture, choose that instead.
+
+ texture = new Texture(_context, info, sizeInfo, range.Value, scaleMode);
+ texture.InitializeGroup(true, true);
+ texture.InitializeData(false, false);
+
+ overlap.SynchronizeMemory();
+ overlap.CreateCopyDependency(texture, oInfo.FirstLayer, oInfo.FirstLevel, true);
+ break;
}
}
+ if (texture != null)
+ {
+ // This texture could be a view of multiple parent textures with different storages, even if it is a view.
+ // When a texture is created, make sure all possible dependencies to other textures are created as copies.
+ // (even if it could be fulfilled without a copy)
+
+ for (int index = 0; index < overlapsCount; index++)
+ {
+ Texture overlap = _textureOverlaps[index];
+ OverlapInfo oInfo = _overlapInfo[index];
+
+ if (oInfo.Compatibility != TextureViewCompatibility.Incompatible && overlap.Group != texture.Group)
+ {
+ overlap.SynchronizeMemory();
+ overlap.CreateCopyDependency(texture, oInfo.FirstLayer, oInfo.FirstLevel, true);
+ }
+ }
+
+ texture.SynchronizeMemory();
+ }
+
+ // =============== Create a New Texture ===============
+
// No match, create a new texture.
if (texture == null)
{
@@ -795,24 +868,53 @@ namespace Ryujinx.Graphics.Gpu.Image
// Any textures that are incompatible will contain garbage data, so they should be removed where possible.
int viewCompatible = 0;
+ fullyCompatible = 0;
bool setData = isSamplerTexture || overlapsCount == 0 || flags.HasFlag(TextureSearchFlags.ForCopy);
+ bool hasLayerViews = false;
+ bool hasMipViews = false;
+
for (int index = 0; index < overlapsCount; index++)
{
Texture overlap = _textureOverlaps[index];
bool overlapInCache = overlap.CacheNode != null;
- TextureViewCompatibility compatibility = texture.IsViewCompatible(overlap.Info, overlap.Range, out int firstLayer, out int firstLevel);
+ TextureViewCompatibility compatibility = texture.IsViewCompatible(overlap.Info, overlap.Range, overlap.LayerSize, out int firstLayer, out int firstLevel);
+
+ if (overlap.IsView && compatibility == TextureViewCompatibility.Full)
+ {
+ compatibility = TextureViewCompatibility.CopyOnly;
+ }
if (compatibility != TextureViewCompatibility.Incompatible)
{
- if (_overlapInfo.Length != _textureOverlaps.Length)
+ if (compatibility == TextureViewCompatibility.Full)
+ {
+ if (viewCompatible == fullyCompatible)
+ {
+ _overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel);
+ _textureOverlaps[viewCompatible++] = overlap;
+ }
+ else
+ {
+ // Swap overlaps so that the fully compatible views have priority.
+
+ _overlapInfo[viewCompatible] = _overlapInfo[fullyCompatible];
+ _textureOverlaps[viewCompatible++] = _textureOverlaps[fullyCompatible];
+
+ _overlapInfo[fullyCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel);
+ _textureOverlaps[fullyCompatible] = overlap;
+ }
+ fullyCompatible++;
+ }
+ else
{
- Array.Resize(ref _overlapInfo, _textureOverlaps.Length);
+ _overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel);
+ _textureOverlaps[viewCompatible++] = overlap;
}
- _overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel);
- _textureOverlaps[viewCompatible++] = overlap;
+ hasLayerViews |= overlap.Info.GetSlices() < texture.Info.GetSlices();
+ hasMipViews |= overlap.Info.Levels < texture.Info.Levels;
}
else if (overlapInCache || !setData)
{
@@ -841,6 +943,8 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
+ texture.InitializeGroup(hasLayerViews, hasMipViews);
+
// We need to synchronize before copying the old view data to the texture,
// otherwise the copied data would be overwritten by a future synchronization.
texture.InitializeData(false, setData);
@@ -848,17 +952,17 @@ namespace Ryujinx.Graphics.Gpu.Image
for (int index = 0; index < viewCompatible; index++)
{
Texture overlap = _textureOverlaps[index];
+
OverlapInfo oInfo = _overlapInfo[index];
- if (oInfo.Compatibility != TextureViewCompatibility.Full)
+ if (overlap.Group == texture.Group)
{
- continue; // Copy only compatibilty.
+ // If the texture group is equal, then this texture (or its parent) is already a view.
+ continue;
}
TextureInfo overlapInfo = AdjustSizes(texture, overlap.Info, oInfo.FirstLevel);
- TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities, overlap.ScaleFactor);
-
if (texture.ScaleFactor != overlap.ScaleFactor)
{
// A bit tricky, our new texture may need to contain an existing texture that is upscaled, but isn't itself.
@@ -867,47 +971,30 @@ namespace Ryujinx.Graphics.Gpu.Image
texture.PropagateScale(overlap);
}
- ITexture newView = texture.HostTexture.CreateView(createInfo, oInfo.FirstLayer, oInfo.FirstLevel);
-
- overlap.HostTexture.CopyTo(newView, 0, 0);
-
- // Inherit modification from overlapping texture, do that before replacing
- // the view since the replacement operation removes it from the list.
- if (overlap.IsModified)
+ if (oInfo.Compatibility != TextureViewCompatibility.Full)
{
- texture.SignalModified();
- }
+ // Copy only compatibility, or target texture is already a view.
- overlap.ReplaceView(texture, overlapInfo, newView, oInfo.FirstLayer, oInfo.FirstLevel);
- }
+ ChangeSizeIfNeeded(overlapInfo, overlap, false, sizeHint); // Force a size match for copy
- // If the texture is a 3D texture, we need to additionally copy any slice
- // of the 3D texture to the newly created 3D texture.
- if (info.Target == Target.Texture3D && viewCompatible > 0)
- {
- // TODO: This copy can currently only happen when the 3D texture is created.
- // If a game clears and redraws the slices, we won't be able to copy the new data to the 3D texture.
- // Disable tracking to try keep at least the original data in there for as long as possible.
- texture.DisableMemoryTracking();
-
- for (int index = 0; index < viewCompatible; index++)
+ overlap.SynchronizeMemory();
+ texture.CreateCopyDependency(overlap, oInfo.FirstLayer, oInfo.FirstLevel, false);
+ }
+ else
{
- Texture overlap = _textureOverlaps[index];
- OverlapInfo oInfo = _overlapInfo[index];
+ TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities, overlap.ScaleFactor);
- if (oInfo.Compatibility != TextureViewCompatibility.Incompatible)
- {
- overlap.BlacklistScale();
+ ITexture newView = texture.HostTexture.CreateView(createInfo, oInfo.FirstLayer, oInfo.FirstLevel);
- overlap.HostTexture.CopyTo(texture.HostTexture, oInfo.FirstLayer, oInfo.FirstLevel);
+ overlap.SynchronizeMemory();
- if (overlap.IsModified)
- {
- texture.SignalModified();
- }
- }
+ overlap.HostTexture.CopyTo(newView, 0, 0);
+
+ overlap.ReplaceView(texture, overlapInfo, newView, oInfo.FirstLayer, oInfo.FirstLevel);
}
}
+
+ texture.SynchronizeMemory();
}
// Sampler textures are managed by the texture pool, all other textures
diff --git a/Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs b/Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs
index d2a05495..92099b6a 100644
--- a/Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs
@@ -49,11 +49,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
- public void Reprotect()
+ public void Reprotect(bool asDirty = false)
{
foreach (var regionHandle in _cpuRegionHandles)
{
- regionHandle.Reprotect();
+ regionHandle.Reprotect(asDirty);
}
}
}
diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs b/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs
index 6df2b630..5607fb40 100644
--- a/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs
+++ b/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs
@@ -18,6 +18,11 @@ namespace Ryujinx.Graphics.OpenGL.Image
throw new NotSupportedException();
}
+ public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel)
+ {
+ throw new NotSupportedException();
+ }
+
public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter)
{
throw new NotSupportedException();
@@ -38,6 +43,11 @@ namespace Ryujinx.Graphics.OpenGL.Image
Buffer.SetData(_buffer, _bufferOffset, data.Slice(0, Math.Min(data.Length, _bufferSize)));
}
+ public void SetData(ReadOnlySpan<byte> data, int layer, int level)
+ {
+ throw new NotSupportedException();
+ }
+
public void SetStorage(BufferRange buffer)
{
if (buffer.Handle == _buffer &&
diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs b/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs
index 20a3b914..b27403b2 100644
--- a/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs
+++ b/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs
@@ -115,18 +115,44 @@ namespace Ryujinx.Graphics.OpenGL.Image
TextureCreateInfo srcInfo = src.Info;
TextureCreateInfo dstInfo = dst.Info;
+ int srcDepth = srcInfo.GetDepthOrLayers();
+ int srcLevels = srcInfo.Levels;
+
+ int dstDepth = dstInfo.GetDepthOrLayers();
+ int dstLevels = dstInfo.Levels;
+
+ if (dstInfo.Target == Target.Texture3D)
+ {
+ dstDepth = Math.Max(1, dstDepth >> dstLevel);
+ }
+
+ int depth = Math.Min(srcDepth, dstDepth);
+ int levels = Math.Min(srcLevels, dstLevels);
+
+ CopyUnscaled(src, dst, srcLayer, dstLayer, srcLevel, dstLevel, depth, levels);
+ }
+
+ public void CopyUnscaled(
+ ITextureInfo src,
+ ITextureInfo dst,
+ int srcLayer,
+ int dstLayer,
+ int srcLevel,
+ int dstLevel,
+ int depth,
+ int levels)
+ {
+ TextureCreateInfo srcInfo = src.Info;
+ TextureCreateInfo dstInfo = dst.Info;
+
int srcHandle = src.Handle;
int dstHandle = dst.Handle;
int srcWidth = srcInfo.Width;
int srcHeight = srcInfo.Height;
- int srcDepth = srcInfo.GetDepthOrLayers();
- int srcLevels = srcInfo.Levels;
int dstWidth = dstInfo.Width;
int dstHeight = dstInfo.Height;
- int dstDepth = dstInfo.GetDepthOrLayers();
- int dstLevels = dstInfo.Levels;
srcWidth = Math.Max(1, srcWidth >> srcLevel);
srcHeight = Math.Max(1, srcHeight >> srcLevel);
@@ -134,11 +160,6 @@ namespace Ryujinx.Graphics.OpenGL.Image
dstWidth = Math.Max(1, dstWidth >> dstLevel);
dstHeight = Math.Max(1, dstHeight >> dstLevel);
- if (dstInfo.Target == Target.Texture3D)
- {
- dstDepth = Math.Max(1, dstDepth >> dstLevel);
- }
-
int blockWidth = 1;
int blockHeight = 1;
bool sizeInBlocks = false;
@@ -166,8 +187,6 @@ namespace Ryujinx.Graphics.OpenGL.Image
int width = Math.Min(srcWidth, dstWidth);
int height = Math.Min(srcHeight, dstHeight);
- int depth = Math.Min(srcDepth, dstDepth);
- int levels = Math.Min(srcLevels, dstLevels);
for (int level = 0; level < levels; level++)
{
diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
index 89f74cee..053fb3c2 100644
--- a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
+++ b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
@@ -10,8 +10,6 @@ namespace Ryujinx.Graphics.OpenGL.Image
private readonly TextureStorage _parent;
- private TextureView _emulatedViewParent;
-
private TextureView _incompatibleFormatView;
public int FirstLayer { get; private set; }
@@ -96,37 +94,10 @@ namespace Ryujinx.Graphics.OpenGL.Image
public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel)
{
- if (Info.IsCompressed == info.IsCompressed)
- {
- firstLayer += FirstLayer;
- firstLevel += FirstLevel;
+ firstLayer += FirstLayer;
+ firstLevel += FirstLevel;
- return _parent.CreateView(info, firstLayer, firstLevel);
- }
- else
- {
- // TODO: Most graphics APIs doesn't support creating a texture view from a compressed format
- // with a non-compressed format (or vice-versa), however NVN seems to support it.
- // So we emulate that here with a texture copy (see the first CopyTo overload).
- // However right now it only does a single copy right after the view is created,
- // so it doesn't work for all cases.
- TextureView emulatedView = (TextureView)_renderer.CreateTexture(info, ScaleFactor);
-
- _renderer.TextureCopy.CopyUnscaled(
- this,
- emulatedView,
- 0,
- firstLayer,
- 0,
- firstLevel);
-
- emulatedView._emulatedViewParent = this;
-
- emulatedView.FirstLayer = firstLayer;
- emulatedView.FirstLevel = firstLevel;
-
- return emulatedView;
- }
+ return _parent.CreateView(info, firstLayer, firstLevel);
}
public int GetIncompatibleFormatViewHandle()
@@ -163,17 +134,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
TextureView destinationView = (TextureView)destination;
_renderer.TextureCopy.CopyUnscaled(this, destinationView, 0, firstLayer, 0, firstLevel);
+ }
- if (destinationView._emulatedViewParent != null)
- {
- _renderer.TextureCopy.CopyUnscaled(
- this,
- destinationView._emulatedViewParent,
- 0,
- destinationView.FirstLayer,
- 0,
- destinationView.FirstLevel);
- }
+ public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel)
+ {
+ TextureView destinationView = (TextureView)destination;
+
+ _renderer.TextureCopy.CopyUnscaled(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
}
public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter)
@@ -308,6 +275,20 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
}
+ public void SetData(ReadOnlySpan<byte> data, int layer, int level)
+ {
+ unsafe
+ {
+ fixed (byte* ptr = data)
+ {
+ int width = Math.Max(Info.Width >> level, 1);
+ int height = Math.Max(Info.Height >> level, 1);
+
+ ReadFrom2D((IntPtr)ptr, layer, level, width, height);
+ }
+ }
+ }
+
public void ReadFromPbo(int offset, int size)
{
ReadFrom(IntPtr.Zero + offset, size);
diff --git a/Ryujinx.Graphics.Texture/SizeCalculator.cs b/Ryujinx.Graphics.Texture/SizeCalculator.cs
index 2dc60869..7eee13c0 100644
--- a/Ryujinx.Graphics.Texture/SizeCalculator.cs
+++ b/Ryujinx.Graphics.Texture/SizeCalculator.cs
@@ -9,6 +9,19 @@ namespace Ryujinx.Graphics.Texture
{
private const int StrideAlignment = 32;
+ private static int Calculate3DOffsetCount(int levels, int depth)
+ {
+ int offsetCount = depth;
+
+ while (--levels > 0)
+ {
+ depth = Math.Max(1, depth >> 1);
+ offsetCount += depth;
+ }
+
+ return offsetCount;
+ }
+
public static SizeInfo GetBlockLinearTextureSize(
int width,
int height,
@@ -27,8 +40,9 @@ namespace Ryujinx.Graphics.Texture
int layerSize = 0;
- int[] allOffsets = new int[levels * layers * depth];
+ int[] allOffsets = new int[is3D ? Calculate3DOffsetCount(levels, depth) : levels * layers * depth];
int[] mipOffsets = new int[levels];
+ int[] sliceSizes = new int[levels];
int mipGobBlocksInY = gobBlocksInY;
int mipGobBlocksInZ = gobBlocksInZ;
@@ -36,6 +50,8 @@ namespace Ryujinx.Graphics.Texture
int gobWidth = (GobStride / bytesPerPixel) * gobBlocksInTileX;
int gobHeight = gobBlocksInY * GobHeight;
+ int depthLevelOffset = 0;
+
for (int level = 0; level < levels; level++)
{
int w = Math.Max(1, width >> level);
@@ -86,13 +102,16 @@ namespace Ryujinx.Graphics.Texture
int zLow = z & mask;
int zHigh = z & ~mask;
- allOffsets[z * levels + level] = baseOffset + zLow * gobSize + zHigh * sliceSize;
+ allOffsets[z + depthLevelOffset] = baseOffset + zLow * gobSize + zHigh * sliceSize;
}
}
mipOffsets[level] = layerSize;
+ sliceSizes[level] = totalBlocksOfGobsInY * robSize;
+
+ layerSize += totalBlocksOfGobsInZ * sliceSizes[level];
- layerSize += totalBlocksOfGobsInZ * totalBlocksOfGobsInY * robSize;
+ depthLevelOffset += d;
}
if (layers > 1)
@@ -133,7 +152,7 @@ namespace Ryujinx.Graphics.Texture
}
}
- return new SizeInfo(mipOffsets, allOffsets, levels, layerSize, totalSize);
+ return new SizeInfo(mipOffsets, allOffsets, sliceSizes, depth, levels, layerSize, totalSize, is3D);
}
public static SizeInfo GetLinearTextureSize(int stride, int height, int blockHeight)
@@ -142,7 +161,7 @@ namespace Ryujinx.Graphics.Texture
// so we only need to handle a single case (2D textures without mipmaps).
int totalSize = stride * BitUtils.DivRoundUp(height, blockHeight);
- return new SizeInfo(new int[] { 0 }, new int[] { 0 }, 1, totalSize, totalSize);
+ return new SizeInfo(totalSize);
}
private static int AlignLayerSize(
diff --git a/Ryujinx.Graphics.Texture/SizeInfo.cs b/Ryujinx.Graphics.Texture/SizeInfo.cs
index 55b22e3a..f518ee4b 100644
--- a/Ryujinx.Graphics.Texture/SizeInfo.cs
+++ b/Ryujinx.Graphics.Texture/SizeInfo.cs
@@ -5,34 +5,46 @@ namespace Ryujinx.Graphics.Texture
public struct SizeInfo
{
private readonly int[] _mipOffsets;
- private readonly int[] _allOffsets;
private readonly int _levels;
+ private readonly int _depth;
+ private readonly bool _is3D;
+ public readonly int[] AllOffsets;
+ public readonly int[] SliceSizes;
public int LayerSize { get; }
public int TotalSize { get; }
public SizeInfo(int size)
{
_mipOffsets = new int[] { 0 };
- _allOffsets = new int[] { 0 };
+ AllOffsets = new int[] { 0 };
+ SliceSizes = new int[] { size };
+ _depth = 1;
_levels = 1;
LayerSize = size;
TotalSize = size;
+ _is3D = false;
}
internal SizeInfo(
int[] mipOffsets,
int[] allOffsets,
+ int[] sliceSizes,
+ int depth,
int levels,
int layerSize,
- int totalSize)
+ int totalSize,
+ bool is3D)
{
_mipOffsets = mipOffsets;
- _allOffsets = allOffsets;
+ AllOffsets = allOffsets;
+ SliceSizes = sliceSizes;
+ _depth = depth;
_levels = levels;
LayerSize = layerSize;
TotalSize = totalSize;
+ _is3D = is3D;
}
public int GetMipOffset(int level)
@@ -47,7 +59,7 @@ namespace Ryujinx.Graphics.Texture
public bool FindView(int offset, out int firstLayer, out int firstLevel)
{
- int index = Array.BinarySearch(_allOffsets, offset);
+ int index = Array.BinarySearch(AllOffsets, offset);
if (index < 0)
{
@@ -57,8 +69,25 @@ namespace Ryujinx.Graphics.Texture
return false;
}
- firstLayer = index / _levels;
- firstLevel = index - (firstLayer * _levels);
+ if (_is3D)
+ {
+ firstLayer = index;
+ firstLevel = 0;
+
+ int levelDepth = _depth;
+
+ while (firstLayer >= levelDepth)
+ {
+ firstLayer -= levelDepth;
+ firstLevel++;
+ levelDepth = Math.Max(levelDepth >> 1, 1);
+ }
+ }
+ else
+ {
+ firstLayer = index / _levels;
+ firstLevel = index - (firstLayer * _levels);
+ }
return true;
}
diff --git a/Ryujinx.Memory/Tracking/IRegionHandle.cs b/Ryujinx.Memory/Tracking/IRegionHandle.cs
index 33628da6..cd33e5c8 100644
--- a/Ryujinx.Memory/Tracking/IRegionHandle.cs
+++ b/Ryujinx.Memory/Tracking/IRegionHandle.cs
@@ -10,7 +10,7 @@ namespace Ryujinx.Memory.Tracking
ulong Size { get; }
ulong EndAddress { get; }
- void Reprotect();
+ void Reprotect(bool asDirty = false);
void RegisterAction(RegionSignal action);
}
}
diff --git a/Ryujinx.Memory/Tracking/RegionHandle.cs b/Ryujinx.Memory/Tracking/RegionHandle.cs
index 3ddcb6db..4da184dd 100644
--- a/Ryujinx.Memory/Tracking/RegionHandle.cs
+++ b/Ryujinx.Memory/Tracking/RegionHandle.cs
@@ -1,4 +1,5 @@
using Ryujinx.Memory.Range;
+using System;
using System.Collections.Generic;
using System.Threading;
@@ -19,9 +20,12 @@ namespace Ryujinx.Memory.Tracking
internal IMultiRegionHandle Parent { get; set; }
internal int SequenceNumber { get; set; }
+ private event Action _onDirty;
+
private RegionSignal _preAction; // Action to perform before a read or write. This will block the memory access.
private readonly List<VirtualRegion> _regions;
private readonly MemoryTracking _tracking;
+ private bool _disposed;
internal MemoryPermission RequiredPermission => _preAction != null ? MemoryPermission.None : (Dirty ? MemoryPermission.ReadAndWrite : MemoryPermission.Read);
internal RegionSignal PreAction => _preAction;
@@ -60,7 +64,12 @@ namespace Ryujinx.Memory.Tracking
if (write)
{
+ bool oldDirty = Dirty;
Dirty = true;
+ if (!oldDirty)
+ {
+ _onDirty?.Invoke();
+ }
Parent?.SignalWrite();
}
}
@@ -68,9 +77,9 @@ namespace Ryujinx.Memory.Tracking
/// <summary>
/// Consume the dirty flag for this handle, and reprotect so it can be set on the next write.
/// </summary>
- public void Reprotect()
+ public void Reprotect(bool asDirty = false)
{
- Dirty = false;
+ Dirty = asDirty;
lock (_tracking.TrackingLock)
{
foreach (VirtualRegion region in _regions)
@@ -101,6 +110,16 @@ namespace Ryujinx.Memory.Tracking
}
/// <summary>
+ /// Register an action to perform when the region is written to.
+ /// This action will not be removed when it is called - it is called each time the dirty flag is set.
+ /// </summary>
+ /// <param name="action">Action to call on dirty</param>
+ public void RegisterDirtyEvent(Action action)
+ {
+ _onDirty += action;
+ }
+
+ /// <summary>
/// Add a child virtual region to this handle.
/// </summary>
/// <param name="region">Virtual region to add as a child</param>
@@ -125,6 +144,13 @@ namespace Ryujinx.Memory.Tracking
/// </summary>
public void Dispose()
{
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+
+ _disposed = true;
+
lock (_tracking.TrackingLock)
{
foreach (VirtualRegion region in _regions)