using System.Drawing;
using System.Diagnostics;
using Cosmos.Kernel.HAL.Devices.Display;
using Cosmos.Kernel.System.Graphics;
using Cosmos.Kernel.System.Graphics.Fonts;
using XFESystem.Desktop;
namespace XFESystem.Kernel;
/// <summary>Native pixels, cached glyph coverage, scene restoration and bounded damaged scanout.</summary>
internal sealed class BaselineCanvasAdapter : IBrandedDesktopCanvas, IDesktopIconCanvas, IDesktopCoverageCanvas, IDesktopSceneMotionCanvas, IDesktopWebCanvas
{
private readonly Canvas canvas = Canvas.GetFullScreen();
private readonly TrueTypeFont font;
private readonly int scale;
private readonly int[] pixels, scene;
private readonly DesktopSceneMotionBuffer motion;
private readonly byte[] unicode = ResourceBytes("XFESystem.Kernel.Unifont.bin");
private readonly byte[] brand = ResourceBytes("XFESystem.Kernel.Icon.rgba");
private readonly byte[] atlas = ResourceBytes("XFESystem.Kernel.DesktopIcons.rgba");
private readonly Dictionary<char, Glyph> glyphs = [];
private readonly Dictionary<(int Icon, int Size), int[]> iconCache = [];
private readonly IDisplay? scanout;
private readonly DisplayDevice? device;
private PixelRect clip;
private readonly Stack<PixelRect> webClips = [];
private readonly Dictionary<(char Character, int Size, int Face, bool Bold), WebGlyphCoverage> webGlyphs = [];
private readonly Dictionary<int, WebFontFace> webFonts = [];
private readonly Dictionary<(string Family,int Weight),WebFontFace> webFontRequests=[];
private readonly Dictionary<(int Primary,char Character,bool Bold),WebFontFace> webFallbackFaces=[];
private sealed record WebFontFace(int Id,string Family,TrueTypeFont Font,NativeWebFontMetrics Metrics,bool SyntheticBold);
private sealed record WebGlyphCoverage(int Width,int Height,int OffsetX,byte[] Alpha);
private DesktopBounds unchangedScanout;
public long FramesPresented { get; private set; }
public long PixelsPresented { get; private set; }
public long PartialFrames { get; private set; }
public long FullCopyFallbackFrames { get; private set; }
public long LastPresentTicks { get; private set; }
public int CachedGlyphCount => glyphs.Count;
public long MotionReuseCount => motion.ReuseCount;
public long MotionPixelsReused => motion.PixelsReused;
public long ScanoutPixelsSaved { get; private set; }
private sealed record Glyph(int Width, int Height, byte[] Alpha);
private readonly record struct PixelRect(int X, int Y, int Width, int Height);
private static readonly (int First, int Last, int Offset)[] GlyphRanges =
[
(128, 2047, 0), (8192, 8303, 63360), (8592, 8703, 67056),
(12288, 12351, 70752), (13312, 40959, 72864), (65280, 65519, 985248),
(65533, 65533, 993168)
];
public BaselineCanvasAdapter()
{
using var resource = typeof(BaselineCanvasAdapter).Assembly.GetManifestResourceStream("XFESystem.Kernel.CascadiaMono.ttf")
?? throw new InvalidOperationException("The embedded desktop font is missing.");
scale = canvas.Width >= 3000 || canvas.Height >= 1800 ? 2 : 1;
font = new TrueTypeFont(resource, 13 * scale);
pixels = canvas.GetBuffer() ?? throw new InvalidOperationException("The desktop canvas is not buffered.");
scene = new int[pixels.Length];
motion = new(pixels, scene, canvas.Width, canvas.Height);
clip = new(0, 0, canvas.Width, canvas.Height);
device = DisplayManager.Primary;
if (device is { } primary && primary.TryGetFacet<IDisplay>(out var display)) scanout = display;
if (atlas.Length != 9 * 64 * 64 * 4) throw new InvalidOperationException("Desktop icon atlas is invalid.");
}
public int Width => canvas.Width / scale;
public int Height => canvas.Height / scale;
public int NativeWidth => canvas.Width;
public int NativeHeight => canvas.Height;
public int ScalePercent => scale * 100;
// Cosmos firmware fabricates a fallback 60 Hz and GOP provides no active
// scanout clock. Only a real mode-setting driver's report can be confirmed.
public int DriverRefreshHz => device?.IsFirmware == false ? scanout?.Mode.RefreshRate ?? 0 : 0;
public bool SupportsSceneCache => true;
public int ToLayout(int nativePosition) => nativePosition / scale;
public int TextWidth(string text)
{
int width = 0;
foreach (char character in text) width += character < 128 ? 8 : unicode[GlyphOffset(character)];
return width;
}
public void BeginScene(DesktopBounds damage) { motion.End(); webClips.Clear(); clip = NativeRect(damage); }
public void PushClip(DesktopBounds bounds) { webClips.Push(clip); clip = Intersect(clip, NativeRect(bounds)); }
public void PopClip() { if (!webClips.TryPop(out clip)) throw new InvalidOperationException("Unbalanced web clipping."); }
public double MeasureWebText(string text, double fontSize, string fontFamily, int fontWeight)
{
var face=WebFace(fontFamily,fontWeight);double size=Math.Clamp(fontSize,1,256),width=0;char previous='\0';
foreach(char ch in text)
{
if(previous!='\0'&&face.Font.HasGlyph(previous)&&face.Font.HasGlyph(ch))width+=WebKerning(face,previous,ch,size);
width+=WebAdvance(face,ch,size);previous=ch;
}
return width;
}
public double MeasureWebLineHeight(double fontSize,string fontFamily,int fontWeight)=>WebFace(fontFamily,fontWeight).Metrics.NaturalLineToEm*Math.Clamp(fontSize,1,256);
public string DescribeWebFont(string requested,int weight)=>requested+" -> "+WebFace(requested,weight).Family;
public string WebFontMetricsEvidence=>"Sans="+DescribeWebFont("system-ui",400)+";SansW="+MeasureWebText("WWW",16,"system-ui",400)+";SansI="+MeasureWebText("iii",16,"system-ui",400)+";Mono="+DescribeWebFont("Consolas",400)+";MonoW="+MeasureWebText("WWW",16,"Consolas",400)+";MonoI="+MeasureWebText("iii",16,"Consolas",400)+";Cjk="+WebCjkFace(false).Family+";CjkGlyph="+WebCjkFace(false).Font.HasGlyph('文')+";CjkBoldGlyph="+WebCjkFace(true).Font.HasGlyph('文')+";CjkAdvance="+MeasureWebText("中文",16,"system-ui",400);
public (int Width, int Height, byte[] Alpha) RasterizeWebText(string text, double fontSize, string fontFamily, int fontWeight)
{
if (text.Length > 4096) throw new ArgumentOutOfRangeException(nameof(text));
// Canvas bitmaps use CSS pixels, independently of desktop UI scale.
int size = Math.Max(1, (int)Math.Round(Math.Clamp(fontSize, 1, 256)));var face=WebFace(fontFamily,fontWeight);
int width = Math.Max(1, (int)Math.Ceiling(MeasureWebText(text, fontSize, fontFamily, fontWeight)));
int height = Math.Max(1, (int)Math.Ceiling(MeasureWebLineHeight(size,fontFamily,fontWeight)));
var alpha = new byte[checked(width * height)]; double left = 0;char previous='\0';
foreach (char ch in text)
{
if(previous!='\0'&&face.Font.HasGlyph(previous)&&face.Font.HasGlyph(ch))left+=WebKerning(face,previous,ch,size);
var glyph = WebGlyph(ch, size, face); int x = (int)Math.Round(left)+glyph.OffsetX;
for (int row = 0; row < Math.Min(height, glyph.Height); row++) for (int col = Math.Max(0,-x); col < Math.Min(glyph.Width, width - x); col++)
alpha[row * width + x + col] = Math.Max(alpha[row * width + x + col], glyph.Alpha[row * glyph.Width + col]);
left += WebAdvance(face,ch,size);previous=ch;
}
return (width, height, alpha);
}
public void WebText(string text, uint rgb, double x, double y, double fontSize, string fontFamily, int fontWeight, double opacity = 1)
{
double size = Math.Clamp(fontSize, 1, 128), left = x * scale;var face=WebFace(fontFamily,fontWeight);char previous='\0';
int nativeSize = Math.Max(1, (int)Math.Round(size * scale)), top = (int)Math.Round(y * scale);
foreach (char ch in text)
{
if(previous!='\0'&&face.Font.HasGlyph(previous)&&face.Font.HasGlyph(ch))left+=WebKerning(face,previous,ch,size)*scale;
double advance = WebAdvance(face,ch,size)*scale;
if (left >= clip.X + clip.Width) break;
if (left + advance > clip.X)
{
var glyph = WebGlyph(ch, nativeSize, face);
if (opacity >= 1) BlendCoverage(rgb, (int)Math.Round(left)+glyph.OffsetX, top, glyph.Width, glyph.Height, glyph.Alpha);
else if (opacity > 0)
{
var alpha = new byte[glyph.Alpha.Length];
for (int i = 0; i < alpha.Length; i++) alpha[i] = (byte)(glyph.Alpha[i] * opacity);
BlendCoverage(rgb, (int)Math.Round(left)+glyph.OffsetX, top, glyph.Width, glyph.Height, alpha);
}
}
left += advance;previous=ch;
}
}
private WebFontFace WebFace(string family,int weight)
{
if(webFontRequests.TryGetValue((family,weight),out var cached))return cached;
int id=1;
foreach(string raw in family.Split(','))
{
string name=raw.Trim(' ','\'','"').ToLowerInvariant();
if(name=="bootstrap-icons"){id=20;break;}
if(name is "monospace" or "ui-monospace" or "cascadia mono" or "cascadia code" or "consolas" or "sfmono-regular" or "sf mono" or "menlo" or "liberation mono"){id=0;break;}
if(name is "system-ui" or "sans-serif" or "-apple-system" or "segoe ui" or "lato" or "arial" or "helvetica neue" or "roboto" or "noto sans" or "liberation sans"){id=weight>=600?2:1;break;}
}
if(webFonts.TryGetValue(id,out var face))
{if(face.SyntheticBold!=(id==0&&weight>=600))face=face with{SyntheticBold=id==0&&weight>=600};if(webFontRequests.Count<256)webFontRequests[(family,weight)]=face;return face;}
string resource=id==20?"BootstrapIcons":id==0?"CascadiaMono":id==2?"LatoBold":"LatoRegular";
byte[] bytes=ResourceBytes("XFESystem.Kernel."+resource+".ttf");
face=new(id,id==20?"bootstrap-icons 1.11.3 (MIT full glyf font)":id==0?"Cascadia Mono Regular (OFL fallback)":id==2?"Lato Bold (OFL fallback)":"Lato Regular (OFL fallback)",new TrueTypeFont(bytes),new(bytes),id==0&&weight>=600);
webFonts[id]=face;if(webFontRequests.Count<256)webFontRequests[(family,weight)]=face;return face;
}
private WebFontFace WebCjkFace(bool bold)
{
int id=bold?11:10;
if(webFonts.TryGetValue(id,out var face))return face;
byte[] bytes=ResourceBytes("XFESystem.Kernel.WebCjk"+(bold?"Bold":"Regular")+".ttf");
face=new(id,"XFE Web CJK "+(bold?"Bold":"Regular")+" (Noto Sans SC 2.004 OFL glyf subset)",new TrueTypeFont(bytes),new(bytes),false);
webFonts[id]=face;return face;
}
private WebFontFace ResolveWebGlyphFace(WebFontFace primary,char ch)
{
if(ch<128||primary.Font.HasGlyph(ch))return primary;
var key=(primary.Id,ch,primary.SyntheticBold);
if(webFallbackFaces.TryGetValue(key,out var found))return found;
var candidate=WebCjkFace(primary.Id==2||primary.SyntheticBold);
var resolved=candidate.Font.HasGlyph(ch)?candidate:primary;
if(webFallbackFaces.Count<8192)webFallbackFaces[key]=resolved;return resolved;
}
private double WebAdvance(WebFontFace face,char ch,double cssSize)
{
if(char.IsControl(ch))return ch=='\t'?WebAdvance(face,' ',cssSize)*4:0;
var selected=ResolveWebGlyphFace(face,ch);
return selected.Font.HasGlyph(ch)?selected.Font.GetAdvance(ch,(int)Math.Clamp(Math.Round(cssSize*64*selected.Metrics.LineToEm),1,65535))/64d:unicode[GlyphOffset(ch)]*cssSize/16d;
}
private static double WebKerning(WebFontFace face,char left,char right,double cssSize)=>face.Font.GetKerning(left,right,(int)Math.Clamp(Math.Round(cssSize*64*face.Metrics.LineToEm),1,65535))/64d;
private WebGlyphCoverage WebGlyph(char ch, int nativeSize, WebFontFace face)
{
var key = (ch, nativeSize, face.Id,face.SyntheticBold);
if (webGlyphs.TryGetValue(key, out var value)) return value;
var selected=ResolveWebGlyphFace(face,ch);
int padding=Math.Max(1,nativeSize/4);
int width = Math.Max(1, (int)Math.Ceiling(WebAdvance(face,ch,nativeSize)))+padding*2, height = Math.Max(1, (int)Math.Ceiling(face.Metrics.NaturalLineToEm*nativeSize));
var alpha = new byte[width * height];
if (selected.Font.HasGlyph(ch))
{
int rasterHeight=Math.Max(1,(int)Math.Round(nativeSize*selected.Metrics.LineToEm));
int baselineOffset=selected.Id==face.Id?0:(int)Math.Round(face.Metrics.AscentToEm*nativeSize)-selected.Font.GetAscent(rasterHeight);
var tile = new Canvas(width, height); tile.Clear(0); tile.DrawString(ch.ToString(), selected.Font, rasterHeight,Color.White, padding, baselineOffset);
var source = tile.GetBuffer()!;
for (int i = 0; i < source.Length; i++) alpha[i] = (byte)((uint)source[i] >> 24);
}
else
{
int offset=GlyphOffset(ch),glyphWidth=Math.Max(1,(int)Math.Ceiling(unicode[offset]*nativeSize/16d)),top=Math.Max(0,(int)Math.Round((face.Metrics.AscentToEm-.85)*nativeSize));
for(int row=0;row<nativeSize&&row+top<height;row++)for(int col=0;col<glyphWidth;col++)
{int sourceRow=Math.Min(15,row*16/nativeSize),sourceCol=Math.Min(unicode[offset]-1,col*unicode[offset]/glyphWidth);int bits=unicode[offset+1+sourceRow*2]<<8|unicode[offset+2+sourceRow*2];if((bits&(0x8000>>sourceCol))!=0)alpha[(row+top)*width+padding+col]=255;}
}
if (face.SyntheticBold&&selected.Id==face.Id) for (int row = 0; row < height; row++) for (int col = width - 1; col > 0; col--) alpha[row * width + col] = Math.Max(alpha[row * width + col], alpha[row * width + col - 1]);
value = new(width, height,-padding,alpha); if (webGlyphs.Count < 4096) webGlyphs[key] = value; return value;
}
public void PreserveTranslatedSceneRegion(DesktopBounds source, int destinationX, int destinationY)
=> PreserveTranslatedSceneRegion(source, destinationX, destinationY, false);
public void PreserveTranslatedSceneRegion(DesktopBounds source, int destinationX, int destinationY, bool unchangedInScanout)
{
motion.Preserve(new(source.X * scale, source.Y * scale, source.Width * scale, source.Height * scale), destinationX * scale, destinationY * scale, unchangedInScanout);
unchangedScanout = unchangedInScanout ? motion.PreservedBounds : default;
}
public void EndScenePreservation() => motion.End();
public void Fill(uint rgb, int x, int y, int width, int height)
{
var rectangle = Intersect(NativeRect(new(x, y, width, height)), clip);
int argb = unchecked((int)(0xFF000000u | rgb));
for (int row = rectangle.Y; row < rectangle.Y + rectangle.Height; row++)
{
motion.RowSegments(rectangle.X, rectangle.Width, row, out int leftEnd, out int rightStart);
Array.Fill(pixels, argb, row * NativeWidth + rectangle.X, leftEnd - rectangle.X);
Array.Fill(pixels, argb, row * NativeWidth + rightStart, rectangle.X + rectangle.Width - rightStart);
}
}
public void BlendCoverage(uint rgb, int nativeX, int nativeY, int width, int height, ReadOnlySpan<byte> coverage)
{
if (width <= 0 || height <= 0) return;
if ((long)width * height != coverage.Length) throw new ArgumentException("Coverage dimensions do not match the mask.", nameof(coverage));
int left = (int)Math.Max(nativeX, (long)clip.X), top = (int)Math.Max(nativeY, (long)clip.Y);
long right = Math.Min((long)nativeX + width, (long)clip.X + clip.Width);
long bottom = Math.Min((long)nativeY + height, (long)clip.Y + clip.Height);
if (right <= left || bottom <= top) return;
if (motion.Contains(new(left, top, (int)right - left, (int)bottom - top))) return;
for (int row = top; row < bottom; row++)
{
motion.RowSegments(left, (int)right - left, row, out int leftEnd, out int rightStart);
for (int col = left; col < right; col = col + 1 == leftEnd ? rightStart : col + 1)
{
if (col == leftEnd && col < rightStart) { col = rightStart; if (col >= right) break; }
byte alpha = coverage[(row - nativeY) * width + col - nativeX];
if (alpha != 0) Blend(row * NativeWidth + col, rgb & 0xFFFFFF, alpha);
}
}
}
public void Line(uint rgb, int x1, int y1, int x2, int y2) => DesktopChromeRenderer.Line(this, rgb, x1, y1, x2, y2);
public void Text(string text, uint rgb, int x, int y)
{
int left = x * scale, top = y * scale;
if (top >= clip.Y + clip.Height || top + 20 * scale <= clip.Y) return;
foreach (char character in text)
{
int width = (character < 128 ? 8 : unicode[GlyphOffset(character)]) * scale;
if (left >= clip.X + clip.Width) break;
if (left + width > clip.X && !motion.Contains(new(left, top, width, 20 * scale)))
{
var glyph = GlyphOf(character);
var visible = Intersect(new(left, top, glyph.Width, glyph.Height), clip);
for (int row = visible.Y; row < visible.Y + visible.Height; row++)
{
motion.RowSegments(visible.X, visible.Width, row, out int leftEnd, out int rightStart);
int right = visible.X + visible.Width;
for (int col = visible.X; col < right; col = col + 1 == leftEnd ? rightStart : col + 1)
{
if (col == leftEnd && col < rightStart) { col = rightStart; if (col >= right) break; }
byte alpha = glyph.Alpha[(row - top) * glyph.Width + col - left];
if (alpha != 0) Blend(row * NativeWidth + col, rgb, alpha);
}
}
}
left += width;
}
}
private Glyph GlyphOf(char character)
{
if (glyphs.TryGetValue(character, out var cached)) return cached;
int width = (character < 128 ? 8 : unicode[GlyphOffset(character)]) * scale, height = 20 * scale;
var alpha = new byte[width * height];
if (character < 128)
{
var tile = new Canvas(width, height);
tile.Clear(0);
tile.DrawString(character.ToString(), font, Color.White, 0, 0);
int[] source = tile.GetBuffer()!;
for (int i = 0; i < source.Length; i++) alpha[i] = (byte)((uint)source[i] >> 24);
}
else
{
int offset = GlyphOffset(character), logicalWidth = width / scale;
for (int row = 0; row < 16; row++)
{
int bits = unicode[offset + 1 + row * 2] << 8 | unicode[offset + 2 + row * 2];
for (int col = 0; col < logicalWidth; col++)
if ((bits & (0x8000 >> col)) != 0)
for (int sy = 0; sy < scale; sy++)
Array.Fill(alpha, (byte)255, (row * scale + sy) * width + col * scale, scale);
}
}
var glyph = new Glyph(width, height, alpha);
if (glyphs.Count < 4096) glyphs.Add(character, glyph);
return glyph;
}
public void DrawIcon(DesktopIcon icon, int x, int y, int size)
{
if (icon == DesktopIcon.Settings)
{
if (size <= 0 || size > 256) return;
// Code-native sliders are distinct from the nine generated atlas
// entries. In particular Settings=9 must never index past atlas.
int margin = Math.Max(2, size / 5), knob = Math.Max(3, size / 4);
int radius = Math.Max(1, knob / 2);
for (int row = 0; row < 3; row++)
{
int cy = y + margin + row * Math.Max(1, (size - 2 * margin) / 2);
int cx = x + (row == 1 ? size * 2 / 3 : size / 3);
DesktopChromeRenderer.Line(this, 0x4086B8, x + margin, cy, x + size - margin, cy);
DesktopChromeRenderer.RoundedFill(this, 0x4086B8, cx - radius, cy - radius, knob, knob, radius);
if (knob >= 5)
DesktopChromeRenderer.RoundedFill(this, 0x75C9F5, cx - radius + 1, cy - radius + 1, knob - 2, knob - 2, Math.Max(1, radius - 1));
}
return;
}
if ((uint)icon >= 9) throw new ArgumentOutOfRangeException(nameof(icon));
DrawRaster((int)icon, atlas, (int)icon * 64 * 64 * 4, x, y, size);
}
public void BrandIcon(int x, int y, int size) => DrawRaster(10, brand, 0, x, y, size);
private void DrawRaster(int icon, byte[] source, int offset, int x, int y, int size)
{
int edge = size * scale;
if (edge <= 0 || edge > 512) return;
int left = x * scale, top = y * scale;
var visible = Intersect(new(left, top, edge, edge), clip);
if (visible.Width <= 0 || visible.Height <= 0) return;
if (motion.Contains(new(visible.X, visible.Y, visible.Width, visible.Height))) return;
if (!iconCache.TryGetValue((icon, edge), out var scaled))
{
scaled = new int[edge * edge];
for (int row = 0; row < edge; row++)
for (int col = 0; col < edge; col++)
{
int at = offset + ((row * 64 / edge) * 64 + col * 64 / edge) * 4;
scaled[row * edge + col] = source[at + 3] << 24 | source[at] << 16 | source[at + 1] << 8 | source[at + 2];
}
if (iconCache.Count < 64) iconCache.Add((icon, edge), scaled);
}
for (int row = visible.Y; row < visible.Y + visible.Height; row++)
{
motion.RowSegments(visible.X, visible.Width, row, out int leftEnd, out int rightStart);
int right = visible.X + visible.Width;
for (int col = visible.X; col < right; col = col + 1 == leftEnd ? rightStart : col + 1)
{
if (col == leftEnd && col < rightStart) { col = rightStart; if (col >= right) break; }
uint argb = (uint)scaled[(row - top) * edge + col - left];
if (argb >> 24 != 0) Blend(row * NativeWidth + col, argb & 0xFFFFFF, (byte)(argb >> 24));
}
}
}
private void Blend(int index, uint rgb, byte alpha)
{
if (alpha == 255) { pixels[index] = unchecked((int)(0xFF000000 | rgb)); return; }
uint old = (uint)pixels[index];
uint r = ((rgb >> 16 & 255) * alpha + (old >> 16 & 255) * (255u - alpha) + 127) / 255;
uint g = ((rgb >> 8 & 255) * alpha + (old >> 8 & 255) * (255u - alpha) + 127) / 255;
uint b = ((rgb & 255) * alpha + (old & 255) * (255u - alpha) + 127) / 255;
pixels[index] = unchecked((int)(0xFF000000 | r << 16 | g << 8 | b));
}
public void SaveScene() { motion.End(); pixels.AsSpan().CopyTo(scene); }
public void SaveScene(DesktopBounds damage) { motion.End(); CopyRegion(pixels, scene, NativeRect(damage)); }
public void RestoreSceneRegion(DesktopBounds bounds) => CopyRegion(scene, pixels, NativeRect(bounds));
private void CopyRegion(int[] source, int[] target, PixelRect rectangle)
{
for (int row = rectangle.Y; row < rectangle.Y + rectangle.Height; row++)
source.AsSpan(row * NativeWidth + rectangle.X, rectangle.Width).CopyTo(target.AsSpan(row * NativeWidth + rectangle.X, rectangle.Width));
}
public void Present() => Present(new(0, 0, Width, Height));
public void Present(DesktopBounds damage)
{
long begin = Stopwatch.GetTimestamp();
var rectangle = NativeRect(damage);
var unchanged = unchangedScanout; unchangedScanout = default;
if (rectangle.Width <= 0 || rectangle.Height <= 0) return;
if (device?.IsWithdrawn == true) return;
if (scanout is not null && scanout.Framebuffer is { } framebuffer && scanout.Mode is var mode &&
mode.Width == NativeWidth && mode.Height == NativeHeight && mode.BitsPerPixel == 32 && mode.Pitch % 4 == 0 && mode.Pitch >= NativeWidth * 4 &&
framebuffer.Length >= (ulong)mode.Pitch * (ulong)NativeHeight)
{
Span<int> target = framebuffer.As<int>();
int stride = mode.Pitch / 4;
long copied = 0;
for (int row = rectangle.Y; row < rectangle.Y + rectangle.Height; row++)
{
DesktopSceneMotionBuffer.RowSegments(unchanged, rectangle.X, rectangle.Width, row, out int leftEnd, out int rightStart);
int leftCount = leftEnd - rectangle.X, rightCount = rectangle.X + rectangle.Width - rightStart;
pixels.AsSpan(row * NativeWidth + rectangle.X, leftCount).CopyTo(target.Slice(row * stride + rectangle.X, leftCount));
pixels.AsSpan(row * NativeWidth + rightStart, rightCount).CopyTo(target.Slice(row * stride + rightStart, rightCount));
copied += leftCount + rightCount;
}
scanout.Flush(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
PixelsPresented += copied;
ScanoutPixelsSaved += (long)rectangle.Width * rectangle.Height - copied;
if (rectangle.Width < NativeWidth || rectangle.Height < NativeHeight) PartialFrames++;
}
else
{
canvas.Display();
PixelsPresented += (long)NativeWidth * NativeHeight;
FullCopyFallbackFrames++;
}
FramesPresented++;
LastPresentTicks = Stopwatch.GetTimestamp() - begin;
}
private PixelRect NativeRect(DesktopBounds value)
{
int x = Math.Clamp(value.X * scale, 0, NativeWidth), y = Math.Clamp(value.Y * scale, 0, NativeHeight);
int right = Math.Clamp((value.X + Math.Max(0, value.Width)) * scale, x, NativeWidth);
int bottom = Math.Clamp((value.Y + Math.Max(0, value.Height)) * scale, y, NativeHeight);
return new(x, y, right - x, bottom - y);
}
private static PixelRect Intersect(PixelRect a, PixelRect b)
{
int x = Math.Max(a.X, b.X), y = Math.Max(a.Y, b.Y);
return new(x, y, Math.Max(0, Math.Min(a.X + a.Width, b.X + b.Width) - x), Math.Max(0, Math.Min(a.Y + a.Height, b.Y + b.Height) - y));
}
private static byte[] ResourceBytes(string name)
{
using var resource = typeof(BaselineCanvasAdapter).Assembly.GetManifestResourceStream(name)
?? throw new InvalidOperationException("An embedded desktop asset is missing: " + name);
var data = new byte[resource.Length]; resource.ReadExactly(data); return data;
}
private int GlyphOffset(int character)
{
foreach (var range in GlyphRanges)
if (character >= range.First && character <= range.Last) return range.Offset + (character - range.First) * 33;
return unicode.Length - 33;
}
}
using System.Drawing;
using System.Diagnostics;
using Cosmos.Kernel.HAL.Devices.Display;
using Cosmos.Kernel.System.Graphics;
using Cosmos.Kernel.System.Graphics.Fonts;
using XFESystem.Desktop;
namespace XFESystem.Kernel;
/// <summary>Native pixels, cached glyph coverage, scene restoration and bounded damaged scanout.</summary>
internal sealed class BaselineCanvasAdapter : IBrandedDesktopCanvas, IDesktopIconCanvas, IDesktopCoverageCanvas, IDesktopSceneMotionCanvas, IDesktopWebCanvas
{
private readonly Canvas canvas = Canvas.GetFullScreen();
private readonly TrueTypeFont font;
private readonly int scale;
private readonly int[] pixels, scene;
private readonly DesktopSceneMotionBuffer motion;
private readonly byte[] unicode = ResourceBytes("XFESystem.Kernel.Unifont.bin");
private readonly byte[] brand = ResourceBytes("XFESystem.Kernel.Icon.rgba");
private readonly byte[] atlas = ResourceBytes("XFESystem.Kernel.DesktopIcons.rgba");
private readonly Dictionary<char, Glyph> glyphs = [];
private readonly Dictionary<(int Icon, int Size), int[]> iconCache = [];
private readonly IDisplay? scanout;
private readonly DisplayDevice? device;
private PixelRect clip;
private readonly Stack<PixelRect> webClips = [];
private readonly Dictionary<(char Character, int Size, int Face, bool Bold), WebGlyphCoverage> webGlyphs = [];
private readonly Dictionary<int, WebFontFace> webFonts = [];
private readonly Dictionary<(string Family,int Weight),WebFontFace> webFontRequests=[];
private readonly Dictionary<(int Primary,char Character,bool Bold),WebFontFace> webFallbackFaces=[];
private sealed record WebFontFace(int Id,string Family,TrueTypeFont Font,NativeWebFontMetrics Metrics,bool SyntheticBold);
private sealed record WebGlyphCoverage(int Width,int Height,int OffsetX,byte[] Alpha);
private DesktopBounds unchangedScanout;
public long FramesPresented { get; private set; }
public long PixelsPresented { get; private set; }
public long PartialFrames { get; private set; }
public long FullCopyFallbackFrames { get; private set; }
public long LastPresentTicks { get; private set; }
public int CachedGlyphCount => glyphs.Count;
public long MotionReuseCount => motion.ReuseCount;
public long MotionPixelsReused => motion.PixelsReused;
public long ScanoutPixelsSaved { get; private set; }
private sealed record Glyph(int Width, int Height, byte[] Alpha);
private readonly record struct PixelRect(int X, int Y, int Width, int Height);
private static readonly (int First, int Last, int Offset)[] GlyphRanges =
[
(128, 2047, 0), (8192, 8303, 63360), (8592, 8703, 67056),
(12288, 12351, 70752), (13312, 40959, 72864), (65280, 65519, 985248),
(65533, 65533, 993168)
];
public BaselineCanvasAdapter()
{
using var resource = typeof(BaselineCanvasAdapter).Assembly.GetManifestResourceStream("XFESystem.Kernel.CascadiaMono.ttf")
?? throw new InvalidOperationException("The embedded desktop font is missing.");
scale = canvas.Width >= 3000 || canvas.Height >= 1800 ? 2 : 1;
font = new TrueTypeFont(resource, 13 * scale);
pixels = canvas.GetBuffer() ?? throw new InvalidOperationException("The desktop canvas is not buffered.");
scene = new int[pixels.Length];
motion = new(pixels, scene, canvas.Width, canvas.Height);
clip = new(0, 0, canvas.Width, canvas.Height);
device = DisplayManager.Primary;
if (device is { } primary && primary.TryGetFacet<IDisplay>(out var display)) scanout = display;
if (atlas.Length != 9 * 64 * 64 * 4) throw new InvalidOperationException("Desktop icon atlas is invalid.");
}
public int Width => canvas.Width / scale;
public int Height => canvas.Height / scale;
public int NativeWidth => canvas.Width;
public int NativeHeight => canvas.Height;
public int ScalePercent => scale * 100;
// Cosmos firmware fabricates a fallback 60 Hz and GOP provides no active
// scanout clock. Only a real mode-setting driver's report can be confirmed.
public int DriverRefreshHz => device?.IsFirmware == false ? scanout?.Mode.RefreshRate ?? 0 : 0;
public bool SupportsSceneCache => true;
public int ToLayout(int nativePosition) => nativePosition / scale;
public int TextWidth(string text)
{
int width = 0;
foreach (char character in text) width += character < 128 ? 8 : unicode[GlyphOffset(character)];
return width;
}
public void BeginScene(DesktopBounds damage) { motion.End(); webClips.Clear(); clip = NativeRect(damage); }
public void PushClip(DesktopBounds bounds) { webClips.Push(clip); clip = Intersect(clip, NativeRect(bounds)); }
public void PopClip() { if (!webClips.TryPop(out clip)) throw new InvalidOperationException("Unbalanced web clipping."); }
public double MeasureWebText(string text, double fontSize, string fontFamily, int fontWeight)
{
var face=WebFace(fontFamily,fontWeight);double size=Math.Clamp(fontSize,1,256),width=0;char previous='\0';
foreach(char ch in text)
{
if(previous!='\0'&&face.Font.HasGlyph(previous)&&face.Font.HasGlyph(ch))width+=WebKerning(face,previous,ch,size);
width+=WebAdvance(face,ch,size);previous=ch;
}
return width;
}
public double MeasureWebLineHeight(double fontSize,string fontFamily,int fontWeight)=>WebFace(fontFamily,fontWeight).Metrics.NaturalLineToEm*Math.Clamp(fontSize,1,256);
public string DescribeWebFont(string requested,int weight)=>requested+" -> "+WebFace(requested,weight).Family;
public string WebFontMetricsEvidence=>"Sans="+DescribeWebFont("system-ui",400)+";SansW="+MeasureWebText("WWW",16,"system-ui",400)+";SansI="+MeasureWebText("iii",16,"system-ui",400)+";Mono="+DescribeWebFont("Consolas",400)+";MonoW="+MeasureWebText("WWW",16,"Consolas",400)+";MonoI="+MeasureWebText("iii",16,"Consolas",400)+";Cjk="+WebCjkFace(false).Family+";CjkGlyph="+WebCjkFace(false).Font.HasGlyph('文')+";CjkBoldGlyph="+WebCjkFace(true).Font.HasGlyph('文')+";CjkAdvance="+MeasureWebText("中文",16,"system-ui",400);
public (int Width, int Height, byte[] Alpha) RasterizeWebText(string text, double fontSize, string fontFamily, int fontWeight)
{
if (text.Length > 4096) throw new ArgumentOutOfRangeException(nameof(text));
// Canvas bitmaps use CSS pixels, independently of desktop UI scale.
int size = Math.Max(1, (int)Math.Round(Math.Clamp(fontSize, 1, 256)));var face=WebFace(fontFamily,fontWeight);
int width = Math.Max(1, (int)Math.Ceiling(MeasureWebText(text, fontSize, fontFamily, fontWeight)));
int height = Math.Max(1, (int)Math.Ceiling(MeasureWebLineHeight(size,fontFamily,fontWeight)));
var alpha = new byte[checked(width * height)]; double left = 0;char previous='\0';
foreach (char ch in text)
{
if(previous!='\0'&&face.Font.HasGlyph(previous)&&face.Font.HasGlyph(ch))left+=WebKerning(face,previous,ch,size);
var glyph = WebGlyph(ch, size, face); int x = (int)Math.Round(left)+glyph.OffsetX;
for (int row = 0; row < Math.Min(height, glyph.Height); row++) for (int col = Math.Max(0,-x); col < Math.Min(glyph.Width, width - x); col++)
alpha[row * width + x + col] = Math.Max(alpha[row * width + x + col], glyph.Alpha[row * glyph.Width + col]);
left += WebAdvance(face,ch,size);previous=ch;
}
return (width, height, alpha);
}
public void WebText(string text, uint rgb, double x, double y, double fontSize, string fontFamily, int fontWeight, double opacity = 1)
{
double size = Math.Clamp(fontSize, 1, 128), left = x * scale;var face=WebFace(fontFamily,fontWeight);char previous='\0';
int nativeSize = Math.Max(1, (int)Math.Round(size * scale)), top = (int)Math.Round(y * scale);
foreach (char ch in text)
{
if(previous!='\0'&&face.Font.HasGlyph(previous)&&face.Font.HasGlyph(ch))left+=WebKerning(face,previous,ch,size)*scale;
double advance = WebAdvance(face,ch,size)*scale;
if (left >= clip.X + clip.Width) break;
if (left + advance > clip.X)
{
var glyph = WebGlyph(ch, nativeSize, face);
if (opacity >= 1) BlendCoverage(rgb, (int)Math.Round(left)+glyph.OffsetX, top, glyph.Width, glyph.Height, glyph.Alpha);
else if (opacity > 0)
{
var alpha = new byte[glyph.Alpha.Length];
for (int i = 0; i < alpha.Length; i++) alpha[i] = (byte)(glyph.Alpha[i] * opacity);
BlendCoverage(rgb, (int)Math.Round(left)+glyph.OffsetX, top, glyph.Width, glyph.Height, alpha);
}
}
left += advance;previous=ch;
}
}
private WebFontFace WebFace(string family,int weight)
{
if(webFontRequests.TryGetValue((family,weight),out var cached))return cached;
int id=1;
foreach(string raw in family.Split(','))
{
string name=raw.Trim(' ','\'','"').ToLowerInvariant();
if(name=="bootstrap-icons"){id=20;break;}
if(name is "monospace" or "ui-monospace" or "cascadia mono" or "cascadia code" or "consolas" or "sfmono-regular" or "sf mono" or "menlo" or "liberation mono"){id=0;break;}
if(name is "system-ui" or "sans-serif" or "-apple-system" or "segoe ui" or "lato" or "arial" or "helvetica neue" or "roboto" or "noto sans" or "liberation sans"){id=weight>=600?2:1;break;}
}
if(webFonts.TryGetValue(id,out var face))
{if(face.SyntheticBold!=(id==0&&weight>=600))face=face with{SyntheticBold=id==0&&weight>=600};if(webFontRequests.Count<256)webFontRequests[(family,weight)]=face;return face;}
string resource=id==20?"BootstrapIcons":id==0?"CascadiaMono":id==2?"LatoBold":"LatoRegular";
byte[] bytes=ResourceBytes("XFESystem.Kernel."+resource+".ttf");
face=new(id,id==20?"bootstrap-icons 1.11.3 (MIT full glyf font)":id==0?"Cascadia Mono Regular (OFL fallback)":id==2?"Lato Bold (OFL fallback)":"Lato Regular (OFL fallback)",new TrueTypeFont(bytes),new(bytes),id==0&&weight>=600);
webFonts[id]=face;if(webFontRequests.Count<256)webFontRequests[(family,weight)]=face;return face;
}
private WebFontFace WebCjkFace(bool bold)
{
int id=bold?11:10;
if(webFonts.TryGetValue(id,out var face))return face;
byte[] bytes=ResourceBytes("XFESystem.Kernel.WebCjk"+(bold?"Bold":"Regular")+".ttf");
face=new(id,"XFE Web CJK "+(bold?"Bold":"Regular")+" (Noto Sans SC 2.004 OFL glyf subset)",new TrueTypeFont(bytes),new(bytes),false);
webFonts[id]=face;return face;
}
private WebFontFace ResolveWebGlyphFace(WebFontFace primary,char ch)
{
if(ch<128||primary.Font.HasGlyph(ch))return primary;
var key=(primary.Id,ch,primary.SyntheticBold);
if(webFallbackFaces.TryGetValue(key,out var found))return found;
var candidate=WebCjkFace(primary.Id==2||primary.SyntheticBold);
var resolved=candidate.Font.HasGlyph(ch)?candidate:primary;
if(webFallbackFaces.Count<8192)webFallbackFaces[key]=resolved;return resolved;
}
private double WebAdvance(WebFontFace face,char ch,double cssSize)
{
if(char.IsControl(ch))return ch=='\t'?WebAdvance(face,' ',cssSize)*4:0;
var selected=ResolveWebGlyphFace(face,ch);
return selected.Font.HasGlyph(ch)?selected.Font.GetAdvance(ch,(int)Math.Clamp(Math.Round(cssSize*64*selected.Metrics.LineToEm),1,65535))/64d:unicode[GlyphOffset(ch)]*cssSize/16d;
}
private static double WebKerning(WebFontFace face,char left,char right,double cssSize)=>face.Font.GetKerning(left,right,(int)Math.Clamp(Math.Round(cssSize*64*face.Metrics.LineToEm),1,65535))/64d;
private WebGlyphCoverage WebGlyph(char ch, int nativeSize, WebFontFace face)
{
var key = (ch, nativeSize, face.Id,face.SyntheticBold);
if (webGlyphs.TryGetValue(key, out var value)) return value;
var selected=ResolveWebGlyphFace(face,ch);
int padding=Math.Max(1,nativeSize/4);
int width = Math.Max(1, (int)Math.Ceiling(WebAdvance(face,ch,nativeSize)))+padding*2, height = Math.Max(1, (int)Math.Ceiling(face.Metrics.NaturalLineToEm*nativeSize));
var alpha = new byte[width * height];
if (selected.Font.HasGlyph(ch))
{
int rasterHeight=Math.Max(1,(int)Math.Round(nativeSize*selected.Metrics.LineToEm));
int baselineOffset=selected.Id==face.Id?0:(int)Math.Round(face.Metrics.AscentToEm*nativeSize)-selected.Font.GetAscent(rasterHeight);
var tile = new Canvas(width, height); tile.Clear(0); tile.DrawString(ch.ToString(), selected.Font, rasterHeight,Color.White, padding, baselineOffset);
var source = tile.GetBuffer()!;
for (int i = 0; i < source.Length; i++) alpha[i] = (byte)((uint)source[i] >> 24);
}
else
{
int offset=GlyphOffset(ch),glyphWidth=Math.Max(1,(int)Math.Ceiling(unicode[offset]*nativeSize/16d)),top=Math.Max(0,(int)Math.Round((face.Metrics.AscentToEm-.85)*nativeSize));
for(int row=0;row<nativeSize&&row+top<height;row++)for(int col=0;col<glyphWidth;col++)
{int sourceRow=Math.Min(15,row*16/nativeSize),sourceCol=Math.Min(unicode[offset]-1,col*unicode[offset]/glyphWidth);int bits=unicode[offset+1+sourceRow*2]<<8|unicode[offset+2+sourceRow*2];if((bits&(0x8000>>sourceCol))!=0)alpha[(row+top)*width+padding+col]=255;}
}
if (face.SyntheticBold&&selected.Id==face.Id) for (int row = 0; row < height; row++) for (int col = width - 1; col > 0; col--) alpha[row * width + col] = Math.Max(alpha[row * width + col], alpha[row * width + col - 1]);
value = new(width, height,-padding,alpha); if (webGlyphs.Count < 4096) webGlyphs[key] = value; return value;
}
public void PreserveTranslatedSceneRegion(DesktopBounds source, int destinationX, int destinationY)
=> PreserveTranslatedSceneRegion(source, destinationX, destinationY, false);
public void PreserveTranslatedSceneRegion(DesktopBounds source, int destinationX, int destinationY, bool unchangedInScanout)
{
motion.Preserve(new(source.X * scale, source.Y * scale, source.Width * scale, source.Height * scale), destinationX * scale, destinationY * scale, unchangedInScanout);
unchangedScanout = unchangedInScanout ? motion.PreservedBounds : default;
}
public void EndScenePreservation() => motion.End();
public void Fill(uint rgb, int x, int y, int width, int height)
{
var rectangle = Intersect(NativeRect(new(x, y, width, height)), clip);
int argb = unchecked((int)(0xFF000000u | rgb));
for (int row = rectangle.Y; row < rectangle.Y + rectangle.Height; row++)
{
motion.RowSegments(rectangle.X, rectangle.Width, row, out int leftEnd, out int rightStart);
Array.Fill(pixels, argb, row * NativeWidth + rectangle.X, leftEnd - rectangle.X);
Array.Fill(pixels, argb, row * NativeWidth + rightStart, rectangle.X + rectangle.Width - rightStart);
}
}
public void BlendCoverage(uint rgb, int nativeX, int nativeY, int width, int height, ReadOnlySpan<byte> coverage)
{
if (width <= 0 || height <= 0) return;
if ((long)width * height != coverage.Length) throw new ArgumentException("Coverage dimensions do not match the mask.", nameof(coverage));
int left = (int)Math.Max(nativeX, (long)clip.X), top = (int)Math.Max(nativeY, (long)clip.Y);
long right = Math.Min((long)nativeX + width, (long)clip.X + clip.Width);
long bottom = Math.Min((long)nativeY + height, (long)clip.Y + clip.Height);
if (right <= left || bottom <= top) return;
if (motion.Contains(new(left, top, (int)right - left, (int)bottom - top))) return;
for (int row = top; row < bottom; row++)
{
motion.RowSegments(left, (int)right - left, row, out int leftEnd, out int rightStart);
for (int col = left; col < right; col = col + 1 == leftEnd ? rightStart : col + 1)
{
if (col == leftEnd && col < rightStart) { col = rightStart; if (col >= right) break; }
byte alpha = coverage[(row - nativeY) * width + col - nativeX];
if (alpha != 0) Blend(row * NativeWidth + col, rgb & 0xFFFFFF, alpha);
}
}
}
public void Line(uint rgb, int x1, int y1, int x2, int y2) => DesktopChromeRenderer.Line(this, rgb, x1, y1, x2, y2);
public void Text(string text, uint rgb, int x, int y)
{
int left = x * scale, top = y * scale;
if (top >= clip.Y + clip.Height || top + 20 * scale <= clip.Y) return;
foreach (char character in text)
{
int width = (character < 128 ? 8 : unicode[GlyphOffset(character)]) * scale;
if (left >= clip.X + clip.Width) break;
if (left + width > clip.X && !motion.Contains(new(left, top, width, 20 * scale)))
{
var glyph = GlyphOf(character);
var visible = Intersect(new(left, top, glyph.Width, glyph.Height), clip);
for (int row = visible.Y; row < visible.Y + visible.Height; row++)
{
motion.RowSegments(visible.X, visible.Width, row, out int leftEnd, out int rightStart);
int right = visible.X + visible.Width;
for (int col = visible.X; col < right; col = col + 1 == leftEnd ? rightStart : col + 1)
{
if (col == leftEnd && col < rightStart) { col = rightStart; if (col >= right) break; }
byte alpha = glyph.Alpha[(row - top) * glyph.Width + col - left];
if (alpha != 0) Blend(row * NativeWidth + col, rgb, alpha);
}
}
}
left += width;
}
}
private Glyph GlyphOf(char character)
{
if (glyphs.TryGetValue(character, out var cached)) return cached;
int width = (character < 128 ? 8 : unicode[GlyphOffset(character)]) * scale, height = 20 * scale;
var alpha = new byte[width * height];
if (character < 128)
{
var tile = new Canvas(width, height);
tile.Clear(0);
tile.DrawString(character.ToString(), font, Color.White, 0, 0);
int[] source = tile.GetBuffer()!;
for (int i = 0; i < source.Length; i++) alpha[i] = (byte)((uint)source[i] >> 24);
}
else
{
int offset = GlyphOffset(character), logicalWidth = width / scale;
for (int row = 0; row < 16; row++)
{
int bits = unicode[offset + 1 + row * 2] << 8 | unicode[offset + 2 + row * 2];
for (int col = 0; col < logicalWidth; col++)
if ((bits & (0x8000 >> col)) != 0)
for (int sy = 0; sy < scale; sy++)
Array.Fill(alpha, (byte)255, (row * scale + sy) * width + col * scale, scale);
}
}
var glyph = new Glyph(width, height, alpha);
if (glyphs.Count < 4096) glyphs.Add(character, glyph);
return glyph;
}
public void DrawIcon(DesktopIcon icon, int x, int y, int size)
{
if (icon == DesktopIcon.Settings)
{
if (size <= 0 || size > 256) return;
// Code-native sliders are distinct from the nine generated atlas
// entries. In particular Settings=9 must never index past atlas.
int margin = Math.Max(2, size / 5), knob = Math.Max(3, size / 4);
int radius = Math.Max(1, knob / 2);
for (int row = 0; row < 3; row++)
{
int cy = y + margin + row * Math.Max(1, (size - 2 * margin) / 2);
int cx = x + (row == 1 ? size * 2 / 3 : size / 3);
DesktopChromeRenderer.Line(this, 0x4086B8, x + margin, cy, x + size - margin, cy);
DesktopChromeRenderer.RoundedFill(this, 0x4086B8, cx - radius, cy - radius, knob, knob, radius);
if (knob >= 5)
DesktopChromeRenderer.RoundedFill(this, 0x75C9F5, cx - radius + 1, cy - radius + 1, knob - 2, knob - 2, Math.Max(1, radius - 1));
}
return;
}
if ((uint)icon >= 9) throw new ArgumentOutOfRangeException(nameof(icon));
DrawRaster((int)icon, atlas, (int)icon * 64 * 64 * 4, x, y, size);
}
public void BrandIcon(int x, int y, int size) => DrawRaster(10, brand, 0, x, y, size);
private void DrawRaster(int icon, byte[] source, int offset, int x, int y, int size)
{
int edge = size * scale;
if (edge <= 0 || edge > 512) return;
int left = x * scale, top = y * scale;
var visible = Intersect(new(left, top, edge, edge), clip);
if (visible.Width <= 0 || visible.Height <= 0) return;
if (motion.Contains(new(visible.X, visible.Y, visible.Width, visible.Height))) return;
if (!iconCache.TryGetValue((icon, edge), out var scaled))
{
scaled = new int[edge * edge];
for (int row = 0; row < edge; row++)
for (int col = 0; col < edge; col++)
{
int at = offset + ((row * 64 / edge) * 64 + col * 64 / edge) * 4;
scaled[row * edge + col] = source[at + 3] << 24 | source[at] << 16 | source[at + 1] << 8 | source[at + 2];
}
if (iconCache.Count < 64) iconCache.Add((icon, edge), scaled);
}
for (int row = visible.Y; row < visible.Y + visible.Height; row++)
{
motion.RowSegments(visible.X, visible.Width, row, out int leftEnd, out int rightStart);
int right = visible.X + visible.Width;
for (int col = visible.X; col < right; col = col + 1 == leftEnd ? rightStart : col + 1)
{
if (col == leftEnd && col < rightStart) { col = rightStart; if (col >= right) break; }
uint argb = (uint)scaled[(row - top) * edge + col - left];
if (argb >> 24 != 0) Blend(row * NativeWidth + col, argb & 0xFFFFFF, (byte)(argb >> 24));
}
}
}
private void Blend(int index, uint rgb, byte alpha)
{
if (alpha == 255) { pixels[index] = unchecked((int)(0xFF000000 | rgb)); return; }
uint old = (uint)pixels[index];
uint r = ((rgb >> 16 & 255) * alpha + (old >> 16 & 255) * (255u - alpha) + 127) / 255;
uint g = ((rgb >> 8 & 255) * alpha + (old >> 8 & 255) * (255u - alpha) + 127) / 255;
uint b = ((rgb & 255) * alpha + (old & 255) * (255u - alpha) + 127) / 255;
pixels[index] = unchecked((int)(0xFF000000 | r << 16 | g << 8 | b));
}
public void SaveScene() { motion.End(); pixels.AsSpan().CopyTo(scene); }
public void SaveScene(DesktopBounds damage) { motion.End(); CopyRegion(pixels, scene, NativeRect(damage)); }
public void RestoreSceneRegion(DesktopBounds bounds) => CopyRegion(scene, pixels, NativeRect(bounds));
private void CopyRegion(int[] source, int[] target, PixelRect rectangle)
{
for (int row = rectangle.Y; row < rectangle.Y + rectangle.Height; row++)
source.AsSpan(row * NativeWidth + rectangle.X, rectangle.Width).CopyTo(target.AsSpan(row * NativeWidth + rectangle.X, rectangle.Width));
}
public void Present() => Present(new(0, 0, Width, Height));
public void Present(DesktopBounds damage)
{
long begin = Stopwatch.GetTimestamp();
var rectangle = NativeRect(damage);
var unchanged = unchangedScanout; unchangedScanout = default;
if (rectangle.Width <= 0 || rectangle.Height <= 0) return;
if (device?.IsWithdrawn == true) return;
if (scanout is not null && scanout.Framebuffer is { } framebuffer && scanout.Mode is var mode &&
mode.Width == NativeWidth && mode.Height == NativeHeight && mode.BitsPerPixel == 32 && mode.Pitch % 4 == 0 && mode.Pitch >= NativeWidth * 4 &&
framebuffer.Length >= (ulong)mode.Pitch * (ulong)NativeHeight)
{
Span<int> target = framebuffer.As<int>();
int stride = mode.Pitch / 4;
long copied = 0;
for (int row = rectangle.Y; row < rectangle.Y + rectangle.Height; row++)
{
DesktopSceneMotionBuffer.RowSegments(unchanged, rectangle.X, rectangle.Width, row, out int leftEnd, out int rightStart);
int leftCount = leftEnd - rectangle.X, rightCount = rectangle.X + rectangle.Width - rightStart;
pixels.AsSpan(row * NativeWidth + rectangle.X, leftCount).CopyTo(target.Slice(row * stride + rectangle.X, leftCount));
pixels.AsSpan(row * NativeWidth + rightStart, rightCount).CopyTo(target.Slice(row * stride + rightStart, rightCount));
copied += leftCount + rightCount;
}
scanout.Flush(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
PixelsPresented += copied;
ScanoutPixelsSaved += (long)rectangle.Width * rectangle.Height - copied;
if (rectangle.Width < NativeWidth || rectangle.Height < NativeHeight) PartialFrames++;
}
else
{
canvas.Display();
PixelsPresented += (long)NativeWidth * NativeHeight;
FullCopyFallbackFrames++;
}
FramesPresented++;
LastPresentTicks = Stopwatch.GetTimestamp() - begin;
}
private PixelRect NativeRect(DesktopBounds value)
{
int x = Math.Clamp(value.X * scale, 0, NativeWidth), y = Math.Clamp(value.Y * scale, 0, NativeHeight);
int right = Math.Clamp((value.X + Math.Max(0, value.Width)) * scale, x, NativeWidth);
int bottom = Math.Clamp((value.Y + Math.Max(0, value.Height)) * scale, y, NativeHeight);
return new(x, y, right - x, bottom - y);
}
private static PixelRect Intersect(PixelRect a, PixelRect b)
{
int x = Math.Max(a.X, b.X), y = Math.Max(a.Y, b.Y);
return new(x, y, Math.Max(0, Math.Min(a.X + a.Width, b.X + b.Width) - x), Math.Max(0, Math.Min(a.Y + a.Height, b.Y + b.Height) - y));
}
private static byte[] ResourceBytes(string name)
{
using var resource = typeof(BaselineCanvasAdapter).Assembly.GetManifestResourceStream(name)
?? throw new InvalidOperationException("An embedded desktop asset is missing: " + name);
var data = new byte[resource.Length]; resource.ReadExactly(data); return data;
}
private int GlyphOffset(int character)
{
foreach (var range in GlyphRanges)
if (character >= range.First && character <= range.Last) return range.Offset + (character - range.First) * 33;
return unicode.Length - 33;
}
}