7

私のアプリのユーザーは、DesktopDuplication API.

キャプチャを開始すると、アプリが のフレームを解放できないため、アプリがクラッシュしOutputDuplicationます。

ユーザーの PC の詳細:

Windows 10.0.18362.0, 64 bits
Nvidia GeForce GTX 960, driver version 441.66

エラーログ:

▬ Message - 
HRESULT: [0x887A0001], Module: [SharpDX.DXGI], ApiCode: [DXGI_ERROR_INVALID_CALL/InvalidCall], 
Message: The application made a call that is invalid. Either the parameters of the call or the state of some object was incorrect.
Enable the D3D debug layer in order to see details via debug messages.

○ Type - 
    SharpDX.SharpDXException
▲ Source - 
    SharpDX
▼ TargetSite - 
    Void CheckError()
♠ StackTrace - 
   at SharpDX.Result.CheckError()
   at SharpDX.DXGI.OutputDuplication.ReleaseFrame()
   at MyApp.Capture.DirectImageCapture.CaptureWithCursor(FrameInfo frame)

----------------------------------

▬ Message - 
    Object reference not set to an instance of an object.
○ Type - 
    System.NullReferenceException
▲ Source - 
    SharpDX.Direct3D11
▼ TargetSite - 
    Void GetDescription(SharpDX.Direct3D11.Texture2DDescription ByRef)
♠ StackTrace - 
   at SharpDX.Direct3D11.Texture2D.GetDescription(Texture2DDescription& descRef)
   at MyApp.Capture.DirectImageCapture.GetCursor(Texture2D screenTexture, OutputDuplicateFrameInformation info, FrameInfo frame)
   at MyApp.Capture.DirectImageCapture.CaptureWithCursor(FrameInfo frame)

