@ -12,6 +12,21 @@ using System.ComponentModel;
namespace Avalonia.OpenGL.Controls
namespace Avalonia.OpenGL.Controls
{
{
/// <summary>
/// Base class for controls that render using OpenGL.
/// Provides infrastructure for OpenGL context management, surface creation, and rendering lifecycle.
/// </summary>
/// <remarks>
/// <para>The control automatically manages OpenGL context creation, surface setup, and cleanup.</para>
/// <para>
/// <b>Important:</b> Any interaction with <see cref="GlInterface"/> should only happen within the
/// <see cref="OnOpenGlInit"/>, <see cref="OnOpenGlDeinit"/>, or <see cref="OnOpenGlRender"/> method overrides.
/// </para>
/// <para>
/// Avalonia ensures proper OpenGL context synchronization and makes the context current only during these method calls.
/// Accessing OpenGL functions outside of these methods may result in undefined behavior, crashes, or rendering corruption.
/// </para>
/// </remarks>
public abstract class OpenGlControlBase : Control
public abstract class OpenGlControlBase : Control
{
{
private CompositionSurfaceVisual ? _ visual ;
private CompositionSurfaceVisual ? _ visual ;
@ -23,8 +38,15 @@ namespace Avalonia.OpenGL.Controls
[MemberNotNullWhen(true, nameof(_resources))]
[MemberNotNullWhen(true, nameof(_resources))]
private bool IsInitializedSuccessfully = > _ initialization is { Status : TaskStatus . RanToCompletion , Result : true } ;
private bool IsInitializedSuccessfully = > _ initialization is { Status : TaskStatus . RanToCompletion , Result : true } ;
/// <summary>
/// Gets the OpenGL version information for the current context.
/// </summary>
protected GlVersion GlVersion = > _ resources ? . Context . Version ? ? default ;
protected GlVersion GlVersion = > _ resources ? . Context . Version ? ? default ;
/// <summary>
/// Initializes a new instance of the <see cref="OpenGlControlBase"/> class.
/// </summary>
public OpenGlControlBase ( )
public OpenGlControlBase ( )
{
{
_ update = Update ;
_ update = Update ;
@ -57,12 +79,14 @@ namespace Avalonia.OpenGL.Controls
_ initialization = null ;
_ initialization = null ;
}
}
/// <inheritdoc/>
protected override void OnDetachedFromVisualTree ( VisualTreeAttachmentEventArgs e )
protected override void OnDetachedFromVisualTree ( VisualTreeAttachmentEventArgs e )
{
{
DoCleanup ( ) ;
DoCleanup ( ) ;
base . OnDetachedFromVisualTree ( e ) ;
base . OnDetachedFromVisualTree ( e ) ;
}
}
/// <inheritdoc/>
protected override void OnAttachedToVisualTree ( VisualTreeAttachmentEventArgs e )
protected override void OnAttachedToVisualTree ( VisualTreeAttachmentEventArgs e )
{
{
base . OnAttachedToVisualTree ( e ) ;
base . OnAttachedToVisualTree ( e ) ;
@ -114,7 +138,8 @@ namespace Avalonia.OpenGL.Controls
return true ;
return true ;
}
}
/// <inheritdoc/>
protected override void OnPropertyChanged ( AvaloniaPropertyChangedEventArgs change )
protected override void OnPropertyChanged ( AvaloniaPropertyChangedEventArgs change )
{
{
if ( _ visual ! = null & & change . Property = = BoundsProperty )
if ( _ visual ! = null & & change . Property = = BoundsProperty )
@ -220,10 +245,14 @@ namespace Avalonia.OpenGL.Controls
return true ;
return true ;
}
}
/// <inheritdoc cref="Visual.InvalidateVisual"/>
[Obsolete("Use RequestNextFrameRendering()"), EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Use RequestNextFrameRendering()"), EditorBrowsable(EditorBrowsableState.Never)]
// ReSharper disable once MemberCanBeProtected.Global
// ReSharper disable once MemberCanBeProtected.Global
public new void InvalidateVisual ( ) = > RequestNextFrameRendering ( ) ;
public new void InvalidateVisual ( ) = > RequestNextFrameRendering ( ) ;
/// <summary>
/// Requests that the control be rendered on the next frame.
/// </summary>
public void RequestNextFrameRendering ( )
public void RequestNextFrameRendering ( )
{
{
if ( ( _ initialization = = null | | IsInitializedSuccessfully ) & &
if ( ( _ initialization = = null | | IsInitializedSuccessfully ) & &
@ -240,22 +269,38 @@ namespace Avalonia.OpenGL.Controls
return new PixelSize ( Math . Max ( 1 , ( int ) ( Bounds . Width * scaling ) ) ,
return new PixelSize ( Math . Max ( 1 , ( int ) ( Bounds . Width * scaling ) ) ,
Math . Max ( 1 , ( int ) ( Bounds . Height * scaling ) ) ) ;
Math . Max ( 1 , ( int ) ( Bounds . Height * scaling ) ) ) ;
}
}
/// <summary>
/// Called when the OpenGL context is first created.
/// </summary>
/// <param name="gl">The interface for making OpenGL calls. Use <see cref="GlInterface.GetProcAddress"/> to access additional APIs not covered by <see cref="GlInterface"/>.</param>
protected virtual void OnOpenGlInit ( GlInterface gl )
protected virtual void OnOpenGlInit ( GlInterface gl )
{
{
}
}
/// <summary>
/// Called when the OpenGL context is being destroyed.
/// </summary>
/// <param name="gl">The OpenGL interface for making OpenGL calls. Use <see cref="GlInterface.GetProcAddress"/> to access additional APIs not covered by <see cref="GlInterface"/>.</param>
protected virtual void OnOpenGlDeinit ( GlInterface gl )
protected virtual void OnOpenGlDeinit ( GlInterface gl )
{
{
}
}
/// <summary>
/// Called when the OpenGL context is lost and cannot be recovered.
/// </summary>
protected virtual void OnOpenGlLost ( )
protected virtual void OnOpenGlLost ( )
{
{
}
}
/// <summary>
/// Called to render the OpenGL content for the current frame.
/// </summary>
/// <param name="gl">The OpenGL interface for making OpenGL calls. Use <see cref="GlInterface.GetProcAddress"/> to access additional APIs not covered by <see cref="GlInterface"/>.</param>
/// <param name="fb">The framebuffer ID to render into.</param>
protected abstract void OnOpenGlRender ( GlInterface gl , int fb ) ;
protected abstract void OnOpenGlRender ( GlInterface gl , int fb ) ;
}
}
}
}