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.
This commit is contained in:
parent
7a90abc035
commit
b530f0e110
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -50,6 +50,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
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>
|
||||
@ -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;
|
||||
|
||||
@ -99,12 +105,20 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
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.
|
||||
@ -221,6 +234,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.
|
||||
@ -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
|
||||
{
|
||||
@ -328,16 +388,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.
|
||||
@ -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,17 +587,42 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return;
|
||||
}
|
||||
|
||||
if (_hasData)
|
||||
if (!_dirty)
|
||||
{
|
||||
if (_memoryTracking?.Dirty != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BlacklistScale();
|
||||
return;
|
||||
}
|
||||
|
||||
_memoryTracking?.Reprotect();
|
||||
_dirty = false;
|
||||
|
||||
if (_hasData)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> data = _context.PhysicalMemory.GetSpan(Range);
|
||||
|
||||
@ -596,7 +664,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
BlacklistScale();
|
||||
|
||||
_memoryTracking?.Reprotect();
|
||||
Group.ConsumeDirty(this);
|
||||
|
||||
IsModified = false;
|
||||
|
||||
@ -605,18 +673,46 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
_hasData = true;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
|
||||
_memoryTracking?.RegisterAction(ExternalFlush);
|
||||
/// <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;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
@ -235,6 +235,27 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
size.Height == otherSize.Height) ? result : TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
@ -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>
|
||||
|
37
Ryujinx.Graphics.Gpu/Image/TextureDependency.cs
Normal file
37
Ryujinx.Graphics.Gpu/Image/TextureDependency.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
971
Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
Normal file
971
Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
327
Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs
Normal file
327
Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -232,6 +232,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>
|
||||
|
@ -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 (overlap.IsView)
|
||||
{
|
||||
overlapCompatibility = TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
else
|
||||
{
|
||||
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];
|
||||
|
||||
if (oInfo.Compatibility == TextureViewCompatibility.Full)
|
||||
{
|
||||
TextureInfo adjInfo = AdjustSizes(overlap, info, oInfo.FirstLevel);
|
||||
|
||||
if (!isSamplerTexture)
|
||||
{
|
||||
info = oInfo;
|
||||
info = adjInfo;
|
||||
}
|
||||
|
||||
texture = overlap.CreateView(oInfo, sizeInfo, range.Value, firstLayer, firstLevel);
|
||||
|
||||
if (overlap.IsModified)
|
||||
{
|
||||
texture.SignalModified();
|
||||
}
|
||||
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)
|
||||
{
|
||||
Array.Resize(ref _overlapInfo, _textureOverlaps.Length);
|
||||
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
|
||||
{
|
||||
_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.
|
||||
|
||||
ChangeSizeIfNeeded(overlapInfo, overlap, false, sizeHint); // Force a size match for copy
|
||||
|
||||
overlap.SynchronizeMemory();
|
||||
texture.CreateCopyDependency(overlap, oInfo.FirstLayer, oInfo.FirstLevel, false);
|
||||
}
|
||||
|
||||
overlap.ReplaceView(texture, overlapInfo, newView, oInfo.FirstLayer, oInfo.FirstLevel);
|
||||
}
|
||||
|
||||
// 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++)
|
||||
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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 &&
|
||||
|
@ -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++)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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 * totalBlocksOfGobsInY * robSize;
|
||||
layerSize += totalBlocksOfGobsInZ * sliceSizes[level];
|
||||
|
||||
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(
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
@ -100,6 +109,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>
|
||||
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user