2019-10-13 08:02:07 +02:00
using Ryujinx.Common ;
2019-12-29 00:45:33 +01:00
using Ryujinx.Common.Logging ;
2019-10-13 08:02:07 +02:00
using Ryujinx.Graphics.GAL ;
using Ryujinx.Graphics.Gpu.Memory ;
using Ryujinx.Graphics.Texture ;
using Ryujinx.Graphics.Texture.Astc ;
using System ;
using System.Collections.Generic ;
2019-10-31 00:45:01 +01:00
using System.Diagnostics ;
2019-10-13 08:02:07 +02:00
namespace Ryujinx.Graphics.Gpu.Image
{
2019-12-30 00:26:37 +01:00
/// <summary>
/// Represents a cached GPU texture.
/// </summary>
2019-12-31 23:37:00 +01:00
class Texture : IRange , IDisposable
2019-10-13 08:02:07 +02:00
{
private GpuContext _context ;
private SizeInfo _sizeInfo ;
2019-12-30 00:26:37 +01:00
/// <summary>
/// Texture format.
/// </summary>
2019-12-29 18:41:50 +01:00
public Format Format = > Info . FormatInfo . Format ;
2019-10-13 08:02:07 +02:00
2019-12-30 00:26:37 +01:00
/// <summary>
/// Texture information.
/// </summary>
2019-12-29 18:41:50 +01:00
public TextureInfo Info { get ; private set ; }
2019-10-13 08:02:07 +02:00
2020-07-07 04:41:07 +02:00
/// <summary>
/// Host scale factor.
/// </summary>
public float ScaleFactor { get ; private set ; }
/// <summary>
/// Upscaling mode. Informs if a texture is scaled, or is eligible for scaling.
/// </summary>
public TextureScaleMode ScaleMode { get ; private set ; }
2020-09-10 21:44:04 +02:00
/// <summary>
/// Set when a texture has been modified since it was last flushed.
/// </summary>
public bool IsModified { get ; internal set ; }
private bool _everModified ;
2019-10-13 08:02:07 +02:00
private int _depth ;
private int _layers ;
2020-07-07 04:41:07 +02:00
private int _firstLayer ;
private int _firstLevel ;
2019-10-13 08:02:07 +02:00
private bool _hasData ;
private ITexture _arrayViewTexture ;
private Target _arrayViewTarget ;
private Texture _viewStorage ;
private List < Texture > _views ;
2019-12-30 00:26:37 +01:00
/// <summary>
/// Host texture.
/// </summary>
2019-10-13 08:02:07 +02:00
public ITexture HostTexture { get ; private set ; }
2019-12-30 00:26:37 +01:00
/// <summary>
/// Intrusive linked list node used on the auto deletion texture cache.
/// </summary>
2019-10-13 08:02:07 +02:00
public LinkedListNode < Texture > CacheNode { get ; set ; }
2019-12-30 00:26:37 +01:00
/// <summary>
2020-02-06 22:49:26 +01:00
/// Event to fire when texture data is modified by the GPU.
2019-12-30 00:26:37 +01:00
/// </summary>
2020-02-06 22:49:26 +01:00
public event Action < Texture > Modified ;
/// <summary>
/// Event to fire when texture data is disposed.
/// </summary>
public event Action < Texture > Disposed ;
2019-10-13 08:02:07 +02:00
2019-12-30 00:26:37 +01:00
/// <summary>
/// Start address of the texture in guest memory.
/// </summary>
public ulong Address = > Info . Address ;
/// <summary>
/// End address of the texture in guest memory.
/// </summary>
2019-12-29 18:41:50 +01:00
public ulong EndAddress = > Info . Address + Size ;
2019-10-13 08:02:07 +02:00
2019-12-30 00:26:37 +01:00
/// <summary>
/// Texture size in bytes.
/// </summary>
2019-10-13 08:02:07 +02:00
public ulong Size = > ( ulong ) _sizeInfo . TotalSize ;
2020-05-04 00:54:50 +02:00
private ( ulong , ulong ) [ ] _modifiedRanges ;
2019-10-13 08:02:07 +02:00
private int _referenceCount ;
private int _sequenceNumber ;
2019-12-30 00:26:37 +01:00
/// <summary>
/// Constructs a new instance of the cached GPU texture.
/// </summary>
/// <param name="context">GPU context that the texture belongs to</param>
/// <param name="info">Texture information</param>
/// <param name="sizeInfo">Size information of the texture</param>
/// <param name="firstLayer">The first layer of the texture, or 0 if the texture has no parent</param>
/// <param name="firstLevel">The first mipmap level of the texture, or 0 if the texture has no parent</param>
2020-07-07 04:41:07 +02:00
/// <param name="scaleFactor">The floating point scale factor to initialize with</param>
/// <param name="scaleMode">The scale mode to initialize with</param>
2019-10-13 08:02:07 +02:00
private Texture (
2020-07-07 04:41:07 +02:00
GpuContext context ,
TextureInfo info ,
SizeInfo sizeInfo ,
int firstLayer ,
int firstLevel ,
float scaleFactor ,
TextureScaleMode scaleMode )
2019-10-13 08:02:07 +02:00
{
InitializeTexture ( context , info , sizeInfo ) ;
_firstLayer = firstLayer ;
_firstLevel = firstLevel ;
2020-07-07 04:41:07 +02:00
ScaleFactor = scaleFactor ;
ScaleMode = scaleMode ;
2020-09-10 21:44:04 +02:00
InitializeData ( true ) ;
2019-10-13 08:02:07 +02:00
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Constructs a new instance of the cached GPU texture.
/// </summary>
/// <param name="context">GPU context that the texture belongs to</param>
/// <param name="info">Texture information</param>
/// <param name="sizeInfo">Size information of the texture</param>
2020-07-07 04:41:07 +02:00
/// <param name="scaleMode">The scale mode to initialize with. If scaled, the texture's data is loaded immediately and scaled up</param>
public Texture ( GpuContext context , TextureInfo info , SizeInfo sizeInfo , TextureScaleMode scaleMode )
2019-10-13 08:02:07 +02:00
{
2020-07-07 04:41:07 +02:00
ScaleFactor = 1f ; // Texture is first loaded at scale 1x.
ScaleMode = scaleMode ;
2019-10-13 08:02:07 +02:00
InitializeTexture ( context , info , sizeInfo ) ;
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Common texture initialization method.
/// This sets the context, info and sizeInfo fields.
/// Other fields are initialized with their default values.
/// </summary>
/// <param name="context">GPU context that the texture belongs to</param>
/// <param name="info">Texture information</param>
/// <param name="sizeInfo">Size information of the texture</param>
2019-10-13 08:02:07 +02:00
private void InitializeTexture ( GpuContext context , TextureInfo info , SizeInfo sizeInfo )
{
_context = context ;
_sizeInfo = sizeInfo ;
2020-05-04 00:54:50 +02:00
_modifiedRanges = new ( ulong , ulong ) [ ( sizeInfo . TotalSize / PhysicalMemory . PageSize ) + 1 ] ;
2019-10-13 08:02:07 +02:00
SetInfo ( info ) ;
_viewStorage = this ;
_views = new List < Texture > ( ) ;
}
2020-09-10 21:44:04 +02:00
/// <summary>
/// Initializes the data for a texture. Can optionally initialize the texture with or without data.
/// If the texture is a view, it will initialize memory tracking to be non-dirty.
/// </summary>
/// <param name="isView">True if the texture is a view, false otherwise</param>
/// <param name="withData">True if the texture is to be initialized with data</param>
public void InitializeData ( bool isView , bool withData = false )
{
if ( withData )
{
Debug . Assert ( ! isView ) ;
TextureCreateInfo createInfo = TextureManager . GetCreateInfo ( Info , _context . Capabilities ) ;
HostTexture = _context . Renderer . CreateTexture ( createInfo , ScaleFactor ) ;
SynchronizeMemory ( ) ; // Load the data.
if ( ScaleMode = = TextureScaleMode . Scaled )
{
SetScale ( GraphicsConfig . ResScale ) ; // Scale the data up.
}
}
else
{
// Don't update this texture the next time we synchronize.
ConsumeModified ( ) ;
_hasData = true ;
if ( ! isView )
{
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.
ScaleFactor = GraphicsConfig . ResScale ;
}
TextureCreateInfo createInfo = TextureManager . GetCreateInfo ( Info , _context . Capabilities ) ;
HostTexture = _context . Renderer . CreateTexture ( createInfo , ScaleFactor ) ;
}
}
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Create a texture view from this texture.
/// A texture view is defined as a child texture, from a sub-range of their parent texture.
/// For example, the initial layer and mipmap level of the view can be defined, so the texture
/// will start at the given layer/level of the parent texture.
/// </summary>
/// <param name="info">Child texture information</param>
/// <param name="sizeInfo">Child texture size information</param>
/// <param name="firstLayer">Start layer of the child texture on the parent texture</param>
/// <param name="firstLevel">Start mipmap level of the child texture on the parent texture</param>
/// <returns>The child texture</returns>
2019-10-13 08:02:07 +02:00
public Texture CreateView ( TextureInfo info , SizeInfo sizeInfo , int firstLayer , int firstLevel )
{
Texture texture = new Texture (
_context ,
info ,
sizeInfo ,
_firstLayer + firstLayer ,
2020-07-07 04:41:07 +02:00
_firstLevel + firstLevel ,
ScaleFactor ,
ScaleMode ) ;
2019-10-13 08:02:07 +02:00
TextureCreateInfo createInfo = TextureManager . GetCreateInfo ( info , _context . Capabilities ) ;
texture . HostTexture = HostTexture . CreateView ( createInfo , firstLayer , firstLevel ) ;
_viewStorage . AddView ( texture ) ;
return texture ;
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Adds a child texture to this texture.
/// </summary>
/// <param name="texture">The child texture</param>
2019-10-13 08:02:07 +02:00
private void AddView ( Texture texture )
{
_views . Add ( texture ) ;
texture . _viewStorage = this ;
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Removes a child texture from this texture.
/// </summary>
/// <param name="texture">The child texture</param>
2019-10-13 08:02:07 +02:00
private void RemoveView ( Texture texture )
{
_views . Remove ( texture ) ;
texture . _viewStorage = null ;
2019-10-31 00:45:01 +01:00
DeleteIfNotUsed ( ) ;
2019-10-13 08:02:07 +02:00
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Changes the texture size.
2020-01-01 16:39:09 +01:00
/// </summary>
/// <remarks>
2019-12-30 00:26:37 +01:00
/// This operation may also change the size of all mipmap levels, including from the parent
/// and other possible child textures, to ensure that all sizes are consistent.
2020-01-01 16:39:09 +01:00
/// </remarks>
2019-12-30 00:26:37 +01:00
/// <param name="width">The new texture width</param>
/// <param name="height">The new texture height</param>
/// <param name="depthOrLayers">The new texture depth (for 3D textures) or layers (for layered textures)</param>
2019-10-13 08:02:07 +02:00
public void ChangeSize ( int width , int height , int depthOrLayers )
{
2020-09-10 21:44:04 +02:00
int blockWidth = Info . FormatInfo . BlockWidth ;
int blockHeight = Info . FormatInfo . BlockHeight ;
2019-10-13 08:02:07 +02:00
width < < = _firstLevel ;
height < < = _firstLevel ;
2019-12-29 18:41:50 +01:00
if ( Info . Target = = Target . Texture3D )
2019-10-13 08:02:07 +02:00
{
depthOrLayers < < = _firstLevel ;
}
else
{
2019-12-29 18:41:50 +01:00
depthOrLayers = _viewStorage . Info . DepthOrLayers ;
2019-10-13 08:02:07 +02:00
}
2020-09-10 21:44:04 +02:00
_viewStorage . RecreateStorageOrView ( width , height , blockWidth , blockHeight , depthOrLayers ) ;
2019-10-13 08:02:07 +02:00
foreach ( Texture view in _viewStorage . _views )
{
int viewWidth = Math . Max ( 1 , width > > view . _firstLevel ) ;
int viewHeight = Math . Max ( 1 , height > > view . _firstLevel ) ;
int viewDepthOrLayers ;
2019-12-29 18:41:50 +01:00
if ( view . Info . Target = = Target . Texture3D )
2019-10-13 08:02:07 +02:00
{
viewDepthOrLayers = Math . Max ( 1 , depthOrLayers > > view . _firstLevel ) ;
}
else
{
2019-12-29 18:41:50 +01:00
viewDepthOrLayers = view . Info . DepthOrLayers ;
2019-10-13 08:02:07 +02:00
}
2020-09-10 21:44:04 +02:00
view . RecreateStorageOrView ( viewWidth , viewHeight , blockWidth , blockHeight , viewDepthOrLayers ) ;
2019-10-13 08:02:07 +02:00
}
}
2020-09-10 21:44:04 +02:00
/// <summary>
/// Recreates the texture storage (or view, in the case of child textures) of this texture.
/// This allows recreating the texture with a new size.
/// A copy is automatically performed from the old to the new texture.
/// </summary>
/// <param name="width">The new texture width</param>
/// <param name="height">The new texture height</param>
/// <param name="width">The block width related to the given width</param>
/// <param name="height">The block height related to the given height</param>
/// <param name="depthOrLayers">The new texture depth (for 3D textures) or layers (for layered textures)</param>
private void RecreateStorageOrView ( int width , int height , int blockWidth , int blockHeight , int depthOrLayers )
{
RecreateStorageOrView (
BitUtils . DivRoundUp ( width * Info . FormatInfo . BlockWidth , blockWidth ) ,
BitUtils . DivRoundUp ( height * Info . FormatInfo . BlockHeight , blockHeight ) ,
depthOrLayers ) ;
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Recreates the texture storage (or view, in the case of child textures) of this texture.
/// This allows recreating the texture with a new size.
/// A copy is automatically performed from the old to the new texture.
/// </summary>
/// <param name="width">The new texture width</param>
/// <param name="height">The new texture height</param>
/// <param name="depthOrLayers">The new texture depth (for 3D textures) or layers (for layered textures)</param>
2019-10-13 08:02:07 +02:00
private void RecreateStorageOrView ( int width , int height , int depthOrLayers )
{
SetInfo ( new TextureInfo (
2019-12-29 18:41:50 +01:00
Info . Address ,
2019-10-13 08:02:07 +02:00
width ,
height ,
depthOrLayers ,
2019-12-29 18:41:50 +01:00
Info . Levels ,
Info . SamplesInX ,
Info . SamplesInY ,
Info . Stride ,
Info . IsLinear ,
Info . GobBlocksInY ,
Info . GobBlocksInZ ,
Info . GobBlocksInTileX ,
Info . Target ,
Info . FormatInfo ,
Info . DepthStencilMode ,
Info . SwizzleR ,
Info . SwizzleG ,
Info . SwizzleB ,
Info . SwizzleA ) ) ;
TextureCreateInfo createInfo = TextureManager . GetCreateInfo ( Info , _context . Capabilities ) ;
2019-10-13 08:02:07 +02:00
if ( _viewStorage ! = this )
{
ReplaceStorage ( _viewStorage . HostTexture . CreateView ( createInfo , _firstLayer , _firstLevel ) ) ;
}
else
{
2020-07-07 04:41:07 +02:00
ITexture newStorage = _context . Renderer . CreateTexture ( createInfo , ScaleFactor ) ;
2019-10-13 08:02:07 +02:00
2019-10-31 00:45:01 +01:00
HostTexture . CopyTo ( newStorage , 0 , 0 ) ;
2019-10-13 08:02:07 +02:00
ReplaceStorage ( newStorage ) ;
}
}
2020-07-07 04:41:07 +02:00
/// <summary>
/// Blacklists this texture from being scaled. Resets its scale to 1 if needed.
/// </summary>
public void BlacklistScale ( )
{
ScaleMode = TextureScaleMode . Blacklisted ;
SetScale ( 1f ) ;
}
/// <summary>
/// Propagates the scale between this texture and another to ensure they have the same scale.
/// If one texture is blacklisted from scaling, the other will become blacklisted too.
/// </summary>
/// <param name="other">The other texture</param>
public void PropagateScale ( Texture other )
{
if ( other . ScaleMode = = TextureScaleMode . Blacklisted | | ScaleMode = = TextureScaleMode . Blacklisted )
{
BlacklistScale ( ) ;
other . BlacklistScale ( ) ;
}
else
{
// Prefer the configured scale if present. If not, prefer the max.
float targetScale = GraphicsConfig . ResScale ;
float sharedScale = ( ScaleFactor = = targetScale | | other . ScaleFactor = = targetScale ) ? targetScale : Math . Max ( ScaleFactor , other . ScaleFactor ) ;
SetScale ( sharedScale ) ;
other . SetScale ( sharedScale ) ;
}
}
/// <summary>
/// Helper method for copying our Texture2DArray texture to the given target, with scaling.
/// This creates temporary views for each array layer on both textures, copying each one at a time.
/// </summary>
/// <param name="target">The texture array to copy to</param>
private void CopyArrayScaled ( ITexture target )
{
TextureInfo viewInfo = new TextureInfo (
Info . Address ,
Info . Width ,
Info . Height ,
1 ,
Info . Levels ,
Info . SamplesInX ,
Info . SamplesInY ,
Info . Stride ,
Info . IsLinear ,
Info . GobBlocksInY ,
Info . GobBlocksInZ ,
Info . GobBlocksInTileX ,
Target . Texture2D ,
Info . FormatInfo ,
Info . DepthStencilMode ,
Info . SwizzleR ,
Info . SwizzleG ,
Info . SwizzleB ,
Info . SwizzleA ) ;
TextureCreateInfo createInfo = TextureManager . GetCreateInfo ( viewInfo , _context . Capabilities ) ;
for ( int i = 0 ; i < Info . DepthOrLayers ; i + + )
{
ITexture from = HostTexture . CreateView ( createInfo , i , 0 ) ;
ITexture to = target . CreateView ( createInfo , i , 0 ) ;
from . CopyTo ( to , new Extents2D ( 0 , 0 , from . Width , from . Height ) , new Extents2D ( 0 , 0 , to . Width , to . Height ) , true ) ;
2020-09-10 21:44:04 +02:00
from . Release ( ) ;
to . Release ( ) ;
2020-07-07 04:41:07 +02:00
}
}
/// <summary>
/// Sets the Scale Factor on this texture, and immediately recreates it at the correct size.
/// When a texture is resized, a scaled copy is performed from the old texture to the new one, to ensure no data is lost.
/// If scale is equivalent, this only propagates the blacklisted/scaled mode.
/// If called on a view, its storage is resized instead.
/// When resizing storage, all texture views are recreated.
/// </summary>
/// <param name="scale">The new scale factor for this texture</param>
public void SetScale ( float scale )
{
TextureScaleMode newScaleMode = ScaleMode = = TextureScaleMode . Blacklisted ? ScaleMode : TextureScaleMode . Scaled ;
if ( _viewStorage ! = this )
{
_viewStorage . ScaleMode = newScaleMode ;
_viewStorage . SetScale ( scale ) ;
return ;
}
if ( ScaleFactor ! = scale )
{
2020-08-04 01:32:53 +02:00
Logger . Debug ? . Print ( LogClass . Gpu , $"Rescaling {Info.Width}x{Info.Height} {Info.FormatInfo.Format.ToString()} to ({ScaleFactor} to {scale}). " ) ;
2020-07-07 04:41:07 +02:00
TextureCreateInfo createInfo = TextureManager . GetCreateInfo ( Info , _context . Capabilities ) ;
ScaleFactor = scale ;
ITexture newStorage = _context . Renderer . CreateTexture ( createInfo , ScaleFactor ) ;
if ( Info . Target = = Target . Texture2DArray )
{
CopyArrayScaled ( newStorage ) ;
}
else
{
HostTexture . CopyTo ( newStorage , new Extents2D ( 0 , 0 , HostTexture . Width , HostTexture . Height ) , new Extents2D ( 0 , 0 , newStorage . Width , newStorage . Height ) , true ) ;
}
2020-08-04 01:32:53 +02:00
Logger . Debug ? . Print ( LogClass . Gpu , $" Copy performed: {HostTexture.Width}x{HostTexture.Height} to {newStorage.Width}x{newStorage.Height}" ) ;
2020-07-07 04:41:07 +02:00
ReplaceStorage ( newStorage ) ;
// All views must be recreated against the new storage.
foreach ( var view in _views )
{
2020-08-04 01:32:53 +02:00
Logger . Debug ? . Print ( LogClass . Gpu , $" Recreating view {Info.Width}x{Info.Height} {Info.FormatInfo.Format.ToString()}." ) ;
2020-07-07 04:41:07 +02:00
view . ScaleFactor = scale ;
TextureCreateInfo viewCreateInfo = TextureManager . GetCreateInfo ( view . Info , _context . Capabilities ) ;
ITexture newView = HostTexture . CreateView ( viewCreateInfo , view . _firstLayer - _firstLayer , view . _firstLevel - _firstLevel ) ;
view . ReplaceStorage ( newView ) ;
view . ScaleMode = newScaleMode ;
}
}
if ( ScaleMode ! = newScaleMode )
{
ScaleMode = newScaleMode ;
foreach ( var view in _views )
{
view . ScaleMode = newScaleMode ;
}
}
}
2020-09-10 21:44:04 +02:00
/// <summary>
/// Checks if the memory for this texture was modified, and returns true if it was.
/// The modified flags are consumed as a result.
/// </summary>
/// <returns>True if the texture was modified, false otherwise.</returns>
public bool ConsumeModified ( )
{
return _context . PhysicalMemory . QueryModified ( Address , Size , ResourceName . Texture , _modifiedRanges ) > 0 ;
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Synchronizes guest and host memory.
/// This will overwrite the texture data with the texture data on the guest memory, if a CPU
/// modification is detected.
/// Be aware that this can cause texture data written by the GPU to be lost, this is just a
/// one way copy (from CPU owned to GPU owned memory).
/// </summary>
2019-10-13 08:02:07 +02:00
public void SynchronizeMemory ( )
{
2020-04-25 15:02:18 +02:00
// Texture buffers are not handled here, instead they are invalidated (if modified)
// when the texture is bound. This is handled by the buffer manager.
if ( ( _sequenceNumber = = _context . SequenceNumber & & _hasData ) | | Info . Target = = Target . TextureBuffer )
2019-10-13 08:02:07 +02:00
{
return ;
}
_sequenceNumber = _context . SequenceNumber ;
2020-05-04 00:54:50 +02:00
int modifiedCount = _context . PhysicalMemory . QueryModified ( Address , Size , ResourceName . Texture , _modifiedRanges ) ;
2019-10-13 08:02:07 +02:00
2020-07-07 04:41:07 +02:00
if ( _hasData )
2019-10-13 08:02:07 +02:00
{
2020-07-07 04:41:07 +02:00
if ( modifiedCount = = 0 )
{
return ;
}
BlacklistScale ( ) ;
2019-10-13 08:02:07 +02:00
}
2020-05-06 03:02:28 +02:00
ReadOnlySpan < byte > data = _context . PhysicalMemory . GetSpan ( Address , ( int ) Size ) ;
2019-10-13 08:02:07 +02:00
2020-09-10 21:44:04 +02:00
// If the texture was ever modified by the host GPU, we do partial invalidation
2020-03-20 04:17:11 +01:00
// of the texture by getting GPU data and merging in the pages of memory
// that were modified.
// Note that if ASTC is not supported by the GPU we can't read it back since
// it will use a different format. Since applications shouldn't be writing
// ASTC textures from the GPU anyway, ignoring it should be safe.
2020-09-10 21:44:04 +02:00
if ( _everModified & & ! Info . FormatInfo . Format . IsAstc ( ) )
2020-03-20 04:17:11 +01:00
{
2020-09-10 21:44:04 +02:00
Span < byte > gpuData = GetTextureDataFromGpu ( true ) ;
2020-03-20 04:17:11 +01:00
ulong endAddress = Address + Size ;
2020-05-04 00:54:50 +02:00
for ( int i = 0 ; i < modifiedCount ; i + + )
2020-03-20 04:17:11 +01:00
{
2020-05-04 00:54:50 +02:00
( ulong modifiedAddress , ulong modifiedSize ) = _modifiedRanges [ i ] ;
2020-03-20 04:17:11 +01:00
ulong endModifiedAddress = modifiedAddress + modifiedSize ;
if ( modifiedAddress < Address )
{
modifiedAddress = Address ;
}
if ( endModifiedAddress > endAddress )
{
endModifiedAddress = endAddress ;
}
modifiedSize = endModifiedAddress - modifiedAddress ;
int offset = ( int ) ( modifiedAddress - Address ) ;
int length = ( int ) modifiedSize ;
data . Slice ( offset , length ) . CopyTo ( gpuData . Slice ( offset , length ) ) ;
}
data = gpuData ;
}
2020-09-10 21:44:04 +02:00
IsModified = false ;
2020-03-20 04:17:11 +01:00
data = ConvertToHostCompatibleFormat ( data ) ;
HostTexture . SetData ( data ) ;
_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 )
{
2019-12-29 18:41:50 +01:00
if ( Info . IsLinear )
2019-10-13 08:02:07 +02:00
{
data = LayoutConverter . ConvertLinearStridedToLinear (
2019-12-29 18:41:50 +01:00
Info . Width ,
Info . Height ,
Info . FormatInfo . BlockWidth ,
Info . FormatInfo . BlockHeight ,
Info . Stride ,
Info . FormatInfo . BytesPerPixel ,
2019-10-13 08:02:07 +02:00
data ) ;
}
else
{
data = LayoutConverter . ConvertBlockLinearToLinear (
2019-12-29 18:41:50 +01:00
Info . Width ,
Info . Height ,
2019-10-13 08:02:07 +02:00
_depth ,
2019-12-29 18:41:50 +01:00
Info . Levels ,
2019-10-13 08:02:07 +02:00
_layers ,
2019-12-29 18:41:50 +01:00
Info . FormatInfo . BlockWidth ,
Info . FormatInfo . BlockHeight ,
Info . FormatInfo . BytesPerPixel ,
Info . GobBlocksInY ,
Info . GobBlocksInZ ,
Info . GobBlocksInTileX ,
2019-10-13 08:02:07 +02:00
_sizeInfo ,
data ) ;
}
2019-12-29 18:41:50 +01:00
if ( ! _context . Capabilities . SupportsAstcCompression & & Info . FormatInfo . Format . IsAstc ( ) )
2019-10-13 08:02:07 +02:00
{
2019-11-09 01:55:53 +01:00
if ( ! AstcDecoder . TryDecodeToRgba8 (
2019-12-27 07:09:49 +01:00
data . ToArray ( ) ,
2019-12-29 18:41:50 +01:00
Info . FormatInfo . BlockWidth ,
Info . FormatInfo . BlockHeight ,
Info . Width ,
Info . Height ,
2019-10-27 21:51:33 +01:00
_depth ,
2019-12-29 18:41:50 +01:00
Info . Levels ,
2019-11-09 01:55:53 +01:00
out Span < byte > decoded ) )
{
2019-12-29 18:41:50 +01:00
string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}" ;
2019-12-29 00:45:33 +01:00
2020-08-04 01:32:53 +02:00
Logger . Debug ? . Print ( LogClass . Gpu , $"Invalid ASTC texture at 0x{Info.Address:X} ({texInfo})." ) ;
2019-11-09 01:55:53 +01:00
}
data = decoded ;
2019-10-13 08:02:07 +02:00
}
2020-03-20 04:17:11 +01:00
return data ;
2019-10-13 08:02:07 +02:00
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Flushes the texture data.
/// This causes the texture data to be written back to guest memory.
/// If the texture was written by the GPU, this includes all modification made by the GPU
/// up to this point.
2020-01-02 00:14:18 +01:00
/// Be aware that this is an expensive operation, avoid calling it unless strictly needed.
2019-12-30 00:26:37 +01:00
/// This may cause data corruption if the memory is already being used for something else on the CPU side.
/// </summary>
2020-09-10 21:44:04 +02:00
/// <param name="tracked">Whether or not the flush triggers write tracking. If it doesn't, the texture will not be blacklisted for scaling either.</param>
public void Flush ( bool tracked = true )
2020-03-20 04:17:11 +01:00
{
2020-09-10 21:44:04 +02:00
IsModified = false ;
if ( Info . FormatInfo . Format . IsAstc ( ) )
{
return ; // Flushing this format is not supported, as it may have been converted to another host format.
}
if ( tracked )
{
_context . PhysicalMemory . Write ( Address , GetTextureDataFromGpu ( tracked ) ) ;
}
else
{
_context . PhysicalMemory . WriteUntracked ( Address , GetTextureDataFromGpu ( tracked ) ) ;
}
2020-03-20 04:17:11 +01:00
}
/// <summary>
/// Gets data from the host GPU.
/// </summary>
/// <remarks>
/// This method should be used to retrieve data that was modified by the host GPU.
/// This is not cheap, avoid doing that unless strictly needed.
/// </remarks>
/// <returns>Host texture data</returns>
2020-09-10 21:44:04 +02:00
private Span < byte > GetTextureDataFromGpu ( bool blacklist )
2019-10-13 08:02:07 +02:00
{
2020-09-10 21:44:04 +02:00
Span < byte > data ;
if ( blacklist )
{
BlacklistScale ( ) ;
data = HostTexture . GetData ( ) ;
}
else if ( ScaleFactor ! = 1f )
{
float scale = ScaleFactor ;
SetScale ( 1f ) ;
data = HostTexture . GetData ( ) ;
SetScale ( scale ) ;
}
else
{
data = HostTexture . GetData ( ) ;
}
2019-12-05 21:34:47 +01:00
2019-12-29 18:41:50 +01:00
if ( Info . IsLinear )
2019-12-05 21:34:47 +01:00
{
data = LayoutConverter . ConvertLinearToLinearStrided (
2019-12-29 18:41:50 +01:00
Info . Width ,
Info . Height ,
Info . FormatInfo . BlockWidth ,
Info . FormatInfo . BlockHeight ,
Info . Stride ,
Info . FormatInfo . BytesPerPixel ,
2019-12-05 21:34:47 +01:00
data ) ;
}
else
{
data = LayoutConverter . ConvertLinearToBlockLinear (
2019-12-29 18:41:50 +01:00
Info . Width ,
Info . Height ,
2019-12-05 21:34:47 +01:00
_depth ,
2019-12-29 18:41:50 +01:00
Info . Levels ,
2019-12-05 21:34:47 +01:00
_layers ,
2019-12-29 18:41:50 +01:00
Info . FormatInfo . BlockWidth ,
Info . FormatInfo . BlockHeight ,
Info . FormatInfo . BytesPerPixel ,
Info . GobBlocksInY ,
Info . GobBlocksInZ ,
Info . GobBlocksInTileX ,
2019-12-05 21:34:47 +01:00
_sizeInfo ,
data ) ;
}
2019-10-13 08:02:07 +02:00
2020-03-20 04:17:11 +01:00
return data ;
2019-10-13 08:02:07 +02:00
}
2019-12-30 00:26:37 +01:00
/// <summary>
2020-09-01 02:06:27 +02:00
/// This performs a strict comparison, used to check if this texture is equal to the one supplied.
2019-12-30 00:26:37 +01:00
/// </summary>
2020-09-01 02:06:27 +02:00
/// <param name="info">Texture information to compare against</param>
2019-12-30 00:26:37 +01:00
/// <param name="flags">Comparison flags</param>
/// <returns>True if the textures are strictly equal or similar, false otherwise</returns>
2019-10-13 08:02:07 +02:00
public bool IsPerfectMatch ( TextureInfo info , TextureSearchFlags flags )
{
2020-09-01 02:06:27 +02:00
if ( ! TextureCompatibility . FormatMatches ( Info , info , ( flags & TextureSearchFlags . ForSampler ) ! = 0 , ( flags & TextureSearchFlags . ForCopy ) ! = 0 ) )
2019-10-13 08:02:07 +02:00
{
return false ;
}
2020-09-01 02:06:27 +02:00
if ( ! TextureCompatibility . LayoutMatches ( Info , info ) )
2019-10-13 08:02:07 +02:00
{
return false ;
}
2020-09-01 08:58:40 +02:00
if ( ! TextureCompatibility . SizeMatches ( Info , info , ( flags & TextureSearchFlags . Strict ) = = 0 ) )
2019-10-13 08:02:07 +02:00
{
return false ;
}
2020-07-13 13:41:30 +02:00
if ( ( flags & TextureSearchFlags . ForSampler ) ! = 0 | | ( flags & TextureSearchFlags . Strict ) ! = 0 )
2019-10-13 08:02:07 +02:00
{
2020-09-01 02:06:27 +02:00
if ( ! TextureCompatibility . SamplerParamsMatches ( Info , info ) )
2019-10-13 08:02:07 +02:00
{
return false ;
}
}
2020-07-13 13:41:30 +02:00
if ( ( flags & TextureSearchFlags . ForCopy ) ! = 0 )
2019-10-13 08:02:07 +02:00
{
2019-12-29 18:41:50 +01:00
bool msTargetCompatible = Info . Target = = Target . Texture2DMultisample & & info . Target = = Target . Texture2D ;
2019-10-13 08:02:07 +02:00
2020-09-01 02:06:27 +02:00
if ( ! msTargetCompatible & & ! TextureCompatibility . TargetAndSamplesCompatible ( Info , info ) )
2019-10-13 08:02:07 +02:00
{
return false ;
}
}
2020-09-01 02:06:27 +02:00
else if ( ! TextureCompatibility . TargetAndSamplesCompatible ( Info , info ) )
2019-10-13 08:02:07 +02:00
{
return false ;
}
2019-12-29 18:41:50 +01:00
return Info . Address = = info . Address & & Info . Levels = = info . Levels ;
2019-10-13 08:02:07 +02:00
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Check if it's possible to create a view, with the given parameters, from this texture.
/// </summary>
/// <param name="info">Texture view information</param>
/// <param name="size">Texture view size</param>
/// <param name="firstLayer">Texture view initial layer on this texture</param>
/// <param name="firstLevel">Texture view first mipmap level on this texture</param>
2020-09-10 21:44:04 +02:00
/// <returns>The level of compatiblilty a view with the given parameters created from this texture has</returns>
public TextureViewCompatibility IsViewCompatible (
2019-10-31 00:45:01 +01:00
TextureInfo info ,
ulong size ,
out int firstLayer ,
out int firstLevel )
2019-10-13 08:02:07 +02:00
{
// Out of range.
if ( info . Address < Address | | info . Address + size > EndAddress )
{
firstLayer = 0 ;
firstLevel = 0 ;
2020-09-10 21:44:04 +02:00
return TextureViewCompatibility . Incompatible ;
2019-10-13 08:02:07 +02:00
}
int offset = ( int ) ( info . Address - Address ) ;
if ( ! _sizeInfo . FindView ( offset , ( int ) size , out firstLayer , out firstLevel ) )
{
2020-09-10 21:44:04 +02:00
return TextureViewCompatibility . Incompatible ;
2019-10-13 08:02:07 +02:00
}
2020-09-01 02:06:27 +02:00
if ( ! TextureCompatibility . ViewLayoutCompatible ( Info , info , firstLevel ) )
2019-10-13 08:02:07 +02:00
{
2020-09-10 21:44:04 +02:00
return TextureViewCompatibility . Incompatible ;
2019-10-13 08:02:07 +02:00
}
2020-09-01 02:06:27 +02:00
if ( ! TextureCompatibility . ViewFormatCompatible ( Info , info ) )
2019-10-13 08:02:07 +02:00
{
2020-09-10 21:44:04 +02:00
return TextureViewCompatibility . Incompatible ;
2019-10-13 08:02:07 +02:00
}
2020-09-10 21:44:04 +02:00
TextureViewCompatibility result = TextureViewCompatibility . Full ;
2019-10-13 08:02:07 +02:00
2020-09-10 21:44:04 +02:00
result = TextureCompatibility . PropagateViewCompatibility ( result , TextureCompatibility . ViewSizeMatches ( Info , info , firstLevel ) ) ;
result = TextureCompatibility . PropagateViewCompatibility ( result , TextureCompatibility . ViewTargetCompatible ( Info , info ) ) ;
return ( Info . SamplesInX = = info . SamplesInX & &
Info . SamplesInY = = info . SamplesInY ) ? result : TextureViewCompatibility . Incompatible ;
}
2019-10-13 08:02:07 +02:00
2020-09-10 21:44:04 +02:00
/// <summary>
/// Checks if the view format is compatible with this texture format.
/// In general, the formats are considered compatible if the bytes per pixel values are equal,
/// but there are more complex rules for some formats, like compressed or depth-stencil formats.
/// This follows the host API copy compatibility rules.
/// </summary>
/// <param name="info">Texture information of the texture view</param>
/// <returns>True if the formats are compatible, false otherwise</returns>
private bool ViewFormatCompatible ( TextureInfo info )
{
return TextureCompatibility . FormatCompatible ( Info . FormatInfo , info . FormatInfo ) ;
2019-10-13 08:02:07 +02:00
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Gets a texture of the specified target type from this texture.
/// This can be used to get an array texture from a non-array texture and vice-versa.
/// If this texture and the requested targets are equal, then this texture Host texture is returned directly.
/// </summary>
/// <param name="target">The desired target type</param>
/// <returns>A view of this texture with the requested target, or null if the target is invalid for this texture</returns>
2019-10-13 08:02:07 +02:00
public ITexture GetTargetTexture ( Target target )
{
2019-12-29 18:41:50 +01:00
if ( target = = Info . Target )
2019-10-13 08:02:07 +02:00
{
return HostTexture ;
}
if ( _arrayViewTexture = = null & & IsSameDimensionsTarget ( target ) )
{
TextureCreateInfo createInfo = new TextureCreateInfo (
2019-12-29 18:41:50 +01:00
Info . Width ,
Info . Height ,
2019-10-13 08:02:07 +02:00
target = = Target . CubemapArray ? 6 : 1 ,
2019-12-29 18:41:50 +01:00
Info . Levels ,
Info . Samples ,
Info . FormatInfo . BlockWidth ,
Info . FormatInfo . BlockHeight ,
Info . FormatInfo . BytesPerPixel ,
Info . FormatInfo . Format ,
Info . DepthStencilMode ,
2019-10-13 08:02:07 +02:00
target ,
2019-12-29 18:41:50 +01:00
Info . SwizzleR ,
Info . SwizzleG ,
Info . SwizzleB ,
Info . SwizzleA ) ;
2019-10-13 08:02:07 +02:00
ITexture viewTexture = HostTexture . CreateView ( createInfo , 0 , 0 ) ;
_arrayViewTexture = viewTexture ;
_arrayViewTarget = target ;
return viewTexture ;
}
else if ( _arrayViewTarget = = target )
{
return _arrayViewTexture ;
}
return null ;
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Check if this texture and the specified target have the same number of dimensions.
/// For the purposes of this comparison, 2D and 2D Multisample textures are not considered to have
/// the same number of dimensions. Same for Cubemap and 3D textures.
/// </summary>
/// <param name="target">The target to compare with</param>
/// <returns>True if both targets have the same number of dimensions, false otherwise</returns>
2019-10-13 08:02:07 +02:00
private bool IsSameDimensionsTarget ( Target target )
{
2019-12-29 18:41:50 +01:00
switch ( Info . Target )
2019-10-13 08:02:07 +02:00
{
case Target . Texture1D :
case Target . Texture1DArray :
return target = = Target . Texture1D | |
target = = Target . Texture1DArray ;
case Target . Texture2D :
case Target . Texture2DArray :
return target = = Target . Texture2D | |
target = = Target . Texture2DArray ;
case Target . Cubemap :
case Target . CubemapArray :
return target = = Target . Cubemap | |
target = = Target . CubemapArray ;
case Target . Texture2DMultisample :
case Target . Texture2DMultisampleArray :
return target = = Target . Texture2DMultisample | |
target = = Target . Texture2DMultisampleArray ;
case Target . Texture3D :
return target = = Target . Texture3D ;
}
return false ;
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Replaces view texture information.
/// This should only be used for child textures with a parent.
/// </summary>
/// <param name="parent">The parent texture</param>
/// <param name="info">The new view texture information</param>
/// <param name="hostTexture">The new host texture</param>
2020-07-07 04:41:07 +02:00
/// <param name="firstLayer">The first layer of the view</param>
/// <param name="firstLevel">The first level of the view</param>
public void ReplaceView ( Texture parent , TextureInfo info , ITexture hostTexture , int firstLayer , int firstLevel )
2019-10-13 08:02:07 +02:00
{
ReplaceStorage ( hostTexture ) ;
2020-07-07 04:41:07 +02:00
_firstLayer = parent . _firstLayer + firstLayer ;
_firstLevel = parent . _firstLevel + firstLevel ;
2019-10-13 08:02:07 +02:00
parent . _viewStorage . AddView ( this ) ;
SetInfo ( info ) ;
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Sets the internal texture information structure.
/// </summary>
/// <param name="info">The new texture information</param>
2019-10-13 08:02:07 +02:00
private void SetInfo ( TextureInfo info )
{
2019-12-29 18:41:50 +01:00
Info = info ;
2019-10-13 08:02:07 +02:00
_depth = info . GetDepth ( ) ;
_layers = info . GetLayers ( ) ;
}
2020-02-06 22:49:26 +01:00
/// <summary>
/// Signals that the texture has been modified.
/// </summary>
public void SignalModified ( )
{
2020-09-10 21:44:04 +02:00
IsModified = true ;
_everModified = true ;
2020-02-06 22:49:26 +01:00
Modified ? . Invoke ( this ) ;
2020-09-10 21:44:04 +02:00
if ( _viewStorage ! = this )
{
_viewStorage . SignalModified ( ) ;
}
2020-02-06 22:49:26 +01:00
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Replaces the host texture, while disposing of the old one if needed.
/// </summary>
/// <param name="hostTexture">The new host texture</param>
2019-10-13 08:02:07 +02:00
private void ReplaceStorage ( ITexture hostTexture )
{
DisposeTextures ( ) ;
HostTexture = hostTexture ;
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Checks if the texture overlaps with a memory range.
/// </summary>
/// <param name="address">Start address of the range</param>
/// <param name="size">Size of the range</param>
/// <returns>True if the texture overlaps with the range, false otherwise</returns>
2019-10-13 08:02:07 +02:00
public bool OverlapsWith ( ulong address , ulong size )
{
return Address < address + size & & address < EndAddress ;
}
2020-09-10 21:44:04 +02:00
/// <summary>
/// Determine if any of our child textures are compaible as views of the given texture.
/// </summary>
/// <param name="texture">The texture to check against</param>
/// <returns>True if any child is view compatible, false otherwise</returns>
public bool HasViewCompatibleChild ( Texture texture )
{
if ( _viewStorage ! = this | | _views . Count = = 0 )
{
return false ;
}
foreach ( Texture view in _views )
{
if ( texture . IsViewCompatible ( view . Info , view . Size , out int _ , out int _ ) ! = TextureViewCompatibility . Incompatible )
{
return true ;
}
}
return false ;
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Increments the texture reference count.
/// </summary>
2019-10-13 08:02:07 +02:00
public void IncrementReferenceCount ( )
{
_referenceCount + + ;
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Decrements the texture reference count.
/// When the reference count hits zero, the texture may be deleted and can't be used anymore.
/// </summary>
2020-09-10 21:44:04 +02:00
/// <returns>True if the texture is now referenceless, false otherwise</returns>
public bool DecrementReferenceCount ( )
2019-10-13 08:02:07 +02:00
{
2019-10-31 00:45:01 +01:00
int newRefCount = - - _referenceCount ;
if ( newRefCount = = 0 )
2019-10-13 08:02:07 +02:00
{
if ( _viewStorage ! = this )
{
_viewStorage . RemoveView ( this ) ;
}
_context . Methods . TextureManager . RemoveTextureFromCache ( this ) ;
2019-10-31 00:45:01 +01:00
}
Debug . Assert ( newRefCount > = 0 ) ;
2019-10-13 08:02:07 +02:00
2019-10-31 00:45:01 +01:00
DeleteIfNotUsed ( ) ;
2020-09-10 21:44:04 +02:00
return newRefCount < = 0 ;
2019-10-31 00:45:01 +01:00
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Delete the texture if it is not used anymore.
/// The texture is considered unused when the reference count is zero,
/// and it has no child views.
/// </summary>
2019-10-31 00:45:01 +01:00
private void DeleteIfNotUsed ( )
{
// We can delete the texture as long it is not being used
// in any cache (the reference count is 0 in this case), and
// also all views that may be created from this texture were
// already deleted (views count is 0).
if ( _referenceCount = = 0 & & _views . Count = = 0 )
{
2020-07-07 04:41:07 +02:00
Dispose ( ) ;
2019-10-13 08:02:07 +02:00
}
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Performs texture disposal, deleting the texture.
/// </summary>
2019-10-13 08:02:07 +02:00
private void DisposeTextures ( )
{
2020-09-10 21:44:04 +02:00
HostTexture . Release ( ) ;
2019-10-13 08:02:07 +02:00
2020-09-10 21:44:04 +02:00
_arrayViewTexture ? . Release ( ) ;
2019-10-13 08:02:07 +02:00
_arrayViewTexture = null ;
}
2019-12-31 23:09:49 +01:00
2020-09-10 21:44:04 +02:00
/// <summary>
/// Called when the memory for this texture has been unmapped.
/// Calls are from non-gpu threads.
/// </summary>
public void Unmapped ( )
{
IsModified = false ; // We shouldn't flush this texture, as its memory is no longer mapped.
}
2019-12-31 23:09:49 +01:00
/// <summary>
/// Performs texture disposal, deleting the texture.
/// </summary>
public void Dispose ( )
{
DisposeTextures ( ) ;
2020-07-07 04:41:07 +02:00
Disposed ? . Invoke ( this ) ;
2019-12-31 23:09:49 +01:00
}
2019-10-13 08:02:07 +02:00
}
}