OverviewFilesCommitsIssuesDocsReleasesPeers

ifsrenderer-linux / AvaloniaShell / Rendering / IRenderQueue.cs

86 lines · csharp ref:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

using IFSEngine.Model;

namespace IFSRenderer.AvaloniaShell.Rendering;

/// <summary>
/// The renderer as the windows see it: a plugin catalogue, the open fractal, a work queue
/// onto the render thread, and the counters the status bar reports.
/// </summary>
/// <remarks>
/// <para>
/// <see cref="RenderService"/> is the real implementation, and owns an OpenGL context pinned
/// to its own thread. Depending on this interface instead lets the UI be constructed and
/// driven with no GPU, no window and no GL — which is what makes the windows testable.
/// </para>
/// <para>
/// <b>Ordering is the load-bearing part of this contract.</b> <see cref="Post"/> only QUEUES:
/// it returns before the engine has run the action, so anything that must observe the result
/// belongs INSIDE the action, not on the line after it. Getting that wrong is what made
/// node-graph and post-effect edits appear one action late.
/// </para>
/// </remarks>
public interface IRenderQueue : IDisposable
{
    /// <summary>Loads the plugin library, then starts the render thread.</summary>
    Task StartAsync(string libraryDir, string transformsDir);

    /// <summary>Re-reads the plugin files from disk and recompiles the kernel.</summary>
    Task ReloadPluginsAsync(string libraryDir, string transformsDir);

    IReadOnlyList<TransformPlugin> Transforms { get; }
    IReadOnlyList<EffectPlugin> Effects { get; }

    /// <summary>The open fractal, or null before one has been loaded.</summary>
    IFS Current { get; }

    /// <summary>Makes a fractal current and sizes the preview to the viewport.</summary>
    void LoadFractal(IFS fractal, int viewportWidth, int viewportHeight);

    /// <summary>Queues an action to run on the render thread. Returns immediately.</summary>
    void Post(Action<IRenderer> action);

    /// <summary>Queues an action and completes once it has run.</summary>
    Task PostAsync(Action<IRenderer> action);

    void SetViewport(int width, int height, bool fillViewport = true);

    /// <summary>Guards <see cref="FrameBuffer"/> against the UI reading a torn frame.</summary>
    object FrameLock { get; }

    /// <summary>Bottom-up BGRA8 pixels, <see cref="FrameWidth"/> x <see cref="FrameHeight"/>.</summary>
    byte[] FrameBuffer { get; }
    int FrameWidth { get; }
    int FrameHeight { get; }

    event Action FrameReady;
    event Action<Exception> Failed;

    string GpuName { get; }

    /// <summary>The exception that stopped the render loop, or null while it is healthy.</summary>
    Exception LastError { get; }

    ulong TotalIterations { get; }
    int IterationLevel { get; }
    double Fps { get; }
    double IterationSpeed { get; }
    int InvocationIters { get; }
    int WorkgroupCount { get; }
    bool Paused { get; set; }

    Task SetWorkgroupCountAsync(int count);

    Task ExportAsync(string path, int width, int height, int targetLevel,
        Action<int> onLevel = null, ExportMetadata metadata = null);

    Task ExportFramesAsync(string directory, int width, int height, int targetLevel,
        Action<int, int> onProgress, Func<bool> isCancelled, bool rawExr = false,
        Action<byte[], int, int> frameSink = null);

    Task<byte[]> RenderThumbnailAsync(IFS candidate, int width, int height, int level);
}