ユーザーは、マウス カーソルがキャプチャされていないときに画面をキャプチャできるため、カーソルもキャプチャするキャプチャ メソッドに何か問題があるはずです。

    var res = Result.Ok;

    try
    {
        //Try to get the duplicated output frame within given time.
        res = DuplicatedOutput.TryAcquireNextFrame(0, out var info, out var resource);

        //Checks how to proceed with the capture. It could have failed, or the screen, cursor or both could have been captured.
        if (res.Failure || resource == null || (info.AccumulatedFrames == 0 && info.LastMouseUpdateTime <= LastProcessTime))
        {
            //Somehow, it was not possible to retrieve the resource, frame or metadata.
            //frame.WasDropped = true;
            //BlockingCollection.Add(frame);

            resource?.Dispose();
            return FrameCount;
        }
        else if (info.AccumulatedFrames == 0 && info.LastMouseUpdateTime > LastProcessTime)
        {
            //Gets the cursor shape if the screen hasn't changed in between, so the cursor will be available for the next frame.
            GetCursor(null, info, frame);
            return FrameCount;
        }

        //Saves the most recent capture time.
        LastProcessTime = Math.Max(info.LastPresentTime, info.LastMouseUpdateTime);

        //Copy resource into memory that can be accessed by the CPU.
        using (var screenTexture = resource.QueryInterface<Texture2D>())
        {
            //Copies from the screen texture only the area which the user wants to capture.
            Device.ImmediateContext.CopySubresourceRegion(screenTexture, 0, new ResourceRegion(TrueLeft, TrueTop, 0, TrueRight, TrueBottom, 1), BackingTexture, 0);

            //Copy the captured desktop texture into a staging texture, in order to show the mouse cursor and not make the captured texture dirty with it.
            Device.ImmediateContext.CopyResource(BackingTexture, StagingTexture);

            //Gets the cursor image and merges with the staging texture.
            GetCursor(StagingTexture, info, frame);
        }

        //Get the desktop capture texture.
        var data = Device.ImmediateContext.MapSubresource(StagingTexture, 0, MapMode.Read, MapFlags.None);

        if (data.IsEmpty)
        {
            //frame.WasDropped = true;
            //BlockingCollection.Add(frame);

            Device.ImmediateContext.UnmapSubresource(StagingTexture, 0);
            resource.Dispose();
            return FrameCount;
        }

        #region Get image data

        var bitmap = new System.Drawing.Bitmap(Width, Height, PixelFormat.Format32bppArgb);
        var boundsRect = new System.Drawing.Rectangle(0, 0, Width, Height);

        //Copy pixels from screen capture Texture to the GDI bitmap.
        var mapDest = bitmap.LockBits(boundsRect, ImageLockMode.WriteOnly, bitmap.PixelFormat);
        var sourcePtr = data.DataPointer;
        var destPtr = mapDest.Scan0;

        for (var y = 0; y < Height; y++)
        {
            //Copy a single line.
            Utilities.CopyMemory(destPtr, sourcePtr, Width * 4);

            //Advance pointers.
            sourcePtr = IntPtr.Add(sourcePtr, data.RowPitch);
            destPtr = IntPtr.Add(destPtr, mapDest.Stride);
        }

        //Release source and dest locks.
        bitmap.UnlockBits(mapDest);

        //Set frame details.
        FrameCount++;
        frame.Path = $"{Project.FullPath}{FrameCount}.png";
        frame.Delay = FrameRate.GetMilliseconds(SnapDelay);
        frame.Image = bitmap;
        BlockingCollection.Add(frame);

        #endregion

        Device.ImmediateContext.UnmapSubresource(StagingTexture, 0);

        resource.Dispose();
        return FrameCount;
    }
    catch (SharpDXException se) when (se.ResultCode.Code == SharpDX.DXGI.ResultCode.WaitTimeout.Result.Code)
    {
        return FrameCount;
    }
    catch (SharpDXException se) when (se.ResultCode.Code == SharpDX.DXGI.ResultCode.DeviceRemoved.Result.Code || se.ResultCode.Code == SharpDX.DXGI.ResultCode.DeviceReset.Result.Code)
    {
        //When the device gets lost or reset, the resources should be instantiated again.
        DisposeInternal();
        Initialize();

        return FrameCount;
    }
    catch (Exception ex)
    {
        LogWriter.Log(ex, "It was not possible to finish capturing the frame with DirectX.");

        OnError.Invoke(ex);
        return FrameCount;
    }
    finally
    {
        try
        {
            //Only release the frame if there was a success in capturing it.
            if (res.Success)
                DuplicatedOutput.ReleaseFrame();
        }
        catch (Exception e)
        {
            LogWriter.Log(e, "It was not possible to release the frame.");

            //HERE
            //What should I do after the frame is not released properly?
            //Should I reset the whole capture?
            //DisposeInternal();
            //Initialize();
        }
    }

各フレームのキャプチャが終了したら、 はDuplicatedOutputフレームを解放する必要があります。

しかし、リリースが で失敗した場合InvalidCall、どうすればよいですか?
また、(開発者のマシンではなく) ユーザーの PC でこの種のエラーをデバッグするにはどうすればよいですか?


編集:

これは私がやろうとしていることです:

onをGraphics Tools有効にしてWindows Settings、次のコードをキャプチャの初期化に追加しました。

#if DEBUG
    Device = new Device(DriverType.Hardware, DeviceCreationFlags.VideoSupport | DeviceCreationFlags.Debug);

    var debug = InfoQueue.TryCreate();
    debug.SetBreakOnSeverity(DebugId.All, InformationQueueMessageSeverity.Corruption, true);
    debug.SetBreakOnSeverity(DebugId.All, InformationQueueMessageSeverity.Error, true);
    debug.SetBreakOnSeverity(DebugId.All, InformationQueueMessageSeverity.Warning, true);

    var debug2 = DXGIDebug.TryCreate();
    debug2.ReportLiveObjects(DebugId.Dx, DebugRloFlags.Summary | DebugRloFlags.Detail);

#else

次に、ノートブックの専用 GPU で実行するようにアプリを設定しますInvalidException

アプリを実行して試してみましoutput1.DuplicateOutput(Device);たが、期待どおりに失敗しました。

