基于 Window 位置的直接 X 裁剪位图并将其渲染回 window

Direct X Cropping Bitmap Based on Window Position and Rendering it back to window

我正在做一个使用 DirectXDirect Composition 的项目。我正在尝试创建类似 windows 丙烯酸模糊的效果。使用直接合成,我能够将模糊效果、饱和度和混合应用于给定的输入。 windows acrylic blur 使用 window 后面的内容作为模糊输入,但这在 win32 中是不可能的,所以我决定使用桌面背景作为模糊功能的输入。但问题是我能够根据 window 位置裁剪背景,但如果我将 window 移动到新位置,位图将根据 [=55] 的新 RECT 裁剪=] 但在移动到新位置时出现闪烁。

预览如下:

这里是实际的截图window:

此处红色框包含使用 window RECT 饱和和裁剪的位图 (看色差)。

这是重现问题的简单代码:

#ifndef UNICODE
#define UNICODE
#endif 
#include <windows.h>
#include <wrl.h>
#include <dxgi1_3.h>
#include <d3d11_2.h>
#include <d2d1_2.h>
#include <d2d1_1.h>
#include <d2d1_2helper.h>
#include <dcomp.h>
#include <dwmapi.h>
#include <wincodec.h>

#pragma comment(lib, "dxgi")
#pragma comment(lib, "d3d11")
#pragma comment(lib, "d2d1")
#pragma comment(lib, "dwmapi")
#pragma comment(lib, "dxguid")
#pragma comment(lib, "dcomp")

using namespace Microsoft::WRL;

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

ComPtr<ID3D11Device> direct3dDevice;
ComPtr<IDXGIDevice> dxgiDevice;
ComPtr<IDXGIFactory2> dxFactory;
ComPtr<IDXGISwapChain1> swapChain;
ComPtr<ID2D1Factory2> d2Factory;
ComPtr<ID2D1Device1> d2Device;
ComPtr<IDCompositionTarget> target;
ComPtr<ID2D1DeviceContext> dc;


//Direct Composition Device,Visual
ComPtr<IDCompositionDevice> dcompDevice;
ComPtr<IDCompositionDevice3> dcompDevice3;
ComPtr<IDCompositionVisual> visual;

//Direct Composition Effects
ComPtr<IDCompositionGaussianBlurEffect> blur;
ComPtr<IDCompositionSaturationEffect> saturation;

IWICImagingFactory* d2dWICFactory = NULL;
IWICBitmapDecoder* d2dDecoder = NULL;
IWICFormatConverter* d2dConverter = NULL;
IWICBitmapFrameDecode* d2dBmpSrc = NULL;
ID2D1Bitmap* d2dBmp = NULL;

HWND hwnd;
RECT windowRect;

void LoadBackground();
void Render();

struct ComException
{
    HRESULT result;
    ComException(HRESULT const value) : result(value)
    {}
};
void HR(HRESULT const result)
{
    if (S_OK != result)
    {
        throw ComException(result);
    }
}

