Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GraphicsCaptureItem.Closed Event not firing #17

Closed
LordTrololo opened this issue Jul 23, 2020 · 7 comments
Closed

GraphicsCaptureItem.Closed Event not firing #17

LordTrololo opened this issue Jul 23, 2020 · 7 comments

Comments

@LordTrololo
Copy link

LordTrololo commented Jul 23, 2020

Hi,
first of all thank you for making this sample project.

I used your example to create a similar project (C++/Win32) with two notable exceptions:

  • I am not using Create() but rather winrt::Direct3D11CaptureFramePool::CreateFreeThreaded() and
  • I dont have any UI stuff, my entire Capturer is one file/class

Now the event OnFrameArrived works and gets triggered and I am able to use it.
But the event GraphicsCaptureItem.Closed doesn't get triggered when I close the Window.

I bind it like so:
m_itemClosedRevoker = m_item.Closed(winrt::auto_revoke, { this, &MyCapturer::OnCaptureItemClosed });
I also tried just this:
m_item.Closed({ this, &MyCapturer::OnCaptureItemClosed });
I use the preinstalled 3DViewer App as Window for testing.

I know that in your project the OnCaptureItemClosed works, could it be that RegisterWindowClass is necessary for that to work ? Do you might have any idea as to why this event wont fire (while the other one works as expected) ?

@LordTrololo LordTrololo changed the title OnCaptureItemClosed Event not firing GraphicsCaptureItem.Closed Event not firing Jul 23, 2020
@robmikh
Copy link
Owner

robmikh commented Jul 24, 2020

Apologies, we've got to update the documentation on that. In order to receive the Closed event, you need to be pumping messages on the thread that creates the GraphicsCaptureItem.

You don't need to create a window or anything. Create a DispatcherQueueController for your thread (similarly to how this sample does it for its main thread) and then pump messages. You should then be able to receive the Closed event. You don't need to do this on the main/ui thread, you just need to have the DispatcherQueue set up and be pumping messages.

@LordTrololo
Copy link
Author

Thanx, I've managed to get it working.

The thing is - I don't have an easily accesable/changable main, so what I've done is:

  • created a function _CreateCaptureItemAndPumpMessages_ which creates DispatcherQueueController, CaptureItem and binds their respective events to some other functions and at its end has the message pumping while loop. You could say that my CreateCaptureItemAndPumpMessages() is my main .

  • I call this function to be executed on a new thread like so:

myCapturerThread = std::thread(&MyCapturer::CreateCaptureItemAndPumpMessages , this);

This ensures I get a working Closed event (as DispatcherQueueController is on same thread as the graphicsCaptureItem) just like you said.

The only problem I now have comes from my lack of C++ threading experience. When I get the Closed event I would ideally like to do two things:

  • call my OnClosedEventFucntion (this works)
  • terminate the myCapturerThread . I could do this the most easily and elegantly by simply exiting the while loop.
    Without doing this, the while loop that I have inside CreateCaptureItemAndPumpMessages :
        MSG msg;
        while (GetMessageW(&msg, nullptr, 0, 0))
        {
             TranslateMessage(&msg);
             DispatchMessageW(&msg);
        }

will otherwise continue to run and I get some exceptions on the next iteration (iteration after the Closed evnet has been successfully fired and recieved and processed ).

So, the question is - is there some safe way to exit the while loop on getting Closed even ? Is there a special msg for Closed, if so a simple:

 if(msg == closed)
break;

would be my solution.

There is one dirty solution which I use immediately but I am not sure how safe it is:

        int i = 0;
        while (GetMessageW(&msg, nullptr, 0, 0))
        {
            if(i == 1)
                break;
            TranslateMessage(&msg);
            DispatchMessageW(&msg);
            i++;
        }

This works as the loop is iterated exactly two times, one on instantiating it, and second time on closing the CaptureItem. But I am not sure how safe is this solution as I have no Ideal what sort of messages are coming and going, who is sending them and under what conditions...

Like you already said, the documentation for using Closed() is maybe not the sharpest pencil in the drawer.

@LordTrololo
Copy link
Author

Ups, I accidentally closed this Issue and I didn't mean to.
I mean, in effect one could close this, as now I am able to use the Close event but I would rather argue that I sent a wrong closed event right now (pun intended)....