その後、DebugView の実行中にアプリを実行しようとしましたが、エラーが発生したときではなく、アプリを閉じたときにのみメッセージが表示されました。

00000001    0.00000000  [14488] OnFocusWindowChanged to Lizard Mode 
00000002    0.39583239  [14488] Lizard Mode: Unprivileged process   
00000003    0.39594769  [14488] Lizard Mode: Restoring app mapping  
00000004    9.81729603  [21620] D3D11 WARNING: Process is terminating. Using simple reporting. Please call ReportLiveObjects() at runtime for standard reporting. [ STATE_CREATION WARNING #0: UNKNOWN] 
00000005    9.81732273  [21620] D3D11: **BREAK** enabled for the previous message, which was: [ WARNING STATE_CREATION #0: UNKNOWN ]    
00000006    9.81803799  [21620] DXGI WARNING: Process is terminating. Using simple reporting. Please call ReportLiveObjects() at runtime for standard reporting. [ STATE_CREATION WARNING #0: ] 
00000007    9.81806469  [21620] DXGI: **BREAK** enabled for the previous message, which was: [ WARNING STATE_CREATION #0:  ]    
00000008    10.78524113 [14488] Lizard Mode: Privileged process 
00000009    10.78589630 [14488] Lizard Mode: Reverting to default M/KB Configuration    
00000010    10.78692913 [14488] OnFocusWindowChanged to Lizard Mode 

そこで、次のdxcapコマンドを使用して、 を使用してエラーをキャプチャしようとしました。

dxcap -debug -toXML '[path]\debug.xml' -file '[path]\debug.vsglog' -c '[path]\bin\Debug\MyApp.exe'

残念ながら、次のようにCreateDevice()失敗します。

HRESULT: [0x887A0004]、モジュール: [SharpDX.DXGI]、ApiCode: [DXGI_ERROR_UNSUPPORTED/Unsupported]、メッセージ: 指定されたデバイス インターフェイスまたは機能レベルは、このシステムではサポートされていません。

その後、もう一度試してみましたが、今回はのみDeviceCreationFlags.Debugでうまくいきました。私はまだファイルを分析しています。

デバッグ

4

1 に答える 1

0

C# デスクトップ複製用に同じコードをフォークしたようですが、フレームの解放にも問題がありました。少し修正しましたが、現時点では、(まだ不完全な) ambilight アプリで問題は発生していません。ここでコードを見ることができます: https://github.com/leocb/Ambilight コードの好きな部分を自由にコピーしてください。関連する部分は DesktopDuplication プロジェクト内にあります。また、私が使用する関数も確認してくださいConfigForm.cs

ReleaseFrame()空白の try/catch 内に配置することでスローされる可能性のあるエラーは無視します。IMO、チェックする必要がある唯一の重要なものは ですDXGI_ERROR_ACCESS_LOST。この場合、新しいIDXGIOutputDuplicationインスタンスが必要になるためです(私のコードではまだこれを行っていません)。これを行うことによるパフォーマンスの低下は測定できませんでした。

SharpDX を正しく初期化していること (デバイス、工場など) を確認し、表面テクスチャに正しいフラグを設定してください。

GDI イメージにダブル バッファを使用することをお勧めします。これにより、競合状態エラーが回避され、パフォーマンスが向上します。

また、パフォーマンス上の理由から、別のスレッドでキャプチャ コードを実行し、Microsoft が推奨するようReleaseFrame()に、別のキャプチャを実行する直前にのみ呼び出します。

[...] IDXGIOutputDuplication::AcquireNextFrame メソッドを呼び出して次のフレームを取得する直前に、フレームを解放することをお勧めします。クライアントがフレームを所有していない場合、オペレーティング システムはすべてのデスクトップ更新をサーフェスにコピーします。

これがすべて失敗した場合、「より簡単な」解決策は、標準の c# コードでマウスの位置をキャプチャし、スクリーン キャプチャの上に描画することです。

于 2020-03-04T14:00:28.483 に答える