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, }