【UWP】ウィンドウキャプチャを CanvasSwapChainPanel に表示する


https://learn.microsoft.com/ja-jp/windows/uwp/audio-video-camera/screen-capture

公式サンプルではCompositionレイヤーのブラシに描写することでUI表示に対応していますが、この記事では CanvasSwapChainPanel を用いて表示する方法を紹介します。

CanvasSwapChainPanel は Win2D のXamlコントロールです。使用には Win2D のnugetパッケージをインストールします。

CanvasSwapChainPanel は CanvasControl への描画に比べてティアリング発生を抑えられることを期待して利用しています。

ただし、実際に CanvasControl を使用してティアリング発生の有無を確認したわけではありません。CanvasSwapChainPanel は GPUメモリの使用量がバッファの分だけ増えるので、適宜 CanvasControl に置き換えてください。


## 公式サンプルコード の挙動について補足

* ウィンドウキャプチャはユーザーに指定されたウィンドウ、デスクトップのみを画像データとして取得できる
* GraphicCaptureItem.Closedは一度だけ呼ばれる。再オープンすることはできない。そのためキャプチャ対象のウィンドウを閉じられた場合は、再度キャプチャピッカーを表示して選択させる
* _framePool.TryGetNextFrame() を呼び出してバッファに空きを作らないと、_framePool.FrameArrived は呼び出されなくなる
* 画面のリフレッシュレートに応じてフレームを処理出来てしまうため、アプリとOSの操作感を損なわないためにも間引きして表示したい
  * 60Hz 1000x700 ぐらいの領域をキャプチャしてもGPU使用率 5% ~ 10%程度（グラボはGTX 1650）
  * これを 5Hz 程度になるよう調整するとGPU使用率 1% 未満に抑えられた
  

## CanvasSwapChainPanel にキャプチャフレームを描画する

```csharp

    private void ProcessFrame(Direct3D11CaptureFrame frame)
    {
        Guard.IsNotNull(_canvasDevice);
        if (CanvasPanel?.SwapChain == null) { return; }

        // Resize and device-lost leverage the same function on the
        // Direct3D11CaptureFramePool. Refactoring it this way avoids
        // throwing in the catch block below (device creation could always
        // fail) along with ensuring that resize completes successfully and
        // isn’t vulnerable to device-lost.
        bool needsReset = false;
        bool recreateDevice = false;

        if ((frame.ContentSize.Width != _lastSize.Width) ||
            (frame.ContentSize.Height != _lastSize.Height))
        {
            needsReset = true;
            _lastSize = frame.ContentSize;
        }

        try
        {
            // Convert our D3D11 surface into a Win2D object.
            CanvasBitmap canvasBitmap = CanvasBitmap.CreateFromDirect3D11Surface(
                _canvasDevice,
                frame.Surface);

            _currentFrame?.Dispose();
            _currentFrame = canvasBitmap;

            // スワップチェインのオフスクリーンサーフェイスに対して描画する
            using (var ds = CanvasPanel.SwapChain.CreateDrawingSession(Colors.Transparent))
            {
                ds.DrawImage(canvasBitmap);
            }

            // オフスクリーン・オンスクリーンのサーフェイスをスイッチして表示させる
            CanvasPanel.SwapChain.Present();
        }

        // This is the device-lost convention for Win2D.
        catch (Exception e) when (_canvasDevice.IsDeviceLost(e.HResult))
        {
            // We lost our graphics device. Recreate it and reset
            // our Direct3D11CaptureFramePool.  
            needsReset = true;
            recreateDevice = true;
        }

        if (needsReset)
        {
            ResetFramePool(frame.ContentSize, recreateDevice);
        }
    }
```

## コントロールにまとめて再利用性を確保する

完成したものがこちらになります

https://gist.github.com/tor4kichi/cd76923d4b58bd4105d31aca0bf2d4d6



## 参考

スクリーンキャプチャ参考

https://learn.microsoft.com/ja-jp/windows/uwp/audio-video-camera/screen-capture

CanvasSwapChainPanel 

https://microsoft.github.io/Win2D/WinUI3/html/T_Microsoft_Graphics_Canvas_UI_Xaml_CanvasSwapChainPanel.htm

Canvas系コントロールのメモリリーク回避

https://microsoft.github.io/Win2D/WinUI3/html/RefCycles.htm