void CreateDevice(HWND hwnd)
{
    
    HR(D3D11CreateDevice(nullptr,    // Adapter
        D3D_DRIVER_TYPE_HARDWARE,
        nullptr,    // Module
        D3D11_CREATE_DEVICE_BGRA_SUPPORT,
        nullptr, 0, // Highest available feature level
        D3D11_SDK_VERSION,
        &direct3dDevice,
        nullptr,    // Actual feature level
        nullptr));  // Device context

    HR(direct3dDevice.As(&dxgiDevice));
    HR(CreateDXGIFactory2(
        DXGI_CREATE_FACTORY_DEBUG,
        __uuidof(dxFactory),
        reinterpret_cast<void**>(dxFactory.GetAddressOf())));

    DXGI_SWAP_CHAIN_DESC1 description = {};

    description.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    description.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    description.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
    description.BufferCount = 2;
    description.SampleDesc.Count = 1;
    description.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;

    RECT rect = {};
    GetClientRect(hwnd, &rect);
    description.Width = rect.right - rect.left;
    description.Height = rect.bottom - rect.top;
 
    HR(dxFactory->CreateSwapChainForComposition(dxgiDevice.Get(),&description,nullptr,swapChain.GetAddressOf()));

    D2D1_FACTORY_OPTIONS const options = { D2D1_DEBUG_LEVEL_INFORMATION };

    HR(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,options,d2Factory.GetAddressOf()));

    HR(d2Factory->CreateDevice(dxgiDevice.Get(),d2Device.GetAddressOf()));

    HR(d2Device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE,dc.GetAddressOf()));

    ComPtr<IDXGISurface2> surface;
    HR(swapChain->GetBuffer(0,__uuidof(surface),reinterpret_cast<void**>(surface.GetAddressOf())));

    D2D1_BITMAP_PROPERTIES1 properties = {};
    properties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
    properties.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM;
    properties.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW;

    ComPtr<ID2D1Bitmap1> bitmap;
    HR(dc->CreateBitmapFromDxgiSurface(surface.Get(),properties,bitmap.GetAddressOf()));
    LoadBackground(); // loads my desktop background to d2dBmp using WIC
    dc->SetTarget(bitmap.Get());

    Render(); //render once

   //Creating Direct Compostion Devices and Visual

    HR(DCompositionCreateDevice(dxgiDevice.Get(),__uuidof(dcompDevice),reinterpret_cast<void**>(dcompDevice.GetAddressOf())));
    HR(DCompositionCreateDevice3(dxgiDevice.Get(), __uuidof(dcompDevice),reinterpret_cast<void**>(dcompDevice.GetAddressOf())));
    HR(dcompDevice->QueryInterface(__uuidof(IDCompositionDevice3), (LPVOID*)&dcompDevice3)); // use IDCompositionDevice3 here
   
    HR(dcompDevice3->CreateSaturationEffect(saturation.GetAddressOf()));
    HR(dcompDevice3->CreateGaussianBlurEffect(blur.GetAddressOf()));

    //setting effect properties
    blur->SetStandardDeviation(30.0f); // blur amount
    blur->SetBorderMode(D2D1_BORDER_MODE_HARD);
    saturation->SetSaturation(2.0f); //saturationamount

    HR(dcompDevice->CreateTargetForHwnd(hwnd,true,target.GetAddressOf()));

    blur->SetInput(NULL, bitmap.Get(), NULL);
    saturation->SetInput(NULL, blur.Get(), NULL);

    HR(dcompDevice->CreateVisual(visual.GetAddressOf()));
    HR(visual->SetContent(swapChain.Get()));
    visual->SetEffect(saturation.Get());
    HR(target->SetRoot(visual.Get()));
    HR(dcompDevice->Commit());

}

void LoadBackground()
{
    HR(CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, __uuidof(IWICImagingFactory), (void**)(&d2dWICFactory)));
    HR(d2dWICFactory->CreateDecoderFromFilename(L"C:/Users/selas/Downloads/wallpaper.jpg", NULL, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &d2dDecoder));
    HR(d2dWICFactory->CreateFormatConverter(&d2dConverter));
    HR(d2dDecoder->GetFrame(0, &d2dBmpSrc));
    HR(d2dConverter->Initialize(d2dBmpSrc, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, NULL, 0.f, WICBitmapPaletteTypeMedianCut));
    HR(dc->CreateBitmapFromWicBitmap(d2dConverter, NULL, &d2dBmp));
}

void Render()
{
    if (dc)
    {
        dc->BeginDraw();
        dc->Clear();
        D2D1_POINT_2F offset = D2D1::Point2F(0, 0);
        D2D1_RECT_F imgRect = D2D1::RectF(windowRect.left, windowRect.top, windowRect.right, windowRect.bottom);
        dc->DrawImage(d2dBmp, offset, imgRect, D2D1_INTERPOLATION_MODE_LINEAR, D2D1_COMPOSITE_MODE_SOURCE_OVER);
        HR(dc->EndDraw());
        HR(swapChain->Present(1, 0));
    }

}

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
    // Register the window class.
    const wchar_t CLASS_NAME[] = L"Sample Window Class";

    WNDCLASS wc = { };

    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;

    RegisterClass(&wc);

    // Create the window.

    hwnd = CreateWindowEx(WS_EX_NOREDIRECTIONBITMAP,
        wc.lpszClassName, L"Sample",
        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
        CW_USEDEFAULT, CW_USEDEFAULT,
        500, 500,
        nullptr, nullptr, hInstance, nullptr);

    if (hwnd == NULL)
    {
        return 0;
    }
    
    CreateDevice(hwnd);
    ShowWindow(hwnd, nCmdShow);

    MSG msg = { };
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}


LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_MOVING:
        {
            GetWindowRect(hwnd, &windowRect);
            Render();
            return 0;
        }
        

        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);
            FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
            EndPaint(hwnd, &ps);
            return 0;
        }

        case WM_DESTROY:
        {
            PostQuitMessage(0);
            d2dWICFactory->Release();
            d2dDecoder->Release();
            d2dConverter->Release();
            d2dBmpSrc->Release();
            d2dBmp->Release();
        }
        return 0;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

Please don't forget to replace "C:/Users/selas/Downloads/wallpaper.jpg" with your desktop wallpaper

注意:示例视频实际上是经过编辑的,不是我得到的输出。

我终于明白了。现在我可以移动 window 而不会闪烁到我渲染为背景的图像。问题出在我的代码中的两个地方。

  • GetWindowRect(): 在 WM_MOVING 中,我使用 GetWindowRect() 获取 window 矩形,这导致延迟获取 window 矩形并导致闪烁问题。我找到的解决方案很简单,我通过类型转换将 lParm 转换为 RECT 并将其用作更新位置的值解决了这个问题。我用来解决问题的代码如下:

