XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
using XFESystem.Desktop;

namespace XFESystem.Platform;

public sealed record DesktopScanout(DesktopDisplay Display, IFramebufferPresenter Presenter);

/// <summary>One desktop scene, independently copied to each real output with its own pitch and format.</summary>
public sealed class MultiDisplayPresenter : IFramebufferPresenter
{
    private readonly DesktopScanout[] outputs;
    private readonly int[][] scanoutPixels;
    private readonly int scalePercent;
    private DesktopDisplayLayout layout;
    private bool fullDamage = true, identifying;
    public int Width => layout.Width * scalePercent / 100;
    public int Height => layout.Height * scalePercent / 100;
    public DesktopDisplayLayout Layout => layout;

    public MultiDisplayPresenter(IEnumerable<DesktopScanout> outputs, DesktopDisplayLayout layout, int scalePercent)
    {
        this.outputs = outputs.ToArray(); this.layout = layout; this.scalePercent = scalePercent;
        if (this.outputs.Length is < 1 or > 8) throw new ArgumentException("One to eight outputs are supported.", nameof(outputs));
        foreach (var output in this.outputs)
            if (output.Display.NativeWidth != output.Presenter.Width || output.Display.NativeHeight != output.Presenter.Height)
                throw new ArgumentException("The display and scanout geometries differ.", nameof(outputs));
        scanoutPixels = this.outputs.Select(output => new int[checked(output.Presenter.Width * output.Presenter.Height)]).ToArray();
        PixelGeometry.ValidateSize(Width, Height);
    }

    public void Configure(DesktopDisplayLayout next) { layout = next; fullDamage = true; }
    public void Identify(bool visible) { if (identifying != visible) { identifying = visible; fullDamage = true; } }

    public void Present(ReadOnlySpan<int> rgbPixels, DesktopBounds nativeDamage)
    {
        if (rgbPixels.Length < (long)Width * Height) throw new ArgumentException("The virtual desktop buffer is incomplete.", nameof(rgbPixels));
        bool full = fullDamage || identifying;
        for (int index = 0; index < outputs.Length; index++)
        {
            var output = outputs[index]; int width = output.Presenter.Width, height = output.Presenter.Height;
            int[] target = scanoutPixels[index];
            DesktopDisplayScreen? screen = null;
            for (int candidate = 0; candidate < layout.Screens.Count; candidate++)
                if (layout.Screens[candidate].Display.Id == output.Display.Id)
                { screen = layout.Screens[candidate]; break; }
            var damage = new DesktopBounds(0, 0, width, height);
            if (screen is null)
            {
                if (!full) continue;
                target.AsSpan().Clear();
            }
            else
            {
                var b = screen.Bounds;
                var source = new DesktopBounds(b.X * scalePercent / 100, b.Y * scalePercent / 100,
                    b.Width * scalePercent / 100, b.Height * scalePercent / 100);
                if (!identifying && source == new DesktopBounds(0, 0, width, height) && Width == width && Height == height)
                {
                    // The normal one-screen and equal-size mirror paths keep
                    // their direct scanout, with no extra full-frame copy.
                    output.Presenter.Present(rgbPixels, full ? damage : nativeDamage);
                    continue;
                }
                int destinationWidth = width, destinationHeight = height;
                if ((long)width * source.Height > (long)height * source.Width) destinationWidth = Math.Max(1, (int)((long)height * source.Width / source.Height));
                else destinationHeight = Math.Max(1, (int)((long)width * source.Height / source.Width));
                var destination = new DesktopBounds((width - destinationWidth) / 2, (height - destinationHeight) / 2, destinationWidth, destinationHeight);
                var inputDamage = full ? source : PixelGeometry.Intersect(nativeDamage, source);
                if (inputDamage.Width == 0 || inputDamage.Height == 0) continue;
                if (full) target.AsSpan().Clear();
                int left = Math.Max(destination.X, destination.X + (int)((long)(inputDamage.X - source.X) * destinationWidth / source.Width) - 1);
                int top = Math.Max(destination.Y, destination.Y + (int)((long)(inputDamage.Y - source.Y) * destinationHeight / source.Height) - 1);
                int right = Math.Min(destination.X + destinationWidth, destination.X + (int)(((long)inputDamage.X + inputDamage.Width - source.X) * destinationWidth / source.Width) + 1);
                int bottom = Math.Min(destination.Y + destinationHeight, destination.Y + (int)(((long)inputDamage.Y + inputDamage.Height - source.Y) * destinationHeight / source.Height) + 1);
                for (int y = top; y < bottom; y++)
                {
                    int sy = source.Y + (int)((long)(y - destination.Y) * source.Height / destinationHeight);
                    if (destinationWidth == source.Width)
                        rgbPixels.Slice(sy * Width + source.X + left - destination.X, right - left).CopyTo(target.AsSpan(y * width + left, right - left));
                    else
                        for (int x = left; x < right; x++)
                        {
                            int sx = source.X + (int)((long)(x - destination.X) * source.Width / destinationWidth);
                            target[y * width + x] = rgbPixels[sy * Width + sx];
                        }
                }
                if (!full) damage = new(left, top, right - left, bottom - top);
            }
            if (identifying) DrawIdentity(target, width, height, index + 1);
            output.Presenter.Present(target, damage);
        }
        fullDamage = false;
    }

    // A small dependency-free digit mask remains distinct on mirrored outputs.
    private static void DrawIdentity(int[] pixels, int width, int height, int number)
    {
        ReadOnlySpan<ushort> glyphs = [0, 0x2492, 0x73e7, 0x73cf, 0x5bc9, 0x79cf, 0x79ef, 0x7249, 0x7bef];
        const int cell = 14, boxWidth = 112, boxHeight = 110;
        int left = Math.Max(0, (width - boxWidth) / 2), top = Math.Max(0, (height - boxHeight) / 2);
        for (int y = top; y < top + boxHeight && y < height; y++)
            pixels.AsSpan(y * width + left, Math.Min(boxWidth, width - left)).Fill(0x17394f);
        for (int row = 0; row < 5; row++)
            for (int column = 0; column < 3; column++)
                if ((glyphs[number] & 1 << (14 - row * 3 - column)) != 0)
                    for (int y = top + 20 + row * cell; y < top + 20 + (row + 1) * cell && y < height; y++)
                        pixels.AsSpan(y * width + left + 35 + column * cell, cell).Fill(0xe7f3fc);
    }
}

/// <summary>Applies the output topology and resizes the CPU canvas as one UI-thread operation.</summary>
public sealed class FramebufferDisplayManager : IDesktopDisplayManager
{
    private CpuFramebufferCanvas? canvas;
    public IReadOnlyList<DesktopDisplay> Displays { get; }
    public DesktopDisplayConfiguration Current { get; private set; }
    public DesktopDisplayLayout Layout => Presenter.Layout;
    public int ScalePercent { get; }
    public MultiDisplayPresenter Presenter { get; }
    public string DetectionMessage => "Firmware outputs detected at startup. Reconnect displays, then restart to detect changes.";
    public event Action? Changed;

    public FramebufferDisplayManager(IEnumerable<DesktopScanout> scanouts, int scalePercent)
    {
        if (scalePercent is < 100 or > 400) throw new ArgumentOutOfRangeException(nameof(scalePercent));
        var outputs = scanouts.ToArray();
        Displays = Array.AsReadOnly(outputs.Select(s => s.Display).ToArray()); ScalePercent = scalePercent;
        if (outputs.Length is < 1 or > 8) throw new ArgumentException("No supported display outputs.", nameof(scanouts));
        Current = DesktopDisplayLayout.Default(Displays, scalePercent);
        Presenter = new(outputs, DesktopDisplayLayout.Create(Displays, Current, scalePercent), scalePercent);
    }
    public void AttachCanvas(CpuFramebufferCanvas canvas) => this.canvas = canvas;
    public bool TryApply(DesktopDisplayConfiguration configuration, out string? error)
    {
        error = null;
        DesktopDisplayLayout previous = Layout;
        try
        {
            var next = DesktopDisplayLayout.Create(Displays, configuration, ScalePercent);
            Presenter.Configure(next);
            try { canvas?.Resize(); }
            catch { Presenter.Configure(previous); throw; }
            Current = configuration;
        }
        catch (Exception failure) when (failure is ArgumentException or OverflowException or OutOfMemoryException)
        { error = failure is OutOfMemoryException ? "There is not enough memory for this display arrangement." : failure.Message; return false; }
        Changed?.Invoke();
        return true;
    }
    public void Identify(bool visible) => Presenter.Identify(visible);
    public void Detect() { /* This backend has an immutable firmware snapshot, not a hotplug driver. */ }
}