using NativeEngine;
namespace Sandbox;
///
/// A texture is an image used in rendering. Can be a static texture loaded from disk, or a dynamic texture rendered to by code.
/// Can also be 2D, 3D (multiple slices), or a cube texture (6 slices).
///
[SkipHotload]
[ResourceType( "vtex" )]
public partial class Texture : Resource, IDisposable
{
internal ITexture native;
private CTextureDesc _desc;
bool gotdesc;
///
/// Allow the texture to keep a reference to its parent object (like a videoplayer).
///
internal object ParentObject;
///
/// Has the native handle changed?
///
internal bool IsDirty;
///
/// Whether this texture is an error or invalid or not.
///
public bool IsError => native.IsNull || !native.IsStrongHandleValid() || native.IsError();
public override bool IsValid => native.IsValid;
///
/// Flags providing hints about this texture
///
public TextureFlags Flags { get; set; } = TextureFlags.None;
///
/// Private constructor, use
///
private Texture( ITexture native )
{
if ( native.IsNull ) throw new Exception( "Texture pointer cannot be null!" );
this.native = native;
if ( native.IsStrongHandleValid() )
{
lock ( LoadedByPointer )
{
IntPtr targetPointer = native.GetBindingPtr();
LoadedByPointer[targetPointer] = new WeakReference( this );
}
}
UpdateSheetInfo();
}
~Texture()
{
Dispose();
}
///
/// Texture index. Bit raw dog and needs a higher level abstraction.
///
public int Index => g_pRenderDevice.GetTextureViewIndex( native, 0, RenderTextureDimension.RENDER_TEXTURE_DIMENSION_2D );
///
/// Replace our strong handle with a copy of the strong handle of the passed texture
/// Which means that this texture will invisibly become that texture.
/// I suspect that there might be a decent way to do this in native using the resource system.
/// In which case we should change all this code to use that way instead of doing this.
///
internal void CopyFrom( Texture texture )
{
// Important - dispose the old handle, we're done with it!
// if we don't do this, we'll leak memory!
Dispose();
// Copy the handle from the other texture.
// Important - we can't just use the handle because when
// they release it, it'll be a hanging pointer!
native = texture.native.CopyStrongHandle();
gotdesc = false;
_desc = default;
IsDirty = true;
}
internal CTextureDesc Desc
{
get
{
if ( gotdesc ) return _desc;
gotdesc = true;
_desc = g_pRenderDevice.GetOnDiskTextureDesc( native );
return _desc;
}
}
///
/// Width of the texture in pixels.
///
public int Width => Desc.m_nWidth;
///
/// Height of the texture in pixels.
///
public int Height => Desc.m_nHeight;
///
/// Depth of a 3D texture in pixels, or slice count for 2D texture arrays, or 6 for slices of cubemap.
///
public int Depth => Desc.IsCube ? 6 : Desc.m_nDepth;
///
/// Number of mip maps this texture has.
///
public int Mips => Desc.m_nNumMipLevels;
///
/// Returns a Vector2 representing the size of the texture (width, height)
///
public Vector2 Size => new( Width, Height );
///
/// Whether this texture has finished loading or not.
///
public bool IsLoaded { get; internal set; } = true;
///
/// Image format of this texture.
///
public ImageFormat ImageFormat => Desc.m_nImageFormat;
///
/// Returns how many frames ago this texture was last used by the renderer
///
public int LastUsed => native.IsValid ? g_pRenderDevice.GetTextureLastUsed( native ).Clamp( 0, 1000 ) : 1000;
internal RenderMultisampleType MultisampleType
{
get
{
return native.IsValid ? g_pRenderDevice.GetTextureMultisampleType( native ) : RenderMultisampleType.RENDER_MULTISAMPLE_NONE;
}
}
///
/// Will release the handle for this texture. If the texture isn't referenced by anything
/// else it'll be released properly. This will happen anyway because it's called in the destructor.
/// By calling it manually you're just telling the engine you're done with this texture right now
/// instead of waiting for the garbage collector.
///
public void Dispose()
{
if ( !native.IsNull )
{
if ( native.IsStrongHandleValid() )
{
IntPtr targetPointer = native.GetBindingPtr();
lock ( LoadedByPointer )
{
LoadedByPointer.Remove( targetPointer );
}
}
native.DestroyStrongHandle();
native = IntPtr.Zero;
}
}
internal void TryReload( BaseFileSystem filesystem, string filename )
{
//
// Try to load the texture again, make a new texture
//
var newTex = TryToLoad( filesystem, filename, false );
//
// If success, copy from this texture
//
if ( newTex != null )
{
CopyFrom( newTex );
}
}
///
/// If this texture is a sprite sheet, will return information about the sheet, which
/// is generally used in the shader. You don't really need to think about the contents.
///
public Vector4 SequenceData
{
get
{
// I think this would be safe to cache off, right?
return g_pRenderDevice.GetSheetInfo( native );
}
}
///
/// The count of sequences in this texture, if any. The rest of the sequence data is encoded into the texture itself.
///
public int SequenceCount { get; private set; }
class SequenceInfo
{
public float Length { get; set; }
public int FrameCount { get; internal set; }
public bool Looped { get; internal set; }
}
SequenceInfo[] sequences;
///
/// Get the frame count for this sequence
///
public int GetSequenceFrameCount( int sequenceId )
{
if ( sequences is null ) return 0;
if ( sequenceId < 0 ) return 0;
if ( sequenceId >= sequences.Length ) return 0;
return sequences[sequenceId].FrameCount;
}
///
/// Get the total length of this seqence
///
private float GetSequenceLength( int sequenceId )
{
if ( sequences is null ) return 0;
if ( sequenceId < 0 ) return 0;
if ( sequenceId >= sequences.Length ) return 0;
return sequences[sequenceId].Length;
}
///
/// TODO: Fill this out, build a structure of Sequence[] for people to access
/// Then make it so we can actually preview them
///
private void UpdateSheetInfo()
{
HasAnimatedSequences = false;
SequenceCount = g_pRenderDevice.GetSequenceCount( native );
if ( SequenceCount <= 0 ) return;
var s = new List();
for ( int i = 0; i < SequenceCount; i++ )
{
var info = new SequenceInfo();
s.Add( info );
SheetSequence_t seq = g_pRenderDevice.GetSequence( native, i );
info.Length = seq.m_flTotalTime;
info.FrameCount = seq.FrameCount();
info.Looped = !seq.m_bClamp;
HasAnimatedSequences = HasAnimatedSequences || info.FrameCount > 1;
}
sequences = s.ToArray();
}
public bool HasAnimatedSequences { get; private set; }
///
/// Tells texture streaming this texture is being used.
/// This is usually automatic, but useful for bindless pipelines.
///
public void MarkUsed( int requiredMipSize = 0 )
{
g_pRenderDevice.MarkTextureUsed( native, requiredMipSize );
}
internal bool IsRenderTarget => g_pRenderDevice.IsTextureRenderTarget( native );
///
/// Mark this texture as loading, create a texture in a task, replace this texture with it, mark it as loaded.
/// This is for situations where you create a placeholder texture then replace it with the real texture later.
///
internal async Task ReplacementAsync( Task task )
{
IsLoaded = false;
//
// Wait for the new texture to exist
//
var texture = await task;
//
// replace us with the new texture
//
if ( texture is not null && texture != this )
{
this.CopyFrom( texture );
}
IsLoaded = true;
}
}
///
/// Flags providing hints about a texture
///
public enum TextureFlags
{
None = 0,
///
/// Hint that this texture has pre-multiplied alpha
///
PremultipliedAlpha = 1 << 0,
}