namespace XFESystem.Desktop;
/// <summary>
/// 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.
/// </summary>
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;
}
/// <summary>Visible columns are [x,leftEnd) and [rightStart,x+width), without allocating a region list.</summary>
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);
}
}
namespace XFESystem.Desktop;
/// <summary>
/// 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.
/// </summary>
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;
}
/// <summary>Visible columns are [x,leftEnd) and [rightStart,x+width), without allocating a region list.</summary>
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);
}
}