Using Windowless Mode
Both the Video Mixing Renderer Filter 7 (VMR-7) and the Video Mixing Renderer Filter 9 (VMR-9) support windowless mode, which represents a major improvement over the IVideoWindow interface. This topic 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.
The VMR-7 filter and the VMR-9 filter expose different interfaces, but the steps are equivalent for each.
Configure the VMR for Windowless Mode
To override the VMR's default behavior, configure the VMR before building the filter graph:
VMR-7
- Create the Filter Graph Manager.
- Create the VMR-7 and add it to the filter graph.
- Call IVMRFilterConfig::SetRenderingMode on the VMR-7 with the VMRMode_Windowless flag.
- Query the VMR-7 for the IVMRWindowlessControl interface.
- Call IVMRWindowlessControl::SetVideoClippingWindow on the VMR-7. Specify a handle to the window where the video should appear.
VMR-9
- Create the Filter Graph Manager.
- Create the VMR-9 and add it to the filter graph.
- Call IVMRFilterConfig9::SetRenderingMode on the VMR-9 with the VMR9Mode_Windowless flag.
- Query the VMR-9 for the IVMRWindowlessControl9 interface.
- Call IVMRWindowlessControl9::SetVideoClippingWindow on the VMR-9. 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-7, 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.
VMR-7
- 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.
VMR-9
- Call the IVMRWindowlessControl9::SetVideoPosition method to specify both rectangles.
- The source rectangle must be equal to or smaller than the native video size; you can use the IVMRWindowlessControl9::GetNativeVideoSize method to get the native video size.
For example, the following code sets the source and destination rectangles for the VMR-7. It 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, it must be notified if it need to repaint or resize the video. Respond to the following window messages by calling the VMR methods listed.
VMR-7
- WM_PAINT. Call IVMRWindowlessControl::RepaintVideo. This method causes the VMR-7 to repaint the most recent video frame.
- WM_DISPLAYCHANGE: Call IVMRWindowlessControl::DisplayModeChanged. This method notifies the VMR-7 that the video must be shown at a new resolution or color depth.
- WM_SIZE or WM_WINDOWPOSCHANGED: Recalculate the position of the video and call IVMRWindowlessControl::SetVideoPosition to update the position, if needed.
VMR-9
- WM_PAINT. Call IVMRWindowlessControl9::RepaintVideo. This method causes the VMR-9 to repaint the most recent video frame.
- WM_DISPLAYCHANGE: Call IVMRWindowlessControl9::DisplayModeChanged. This method notifies the VMR-9 that the video must be shown at a new resolution or color depth.
- WM_SIZE or WM_WINDOWPOSCHANGED: Recalculate the position of the video and call IVMRWindowlessControl9::SetVideoPosition to update the position, if needed.
Note The default handler for the WM_WINDOWPOSCHANGED message sends a WM_SIZE message. But if your application intercepts WM_WINDOWPOSCHANGED and does not pass it to DefWindowProc, you should call SetVideoPosition in your WM_WINDOWPOSCHANGED handler, in addition to your WM_SIZE handler.
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.
Related topics
Send comments about this topic to Microsoft
Build date: 3/6/2012
- 11/10/2011
- Мишуля
Anyone know what the deal could be?
Code attached in case it helps illuminate anything, based heavily on the directx tutorial here: http://www.directxtutorial.com/tutorial9/b-direct3dbasics/dx9b2.aspx#still
// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <DShow.h>
#include "assert.h"
// define the screen resolution
#define SCREEN_WIDTH 1280//800
#define SCREEN_HEIGHT 720//600
// include the Direct3D Library file
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "Strmiids.lib")
// global declarations
LPDIRECT3D9 d3d; // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev; // the pointer to the device class
D3DPRESENT_PARAMETERS d3dpp; // create a struct to hold various device information
// function prototypes
void initD3D(HWND hWnd); // sets up and initializes Direct3D
void render_frame(void); // renders a single frame
void cleanD3D(void); // closes Direct3D and releases memory
void checkForLostDevice(void);
// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
bool deviceIsLost = false;
bool moviePlaying = false;
HWND hWnd;
// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
// wc.hbrBackground = (HBRUSH)COLOR_WINDOW; // not needed any more
wc.lpszClassName = L"WindowClass";
RegisterClassEx(&wc);
hWnd = CreateWindowEx(NULL,
L"WindowClass",
L"Our Direct3D Program",
//WS_EX_TOPMOST | WS_POPUP, // fullscreen values
(WS_OVERLAPPEDWINDOW) & ~(WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME),
0, 0, // the starting x and y positions should be 0
SCREEN_WIDTH, SCREEN_HEIGHT, // set the window to 640 x 480
NULL,
NULL,
hInstance,
NULL);
ShowWindow(hWnd, nCmdShow);
// set up and initialize Direct3D
initD3D(hWnd);
// enter the main loop:
MSG msg;
while(TRUE)
{
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if(msg.message == WM_QUIT)
break;
checkForLostDevice();
if(!deviceIsLost)
render_frame();
}
// clean up DirectX and COM
cleanD3D();
return msg.wParam;
}
struct IGraphBuilder *pGB;
struct IMediaControl *pMC;
struct IMediaEvent *pME;
IVMRWindowlessControl* pWC = NULL;
HRESULT InitializeWindowlessVMR(IVMRWindowlessControl** ppWC, DWORD dwNumStreams, bool blendAppImage)
{
IBaseFilter* pBF = NULL;
IVMRWindowlessControl* pWC = NULL;
*ppWC = NULL;
// Create VMR and add it to the filter graph
HRESULT hr = CoCreateInstance(CLSID_VideoMixingRenderer, NULL, CLSCTX_INPROC, IID_IBaseFilter, (void**)&pBF);
assert(!FAILED(hr));
hr = pGB->AddFilter(pBF, L"Video Mixing Renderer");
assert(!FAILED(hr));
// set rendering mode and number of streams
IVMRFilterConfig* pConfig;
hr = pBF->QueryInterface(IID_IVMRFilterConfig, (void**)&pConfig);
if(SUCCEEDED(hr))
{
pConfig->SetRenderingMode(VMRMode_Windowless);
// Set the VMR-7 to mixing mode if you want more than one video
// stream, or you want to mix a static bitmap over the video.
// (The VMR-9 defaults to mixing mode with four inputs.)
if(dwNumStreams > 1 || blendAppImage)
{
pConfig->SetNumberOfStreams(dwNumStreams);
}
pConfig->Release();
hr = pBF->QueryInterface(IID_IVMRWindowlessControl, (void**)&pWC);
if(SUCCEEDED(hr))
{
hr = pWC->SetVideoClippingWindow(hWnd);
*ppWC = pWC;
}
}
pBF->Release();
return hr;
}
void StopMovie()
{
pMC->Stop();
pWC->Release();
pWC = NULL;
moviePlaying = false;
}
void PlayMovie(const char *fileName)
{
if(moviePlaying)
{
StopMovie();
return;
}
//This creates the filter graph manager
HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, (void **)&pGB);
assert(!FAILED(hr));
//Query COM interfaces
pGB->QueryInterface(IID_IMediaControl, (void **)&pMC);
pGB->QueryInterface(IID_IMediaEvent, (void **)&pME);
int length; // length of filename
WCHAR* wfilename; // where we store WCHAR version of filename
length = strlen(fileName)+1;
wfilename = new WCHAR[length];
MultiByteToWideChar(CP_ACP, 0, fileName, -1, wfilename, length);
pWC = NULL;
hr = InitializeWindowlessVMR(&pWC, 1, false);
if(SUCCEEDED(hr))
{
// build the filter graph
hr = pGB->RenderFile(wfilename, NULL);
assert(!FAILED(hr));
// run the movie
hr = pMC->Run();
assert(!FAILED(hr));
}
long vidWidth, vidHeight;
hr = pWC->GetNativeVideoSize(&vidWidth, &vidHeight, NULL, NULL);
if(SUCCEEDED(hr))
{
RECT rcSrc, rcDest;
// Set Source Rect
SetRect(&rcSrc, 0, 0, vidWidth, vidHeight);
// Get Client Rect
GetClientRect(hWnd, &rcDest);
// Set Destination Rect, taking into account letterboxing
SetRect(&rcDest, 0, 0, rcDest.right, rcDest.bottom);
// Set video position
hr = pWC->SetVideoPosition(&rcSrc, &rcDest);
}
moviePlaying = true;
}
void onPaint()
{
PAINTSTRUCT ps;
HDC hdc;
RECT rcClient;
GetClientRect(hWnd, &rcClient);
hdc = BeginPaint(hWnd, &ps);
if (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 = pWC->RepaintVideo(hWnd, hdc);
}
EndPaint(hWnd, &ps);
}
// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_KEYUP:
{
if(wParam == 'F')
{
d3dpp.Windowed = !d3dpp.Windowed;
d3ddev->Reset(&d3dpp);
if(d3dpp.Windowed)
{
//SetWindowLong(hWnd, GWL_STYLE, (WS_OVERLAPPEDWINDOW) & ~(WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME));
//ShowWindow(hWnd,1);
}
else
{
//SetWindowLong(hWnd, GWL_STYLE, WS_EX_TOPMOST | WS_POPUP);
//ShowWindow(hWnd,1);
}
}
else if(wParam = 'P')
{
PlayMovie("AttractMovie.wmv");
}
break;
}
case WM_DISPLAYCHANGE:
{
if(pWC)
pWC->DisplayModeChanged();
break;
}
case WM_PAINT:
{
onPaint();
break;
}
case WM_WINDOWPOSCHANGED:
case WM_SIZE:
{
long vidWidth, vidHeight;
HRESULT hr = pWC->GetNativeVideoSize(&vidWidth, &vidHeight, NULL, NULL);
if(SUCCEEDED(hr))
{
RECT rcSrc, rcDest;
// Set Source Rect
SetRect(&rcSrc, 0, 0, vidWidth, vidHeight);
// Get Client Rect
GetClientRect(hWnd, &rcDest);
// Set Destination Rect, taking into account letterboxing
SetRect(&rcDest, 0, 0, rcDest.right, rcDest.bottom);
// Set video position
hr = pWC->SetVideoPosition(&rcSrc, &rcDest);
}
break;
}
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
} break;
}
return DefWindowProc (hWnd, message, wParam, lParam);
}
// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
d3d = Direct3DCreate9(D3D_SDK_VERSION); // create the Direct3D interface
ZeroMemory(&d3dpp, sizeof(d3dpp)); // clear out the struct for use
d3dpp.Windowed = TRUE; // program fullscreen, not windowed
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; // discard old frames
d3dpp.hDeviceWindow = hWnd; // set the window to be used by Direct3D
d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8; // set the back buffer format to 32-bit
d3dpp.BackBufferWidth = SCREEN_WIDTH; // set the width of the buffer
d3dpp.BackBufferHeight = SCREEN_HEIGHT; // set the height of the buffer
// create a device class using this information and the info from the d3dpp stuct
d3d->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp,
&d3ddev);
}
// this is the function used to render a single frame
void render_frame(void)
{
if(!moviePlaying)
{
// clear the window to a deep blue
d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 40, 100), 1.0f, 0);
d3ddev->BeginScene(); // begins the 3D scene
// do 3D rendering on the back buffer here
d3ddev->EndScene(); // ends the 3D scene
d3ddev->Present(NULL, NULL, NULL, NULL); // displays the created frame on the screen
}
}
// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
d3ddev->Release(); // close and release the 3D device
d3d->Release(); // close and release Direct3D
}
void checkForLostDevice(void)
{
HRESULT hr = d3ddev->TestCooperativeLevel();
if(hr == D3DERR_DEVICELOST || hr == D3DERR_DEVICENOTRESET)
{
deviceIsLost = true;
//freeVolatileResources();
if(hr == D3DERR_DEVICENOTRESET)
{
hr = d3ddev->Reset(&d3dpp);
if(SUCCEEDED(hr))
{
//initVolatileResources();
}
else if(hr == D3DERR_INVALIDCALL)
{
//platform->log("Device Reset returned D3DERR_INVALIDCALL. Some unmanaged resources haven't yet been destroyed like they should have been!");
}
else
{
//platform->log("Device Reset returned an unknown error: %i", hr);
}
}
}
else if(FAILED(hr))
{
//platform->log("WindowsDirect3DRenderer TestCooperativeLevel returned an unknown error: %i", hr);
deviceIsLost = true;
}
else
{
deviceIsLost = false;
}
}
- 11/4/2011
- Atrix256
- 11/3/2011
- Мишуля
IVMRWindowlessControl *pWc = NULL;
hr = InitWindowlessVMR(hwnd, pGraph, &g_pWc);
Should g_pWc be pWc?
- 10/9/2009
- cybercockroach