using XFESystem.Desktop;
using XFESystem.Platform;
namespace XFESystem.Platform.Tests;
public sealed class FramebufferTests
{
[Theory]
[InlineData(32, 0x00FF0000u, 0x0000FF00u, 0x000000FFu, 0x00FF0000u, 0x0000FF00u, 0x000000FFu)]
[InlineData(24, 0x000000FFu, 0x0000FF00u, 0x00FF0000u, 0x000000FFu, 0x0000FF00u, 0x00FF0000u)]
[InlineData(16, 0x0000F800u, 0x000007E0u, 0x0000001Fu, 0x0000F800u, 0x000007E0u, 0x0000001Fu)]
public unsafe void MappedScanoutUsesRgbMasksPitchAndLeavesGuardBytesUntouched(int bits, uint redMask, uint greenMask, uint blueMask, uint red, uint green, uint blue)
{
const int width = 3, height = 2;
int bytes = bits / 8, pitch = width * bytes + 5;
var scanout = Enumerable.Repeat((byte)0xA5, pitch * height + 16).ToArray();
int[] pixels = [0xFF0000, 0x00FF00, 0x0000FF, 0xFFFFFF, 0, 0xFFFFFF];
fixed (byte* address = scanout)
{
var target = new MemoryFramebufferPresenter(new((nuint)address, (nuint)(pitch * height), width, height, pitch, bits, redMask, greenMask, blueMask));
target.Present(pixels, new(-20, -20, 23, 21));
}
uint Read(int pixel) { uint value = 0; for (int b = 0; b < bytes; b++) value |= (uint)scanout[pixel * bytes + b] << (8 * b); return value; }
Assert.Equal(red, Read(0)); Assert.Equal(green, Read(1)); Assert.Equal(blue, Read(2));
Assert.All(scanout.Skip(width * bytes), value => Assert.Equal(0xA5, value));
}
[Fact]
public unsafe void InvalidMappingAndMasksAreRejectedBeforeAnyWrite()
{
var data = new byte[64];
fixed (byte* p = data)
{
nuint address = (nuint)p;
Assert.Throws<ArgumentException>(() => new MemoryFramebufferPresenter(new(address, 4, 3, 2, 16, 32, 0xFF0000, 0xFF00, 0xFF)));
Assert.Throws<ArgumentException>(() => new MemoryFramebufferPresenter(new(address, 64, 3, 2, 8, 32, 0xFF0000, 0xFF00, 0xFF)));
Assert.Throws<ArgumentException>(() => new MemoryFramebufferPresenter(new(address, 64, 3, 2, 16, 32, 0xFF00, 0xFF00, 0xFF)));
Assert.Throws<ArgumentException>(() => new MemoryFramebufferPresenter(new(address, 64, 3, 2, 16, 32, 0xA00000, 0xFF00, 0xFF)));
}
Assert.All(data, value => Assert.Equal(0, value));
}
[Fact]
public void CoverageBlendsAgainstExistingPixelsAndHonorsNestedClipping()
{
var output = new ArrayFramebufferPresenter(8, 8); var canvas = new CpuFramebufferCanvas(output);
canvas.Fill(0x0000FF, 0, 0, 8, 8);
canvas.PushClip(new(2, 2, 3, 3)); canvas.PushClip(new(3, 1, 3, 3));
canvas.BlendCoverage(0xFF0000, 0, 0, 8, 8, Enumerable.Repeat((byte)128, 64).ToArray());
canvas.PopClip(); canvas.PopClip(); canvas.Present();
Assert.Equal(0x80007F, output.Pixels.Span[2 * 8 + 3]);
Assert.Equal(0x0000FF, output.Pixels.Span[2 * 8 + 2]);
Assert.Equal(0x0000FF, output.Pixels.Span[4 * 8 + 3]);
Assert.Throws<InvalidOperationException>(canvas.PopClip);
Assert.Throws<ArgumentException>(() => canvas.BlendCoverage(0, 0, 0, 8, 8, new byte[63]));
}
[Fact]
public void FullPresentationIncludesNativeRemainderAtFractionalScale()
{
var output = new ArrayFramebufferPresenter(11, 9); var canvas = new CpuFramebufferCanvas(output, scalePercent: 125);
canvas.Fill(0x123456, 0, 0, canvas.Width, canvas.Height); canvas.Present();
Assert.All(output.Pixels.ToArray(), pixel => Assert.Equal(0x123456, pixel));
}
[Fact]
public void EmptyFillAtFractionalLayoutEdgeDoesNotPaintNativeRemainder()
{
var output = new ArrayFramebufferPresenter(11, 9); var canvas = new CpuFramebufferCanvas(output, scalePercent: 125);
canvas.Fill(0x123456, canvas.Width, 0, 0, canvas.Height);
canvas.Fill(0xFFFFFF, 0, 0, -1, canvas.Height); canvas.Present();
Assert.All(output.Pixels.ToArray(), pixel => Assert.Equal(0, pixel));
}
[Fact]
public void HugeLineEndpointsAreClippedBeforeWalkingPixels()
{
var output = new ArrayFramebufferPresenter(8, 8); var canvas = new CpuFramebufferCanvas(output);
canvas.Line(0xFFFFFF, int.MinValue, 3, int.MaxValue, 3); canvas.Present();
Assert.All(output.Pixels.Slice(24, 8).ToArray(), pixel => Assert.Equal(0xFFFFFF, pixel));
Assert.Equal(8, output.Pixels.ToArray().Count(pixel => pixel != 0));
}
[Fact]
public void PointerSceneRestorationAndTranslatedPreservationUsePointerFreePixels()
{
var output = new ArrayFramebufferPresenter(8, 8); var canvas = new CpuFramebufferCanvas(output);
canvas.Fill(0x336699, 0, 0, 8, 8); canvas.SaveScene();
canvas.Fill(0xFFFFFF, 1, 1, 2, 2);
canvas.BeginScene(new(0, 0, 8, 8)); canvas.RestoreSceneRegion(new(1, 1, 2, 2));
canvas.PreserveTranslatedSceneRegion(new(1, 1, 2, 2), 4, 4);
canvas.Fill(0x112233, 0, 0, 8, 8); canvas.EndScenePreservation(); canvas.Present();
Assert.Equal(0x336699, output.Pixels.Span[4 * 8 + 4]);
Assert.Equal(0x112233, output.Pixels.Span[1 * 8 + 1]);
}
[Fact]
public void FingerprintDescribesSuccessfulPresentationRatherThanPendingCpuWrites()
{
var output = new ArrayFramebufferPresenter(8, 8); var canvas = new CpuFramebufferCanvas(output, timestamp: () => 42);
Assert.False(canvas.TryFingerprintPresentedPixels(new(0, 0, 8, 8), out _));
canvas.Fill(0x112233, 0, 0, 8, 8); canvas.Present();
Assert.True(canvas.TryFingerprintPresentedPixels(new(0, 0, 8, 8), out var first));
canvas.Fill(0xFFFFFF, 0, 0, 8, 8);
Assert.True(canvas.TryFingerprintPresentedPixels(new(0, 0, 8, 8), out var pending));
Assert.Equal(first, pending); Assert.Equal(42, canvas.LastPresentTicks);
canvas.Present(new(0, 0, 1, 1));
Assert.True(canvas.TryFingerprintPresentedPixels(new(0, 0, 8, 8), out var second));
Assert.NotEqual(first.Fingerprint, second.Fingerprint); Assert.Equal(2, second.PresentationSequence);
}
[Fact]
public void ActualCachedWebPixelsFollowAMovedViewportAndDoNotCacheOccludedPixels()
{
var output = new ArrayFramebufferPresenter(10, 10); var canvas = new CpuFramebufferCanvas(output);
canvas.Fill(0xFFFFFF, 1, 1, 4, 4); canvas.StoreWebFrame(1, 2, 3, new(1, 1, 4, 4), 0, 0);
canvas.Fill(0, 0, 0, 10, 10);
Assert.True(canvas.TryRestoreWebFrame(1, 2, 3, new(5, 5, 4, 4), 0, 0)); canvas.Present();
Assert.Equal(0xFFFFFF, output.Pixels.Span[5 * 10 + 5]); Assert.Equal(0, output.Pixels.Span[1 * 10 + 1]);
canvas.SaveScene(); canvas.PreserveTranslatedSceneRegion(new(5, 5, 4, 4), 5, 5);
canvas.StoreWebFrame(2, 2, 3, new(5, 5, 4, 4), 0, 0); canvas.EndScenePreservation();
Assert.False(canvas.TryRestoreWebFrame(2, 2, 3, new(0, 0, 4, 4), 0, 0));
}
}
using XFESystem.Desktop;
using XFESystem.Platform;
namespace XFESystem.Platform.Tests;
public sealed class FramebufferTests
{
[Theory]
[InlineData(32, 0x00FF0000u, 0x0000FF00u, 0x000000FFu, 0x00FF0000u, 0x0000FF00u, 0x000000FFu)]
[InlineData(24, 0x000000FFu, 0x0000FF00u, 0x00FF0000u, 0x000000FFu, 0x0000FF00u, 0x00FF0000u)]
[InlineData(16, 0x0000F800u, 0x000007E0u, 0x0000001Fu, 0x0000F800u, 0x000007E0u, 0x0000001Fu)]
public unsafe void MappedScanoutUsesRgbMasksPitchAndLeavesGuardBytesUntouched(int bits, uint redMask, uint greenMask, uint blueMask, uint red, uint green, uint blue)
{
const int width = 3, height = 2;
int bytes = bits / 8, pitch = width * bytes + 5;
var scanout = Enumerable.Repeat((byte)0xA5, pitch * height + 16).ToArray();
int[] pixels = [0xFF0000, 0x00FF00, 0x0000FF, 0xFFFFFF, 0, 0xFFFFFF];
fixed (byte* address = scanout)
{
var target = new MemoryFramebufferPresenter(new((nuint)address, (nuint)(pitch * height), width, height, pitch, bits, redMask, greenMask, blueMask));
target.Present(pixels, new(-20, -20, 23, 21));
}
uint Read(int pixel) { uint value = 0; for (int b = 0; b < bytes; b++) value |= (uint)scanout[pixel * bytes + b] << (8 * b); return value; }
Assert.Equal(red, Read(0)); Assert.Equal(green, Read(1)); Assert.Equal(blue, Read(2));
Assert.All(scanout.Skip(width * bytes), value => Assert.Equal(0xA5, value));
}
[Fact]
public unsafe void InvalidMappingAndMasksAreRejectedBeforeAnyWrite()
{
var data = new byte[64];
fixed (byte* p = data)
{
nuint address = (nuint)p;
Assert.Throws<ArgumentException>(() => new MemoryFramebufferPresenter(new(address, 4, 3, 2, 16, 32, 0xFF0000, 0xFF00, 0xFF)));
Assert.Throws<ArgumentException>(() => new MemoryFramebufferPresenter(new(address, 64, 3, 2, 8, 32, 0xFF0000, 0xFF00, 0xFF)));
Assert.Throws<ArgumentException>(() => new MemoryFramebufferPresenter(new(address, 64, 3, 2, 16, 32, 0xFF00, 0xFF00, 0xFF)));
Assert.Throws<ArgumentException>(() => new MemoryFramebufferPresenter(new(address, 64, 3, 2, 16, 32, 0xA00000, 0xFF00, 0xFF)));
}
Assert.All(data, value => Assert.Equal(0, value));
}
[Fact]
public void CoverageBlendsAgainstExistingPixelsAndHonorsNestedClipping()
{
var output = new ArrayFramebufferPresenter(8, 8); var canvas = new CpuFramebufferCanvas(output);
canvas.Fill(0x0000FF, 0, 0, 8, 8);
canvas.PushClip(new(2, 2, 3, 3)); canvas.PushClip(new(3, 1, 3, 3));
canvas.BlendCoverage(0xFF0000, 0, 0, 8, 8, Enumerable.Repeat((byte)128, 64).ToArray());
canvas.PopClip(); canvas.PopClip(); canvas.Present();
Assert.Equal(0x80007F, output.Pixels.Span[2 * 8 + 3]);
Assert.Equal(0x0000FF, output.Pixels.Span[2 * 8 + 2]);
Assert.Equal(0x0000FF, output.Pixels.Span[4 * 8 + 3]);
Assert.Throws<InvalidOperationException>(canvas.PopClip);
Assert.Throws<ArgumentException>(() => canvas.BlendCoverage(0, 0, 0, 8, 8, new byte[63]));
}
[Fact]
public void FullPresentationIncludesNativeRemainderAtFractionalScale()
{
var output = new ArrayFramebufferPresenter(11, 9); var canvas = new CpuFramebufferCanvas(output, scalePercent: 125);
canvas.Fill(0x123456, 0, 0, canvas.Width, canvas.Height); canvas.Present();
Assert.All(output.Pixels.ToArray(), pixel => Assert.Equal(0x123456, pixel));
}
[Fact]
public void EmptyFillAtFractionalLayoutEdgeDoesNotPaintNativeRemainder()
{
var output = new ArrayFramebufferPresenter(11, 9); var canvas = new CpuFramebufferCanvas(output, scalePercent: 125);
canvas.Fill(0x123456, canvas.Width, 0, 0, canvas.Height);
canvas.Fill(0xFFFFFF, 0, 0, -1, canvas.Height); canvas.Present();
Assert.All(output.Pixels.ToArray(), pixel => Assert.Equal(0, pixel));
}
[Fact]
public void HugeLineEndpointsAreClippedBeforeWalkingPixels()
{
var output = new ArrayFramebufferPresenter(8, 8); var canvas = new CpuFramebufferCanvas(output);
canvas.Line(0xFFFFFF, int.MinValue, 3, int.MaxValue, 3); canvas.Present();
Assert.All(output.Pixels.Slice(24, 8).ToArray(), pixel => Assert.Equal(0xFFFFFF, pixel));
Assert.Equal(8, output.Pixels.ToArray().Count(pixel => pixel != 0));
}
[Fact]
public void PointerSceneRestorationAndTranslatedPreservationUsePointerFreePixels()
{
var output = new ArrayFramebufferPresenter(8, 8); var canvas = new CpuFramebufferCanvas(output);
canvas.Fill(0x336699, 0, 0, 8, 8); canvas.SaveScene();
canvas.Fill(0xFFFFFF, 1, 1, 2, 2);
canvas.BeginScene(new(0, 0, 8, 8)); canvas.RestoreSceneRegion(new(1, 1, 2, 2));
canvas.PreserveTranslatedSceneRegion(new(1, 1, 2, 2), 4, 4);
canvas.Fill(0x112233, 0, 0, 8, 8); canvas.EndScenePreservation(); canvas.Present();
Assert.Equal(0x336699, output.Pixels.Span[4 * 8 + 4]);
Assert.Equal(0x112233, output.Pixels.Span[1 * 8 + 1]);
}
[Fact]
public void FingerprintDescribesSuccessfulPresentationRatherThanPendingCpuWrites()
{
var output = new ArrayFramebufferPresenter(8, 8); var canvas = new CpuFramebufferCanvas(output, timestamp: () => 42);
Assert.False(canvas.TryFingerprintPresentedPixels(new(0, 0, 8, 8), out _));
canvas.Fill(0x112233, 0, 0, 8, 8); canvas.Present();
Assert.True(canvas.TryFingerprintPresentedPixels(new(0, 0, 8, 8), out var first));
canvas.Fill(0xFFFFFF, 0, 0, 8, 8);
Assert.True(canvas.TryFingerprintPresentedPixels(new(0, 0, 8, 8), out var pending));
Assert.Equal(first, pending); Assert.Equal(42, canvas.LastPresentTicks);
canvas.Present(new(0, 0, 1, 1));
Assert.True(canvas.TryFingerprintPresentedPixels(new(0, 0, 8, 8), out var second));
Assert.NotEqual(first.Fingerprint, second.Fingerprint); Assert.Equal(2, second.PresentationSequence);
}
[Fact]
public void ActualCachedWebPixelsFollowAMovedViewportAndDoNotCacheOccludedPixels()
{
var output = new ArrayFramebufferPresenter(10, 10); var canvas = new CpuFramebufferCanvas(output);
canvas.Fill(0xFFFFFF, 1, 1, 4, 4); canvas.StoreWebFrame(1, 2, 3, new(1, 1, 4, 4), 0, 0);
canvas.Fill(0, 0, 0, 10, 10);
Assert.True(canvas.TryRestoreWebFrame(1, 2, 3, new(5, 5, 4, 4), 0, 0)); canvas.Present();
Assert.Equal(0xFFFFFF, output.Pixels.Span[5 * 10 + 5]); Assert.Equal(0, output.Pixels.Span[1 * 10 + 1]);
canvas.SaveScene(); canvas.PreserveTranslatedSceneRegion(new(5, 5, 4, 4), 5, 5);
canvas.StoreWebFrame(2, 2, 3, new(5, 5, 4, 4), 0, 0); canvas.EndScenePreservation();
Assert.False(canvas.TryRestoreWebFrame(2, 2, 3, new(0, 0, 4, 4), 0, 0));
}
}