microsoft / Windows.UI.Composition-Win32-Samples

Windows.UI.Composition Win32 Samples
MIT License
459 stars 186 forks source link

Reduce frame rate by adding sleep inside the message loop? #98

Closed gileli121 closed 2 years ago

gileli121 commented 2 years ago

Hello, I found simple method to reduce the capture frame rate but I did not saw that it was done before so I wonder if it is fine and if I may get issue with it.

The idea is to put Sleep inside the message loop that use PeekMessage function.

In my case I have thread for each capture. Each thread doing this:

void CaptureThreadMethod()
{
    CoreMessagingHelper.CreateDispatcherQueueControllerForCurrentThread();

    try
    {
        framePool = Direct3D11CaptureFramePool.Create(
            device,
            DirectXPixelFormat.B8G8R8A8UIntNormalized,
            1,
            item.Size);
        session = framePool.CreateCaptureSession(item);

        RemoveCaptureBorderAndMouse();

        framePool.FrameArrived += OnFrameArrived;
        session.StartCapture();
    }
    catch (Exception e)
    {
        log.Error("Failed to start capturing");
        log.Error(e);
        Capturing = false;
    }

    while (!requestAbortThread)
    {
        if (Win32Native.PeekMessage(out var msg, IntPtr.Zero, 0, 0, Win32Native.PM_REMOVE))
        {
            Win32Native.TranslateMessage(ref msg);
            Win32Native.DispatchMessage(ref msg);
        }
        else
        {
            Thread.Sleep(10);
        }
    }

    try
    {
        session?.Dispose();
    }
    catch (Exception e)
    {
        log.Error("Failed to dispose session");
        log.Error(e);
    }

    try
    {
        framePool?.Dispose();
    }
    catch (Exception e)
    {
        log.Error("Failed to dispose framePool");
        log.Error(e);
    }

    try
    {
        CapturedTexture?.Dispose();
    }
    catch (Exception e)
    {
        log.Error("Failed to dispose CapturedTexture");
        log.Error(e);
    }

    session = null;
    framePool = null;
    CapturedTexture = null;
    Capturing = false;
    requestAbortThread = false;
}

The capture started/aborted using:

public void StartCapture()
{
    if (Capturing) return;
    try
    {
        device = Direct3D11Helper.GetDevice();
        item = CaptureHelper.CreateItemForWindow(WindowItem.Hwnd);

        Capturing = true;
        requestAbortThread = false;

        captureThread = new Thread(CaptureThreadMethod);
        captureThread.Start();
    }
    catch (Exception e)
    {
        log.Fatal(e);
        // Capturing = false; // Prefer to fake that it currently capturing because this way it will not try again and again to capture it
    }
}

public void StopCapture()
{
    requestAbortThread = true;
}

So I my case, to get frame rate of 1 second each frame, I replace the Thread.Sleep(10); line with Thread.Sleep(1000); line so that:

while (!requestAbortThread)
{
    if (Win32Native.PeekMessage(out var msg, IntPtr.Zero, 0, 0, Win32Native.PM_REMOVE))
    {
        Win32Native.TranslateMessage(ref msg);
        Win32Native.DispatchMessage(ref msg);
    }
    else
    {
        // Thread.Sleep(10); // Removed
        Thread.Sleep(1000); // Added
    }
}
robmikh commented 2 years ago

That might in effect slow down the rate that your FrameArrived handler gets fired... but it also slows down every message you'll get on that message pump. I would not do this on a UI thread.

Another method would be to hold onto the Direct3D11CaptureFrame objects, if all frames from the frame pool are outstanding, no new frames will be written/delivered. You can use the lifetime of those objects to throttle.

gileli121 commented 2 years ago

@robmikh I would not do this on a UI thread I know but in this case the thread is only for capturing. It is not real UI thread.. It doesn't get user inputs. So maybe it is fine?

What to you mean by saying "would be to hold onto the Direct3D11CaptureFrame objects"? Do you have some example?