// Copy tile to output
        private void CopyToOutput(TileRenderState state, Tensor <float> tensor, Rectangle tile)
        {
            if (tile.Y > 0)
            {
                BlendTopMargin(state, tensor, tile);
            }

            var sx = tile.X > 0 ? Margin : 0;
            var sy = tile.Y > 0 ? Margin : 0;

            var(x, y) = (tile.X, tile.Y);
            var cols = Math.Min(tensor.Width() - sx, Output.Width - tile.X);
            var rows = Math.Min(tensor.Height() - sy, Output.Height - tile.Y);

            var tensorRow  = new ArraySegment <byte>(state.TensorRow, 0, cols * 4);
            var marginLeft = new ArraySegment <byte>(state.OutputRow, 0, sx * 4);

            for (int row = sy; row < rows; ++row)
            {
                _ = tensor.GetRowArgb(tensorRow, sx, row + sy);
                Output.GetRowArgb(marginLeft, tile.X, row + y);
                marginLeft.LinearBlend(tensorRow, tensorRow);
                Output.SetRowArgb(tensorRow, x, row + y);
            }
        }
        /// <summary>
        /// Process an effect graph using tiled input
        /// </summary>
        /// <param name="graph">Fully configured effect graph instance</param>
        public void Process(EffectGraph graph)
        {
            Contract.Requires(graph != null);
            var state = new TileRenderState(this);
            var tiles = GetTiles(new Size(Input.Width, Input.Height), TileSize);
            int index = 0;

            graph.Update += GraphUpdate;

            foreach (var tile in tiles)
            {
                ++index;
                var expanded = AddMargins(tile);
                state.SetTileSize(expanded.Size);
                graph.Params.Content = Input.CopyToTensor(state.Tensor, expanded);
                var result = graph.Run(index == 1);
                CopyToOutput(state, result, tile);
            }

            graph.Update -= GraphUpdate;

            void GraphUpdate(object sender, EffectGraphEventArgs e)
            {
                var args = new TileEventArgs(e, tiles[index - 1], index, tiles.Count);

                Update?.Invoke(this, args);
            }
        }
        // Blend a tile's top rows with previous output
        private void BlendTopMargin(TileRenderState state, Tensor <float> tensor, Rectangle tile)
        {
            var(ix, iy) = (tile.X > 0 ? Margin : 0, Margin);
            var(ox, oy) = (tile.X, tile.Y);
            var delta     = 1.0f / Margin;
            var c         = delta;
            var elements  = (tensor.Width() - ix) * 4;
            var tensorRow = new ArraySegment <byte>(state.TensorRow, 0, elements);
            var outputRow = new ArraySegment <byte>(state.OutputRow, 0, elements);
            var resultRow = new ArraySegment <byte>(state.BufferRow, 0, elements);

            for (int i = 0; i < Margin; ++i, ++iy, ++oy, c += delta)
            {
                _ = tensor.GetRowArgb(tensorRow, ix, iy);
                _ = Output.GetRowArgb(outputRow, ox, oy);
                outputRow.MixArgb(tensorRow, resultRow, c);
                Output.SetRowArgb(resultRow, ox, oy);
            }
        }