namespace XFESystem.Desktop; /// /// Copies unchanged scene pixels without including the software pointer. While /// composing the rest of the frame, row segments exclude this opaque rectangle. /// All coordinates here are native pixels, independent of layout scaling. /// public sealed class DesktopSceneMotionBuffer { private readonly int[] _pixels, _scene; private readonly int _width, _height; public DesktopBounds PreservedBounds { get; private set; } public long PixelsReused { get; private set; } public long ReuseCount { get; private set; } public DesktopSceneMotionBuffer(int[] pixels, int[] scene, int width, int height) { if (width <= 0 || height <= 0 || (long)width * height > pixels.Length || (long)width * height > scene.Length) throw new ArgumentOutOfRangeException(nameof(width)); if (ReferenceEquals(pixels, scene)) throw new ArgumentException("The pointer-free scene must have a separate buffer.", nameof(scene)); _pixels = pixels; _scene = scene; _width = width; _height = height; } public void End() => PreservedBounds = default; public void Preserve(DesktopBounds source, int destinationX, int destinationY, bool pixelsAlreadyMatch = false) { End(); if (pixelsAlreadyMatch && (source.X != destinationX || source.Y != destinationY)) throw new ArgumentException("Only an unchanged region at the same position can skip the pixel copy."); long left = Math.Max(0, Math.Max(-(long)source.X, -(long)destinationX)); long top = Math.Max(0, Math.Max(-(long)source.Y, -(long)destinationY)); long right = Math.Min(Math.Max(0, source.Width), Math.Min((long)_width - source.X, (long)_width - destinationX)); long bottom = Math.Min(Math.Max(0, source.Height), Math.Min((long)_height - source.Y, (long)_height - destinationY)); if (right <= left || bottom <= top) return; int width = (int)(right - left), height = (int)(bottom - top); int sourceX = (int)(source.X + left), sourceY = (int)(source.Y + top); int targetX = (int)(destinationX + left), targetY = (int)(destinationY + top); if (!pixelsAlreadyMatch) for (int row = 0; row < height; row++) _scene.AsSpan((sourceY + row) * _width + sourceX, width) .CopyTo(_pixels.AsSpan((targetY + row) * _width + targetX, width)); PreservedBounds = new(targetX, targetY, width, height); PixelsReused += (long)width * height; ReuseCount++; } public bool Contains(DesktopBounds rectangle) { var p = PreservedBounds; return p.Width > 0 && p.Height > 0 && rectangle.X >= p.X && rectangle.Y >= p.Y && (long)rectangle.X + rectangle.Width <= (long)p.X + p.Width && (long)rectangle.Y + rectangle.Height <= (long)p.Y + p.Height; } /// Visible columns are [x,leftEnd) and [rightStart,x+width), without allocating a region list. public void RowSegments(int x, int width, int row, out int leftEnd, out int rightStart) => RowSegments(PreservedBounds, x, width, row, out leftEnd, out rightStart); public static void RowSegments(DesktopBounds excluded, int x, int width, int row, out int leftEnd, out int rightStart) { int right = x + width; var p = excluded; if (p.Width == 0 || row < p.Y || row >= p.Y + p.Height || right <= p.X || x >= p.X + p.Width) { leftEnd = right; rightStart = right; return; } leftEnd = Math.Clamp(p.X, x, right); rightStart = Math.Clamp(p.X + p.Width, x, right); } }