Skip to content

Introduced a better opengl context management API on top of existing interop APIs#21853

Draft
kekekeks wants to merge 1 commit into
mainfrom
feature/opengl-image-api
Draft

Introduced a better opengl context management API on top of existing interop APIs#21853
kekekeks wants to merge 1 commit into
mainfrom
feature/opengl-image-api

Conversation

@kekekeks

@kekekeks kekekeks commented Jul 24, 2026

Copy link
Copy Markdown
Member

Supercedes #14653

This PR allows:

  1. creation of long-lived OpenGL contexts that aren't bound to a particular control
  2. presentation of OpenGL textures created with such contexts via CompositionDrawingSurface without touching low-level graphics interop APIs

The existing OpenGLControlBase.cs is refactored to use the new API internally.

/// <summary>
/// Provides OpenGL interop facilities for the composition engine.
/// </summary>
public static class OpenGlCompositionInterop
{
    /// <summary>
    /// Attempts to create an OpenGL context that is suitable for interop with the compositor's GPU context.
    /// Must be called on the thread the compositor belongs to.
    /// </summary>
    /// <returns>
    /// The created context, or null when the current platform or compositor configuration doesn't
    /// support OpenGL interop. The concrete reason is logged to the "OpenGL" log area.
    /// </returns>
    public static async ValueTask<ICompositionGlContext?> TryCreateCompatibleGlContextAsync(
        this Compositor compositor, CompositionGlContextOptions? options = null);
}


/// <summary>
/// Options for <see cref="OpenGlCompositionInterop.TryCreateCompatibleGlContextAsync"/>.
/// </summary>
public class CompositionGlContextOptions
{
    /// <summary>
    /// The list of desired OpenGL(ES) versions in order of preference.
    /// </summary>
    public IReadOnlyList<GlVersion>? GlProfiles { get; set; }
}

/// <summary>
/// An OpenGL context that is suitable for interop with a <see cref="Rendering.Composition.Compositor"/>.
/// Create instances via <see cref="OpenGlCompositionInterop.TryCreateCompatibleGlContextAsync"/>.
/// </summary>
/// <remarks>
/// All members can only be used on the thread the associated <see cref="Rendering.Composition.Compositor"/>
/// belongs to.
/// </remarks>
[NotClientImplementable]
public interface ICompositionGlContext : IAsyncDisposable
{
    /// <summary>
    /// The associated compositor.
    /// </summary>
    Compositor Compositor { get; }

    /// <summary>
    /// The OpenGL context.
    /// </summary>
    IGlContext GlContext { get; }

    /// <summary>
    /// Returns true if this OpenGL context is still suitable for interop with the Compositor.
    /// This property might become false in cases like a device loss event on the compositor side.
    /// In that case presentation is no longer possible, however <see cref="GlContext"/> itself might
    /// still be usable for salvaging user resources. Once the property becomes false, this instance
    /// should be disposed and recreated via
    /// <see cref="OpenGlCompositionInterop.TryCreateCompatibleGlContextAsync"/>.
    /// A recreated context shares no OpenGL objects with the old one.
    /// </summary>
    bool IsValidForInterop { get; }

    /// <summary>
    /// Creates a texture that can be used to update a <see cref="CompositionDrawingSurface"/> instance.
    /// </summary>
    ICompositionGlTexture CreateTexture(CompositionDrawingSurface surface, PixelSize size);
}

/// <summary>
/// A texture that can be drawn to by user code and presented to a <see cref="CompositionDrawingSurface"/>.
/// Create instances via <see cref="ICompositionGlContext.CreateTexture"/>.
/// </summary>
/// <remarks>
/// All members can only be used on the thread the associated <see cref="Compositor"/> belongs to.
/// </remarks>
[NotClientImplementable]
public interface ICompositionGlTexture : IAsyncDisposable
{
    /// <summary>
    /// The pixel size of the texture.
    /// </summary>
    PixelSize Size { get; }

    /// <summary>
    /// Returns true when the texture can be drawn to: there is no active lease and the last
    /// presentation, if any, has completed successfully.
    /// A presentation failure leaves this property permanently false, check
    /// <see cref="ICompositionGlContext.IsValidForInterop"/> in that case.
    /// </summary>
    bool IsReadyForDraw { get; }

    /// <summary>
    /// Prepares the texture to be rendered to by user code.
    /// User code can bind the texture to their framebuffer, however the texture should be unbound
    /// before ending the returned lease.
    /// </summary>
    /// <exception cref="InvalidOperationException">
    /// The texture is not ready for drawing (<see cref="IsReadyForDraw"/> is false) or the owning
    /// context is no longer valid for interop.
    /// </exception>
    /// <exception cref="ObjectDisposedException">The texture has been disposed.</exception>
    ICompositionGlTextureLease BeginDraw();
}

/// <summary>
/// An active drawing session on an <see cref="ICompositionGlTexture"/>.
/// The lease is ended either by <see cref="PresentAsync"/> or by <see cref="IDisposable.Dispose"/>,
/// the latter discards the frame. Disposing after <see cref="PresentAsync"/> is a no-op.
/// </summary>
[NotClientImplementable]
public interface ICompositionGlTextureLease : IDisposable
{
    /// <summary>
    /// Describes the OpenGL texture to draw into. Only valid until the lease ends;
    /// <see cref="CompositionGlTextureInfo.TextureId"/> is not guaranteed to be stable between leases.
    /// </summary>
    CompositionGlTextureInfo Texture { get; }

    /// <summary>
    /// Ends the lease and asynchronously presents the texture to the associated
    /// <see cref="CompositionDrawingSurface"/>. This will happen when the next composition commit
    /// is processed by the render thread.
    /// The texture must be unbound from user framebuffers before calling this method.
    /// The returned task completes when the texture can be drawn to again, NOT when the frame
    /// becomes visible on the screen.
    /// You should NOT await the returned task while keeping hold of a
    /// <see cref="IGlContext.MakeCurrent"/> disposable.
    /// </summary>
    Task PresentAsync();
}

/// <summary>
/// Describes an OpenGL texture provided by <see cref="ICompositionGlTextureLease"/>.
/// </summary>
public readonly record struct CompositionGlTextureInfo(int TextureId, int Target, int InternalFormat, PixelSize Size);

@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.2.999-cibuild0067753-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants