diff --git a/src/Dragon-ISO.Engine/Pipeline/ManagedNearestNeighborFrameScaler.cs b/src/Dragon-ISO.Engine/Pipeline/ManagedNearestNeighborFrameScaler.cs index cae2a6b..a6b62c7 100644 --- a/src/Dragon-ISO.Engine/Pipeline/ManagedNearestNeighborFrameScaler.cs +++ b/src/Dragon-ISO.Engine/Pipeline/ManagedNearestNeighborFrameScaler.cs @@ -15,6 +15,15 @@ public sealed class ManagedNearestNeighborFrameScaler : IFrameScaler $"ManagedNearestNeighborFrameScaler only supports BGRA input (got {source.Format}). " + "v1.0 receivers request BGRA from NDI; UYVY support comes in v1.5."); + // Degenerate source guard: a frame reporting a zero (or negative) dimension + // would divide-by-zero in ComputeFitRect (srcW/srcH) and produce NaN extents, + // or drive a degenerate copy loop on the Stretch path. Rather than throw out + // of the hot path and cost the pipeline a supervisor restart, emit a black + // opaque frame at the target size — identical to what the no-signal slate + // would render — so a single bad frame is absorbed without a reconnect. + if (source.Width <= 0 || source.Height <= 0) + return BlackFrame(targetWidth, targetHeight, timestampTicks); + var output = new byte[targetWidth * targetHeight * 4]; // Compute the destination rectangle within the target according to aspect mode. @@ -56,6 +65,14 @@ public sealed class ManagedNearestNeighborFrameScaler : IFrameScaler return new ProcessedFrame(targetWidth, targetHeight, timestampTicks, output, PixelFormat.Bgra); } + /// Allocates a black, fully-opaque BGRA frame at the requested size. + private static ProcessedFrame BlackFrame(int width, int height, long timestampTicks) + { + var output = new byte[width * height * 4]; + for (var i = 3; i < output.Length; i += 4) output[i] = 0xFF; // alpha; BGR stay 0 + return new ProcessedFrame(width, height, timestampTicks, output, PixelFormat.Bgra); + } + private static (int X, int Y, int W, int H) ComputeFitRect(int srcW, int srcH, int dstW, int dstH, AspectMode aspect) { switch (aspect)