RECT *hostRect = reinterpret_cast<RECT*>(lParam)


  • MagSetWindowSource():这是一件很有趣的事情。我不知道放大镜在这里有什么用。在我调用 MagSetWindowSource() 函数之前,代码不会消除闪烁,我不知道为什么需要它,仅此一项就在我的程序中消耗了更多内存。如果我删除这行代码,它会再次开始闪烁。函数的输入并不重要,您只需调用函数即可。

更新:

Finally figured out what is MagSetWindowSource() is doing, it actually calls a DWM function called DwmFlush() which Issues a flush call that blocks the caller until the next present. and that solved my issue. now it is flickerless and smooth.

这是完全更新的代码:

#ifndef UNICODE
#define UNICODE
#endif 
#include <windows.h>
#include <wrl.h>
#include <dxgi1_3.h>
#include <d3d11_2.h>
#include <d2d1_2.h>
#include <d2d1_1.h>
#include <d2d1_2helper.h>
#include <dcomp.h>
#include <dwmapi.h>
#include <wincodec.h>
#include <magnification.h>

#pragma comment(lib, "dxgi")
#pragma comment(lib, "d3d11")
#pragma comment(lib, "d2d1")
#pragma comment(lib, "dwmapi")
#pragma comment(lib, "dxguid")
#pragma comment(lib, "dcomp")
#pragma comment(lib, "magnification")

using namespace Microsoft::WRL;

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

ComPtr<ID3D11Device> direct3dDevice;
ComPtr<IDXGIDevice> dxgiDevice;
ComPtr<IDXGIFactory2> dxFactory;
ComPtr<IDXGISwapChain1> swapChain;
ComPtr<ID2D1Factory2> d2Factory;
ComPtr<ID2D1Device1> d2Device;
ComPtr<IDCompositionTarget> target;
ComPtr<ID2D1DeviceContext> dc;


//Direct Composition Device,Visual
ComPtr<IDCompositionDevice> dcompDevice;
ComPtr<IDCompositionDevice3> dcompDevice3;
ComPtr<IDCompositionVisual> visual;

//Direct Composition Effects
ComPtr<IDCompositionGaussianBlurEffect> blur;
ComPtr<IDCompositionSaturationEffect> saturation;

IWICImagingFactory* d2dWICFactory = NULL;
IWICBitmapDecoder* d2dDecoder = NULL;
IWICFormatConverter* d2dConverter = NULL;
IWICBitmapFrameDecode* d2dBmpSrc = NULL;
ID2D1Bitmap* d2dBmp = NULL;

HWND hwnd;
RECT windowRect,temp;
RECT* hostRect;

void LoadBackground();
void Render();

struct ComException
{
    HRESULT result;
    ComException(HRESULT const value) : result(value)
    {}
};
void HR(HRESULT const result)
{
    if (S_OK != result)
    {
        throw ComException(result);
    }
}