@LordTrololo LordTrololo reopened this Jul 28, 2020
@robmikh
Copy link
Owner

robmikh commented Jul 28, 2020

I think you want DispatcherQueueController::CreateOnDedicatedThread, that will create a thread for you and pump messages on your behalf. Then to create the item you could do the following:

auto controller = winrt::DispatcherQueueController::CreateOnDedicatedThread();
auto queue = controller.DispatcherQueue();
wil::shared_event initialized(wil::EventOptions::None);
winrt::check_bool(queue.TryEnqueue([=, initialized]()
{
    // ... 
    // Create the item and do stuff with it here
    // ...
    initialized.SetEvent();
}));
initialized.wait(INFINITE);

NOTE: shared_event is a wrapper around Win32 event objects from the WIL library.

When you want the thread to shut down, call the ShutdownQueueAsync function:

controller.ShutdownQueueAsync().get(); // Use .get() if you need this to be synchronous, 
                                       // otherwise use async as you normally would

That will shut down the message pump for that thread.

With that said, make sure you're set up to deal with this cross-thread structure. The reason we use the dispatcher queues is to properly marshal back to an expected thread. We can't arbitrarily call back into your thread unless we're given an opportunity (by pumping messages). The setup you've described will mean that the Closed event will fire on the other thread. This may be fine, but I thought it'd mention it for any people in the future that stumble upon this issue.

@robmikh
Copy link
Owner

robmikh commented Jul 28, 2020

If you need to call GetMessageW and friends yourself, I'd setup a signaling mechanism to shut down the message pump by using something like an atomic bool.

@LordTrololo
Copy link
Author

LordTrololo commented Jul 28, 2020

Your suggestion works. Having all inside the lambda:

        winrt::check_bool(queue.TryEnqueue([=, &initialized]()
        {
            m_item = util::CreateCaptureItemForWindow(windowHandle);
            m_pixelFormat = winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized;            d3dDevice->GetImmediateContext(m_d3dContext.put());
            m_swapChain = util::CreateDXGISwapChain(d3dDevice, static_cast<uint32_t>(m_item.Size().Width), static_cast<uint32_t>(m_item.Size().Height),
                static_cast<DXGI_FORMAT>(m_pixelFormat), 2);

            m_framePool = winrt::Direct3D11CaptureFramePool::CreateFreeThreaded(m_device, m_pixelFormat, 2, m_item.Size());
            m_session = m_framePool.CreateCaptureSession(m_item);
            m_lastSize = m_item.Size();
            ....

ensures both events get properly recieved.

But, when following your approach, out of some reason, in my function bound to FrameArrived the line_

        _m_d3dContext->CopyResource(backBuffer.get(), surfaceTexture.get());_

doesnt seem to work, only black frame gets copied. The size of the windowFrame seems to be correctly copied, but not the content. The content of the window frame is correct (surfacteTexture is ok), its just that the copy seems to not be really working. But maybe I am messing something up, I have to debug everything a bit...

EDIT;
So to sum it up, using this here:

        controller = winrt::DispatcherQueueController::CreateOnDedicatedThread();
        auto queue = controller.DispatcherQueue();
        wil::shared_event initialized(wil::EventOptions::None);
        winrt::check_bool(queue.TryEnqueue([=, &initialized]()
        {
            d3dDevice->GetImmediateContext(m_d3dContext.put());
            m_item = util::CreateCaptureItemForWindow(window.WindowHandle);
            ...

works in the effect that it binds both events (FrameArrived and Closed) properly, BUT there seems to be sum problem when using
m_d3dContext->CopyResource(backBuffer.get(), surfaceTexture.get());
in the function OnFrameArrived bound to FrameArrived

On the other hand, instantiating everything in a seperate function on a new thread like so:
myCapturerThread = std::thread(&MyCapturer::CreateCaptureItemAndPumpMessages , this);
seems to also work and there are no problems (outside of necessity to destroy the myCapturerThread thread later on). Here we use while loop for Messaging.

@robmikh
Copy link
Owner

robmikh commented Jul 28, 2020

I think your implementation must have some else going on in regards to the device context behavior your seeing.

I'm going to close this issue given that the original problem has been resolved. Good luck!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants