Using Windowless Mode

 
Microsoft DirectShow 9.0

Using Windowless Mode

The Video Mixing Renderer filters (VMR-7 and VMR-9) both support windowless mode, which represents a major improvement over the IVideoWindow interface. This article describes the differences between windowless mode and windowed mode, and how to use windowless mode.

To remain backward-compatible with existing applications, the VMR defaults to windowed mode. In windowed mode, the renderer creates its own window to display the video. Typically the application sets the video window to be a child of the application window. The existence of a separate video window causes some problems, however:

  • Most importantly, there is a potential for deadlocks if window messages are sent between threads.
  • The Filter Graph Manager must forward certain window messages, such as WM_PAINT, to the Video Renderer. The application must use the Filter Graph Manager's implementation of IVideoWindow (and not the Video Renderer's), so that the Filter Graph Manager maintains the correct internal state.
  • To receive mouse or keyboard events from the video window, the application must set a "message drain," causing the video window to forward these messages to the application.
  • To prevent clipping problems, the video window must have the right window styles.

Windowless mode avoids these problems by having the VMR draw directly on the application window's client area, using DirectDraw to clip the video rectangle. Windowless mode significantly reduces the chance of deadlocks. Also, the application does not have to set the owner window or the window styles. In fact, when the VMR is in windowless mode, it does not even expose the IVideoWindow interface, which is no longer needed.

To use windowless mode, you must explicitly configure the VMR. However, you will find that is more flexible and easier to use than windowed mode.

Configure the VMR for Windowless Mode

To override the VMR's default behavior, configure the VMR before building the filter graph:

  1. Create the Filter Graph Manager.
  2. Create the VMR and add it to the filter graph.
  3. Call IVMRFilterConfig::SetRenderingMode on the VMR with the VMRMode_Windowless flag.
  4. Call IVMRWindowlessControl::SetVideoClippingWindow on the VMR. Specify a handle to the window where the video should appear.

Now build the rest of the filter graph by calling IGraphBuilder::RenderFile or other graph-building methods. The Filter Graph Manager automatically uses the instance of the VMR that you added to the graph. (For details on why this happens, see Intelligent Connect.)

The following code shows a helper function that creates the VMR, adds it to the graph, and sets up windowless mode.

HRESULT InitWindowlessVMR( 
    HWND hwndApp,                  // Window to hold the video. 
    IGraphBuilder* pGraph,         // Pointer to the Filter Graph Manager. 
    IVMRWindowlessControl** ppWc   // Receives a pointer to the VMR.
    ) 
{ 
    if (!pGraph || !ppWc) 
    {
        return E_POINTER;
    }
    IBaseFilter* pVmr = NULL; 
    IVMRWindowlessControl* pWc = NULL; 
    // Create the VMR. 
    HRESULT hr = CoCreateInstance(CLSID_VideoMixingRenderer, NULL, 
        CLSCTX_INPROC, IID_IBaseFilter, (void**)&pVmr); 
    if (FAILED(hr))
    {
        return hr;
    }
    
    // Add the VMR to the filter graph.
    hr = pGraph->AddFilter(pVmr, L"Video Mixing Renderer"); 
    if (FAILED(hr)) 
    {
        pVmr->Release();
        return hr;
    }
    // Set the rendering mode.  
    IVMRFilterConfig* pConfig; 
    hr = pVmr->QueryInterface(IID_IVMRFilterConfig, (void**)&pConfig); 
    if (SUCCEEDED(hr)) 
    { 
        hr = pConfig->SetRenderingMode(VMRMode_Windowless); 
        pConfig->Release(); 
    }
    if (SUCCEEDED(hr))
    {
        // Set the window. 
        hr = pVmr->QueryInterface(IID_IVMRWindowlessControl, (void**)&pWc);
        if( SUCCEEDED(hr)) 
        { 
            hr = pWc->SetVideoClippingWindow(hwndApp); 
            if (SUCCEEDED(hr))
            {
                *ppWc = pWc; // Return this as an AddRef'd pointer. 
            }
            else
            {
                // An error occurred, so release the interface.
                pWc->Release();
            }
        } 
    } 
    pVmr->Release(); 
    return hr; 
} 

