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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text.Json;
using Cosmos.Kernel.System.Graphics;
using XFESystem.Browser;
using XFESystem.Desktop;
using XFESystem.Kernel;
using XfePaintBaseline;

System.Runtime.Loader.AssemblyLoadContext.Default.Resolving += (_, name) =>
{
    string path = Path.Combine(AppContext.BaseDirectory, name.Name + ".dll");
    return File.Exists(path) ? System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath(path) : null;
};
Probe.Run(args);

static class Probe
{
    [MethodImpl(MethodImplOptions.NoInlining)]
    public static void Run(string[] args)
    {
        string root = Path.GetFullPath(args.Length > 0 ? args[0] : ".");
        string output = Path.GetFullPath(args.Length > 1 ? args[1] : Path.Combine(root, "build", "PaintPerformance-" + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()));
        Directory.CreateDirectory(output);
        var baseline = Create(typeof(BaselineCanvasAdapter), root);
        var current = Create(typeof(CanvasAdapter), root);
        string markdown = File.ReadAllText(Path.Combine(root, "Source/XFESystem.Documents/Assets/Website/Fixtures/WebsiteParity.md"));
        var results = new List<object>();
        foreach (bool dark in new[] { true, false })
        {
            var browser = new BrowserService();
            browser.SetCanvas((IDesktopCanvas)current);
            browser.NavigateDocument(markdown, "WebsiteParity.md", dark);
            var list = browser.Layout(new(796, 404));
            var viewport = new DesktopBounds(0, 0, 796, 404);
            int[] before = Pixels(baseline), after = Pixels(current);
            Array.Clear(before); Array.Clear(after);
            var oldCold = Time(() => BaselinePagePainter.Paint((IDesktopCanvas)baseline, viewport, list));
            var newCold = Time(() => BrowserRasterPainter.Paint((IDesktopCanvas)current, viewport, list));
            string oldHash = Hash(before), newHash = Hash(after);
            int differing = before.Zip(after).Count(pair => pair.First != pair.Second);
            if (differing != 0) throw new InvalidOperationException("Raster pixels changed: " + differing);
            var snapshot = BrowserPaintSnapshot.Capture(list);
            Array.Clear(after); snapshot.Paint((IDesktopCanvas)current, viewport);
            if (Hash(after) != newHash) throw new InvalidOperationException("Frozen DOM-free snapshot changed actual font/CSS raster pixels.");
            const int iterations = 8;
            var oldWarm = Time(() => { for (int i = 0; i < iterations; i++) BaselinePagePainter.Paint((IDesktopCanvas)baseline, viewport, list); });
            var newWarm = Time(() => { for (int i = 0; i < iterations; i++) BrowserRasterPainter.Paint((IDesktopCanvas)current, viewport, list); });
            var native = (CanvasAdapter)current;
            native.BeginScene(viewport); native.StoreWebFrame(dark ? 1 : 2, 1, 1, viewport, 0, 0);
            var expected = (int[])after.Clone(); Array.Fill(after, -123);
            if (!native.TryRestoreWebFrame(dark ? 1 : 2, 1, 1, viewport, 0, 0)) throw new Exception("Native website frame cache missed its full capture.");
            for (int y = 0; y < 900; y++) for (int x = 0; x < 1600; x++)
                if (after[y * 1600 + x] != (x < 1592 && y < 808 ? expected[y * 1600 + x] : -123)) throw new Exception("Native website cache pixels changed.");
            const int restoreIterations = 64;
            double restoreTime = Time(() => { for (int i = 0; i < restoreIterations; i++) native.TryRestoreWebFrame(dark ? 1 : 2, 1, 1, viewport, 0, 0); }) / restoreIterations;
            results.Add(new { Dark = dark, Commands = list.Commands.Count, DocumentHeight = list.DocumentHeight,
                NativeWidth = 1600, NativeHeight = 900, CssViewport = viewport, ScalePercent = 200,
                BaselineColdMilliseconds = oldCold, CurrentColdMilliseconds = newCold,
                Iterations = iterations, BaselineWarmMillisecondsPerFrame = oldWarm / iterations,
                CurrentWarmMillisecondsPerFrame = newWarm / iterations, WarmSpeedup = oldWarm / newWarm,
                FrameCacheRestoreMillisecondsPerFrame = restoreTime, FrameCacheRestoredPixelsExact = true,
                BaselinePixelsSha256 = oldHash, CurrentPixelsSha256 = newHash, DifferingPixels = differing, ExactPixels = true,
                FrozenSnapshotPixelsExact = true, FrozenSnapshotNodeReferences = snapshot.Commands.Count(command => command.Node is not null) });
        }
        var live = (CanvasAdapter)current;
        var measures = new List<object>();
        foreach (int weight in new[] { 400, 700 })
        foreach (string family in new[] { "Consolas", "system-ui", "bootstrap-icons" })
            foreach (string text in new[] { "WWW iii AV Tof = 42;", "中文文档预览,这是测试。", "using System; var value = 3;", "\uf475\uf30a\uf14d\uf759" })
            {
                var old = (IDesktopWebCanvas)baseline;
                double expected = old.MeasureWebText(text, 14.25, family, weight);
                double actual = live.MeasureWebText(text, 14.25, family, weight);
                if (actual != expected) throw new Exception("Font widths changed.");
                var a = old.RasterizeWebText(text, 14.25, family, weight); var b = live.RasterizeWebText(text, 14.25, family, weight);
                if (a.Width != b.Width || a.Height != b.Height || !a.Alpha.AsSpan().SequenceEqual(b.Alpha)) throw new Exception("Font alpha changed.");
                b.Alpha.AsSpan().Fill(0); var again = live.RasterizeWebText(text, 14.25, family, weight);
                if (!a.Alpha.AsSpan().SequenceEqual(again.Alpha)) throw new Exception("Writable caller mask corrupted cached data.");
                measures.Add(new { Family = family, Weight = weight, Text = text, Width = actual, MaskSha256 = Convert.ToHexStringLower(SHA256.HashData(a.Alpha)), Exact = true, WritableCopyIsolated = true });
            }
        var clock = Stopwatch.StartNew();
        for (int i = 0; i < 4096; i++) live.MeasureWebText("Unique" + i, 1 + i % 128, "Consolas", 400);
        for (int i = 0; i < 1100; i++) live.RasterizeWebText("Mask" + i, 14, "Consolas", 400);
        if (live.CachedWebTextMaskCount > 1024 || live.CachedWebTextMaskBytes > 2097152) throw new Exception("Text mask cache exceeded its bound.");
        var counts = new Dictionary<string,int>();
        foreach(var pair in new[]{("webGlyphPresence",8192),("webAdvances",8192),("webKernings",4096),("webTextWidths",2048)})
        {
            var dictionary=(System.Collections.IDictionary)typeof(CanvasAdapter).GetField(pair.Item1,BindingFlags.NonPublic|BindingFlags.Instance)!.GetValue(live)!;
            counts[pair.Item1]=dictionary.Count;if(dictionary.Count>pair.Item2)throw new Exception("Font cache exceeded bound: "+pair.Item1);
        }
        var result = new { Schema = "XfeSoftwarePaintPerformance1", Passed = true, HostOnly = true, GuestAcceptance = false,
            Note = "Executes production software painter, CanvasAdapter and pinned Cosmos TrueTypeFont on a private host framebuffer. Frozen pre-optimization baseline shares the exact same website display list. Timings are host CPU timings, not QEMU FPS or GPU evidence.",
            Cases = results, FontChecks = measures,
            CacheBounds = new { live.CachedWebTextMaskCount, live.CachedWebTextMaskBytes, MaximumEntries = 1024, MaximumBytes = 2097152, OtherCacheEntries = counts, live.WebTextMeasureCacheHits, live.WebTextMaskCacheHits, Passed = true },
            NativeFrameCache = CheckNativeFrameCache(root),
            NativeRenderYield = CheckNativeRenderYield(root),
            NativeAbortAndCoverage = CheckNativeAbortAndCoverage(root),
            NativeMotionFrameCache = CheckNativeMotionFrameCache(root),
            NativeEditorPreparationHandoff = CheckEditorPreparationHandoff(root),
            SourceHashes = new[] { "Source/XFESystem.Browser/BrowserBoxDecoration.cs", "Source/XFESystem.Browser/BrowserCssGradient.cs", "Source/XFESystem.Browser/BrowserClipRasterCanvas.cs", "Source/XFESystem.Kernel/CanvasAdapter.cs", "Source/XFESystem.Desktop/IDesktopWebFrameCache.cs", "Source/XFESystem.Desktop/IDesktopRenderYieldCanvas.cs", "Source/XFESystem.Kernel/NativeKeyboardCapture.cs", "Source/XFESystem.Kernel/NativePointerCapture.cs" }
                .ToDictionary(path => path, path => Convert.ToHexStringLower(SHA256.HashData(File.ReadAllBytes(Path.Combine(root,path))))) };
        string json = JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true });
        File.WriteAllText(Path.Combine(output, "Result.json"), json); Console.WriteLine(json);
    }
    private static double Time(Action action) { var timer = Stopwatch.StartNew(); action(); return timer.Elapsed.TotalMilliseconds; }
    private static string Hash(int[] pixels) => Convert.ToHexStringLower(SHA256.HashData(System.Runtime.InteropServices.MemoryMarshal.AsBytes(pixels.AsSpan())));
    private static int[] Pixels(object target) => (int[])target.GetType().GetField("pixels", BindingFlags.NonPublic | BindingFlags.Instance)!.GetValue(target)!;
    private static object CheckEditorPreparationHandoff(string root)
    {
        var prepared = (CanvasAdapter)Create(typeof(CanvasAdapter), root);
        var main = (CanvasAdapter)Create(typeof(CanvasAdapter), root);
        var cold = (CanvasAdapter)Create(typeof(CanvasAdapter), root);
        var mismatch = (CanvasAdapter)Create(typeof(CanvasAdapter), root);
        void Set(CanvasAdapter target, string name, object value) => typeof(CanvasAdapter).GetField(name, BindingFlags.NonPublic | BindingFlags.Instance)!.SetValue(target, value);
        int Count(CanvasAdapter target, string name) => ((System.Collections.IDictionary)typeof(CanvasAdapter).GetField(name, BindingFlags.NonPublic | BindingFlags.Instance)!.GetValue(target)!).Count;
        Set(prepared, "preparationOnly", true); Set(mismatch, "scale", 1);
        Array.Fill(Pixels(prepared), -123); Array.Fill(Pixels(main), -123);
        string preparedPixels = Hash(Pixels(prepared)), mainPixels = Hash(Pixels(main));
        var samples = new[] { (Text: "using System; public class XFE { int value = 42; }", Family: "Consolas", Weight: 400),
            (Text: "中文 C# Preview", Family: "system-ui", Weight: 700), (Text: "\uf475\uf30a", Family: "bootstrap-icons", Weight: 400) };
        foreach (var sample in samples)
        {
            prepared.MeasureWebText(sample.Text, 13, sample.Family, sample.Weight);
            prepared.PrepareEditorText(sample.Text, 13, sample.Family, sample.Weight);
            prepared.RasterizeWebText(sample.Text, 13, sample.Family, sample.Weight);
        }
        prepared.MeasureWebText("中文", 13, "system-ui", 400);
        main.MeasureWebText("中文", 13, "system-ui", 400);
        object CjkFont(CanvasAdapter value)
        {
            var fonts = (System.Collections.IDictionary)typeof(CanvasAdapter).GetField("webFonts", BindingFlags.NonPublic | BindingFlags.Instance)!.GetValue(value)!;
            object face = fonts[10]!;
            return face.GetType().GetProperty("Font")!.GetValue(face)!;
        }
        var preparedFont = (Cosmos.Kernel.System.Graphics.Fonts.TrueTypeFont)CjkFont(prepared);
        var mainFont = (Cosmos.Kernel.System.Graphics.Fonts.TrueTypeFont)CjkFont(main);
        if (ReferenceEquals(preparedFont, mainFont) || !ReferenceEquals(preparedFont.Data, mainFont.Data)) throw new Exception("Font resource bytes are duplicated or mutable font caches are shared.");
        string resourceHash = Convert.ToHexStringLower(SHA256.HashData(preparedFont.Data));
        prepared.PrepareEditorText("中文", 13, "system-ui", 400);
        prepared.RasterizeWebText("中文", 13, "system-ui", 400);
        main.RasterizeWebText("中文", 13, "system-ui", 400);
        if (resourceHash != Convert.ToHexStringLower(SHA256.HashData(mainFont.Data))) throw new Exception("The pinned font parser/rasterizer mutated shared font resource bytes.");
        int warmedGlyphs = Count(prepared, "webGlyphs");
        if (warmedGlyphs == 0 || preparedPixels != Hash(Pixels(prepared)) || mainPixels != Hash(Pixels(main))) throw new Exception("Editor preparation did not warm real glyphs without touching pixels.");
        if (prepared.TryCompleteEditorPreparationHandoff(mismatch) || main.TryCompleteEditorPreparationHandoff(prepared) || prepared.TryCompleteEditorPreparationHandoff(prepared)) throw new Exception("Editor handoff accepted an incompatible canvas.");
        if (!prepared.TryCompleteEditorPreparationHandoff(main) || !prepared.TryCompleteEditorPreparationHandoff(main) || prepared.TryCompleteEditorPreparationHandoff(cold)) throw new Exception("Editor handoff was not exclusive and idempotent.");
        if (warmedGlyphs != Count(main, "webGlyphs") || preparedPixels != Hash(Pixels(prepared)) || mainPixels != Hash(Pixels(main))) throw new Exception("Editor handoff lost glyphs or changed pixels.");
        Array.Clear(Pixels(main)); Array.Clear(Pixels(cold)); main.BeginScene(new(0, 0, 800, 450)); cold.BeginScene(new(0, 0, 800, 450));
        long hitsBefore = main.WebTextMeasureCacheHits;
        int glyphsBefore = Count(main, "webGlyphs"), y = 4;
        foreach (var sample in samples)
        {
            if (main.MeasureWebText(sample.Text, 13, sample.Family, sample.Weight) != cold.MeasureWebText(sample.Text, 13, sample.Family, sample.Weight)) throw new Exception("Prepared metrics differ from the actual main canvas.");
            main.WebText(sample.Text, 0xAC99EA, 4, y, 13, sample.Family, sample.Weight);
            cold.WebText(sample.Text, 0xAC99EA, 4, y, 13, sample.Family, sample.Weight);
            y += 24;
        }
        if (Hash(Pixels(main)) != Hash(Pixels(cold)) || glyphsBefore != Count(main, "webGlyphs") || main.WebTextMeasureCacheHits - hitsBefore < samples.Length) throw new Exception("Prepared native glyph handoff changed pixels or missed the scanout glyph cache.");
        bool rejectedAfterHandoff = false;
        try { prepared.PrepareEditorText("not owned by worker", 13, "Consolas", 400); } catch (InvalidOperationException) { rejectedAfterHandoff = true; }
        if (!rejectedAfterHandoff) throw new Exception("Worker preparation remained available after ownership transfer.");
        var mask = main.RasterizeWebText(samples[0].Text, 13, "Consolas", 400); string maskHash = Convert.ToHexStringLower(SHA256.HashData(mask.Alpha)); mask.Alpha.AsSpan().Clear();
        if (maskHash != Convert.ToHexStringLower(SHA256.HashData(main.RasterizeWebText(samples[0].Text, 13, "Consolas", 400).Alpha))) throw new Exception("A writable caller corrupted a transferred mask.");
        return new { Passed = true, ActualTrueTypeFonts = true, ScalePercent = 200, CssFontSize = 13, NativeGlyphSize = 26,
            PreparedNativeGlyphs = warmedGlyphs, ExactColdRasterPixels = true, NoGlyphRasterizationAfterHandoff = true,
            PreparationAndHandoffDoNotWritePixels = true, IncompatibleTargetRejected = true, ExclusiveAndIdempotent = true,
            WorkerPreparationRejectedAfterHandoff = true, WritableMaskIsolated = true,
            SharedImmutableCjkBytes = preparedFont.Data.Length, SharedFontResourceHashUnchanged = true,
            IndependentMutableTrueTypeFontObjects = true };
    }
    private static object CheckNativeFrameCache(string root)
    {
        var native = (CanvasAdapter)Create(typeof(CanvasAdapter), root); var pixels = Pixels(native);
        for (int i = 0; i < pixels.Length; i++) pixels[i] = unchecked(i * 123457);
        var original = (int[])pixels.Clone(); var bounds = new DesktopBounds(10, 20, 30, 25);
        native.BeginScene(bounds); native.StoreWebFrame(9, 10, 11, bounds, 0, .25);
        Array.Fill(pixels, -123); var damage = new DesktopBounds(21, 27, 2, 3); native.BeginScene(damage);
        if (native.WebPaintClip != damage || !native.TryRestoreWebFrame(9, 10, 11, bounds, 0, .25)) throw new Exception("Native damage restore missed.");
        for (int y = 0; y < 900; y++) for (int x = 0; x < 1600; x++)
            if (pixels[y * 1600 + x] != (x >= 42 && x < 46 && y >= 54 && y < 60 ? original[y * 1600 + x] : -123)) throw new Exception("Native damage restore touched incorrect pixels.");
        Array.Fill(pixels, -123); var moved = bounds with { X = 30, Y = 30 }; native.BeginScene(moved);
        if (!native.TryRestoreWebFrame(9, 10, 11, moved, 0, .25)) throw new Exception("Window movement invalidated native frame.");
        for (int y = 0; y < 900; y++) for (int x = 0; x < 1600; x++)
            if (pixels[y * 1600 + x] != (x >= 60 && x < 120 && y >= 60 && y < 110 ? original[(y - 20) * 1600 + x - 40] : -123)) throw new Exception("Native moved restore pixels changed.");
        native.BeginScene(damage); native.StoreWebFrame(9, 12, 11, moved, 0, .25);
        if (native.TryRestoreWebFrame(9, 12, 11, moved, 0, .25) || native.TryRestoreWebFrame(9, 10, 12, moved, 0, .25) ||
            native.TryRestoreWebFrame(9, 10, 11, moved, 0, .5)) throw new Exception("Native stale/partial cache frame was accepted.");
        if(native.CachedWebFrameCount != 1 || native.CachedWebFrameBytes != 12000 || native.WebFramePixelsRestored != 3024) throw new Exception("Native cache accounting changed.");
        return new { Passed = true, DamagePixelsExact = true, MovedPixelsExact = true, PartialCaptureRejected = true,
            RevisionLayoutScrollMissesVerified = true, native.CachedWebFrameCount, native.CachedWebFrameBytes, native.WebFrameRestoreHits, native.WebFramePixelsRestored };
    }
    private static object CheckNativeRenderYield(string root)
    {
        NativeKeyboardCapture.Prepare();while(NativeKeyboardCapture.TryRead(out _,out _,out _)){ }
        NativePointerCapture.Prepare();while(NativePointerCapture.TryRead(out _)){ }
        NativePointerCapture.Observe(0,0,false,false,false,0,0);while(NativePointerCapture.TryRead(out _)){ }
        var native=(CanvasAdapter)Create(typeof(CanvasAdapter),root);var pixels=Pixels(native);Array.Fill(pixels,-123);
        native.BeginScene(new(0,0,800,450));NativeKeyboardCapture.Observe(0x5B,false);bool yielded=false;
        try{native.BlendCoverage(0xFFFFFF,0,0,1,4,new byte[]{255,255,255,255});}catch(DesktopRenderYieldException){yielded=true;}
        if(!yielded||pixels.Any(value=>value!=-123)||!NativeKeyboardCapture.HasPendingReports)throw new Exception("Native keyboard render yield consumed input or changed a row.");
        if(!NativeKeyboardCapture.TryRead(out var scan,out var released,out var lost)||scan!=0x5B||released||lost)throw new Exception("Native keyboard yield altered raw report.");
        NativePointerCapture.Observe(20,30,false,false,false,0,1);
        if(native.RenderYieldRequested)throw new Exception("Ordinary pointer motion cancelled native paint.");
        native.BlendCoverage(0xFFFFFF,0,0,1,1,new byte[]{255});
        if(!NativePointerCapture.TryRead(out var motion)||motion.X!=20||motion.Y!=30)throw new Exception("Native paint consumed motion.");
        NativePointerCapture.Observe(20,30,true,false,false,0,2);yielded=false;
        try{native.BlendCoverage(0xFFFFFF,0,1,1,1,new byte[]{255});}catch(DesktopRenderYieldException){yielded=true;}
        if(!yielded||pixels[1600]!=-123||native.FramesPresented!=0||native.CachedWebFrameCount!=0)throw new Exception("Cancelled native row was cached/presented or painted.");
        if(!NativePointerCapture.TryRead(out var down)||!down.LeftButton)throw new Exception("Native render yield consumed the button make.");
        NativePointerCapture.Observe(20,30,false,false,false,0,3);while(NativePointerCapture.TryRead(out _)){ }
        return new{Passed=true,KeyboardAndButtonYield=true,PendingReportsNotConsumed=true,OrdinaryMotionPaints=true,
            CancelledRowUnmodified=true,CancelledFrameNotCachedOrPresented=true};
    }
    private static object CheckNativeAbortAndCoverage(string root)
    {
        var native=(CanvasAdapter)Create(typeof(CanvasAdapter),root);
        var area=new DesktopBounds(4,3,12,5);
        native.BeginScene(new(0,0,800,450));native.PreserveTranslatedSceneRegion(area,area.X,area.Y,true);native.EndScenePreservation();
        long PendingPixels()
        {
            object rectangle=typeof(CanvasAdapter).GetField("unchangedScanout",BindingFlags.NonPublic|BindingFlags.Instance)!.GetValue(native)!;
            return (long)(int)rectangle.GetType().GetProperty("Width")!.GetValue(rectangle)!*(int)rectangle.GetType().GetProperty("Height")!.GetValue(rectangle)!;
        }
        long normalPending=PendingPixels();if(normalPending!=240)throw new Exception("Normal scene end lost the valid scanout optimization.");
        native.AbortScenePreservation();native.BeginScene(new(0,0,800,450));
        if(PendingPixels()!=0)throw new Exception("Cancelled frame retained a stale scanout copy exclusion.");
        var comparisons=new List<object>();var baseline=(BaselineCanvasAdapter)Create(typeof(BaselineCanvasAdapter),root);
        int[] old=Pixels(baseline),current=Pixels(native);var random=new Random(74071);
        for(int test=0;test<6;test++)
        {
            for(int i=0;i<old.Length;i++)old[i]=current[i]=random.Next();
            baseline.BeginScene(new(0,0,800,450));native.BeginScene(new(0,0,800,450));
            if((test&1)!=0)
            {
                baseline.SaveScene();native.SaveScene();baseline.PreserveTranslatedSceneRegion(area,8,4);native.PreserveTranslatedSceneRegion(area,8,4);
            }
            const int width=157,height=33;var mask=new byte[width*height];
            for(int i=0;i<mask.Length;i++)mask[i]=test/2==0?(byte)255:test/2==1?(byte)0:i%64<8?(byte)0:i%64<40?(byte)255:(byte)(1+i%254);
            baseline.BlendCoverage(0x8A72D5,-11,-3,width,height,mask);native.BlendCoverage(0x8A72D5,-11,-3,width,height,mask);
            if(!old.AsSpan().SequenceEqual(current))throw new Exception("Native zero/opaque/mixed coverage changed clipped or motion-preserved pixels.");
            comparisons.Add(new{Case=test,MotionPreserved=(test&1)!=0,AlphaMode=test/2==0?"Opaque":test/2==1?"Transparent":"Mixed",PixelsSha256=Hash(current),ExactPixels=true});
        }
        baseline.EndScenePreservation();native.EndScenePreservation();
        var opaque=new byte[1280*720];Array.Fill(opaque,(byte)255);const int iterations=16;
        double oldTime=Time(()=>{for(int i=0;i<iterations;i++)baseline.BlendCoverage(0x8A72D5,0,0,1280,720,opaque);})/iterations;
        double newTime=Time(()=>{for(int i=0;i<iterations;i++)native.BlendCoverage(0x8A72D5,0,0,1280,720,opaque);})/iterations;
        if(!old.AsSpan().SequenceEqual(current))throw new Exception("Native opaque benchmark changed pixels.");
        return new{Passed=true,NormalEndKeepsScanoutSkipPixels=normalPending,AbortClearsScanoutSkipPixels=PendingPixels(),
            CoverageComparisons=comparisons,BaselineOpaqueMilliseconds=oldTime,CurrentOpaqueMilliseconds=newTime,OpaqueSpeedup=oldTime/newTime,
            HostOnly=true,NativeFrameAcceptance=false};
    }
    private static object CheckNativeMotionFrameCache(string root)
    {
        var native=(CanvasAdapter)Create(typeof(CanvasAdapter),root);var pixels=Pixels(native);
        const int blue=unchecked((int)0xff0000ff),red=unchecked((int)0xffef3344);
        Array.Fill(pixels,blue);var viewport=new DesktopBounds(0,0,40,30);native.BeginScene(new(0,0,800,450));
        native.StoreWebFrame(1,1,1,viewport,0,0);
        for(int y=6;y<16;y++)for(int x=8;x<32;x++)pixels[y*1600+x]=red;
        native.SaveScene();native.PreserveTranslatedSceneRegion(new(4,3,12,5),6,4);
        if(!native.TryRestoreWebFrame(1,1,1,viewport,0,0))throw new Exception("Protected background cache missed.");
        for(int y=0;y<60;y++)for(int x=0;x<80;x++)
            if(pixels[y*1600+x]!=(x>=12&&x<36&&y>=8&&y<18?red:blue))throw new Exception("Background frame cache overwrote a moved foreground window.");
        if(native.WebFramePixelsRestored!=4560)throw new Exception("Protected motion pixels were counted as actual restored pixels.");
        native.Fill(0x0000ff,0,0,40,30);native.StoreWebFrame(2,1,1,viewport,0,0);
        if(native.WebFrameCaptures!=1)throw new Exception("Protected foreground pixels were captured as a background browser frame.");
        native.AbortScenePreservation();native.BeginScene(new(0,0,800,450));Array.Fill(pixels,0);
        if(native.TryRestoreWebFrame(2,1,1,viewport,0,0))throw new Exception("A partly obscured viewport was cached.");
        if(!native.TryRestoreWebFrame(1,1,1,viewport,0,0))throw new Exception("Rejected motion capture evicted the good frame.");
        for(int y=0;y<60;y++)for(int x=0;x<80;x++)if(pixels[y*1600+x]!=blue)throw new Exception("Good browser frame contains foreground pixels.");
        return new{Passed=true,ForegroundPreservedPixels=240,ActualRestoredPixelsBeforeAbort=4560,
            BackgroundCaptureRejected=true,GoodFramePreserved=true,ActualRestoredPixelsTotal=native.WebFramePixelsRestored,
            Captures=native.WebFrameCaptures,HostOnly=true,NativeFrameAcceptance=false};
    }
    private static object Create(Type type, string root)
    {
        // Avoid firmware/HAL initialization on the host. Only the backing canvas,
        // font dictionaries and clipping state used by the real painter are set.
        object target = RuntimeHelpers.GetUninitializedObject(type);
        const int width = 1600, height = 900;
        var surface = new Canvas(width, height); int[] pixels = surface.GetBuffer()!; var scene = new int[pixels.Length];
        void Set(string name, object value) => type.GetField(name, BindingFlags.NonPublic | BindingFlags.Instance)!.SetValue(target, value);
        foreach (var field in type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
        {
            if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) field.SetValue(target, Activator.CreateInstance(field.FieldType));
            else if(field.FieldType == typeof(DesktopWebFrameCache)) field.SetValue(target,new DesktopWebFrameCache());
        }
        Set("canvas", surface); Set("scale", 2); Set("pixels", pixels); Set("scene", scene);
        Set("motion", new DesktopSceneMotionBuffer(pixels, scene, width, height));
        Set("unicode", File.ReadAllBytes(Path.Combine(root, "assets/branding/console/xfe-unicode.bin")));
        var clip = type.GetField("clip", BindingFlags.NonPublic | BindingFlags.Instance)!;
        clip.SetValue(target, Activator.CreateInstance(clip.FieldType, 0, 0, width, height));
        var clips = type.GetField("webClips", BindingFlags.NonPublic | BindingFlags.Instance)!;
        clips.SetValue(target, Activator.CreateInstance(clips.FieldType));
        return target;
    }
}