public override void Draw() { if (video == null) { return; } if (!stopped && !paused) { var nextFrame = 0; if (video.HasAudio && !Game.Sound.DummyEngine) { nextFrame = (int)float2.Lerp(0, video.Frames, Game.Sound.VideoSeekPosition * invLength); } else { nextFrame = video.CurrentFrame + 1; } // Without the 2nd check the sound playback sometimes ends before the final frame is displayed which causes the player to be stuck on the first frame if (nextFrame > video.Frames || nextFrame < video.CurrentFrame) { Stop(); return; } var skippedFrames = 0; while (nextFrame > video.CurrentFrame) { video.AdvanceFrame(); videoSprite.Sheet.GetTexture().SetData(video.FrameData); skippedFrames++; } if (skippedFrames > 1) { Log.Write("perf", "VqaPlayer : {0} skipped {1} frames at position {2}", cachedVideo, skippedFrames, video.CurrentFrame); } } WidgetUtils.DrawSprite(videoSprite, videoOrigin, videoSize); if (DrawOverlay) { // Create the scan line grid to render over the video // To avoid aliasing, this must be an integer number of screen pixels. // A few complications to be aware of: // - The video may have a different aspect ratio to the widget RenderBounds // - The RenderBounds coordinates may be a non-integer scale of the screen pixel size // - The screen pixel size may change while the video is playing back // (user moves a window between displays with different DPI on macOS) var scale = Game.Renderer.WindowScale; if (overlaySheet == null || overlayScale != scale) { overlaySheet?.Dispose(); // Calculate the scan line height by converting the video scale (copied from Open()) to screen // pixels, halving it (scan lines cover half the pixel height), and rounding to the nearest integer. var videoScale = Math.Min((float)RenderBounds.Width / video.Width, RenderBounds.Height / (video.Height * AspectRatio)); var halfRowHeight = (int)(videoScale * scale / 2 + 0.5f); // The overlay can be minimally stored in a 1px column which is stretched to cover the full screen var overlayHeight = (int)(RenderBounds.Height * scale / halfRowHeight); var overlaySheetSize = new Size(1, Exts.NextPowerOf2(overlayHeight)); var overlay = new byte[4 * Exts.NextPowerOf2(overlayHeight)]; overlaySheet = new Sheet(SheetType.BGRA, overlaySheetSize); // Every second pixel is the scan line - set alpha to 128 to make the lines less harsh for (var i = 3; i < 4 * overlayHeight; i += 8) { overlay[i] = 128; } overlaySheet.GetTexture().SetData(overlay, overlaySheetSize.Width, overlaySheetSize.Height); overlaySprite = new Sprite(overlaySheet, new Rectangle(0, 0, 1, overlayHeight), TextureChannel.RGBA); // Overlay origin must be rounded to the nearest screen pixel to prevent aliasing overlayOrigin = new float2((int)(RenderBounds.X * scale + 0.5f), (int)(RenderBounds.Y * scale + 0.5f)) / scale; overlaySize = new float2(RenderBounds.Width, overlayHeight * halfRowHeight / scale); overlayScale = scale; } WidgetUtils.DrawSprite(overlaySprite, overlayOrigin, overlaySize); } }