This function assumes that are displaying only one video stream and are not mixing a static bitmap over the video. For details, see VMR Windowless Mode. You would call this function as follows:

IVMRWindowlessControl *pWc = NULL;
hr = InitWindowlessVMR(hwnd, pGraph, &g_pWc);
if (SUCCEEDED(hr))
{
    // Build the graph. For example:
    pGraph->RenderFile(wszMyFileName, 0);
    // Release the VMR interface when you are done.
    pWc->Release();
}

Position the Video

After configuring the VMR, the next step is to set the position of the video. There are two rectangles to consider, the source rectangle and the destination rectangle. The source rectangle defines which portion of the video to display. The destination rectangle specifies the region in the window's client area that will contain the video. The VMR crops the video image to the source rectangle and stretches the cropped image to fit the destination rectangle.

Call the IVMRWindowlessControl::SetVideoPosition method to specify both rectangles. The source rectangle must be equal to or smaller than the native video size; you can use the IVMRWindowlessControl::GetNativeVideoSize method to get the native video size. 

For example, the following code sets the source rectangle equal to the entire video image and the destination rectangle equal to the entire window client area:

// Find the native video size.
long lWidth, lHeight; 
HRESULT hr = g_pWc->GetNativeVideoSize(&lWidth, &lHeight, NULL, NULL); 
if (SUCCEEDED(hr))
{
    RECT rcSrc, rcDest; 
    // Set the source rectangle.
    SetRect(&rcSrc, 0, 0, lWidth, lHeight); 
    
    // Get the window client area.
    GetClientRect(hwnd, &rcDest); 
    // Set the destination rectangle.
    SetRect(&rcDest, 0, 0, rcDest.right, rcDest.bottom); 
    
    // Set the video position.
    hr = g_pWc->SetVideoPosition(&rcSrc, &rcDest); 
}

If you want to video to occupy a smaller portion of the client area, modify the rcDest parameter. If you want to crop the video image, modify the rcSrc parameter.

Handle Window Messages

Because the VMR does not have its own window, you must inform it when the video needs to be repainted or modified.

  • When you receive a WM_PAINT message, call IVMRWindowlessControl::RepaintVideo to repaint the image.
  • When you receive a WM_DISPLAYCHANGE message, call IVMRWindowlessControl::DisplayModeChanged. The VMR takes any actions that are needed to display the video at the new resolution or color depth.
  • When you receive a WM_SIZE message, recalculate the position of the video and call SetVideoPosition again if necessary.

The following example shows a WM_PAINT message handler. It paints a region defined by the client rectangle minus the video rectangle. Do not draw onto the video rectangle, because the VMR will paint over it, causing flickering. For the same reason, do not set a background brush in your window class.

void OnPaint(HWND hwnd) 
{ 
    PAINTSTRUCT ps; 
    HDC         hdc; 
    RECT        rcClient; 
    GetClientRect(hwnd, &rcClient); 
    hdc = BeginPaint(hwnd, &ps); 
    if (g_pWc != NULL) 
    { 
        // Find the region where the application can paint by subtracting 
        // the video destination rectangle from the client area.
        // (Assume that g_rcDest was calculated previously.)
        HRGN rgnClient = CreateRectRgnIndirect(&rcClient); 
        HRGN rgnVideo  = CreateRectRgnIndirect(&g_rcDest);  
        CombineRgn(rgnClient, rgnClient, rgnVideo, RGN_DIFF);  
        
        // Paint on window.
        HBRUSH hbr = GetSysColorBrush(COLOR_BTNFACE); 
        FillRgn(hdc, rgnClient, hbr); 

        // Clean up.
        DeleteObject(hbr); 
        DeleteObject(rgnClient); 
        DeleteObject(rgnVideo); 

        // Request the VMR to paint the video.
        HRESULT hr = g_pWc->RepaintVideo(hwnd, hdc);  
    } 
    else  // There is no video, so paint the whole client area. 
    { 
        FillRect(hdc, &rc2, (HBRUSH)(COLOR_BTNFACE + 1)); 
    } 
    EndPaint(hwnd, &ps); 
} 

Although you must respond to WM_PAINT messages, there is nothing you need to do between WM_PAINT messages to update the video. As this example shows, windowless mode lets you treat the video image simply as a self-drawing region on the window.

See Also