D3DLayeredWindow 一个分层窗口茶壶
//----------------------------------------------------------------------------- // Copyright (c) 2006 dhpoware. All Rights Reserved. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- // // This demo builds on the previous layered window demo. // (http://www.dhpoware.com/downloads/LayeredWindow.zip). // // In the previous demo, we used a TGA image with an embedded alpha mask as the // source of our layered window. // // In this demo we use Direct3D to draw a rotating teapot to an off-screen // texture. We then use this off-screen texture as our non-rectangular, per // pixel alpha blended layered window. // // You can move the teapot around the screen by holding down the left mouse // button and dragging. To exit the demo press the ESC key. // // This demo requires Windows 2000, XP, or higher to run. The minimum supported // operating system for the layered windows API is Windows 2000. // //----------------------------------------------------------------------------- //include dir $(DXSDK_DIR)Include; //lib dir $(DXSDK_DIR)Lib\x86 //libs dxguid.lib d3d9.lib d3dx9.lib winmm.lib // Force the layered windows APIs to be visible. #define _WIN32_WINNT 0x0500 // Disable warning C4244: conversion from 'float' to 'BYTE', possible loss of // data. Used in the ImagePreMultAlpha() function. #pragma warning (disable : 4244) #if defined _DEBUG #define D3D_DEBUG_INFO #endif #include <windows.h> #include <tchar.h> #include <mmsystem.h> #include <cstdio> #include <d3dx9.h> #include <crtdbg.h> #define IMAGE_WIDTH 512 #define IMAGE_HEIGHT 512 // Generic wrapper around a DIB with a 32-bit color depth. struct Image { int width; int height; int pitch; HDC hdc; HBITMAP hBitmap; BITMAPINFO info; BYTE *pPixels; }; HWND g_hWnd; Image g_image; IDirect3D9 *g_pDirect3D; IDirect3DDevice9 *g_pDevice; IDirect3DTexture9 *g_pTexture; IDirect3DSurface9 *g_pSurface; IDirect3DSurface9 *g_pRenderTargetSurface; IDirect3DSurface9 *g_pDepthStencilSurface; ID3DXMesh *g_pMesh; D3DXMATRIX g_worldMtx; int g_frames; void ImageDestroy(Image *pImage) { if (!pImage) return; pImage->width = 0; pImage->height = 0; pImage->pitch = 0; if (pImage->hBitmap) { DeleteObject(pImage->hBitmap); pImage->hBitmap = 0; } if (pImage->hdc) { DeleteDC(pImage->hdc); pImage->hdc = 0; } memset(&pImage->info, 0, sizeof(pImage->info)); pImage->pPixels = 0; } bool ImageCreate(Image *pImage, int width, int height) { if (!pImage) return false; // All Windows DIBs are aligned to 4-byte (DWORD) memory boundaries. This // means that each scan line is padded with extra bytes to ensure that the // next scan line starts on a 4-byte memory boundary. The 'pitch' member // of the Image structure contains width of each scan line (in bytes). pImage->width = width; pImage->height = height; pImage->pitch = ((width * 32 + 31) & ~31) >> 3; pImage->pPixels = NULL; pImage->hdc = CreateCompatibleDC(NULL); if (!pImage->hdc) return false; memset(&pImage->info, 0, sizeof(pImage->info)); pImage->info.bmiHeader.biSize = sizeof(pImage->info.bmiHeader); pImage->info.bmiHeader.biBitCount = 32; pImage->info.bmiHeader.biWidth = width; pImage->info.bmiHeader.biHeight = -height; pImage->info.bmiHeader.biCompression = BI_RGB; pImage->info.bmiHeader.biPlanes = 1; pImage->hBitmap = CreateDIBSection(pImage->hdc, &pImage->info, DIB_RGB_COLORS, (void**)&pImage->pPixels, NULL, 0); if (!pImage->hBitmap) { ImageDestroy(pImage); return false; } GdiFlush(); return true; } void ImagePreMultAlpha(Image *pImage) { // The per pixel alpha blending API for layered windows deals with // pre-multiplied alpha values in the RGB channels. For further details see // the MSDN documentation for the BLENDFUNCTION structure. It basically // means we have to multiply each red, green, and blue channel in our image // with the alpha value divided by 255. // // Notes: // 1. ImagePreMultAlpha() needs to be called before every call to // UpdateLayeredWindow() (in the RedrawLayeredWindow() function). // // 2. Must divide by 255.0 instead of 255 to prevent alpha values in range // [1, 254] from causing the pixel to become black. This will cause a // conversion from 'float' to 'BYTE' possible loss of data warning which // can be safely ignored. if (!pImage) return; BYTE *pPixel = NULL; if (pImage->width * 4 == pImage->pitch) { // This is a special case. When the image width is already a multiple // of 4 the image does not require any padding bytes at the end of each // scan line. Consequently we do not need to address each scan line // separately. This is much faster than the below case where the image // width is not a multiple of 4. int totalBytes = pImage->width * pImage->height * 4; for (int i = 0; i < totalBytes; i += 4) { pPixel = &pImage->pPixels[i]; pPixel[0] *= (BYTE)((float)pPixel[3] / 255.0f); pPixel[1] *= (BYTE)((float)pPixel[3] / 255.0f); pPixel[2] *= (BYTE)((float)pPixel[3] / 255.0f); } } else { // Width of the image is not a multiple of 4. So padding bytes have // been included in the DIB's pixel data. Need to address each scan // line separately. This is much slower than the above case where the // width of the image is already a multiple of 4. for (int y = 0; y < pImage->height; ++y) { for (int x = 0; x < pImage->width; ++x) { pPixel = &pImage->pPixels[(y * pImage->pitch) + (x * 4)]; pPixel[0] *= (BYTE)((float)pPixel[3] / 255.0f); pPixel[1] *= (BYTE)((float)pPixel[3] / 255.0f); pPixel[2] *= (BYTE)((float)pPixel[3] / 255.0f); } } } } void RedrawLayeredWindow() { // The call to UpdateLayeredWindow() is what makes a non-rectangular // window possible. To enable per pixel alpha blending we pass in the // argument ULW_ALPHA, and provide a BLENDFUNCTION structure filled in // to do per pixel alpha blending. HDC hdc = GetDC(g_hWnd); if (hdc) { HGDIOBJ hPrevObj = 0; POINT ptDest = {0, 0}; POINT ptSrc = {0, 0}; SIZE client = {g_image.width, g_image.height}; BLENDFUNCTION blendFunc = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA}; hPrevObj = SelectObject(g_image.hdc, g_image.hBitmap); ClientToScreen(g_hWnd, &ptDest); UpdateLayeredWindow(g_hWnd, hdc, &ptDest, &client, g_image.hdc, &ptSrc, 0, &blendFunc, ULW_ALPHA); SelectObject(g_image.hdc, hPrevObj); ReleaseDC(g_hWnd, hdc); } } void Cleanup() { if (g_pMesh) { g_pMesh->Release(); g_pMesh = 0; } if (g_pSurface) { g_pSurface->Release(); g_pSurface = 0; } if (g_pDepthStencilSurface) { g_pDepthStencilSurface->Release(); g_pDepthStencilSurface = 0; } if (g_pRenderTargetSurface) { g_pRenderTargetSurface->Release(); g_pRenderTargetSurface = 0; } if (g_pTexture) { g_pTexture->Release(); g_pTexture = 0; } if (g_pDevice) { g_pDevice->Release(); g_pDevice = 0; } if (g_pDirect3D) { g_pDirect3D->Release(); g_pDirect3D = 0; } ImageDestroy(&g_image); } BOOL InitRenderToTexture(D3DFORMAT format, D3DFORMAT depthStencil) { HRESULT hr = 0; int width = g_image.width; int height = g_image.height; // Create the dynamic render target texture. Since we want use this texture // with the Win32 layered windows API to create a per pixel alpha blended // non-rectangular window we *must* use a D3DFORMAT that contains an ALPHA // channel. hr = D3DXCreateTexture(g_pDevice, width, height, 0, D3DUSAGE_RENDERTARGET, format, D3DPOOL_DEFAULT, &g_pTexture); if (FAILED(hr)) return FALSE; // Cache the top level surface of the render target texture. This will make // it easier to bind the dynamic render target texture to the device. hr = g_pTexture->GetSurfaceLevel(0, &g_pRenderTargetSurface); if (FAILED(hr)) return FALSE; // Create a depth-stencil surface so the scene rendered to the dynamic // texture will be correctly depth sorted. hr = g_pDevice->CreateDepthStencilSurface(width, height, depthStencil, D3DMULTISAMPLE_NONE, 0, TRUE, &g_pDepthStencilSurface, 0); if (FAILED(hr)) return FALSE; // Create an off-screen plain system memory surface. This system memory // surface will be used to fetch a copy of the pixel data rendered into the // render target texture. We can't directly read the render target texture // because textures created with D3DPOOL_DEFAULT can't be locked. hr = g_pDevice->CreateOffscreenPlainSurface(width, height, format, D3DPOOL_SYSTEMMEM, &g_pSurface, 0); return TRUE; } BOOL InitD3D() { HRESULT hr = 0; D3DPRESENT_PARAMETERS params = {0}; D3DDISPLAYMODE desktop = {0}; g_pDirect3D = Direct3DCreate9(D3D_SDK_VERSION); if (!g_pDirect3D) return FALSE; // Just use the current desktop display mode. hr = g_pDirect3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &desktop); if (FAILED(hr)) return FALSE; // Setup Direct3D for windowed rendering. params.BackBufferWidth = 0; params.BackBufferHeight = 0; params.BackBufferFormat = desktop.Format; params.BackBufferCount = 1; params.MultiSampleType = D3DMULTISAMPLE_NONE; params.MultiSampleQuality = 0; params.SwapEffect = D3DSWAPEFFECT_DISCARD; params.hDeviceWindow = g_hWnd; params.Windowed = TRUE; params.EnableAutoDepthStencil = TRUE; params.AutoDepthStencilFormat = D3DFMT_D24S8; params.Flags = D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL; params.FullScreen_RefreshRateInHz = 0; params.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; // Most modern video cards should have no problems creating pure devices. hr = g_pDirect3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_PUREDEVICE, ¶ms, &g_pDevice); if (FAILED(hr)) { // Fall back to software vertex processing for less capable hardware. hr = g_pDirect3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, ¶ms, &g_pDevice); } if (FAILED(hr)) return FALSE; // Create the textures and surfaces required for off-screen rendering. // Since we intend to use the rendered pixel data with the Win32 layered // windows API (to draw an alpha blended window) we *must* ensure the // created off-screen texture uses a format that supports an alpha channel. if (!InitRenderToTexture(D3DFMT_A8R8G8B8, D3DFMT_D24S8)) return FALSE; if (FAILED(D3DXCreateTeapot(g_pDevice, &g_pMesh, 0))) return FALSE; D3DLIGHT9 defaultLight = { D3DLIGHT_DIRECTIONAL, // type 1.0f, 1.0f, 1.0f, 0.0f, // diffuse 1.0f, 1.0f, 1.0f, 0.0f, // specular 1.0f, 1.0f, 1.0f, 0.0f, // ambient 0.0f, 0.0f, 0.0f, // position 0.0f, 0.0f, 1.0f, // direction 1000.0f, // range 1.0f, // falloff 0.0f, // attenuation0 (constant) 1.0f, // attenuation1 (linear) 0.0f, // attenuation2 (quadratic) 0.0f, // theta 0.0f // phi }; D3DMATERIAL9 defaultMaterial = { 0.8f, 0.8f, 0.8f, 1.0f, // diffuse 0.2f, 0.2f, 0.2f, 1.0f, // ambient 0.0f, 0.0f, 0.0f, 1.0f, // specular 0.0f, 0.0f, 0.0f, 1.0f, // emissive 0.0f // power }; D3DXMATRIX projMtx; D3DXMATRIX viewMtx; D3DXMatrixPerspectiveFovLH(&projMtx, D3DX_PI / 4.0f, (float)g_image.width / (float)g_image.height, 1.0f, 100.0f); D3DXMatrixLookAtLH(&viewMtx, &D3DXVECTOR3(0.0f, 0.0f, -5.0f), &D3DXVECTOR3(0.0f, 0.0f, 0.0f), &D3DXVECTOR3(0.0f, 1.0f, 0.0f)); g_pDevice->SetRenderState(D3DRS_ZENABLE, TRUE); g_pDevice->SetRenderState(D3DRS_LIGHTING, TRUE); g_pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); g_pDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); g_pDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); g_pDevice->SetLight(0, &defaultLight); g_pDevice->LightEnable(0, TRUE); g_pDevice->SetMaterial(&defaultMaterial); g_pDevice->SetTransform(D3DTS_PROJECTION, &projMtx); g_pDevice->SetTransform(D3DTS_VIEW, &viewMtx); return TRUE; } BOOL Init() { if (!ImageCreate(&g_image, IMAGE_WIDTH, IMAGE_HEIGHT)) return FALSE; if (!InitD3D()) { Cleanup(); return FALSE; } return TRUE; } void CopyRenderTextureToImage() { // Dynamic render target textures are created with D3DPOOL_DEFAULT and so // cannot be locked. To access the dynamic texture's pixel data we need to // get Direct3D to make a system memory copy of the texture. The system // memory copy can then be accessed and copies across to our Image // structure. This copying from video memory to system memory is slow. // So try not to make your dynamic textures too big if you intend to access // their pixel data. HRESULT hr = 0; D3DLOCKED_RECT rcLock = {0}; hr = g_pDevice->GetRenderTargetData(g_pRenderTargetSurface, g_pSurface); if (SUCCEEDED(hr)) { hr = g_pSurface->LockRect(&rcLock, 0, 0); if (SUCCEEDED(hr)) { const BYTE *pSrc = (const BYTE *)rcLock.pBits; BYTE *pDest = g_image.pPixels; int srcPitch = rcLock.Pitch; int destPitch = g_image.pitch; if (srcPitch == destPitch) { memcpy(pDest, pSrc, destPitch * g_image.height); } else { for (int i = 0; i < g_image.height; ++i) memcpy(&pDest[destPitch * i], &pSrc[srcPitch * i], destPitch); } g_pSurface->UnlockRect(); } } } void DrawFrame() { // Tell Direct3D that we want to off-screen rendering to our dynamic render // texture. This is done by binding our depth-stencil, and render target // texture surfaces. g_pDevice->SetDepthStencilSurface(g_pDepthStencilSurface); g_pDevice->SetRenderTarget(0, g_pRenderTargetSurface); // Clear the off-screen texture. We don't want the background to be visible // in our final layered window image so we set the clear color to be fully // transparent (by specifying a zero alpha component). g_pDevice->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_ZBUFFER, D3DCOLOR_COLORVALUE(0.0f, 0.0f, 0.0f, 0.0f), 1.0f, 0); if (SUCCEEDED(g_pDevice->BeginScene())) { // Draw a rotating teapot. D3DXMatrixRotationY(&g_worldMtx, timeGetTime() / 150.0f); g_pDevice->SetTransform(D3DTS_WORLD, &g_worldMtx); g_pMesh->DrawSubset(0); // Since we're doing off-screen rendering we don't call Present(). // However we still must call EndScene() to finish rendering the scene. g_pDevice->EndScene(); // At this stage the off-screen texture will contain our teapot scene. // Now we make a system memory copy of the off-screen texture (located // in video memory). CopyRenderTextureToImage(); // Then we pre-multiply each pixel with its alpha component. This is how // the layered windows API expects images containing alpha information. ImagePreMultAlpha(&g_image); // Finally we update our layered window with our teapot scene. RedrawLayeredWindow(); ++g_frames; } } LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { static TCHAR szBuffer[32] = {0}; switch (msg) { case WM_CREATE: timeBeginPeriod(1); SetTimer(hWnd, 1, 1000, 0); return 0; case WM_DESTROY: KillTimer(hWnd, 1); timeEndPeriod(1); PostQuitMessage(0); return 0; case WM_KEYDOWN: if (wParam == VK_ESCAPE) { SendMessage(hWnd, WM_CLOSE, 0, 0); return 0; } break; case WM_NCHITTEST: return HTCAPTION; // allows dragging of the window case WM_TIMER: _stprintf(szBuffer, _TEXT("%d FPS"), g_frames); SetWindowText(hWnd, szBuffer); g_frames = 0; return 0; default: break; } return DefWindowProc(hWnd, msg, wParam, lParam); } void DetectMemoryLeaks() { // Enable the memory leak tracking tools built into the CRT runtime. #if defined _DEBUG _CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF); _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE); _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); #endif } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { DetectMemoryLeaks(); MSG msg = {0}; WNDCLASSEX wcl = {0}; wcl.cbSize = sizeof(wcl); wcl.style = CS_HREDRAW | CS_VREDRAW; wcl.lpfnWndProc = WindowProc; wcl.cbClsExtra = 0; wcl.cbWndExtra = 0; wcl.hInstance = hInstance; wcl.hIcon = LoadIcon(0, IDI_APPLICATION); wcl.hCursor = LoadCursor(0, IDC_ARROW); wcl.hbrBackground = 0; wcl.lpszMenuName = 0; wcl.lpszClassName = TEXT("D3DLayeredWindowClass"); wcl.hIconSm = 0; if (!RegisterClassEx(&wcl)) return 0; g_hWnd = CreateWindowEx(WS_EX_LAYERED | WS_EX_TOPMOST, wcl.lpszClassName, TEXT("D3D Layered Window Demo"), WS_POPUP, 0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, 0, 0, wcl.hInstance, 0); if (g_hWnd) { if (Init()) { ShowWindow(g_hWnd, nShowCmd); UpdateWindow(g_hWnd); while (TRUE) { if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) break; TranslateMessage(&msg); DispatchMessage(&msg); } else { DrawFrame(); } } } Cleanup(); UnregisterClass(wcl.lpszClassName, hInstance); } return (int)(msg.wParam); }