void CreateDevice(HWND hwnd)
{

    HR(D3D11CreateDevice(nullptr,    // Adapter
        D3D_DRIVER_TYPE_HARDWARE,
        nullptr,    // Module
        D3D11_CREATE_DEVICE_BGRA_SUPPORT,
        nullptr, 0, // Highest available feature level
        D3D11_SDK_VERSION,
        &direct3dDevice,
        nullptr,    // Actual feature level
        nullptr));  // Device context

    HR(direct3dDevice.As(&dxgiDevice));
    HR(CreateDXGIFactory2(
        DXGI_CREATE_FACTORY_DEBUG,
        __uuidof(dxFactory),
        reinterpret_cast<void**>(dxFactory.GetAddressOf())));

    DXGI_SWAP_CHAIN_DESC1 description = {};

    description.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    description.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    description.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
    description.BufferCount = 2;
    description.SampleDesc.Count = 1;
    description.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;

    RECT rect = {};
    GetClientRect(hwnd, &rect);
    description.Width = rect.right - rect.left;
    description.Height = rect.bottom - rect.top;

    HR(dxFactory->CreateSwapChainForComposition(dxgiDevice.Get(), &description, nullptr, swapChain.GetAddressOf()));

    D2D1_FACTORY_OPTIONS const options = { D2D1_DEBUG_LEVEL_INFORMATION };

    HR(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, options, d2Factory.GetAddressOf()));

    HR(d2Factory->CreateDevice(dxgiDevice.Get(), d2Device.GetAddressOf()));

    HR(d2Device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, dc.GetAddressOf()));

    ComPtr<IDXGISurface2> surface;
    HR(swapChain->GetBuffer(0, __uuidof(surface), reinterpret_cast<void**>(surface.GetAddressOf())));

    D2D1_BITMAP_PROPERTIES1 properties = {};
    properties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
    properties.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM;
    properties.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW;

    ComPtr<ID2D1Bitmap1> bitmap;
    HR(dc->CreateBitmapFromDxgiSurface(surface.Get(), properties, bitmap.GetAddressOf()));
    LoadBackground(); // loads my desktop background to d2dBmp using WIC
    dc->SetTarget(bitmap.Get());

    Render(); //render once

   //Creating Direct Compostion Devices and Visual

    HR(DCompositionCreateDevice(dxgiDevice.Get(), __uuidof(dcompDevice), reinterpret_cast<void**>(dcompDevice.GetAddressOf())));
    HR(DCompositionCreateDevice3(dxgiDevice.Get(), __uuidof(dcompDevice), reinterpret_cast<void**>(dcompDevice.GetAddressOf())));
    HR(dcompDevice->QueryInterface(__uuidof(IDCompositionDevice3), (LPVOID*)&dcompDevice3)); // use IDCompositionDevice3 here

    HR(dcompDevice3->CreateSaturationEffect(saturation.GetAddressOf()));
    HR(dcompDevice3->CreateGaussianBlurEffect(blur.GetAddressOf()));

    //setting effect properties
    blur->SetStandardDeviation(0.0f); // blur amount
    blur->SetBorderMode(D2D1_BORDER_MODE_HARD);
    saturation->SetSaturation(2.0f); //saturationamount

    HR(dcompDevice->CreateTargetForHwnd(hwnd, true, target.GetAddressOf()));

    blur->SetInput(NULL, bitmap.Get(), NULL);
    saturation->SetInput(NULL, blur.Get(), NULL);

    HR(dcompDevice->CreateVisual(visual.GetAddressOf()));
    HR(visual->SetContent(swapChain.Get()));
    visual->SetEffect(saturation.Get());
    HR(target->SetRoot(visual.Get()));
    HR(dcompDevice->Commit());

}

void LoadBackground()
{
    HR(CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, __uuidof(IWICImagingFactory), (void**)(&d2dWICFactory)));
    HR(d2dWICFactory->CreateDecoderFromFilename(L"C:/Users/selas/Downloads/wallpaper.jpg", NULL, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &d2dDecoder));
    HR(d2dWICFactory->CreateFormatConverter(&d2dConverter));
    HR(d2dDecoder->GetFrame(0, &d2dBmpSrc));
    HR(d2dConverter->Initialize(d2dBmpSrc, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, NULL, 0.f, WICBitmapPaletteTypeMedianCut));
    HR(dc->CreateBitmapFromWicBitmap(d2dConverter, NULL, &d2dBmp));
}

void Render()
{
    if (dc)
    {
        dc->BeginDraw();
        dc->Clear();
        if (hostRect)
        {
            D2D1_POINT_2F offset = D2D1::Point2F(0, 0);
            D2D1_RECT_F imgRect = D2D1::RectF(hostRect->left, hostRect->top, hostRect->right, hostRect->bottom);
            dc->DrawImage(d2dBmp, offset, imgRect, D2D1_INTERPOLATION_MODE_LINEAR, D2D1_COMPOSITE_MODE_SOURCE_OVER);
        }
        HR(dc->EndDraw());
        HR(swapChain->Present(1, 0));
    }

}

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
    // Register the window class.
    const wchar_t CLASS_NAME[] = L"Sample Window Class";

    WNDCLASS wc = { };

    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;

    RegisterClass(&wc);

    // Create the window.

    hwnd = CreateWindowEx(WS_EX_NOREDIRECTIONBITMAP,
        wc.lpszClassName, L"Sample",
        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
        CW_USEDEFAULT, CW_USEDEFAULT,
        500, 500,
        nullptr, nullptr, hInstance, nullptr);

    if (hwnd == NULL)
    {
        return 0;
    }

    CreateDevice(hwnd);
    ShowWindow(hwnd, nCmdShow);

    MSG msg = { };
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}


LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_MOVING:
    {
        hostRect = reinterpret_cast<RECT*>(lParam);
        windowRect.left = hostRect->left;
        windowRect.top = hostRect->top;
        windowRect.right = hostRect->right;
        windowRect.bottom = hostRect->bottom;
        if (hostRect->left == 0 || hostRect->top == 0)
        {
            GetWindowRect(hwnd, &temp);
            hostRect->left = temp.left;
            hostRect->top = temp.top;
        }
        DwmFlush();
        Render();
        return 0;
    }


    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);
        FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
        EndPaint(hwnd, &ps);
        return 0;
    }

    case WM_DESTROY:
    {
        PostQuitMessage(0);
        d2dWICFactory->Release();
        d2dDecoder->Release();
        d2dConverter->Release();
        d2dBmpSrc->Release();
        d2dBmp->Release();
    }
    return 0;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}