public FrameRequest(int frameId, Duration frameTime, Duration circleTime, FrameRequest previousFrameRequest)
 {
     FrameId              = frameId;
     FrameTime            = frameTime;
     CircleTime           = circleTime;
     PreviousFrameRequest = previousFrameRequest;
     Rendered             = new ManualResetEvent(false);
     Compressed           = new ManualResetEvent(false);
     Transmitted          = new ManualResetEvent(false);
 }
        private Task Transmit(FrameRequest request, ArraySegment <byte> segment)
        {
            var sw = new Stopwatch();

            sw.Start();

            var task = _webSocket.SendDataAsync(segment, _cancellationToken);

            return(task.ContinueWith(t => _stats.AddTransmitDuration(sw.Elapsed), _cancellationToken));
        }
        private void Render(FrameRequest request, Graphics gfx)
        {
            Duration frameTime  = request.FrameTime;
            Duration circleTime = request.CircleTime;

            var frameTimeMs = frameTime.TotalMilliseconds;

            var sw = new Stopwatch();

            sw.Start();

            var width  = _spec.Width;
            var height = _spec.Height;
            var radius = _spec.Radius;
            var center = _spec.Center;

            gfx.ResetTransform();

            gfx.Clear(Color.SkyBlue);

            gfx.TranslateTransform(width * 0.5f, height * 0.5f);

            var frameAngle = (circleTime.TotalSeconds * 360 / _spec.SpinDurationSec) % 360;

            gfx.RotateTransform((float)frameAngle);

            if (_background != null)
            {
                var state = gfx.Save();
                gfx.CompositingMode    = CompositingMode.SourceCopy;
                gfx.CompositingQuality = CompositingQuality.HighSpeed;
                gfx.InterpolationMode  = InterpolationMode.NearestNeighbor;
                gfx.SmoothingMode      = SmoothingMode.None;
                gfx.DrawImage(_background, -width * 0.5f, -height * 0.5f);
                gfx.Restore(state);
            }

            var transform = gfx.Transform;

            gfx.Transform = transform;
            gfx.FillRectangle(Brushes.Black, center, -5, radius, 10);

            _stats.AddRenderDuration(sw.Elapsed);

            gfx.ResetTransform();

            // Draw a bouncing ball
            const float ballRadius = 20;

            gfx.FillEllipse(Brushes.Orange,
                            width * 0.5f - ballRadius, height - (height - 2 * ballRadius) * (float)Math.Abs(Math.Sin((float)(frameTimeMs / 1000f))),
                            ballRadius * 2, ballRadius * 2);

            request.Rendered.Set();
        }
 public void PostRequest(FrameRequest request)
 {
     Debug.Assert(_requests.IsEmpty);
     _requests.Enqueue(request);
     _queued.Set();
 }
        private async Task Compress(FrameRequest request, Bitmap srcImage, Compressor[] compressors, int quality = 90)
        {
            var imageOffset = FrameHeader.MessageSize;

            var header = _stats.Update(request.FrameId, request.FrameTime);

            PixelFormat pixelFormat   = srcImage.PixelFormat;
            int         width         = srcImage.Width;
            int         height        = srcImage.Height;
            BitmapData  bitmapData    = srcImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, pixelFormat);
            int         stride        = bitmapData.Stride;
            int         bytesPerPixel = pixelFormat.GetBytesPerPixel();
            IntPtr      scan0         = bitmapData.Scan0;

            try
            {
                int remainingCompressors = compressors.Length;

                var tasks = compressors
                            .Select((compressor, index) => Task.Run(
                                        delegate
                {
                    var sw = new Stopwatch();
                    sw.Start();

                    var segmentX = (index >> 1) * (_spec.Width / 2);
                    var segmentY = (index & 1) * (_spec.Height / 2);

                    var ptr = scan0.ToInt64() + segmentY * stride + segmentX * bytesPerPixel;

                    var data = compressor.Compress(new IntPtr(ptr), stride, imageOffset, pixelFormat, quality);

                    if (Interlocked.Decrement(ref remainingCompressors) == 0)
                    {
                        // All compression threads are done.
                        request.Compressed.Set();
                    }

                    var span = MemoryMarshal.Cast <byte, FrameHeader>(
                        new Span <byte>(data.Array, data.Offset, data.Count));

                    span[0]          = header;
                    span[0].SegmentX = segmentX;
                    span[0].SegmentY = segmentY;

                    _stats.AddCompressedSize(data.Count);
                    _stats.AddCompressDuration(sw.Elapsed);

                    // Make sure the previous frame is transmitted first
                    request.PreviousFrameRequest.Transmitted.WaitOne();

                    // Send this segment
                    return(Transmit(request, data));
                }, _cancellationToken))
                            .ToArray();

                Stopwatch tw = new Stopwatch();
                tw.Start();
                await Task.WhenAll(tasks);

                _stats.AddTransmitDuration(tw.Elapsed);

                request.Transmitted.Set();
            }
            finally
            {
                srcImage.UnlockBits(bitmapData);
            }
        }