Reputation: 33
I want to make UI control blur-behind effect, like css backdrop-filter:blur()
. Logic is seems to be simple:
ID2D1DeviceContext*
render bitmap;ID2D1Effect
blur, and pass there bitmap;DrawImage
.I am facing issues on the first step.
I found two ways to get bitmap from ID2D1DeviceContext*
: ID2D1DeviceContext::GetTarget(ID2D1Image**)
and ID2D1Bitmap::CopyFromRenderTarget()
.
Seems, that calling that functions between BeginDraw()
and EndDraw()
returns white bitmap from calling Clear
before BeginDraw()
, as I assume due to double-buffering. If in the middle of render loop, add another EndDraw()
and BeginDraw()
pair, then, for some reason forward draw calls until the next EndDraw()
will not do anything (perhaps because middle EndDraw()
returned D2DERR_RECREATE_TARGET
, and I currently didn't handle it.
Anyway, rendering the whole window in the middle of render loop for every blurred ui element seems to be bad idea, and also, probably it will cause flickering.
I there way to obtain current-state (compatible) bitmap from ID2D1DeviceContext*
without bliting it to target? Or maybe there is better way to achieve effect I want?
Another example of blur effect in case of uwp in-app blur (not blur-behind window effect):
Minimal example with ID2D1DeviceContext::GetTarget(ID2D1Image**)
, that just fills target white, instead of drawing blurred red rectangle on blue background:
#include <Windows.h>
HDC hdcDevice = GetDC(NULL);
int xw = GetDeviceCaps(hdcDevice, HORZRES);
int yw = GetDeviceCaps(hdcDevice, VERTRES);
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp);
HWND hwnd;
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <wchar.h>
#include <math.h>
#include <d2d1_1.h>
#include <d2d1helper.h>
#include <dwrite.h>
#include <wincodec.h>
#pragma comment(lib, "d2d1")
#pragma comment(lib, "dxguid.lib")
template<class Interface>
inline void SafeRelease(
Interface** ppInterfaceToRelease)
{
if (*ppInterfaceToRelease != NULL)
{
(*ppInterfaceToRelease)->Release();
(*ppInterfaceToRelease) = NULL;
}
}
#ifndef Assert
#if defined( DEBUG ) || defined( _DEBUG )
#define Assert(b) do {if (!(b)) {OutputDebugStringA("Assert: " #b "\n");}} while(0)
#else
#define Assert(b)
#endif //DEBUG || _DEBUG
#endif
#ifndef HINST_THISCOMPONENT
EXTERN_C IMAGE_DOS_HEADER __ImageBase;
#define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase)
#endif
ID2D1Factory* m_pDirect2dFactory;
ID2D1HwndRenderTarget* m_pRenderTarget;
ID2D1DeviceContext* target;
ID2D1SolidColorBrush* brush;
void Release()
{
SafeRelease(&m_pRenderTarget);
SafeRelease(&target);
SafeRelease(&brush);
}
void Init()
{
Release();
m_pDirect2dFactory = nullptr;
m_pRenderTarget = nullptr;
SUCCEEDED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pDirect2dFactory));
RECT rc;
GetClientRect(hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(
rc.right - rc.left,
rc.bottom - rc.top);
// Create a Direct2D render target.
SUCCEEDED(m_pDirect2dFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(hwnd, size),
&m_pRenderTarget));
m_pRenderTarget->QueryInterface(&target);
m_pRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(255,0,0),
&brush
);
}
void Render()
{
target->BeginDraw();
target->Clear(D2D1::ColorF(D2D1::ColorF::Blue));
target->SetTransform(D2D1::Matrix3x2F::Identity());
D2D1_SIZE_F rtSize = target->GetSize();
target->FillRectangle(D2D1::RectF(30, 30, 100, 100), brush);
ID2D1Effect *blur = nullptr;
target->CreateEffect(CLSID_D2D1GaussianBlur, &blur);
if (blur)
blur->SetValue(D2D1_GAUSSIANBLUR_PROP::D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, 10);
ID2D1Image* img = nullptr;
target->GetTarget(&img); // Checked, img is not nullptr, after call
blur->SetInput(0, img);
target->DrawImage(blur); // DrawImage(img) also draws white
// If to remove DrawImage call, red rectangle on blue background will be displayed,
// yet, of course, not blurred
SafeRelease(&blur);
auto hr = target->EndDraw();
if (hr == D2DERR_RECREATE_TARGET)
{
hr = S_OK;
Init();
}
}
int WINAPI WinMain(HINSTANCE hin, HINSTANCE, LPSTR, int)
{
ReleaseDC(NULL, hdcDevice);
WNDCLASS c = { NULL };
c.lpszClassName = L"GROKEN";
c.lpfnWndProc = WndProc;
c.hInstance = hin;
c.style = CS_VREDRAW | CS_HREDRAW;
c.hCursor = LoadCursor(NULL, IDC_ARROW);
c.hbrBackground = CreateSolidBrush(RGB(255, 255, 255));
RegisterClass(&c);
int cx = 500, cy = 500;
int x = xw / 2 - cx / 2, y = yw / 2 - cy / 2;
hwnd = CreateWindowEx(NULL, L"GROKEN", L"asd", WS_POPUP | WS_VISIBLE, x, y, cx, cy, NULL, NULL, hin, 0);
HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
CoInitialize(NULL);
Init();
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
Render();
TranslateMessage(&msg);
DispatchMessage(&msg);
}
CoUninitialize();
return 0;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
{
switch (message)
{
default:
return DefWindowProc(hwnd, message, wp, lp);
}
return NULL;
}
More particulary, in my case I have array of pointers to ui controll class objects, each has own Render(ID2D1DeviceContext*)
, so window's render loop looks like this:
inline void WINDOW::Render()
{
Init(); // (re)Initialize target as ID2D1DeviceContext*, if needed;
target->BeginDraw();
target->Clear(D2D1::ColorF(D2D1::ColorF::Blue));
target->SetTransform(D2D1::Matrix3x2F::Identity());
for(int i = 0; i < ui_elements_count; i++)
ui_element[i]->Draw(target);
this->hr = target->EndDraw();
}
...
inline void UI_ELEMENT::Draw(ID2D1DeviceContext *target)
{
...
if(this->blurRadius > 0)
{
BlurRectangle(this->x, this->y, this->cx, this->cy, this->blurRadius);
}
}
Update
Using Simon's Mourier answer I tried to create the solution I need, but stuck, trying to SetInput
backbuffer' bitmap to ID2D1Effect*
. For some reasone, it either does not set in SetInput
method correctly, or in DrawImage
call. The saddest thing is that backbuffer bitmap is actually valid, it could be drawn by same DrawImage
call. Maybe I should specify another bitmap options when create it?
// other code is same
ID2D1DeviceContext* target;
void Init()
{
Release();
m_pRenderTarget = NULL;
RECT rc;
GetClientRect(hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(
rc.right - rc.left,
rc.bottom - rc.top);
// Create a Direct2D render target.
SUCCEEDED(m_pDirect2dFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(hwnd, size),
&m_pRenderTarget));
m_pRenderTarget->QueryInterface(&target);
}
inline void Blur(ID2D1DeviceContext* backTarget, int rad, RECT r)
// r is not used, should contain element bound box
{
RECT rc;
GetClientRect(hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(
rc.right - rc.left,
rc.bottom - rc.top);
// Draw rectangle for test
backTarget->FillRectangle(D2D1::RectF(30, 30, 100, 100), brush);
ID2D1Bitmap1* bb = nullptr;
// Create bitmap
backTarget->CreateBitmap(size, 0, 0, D2D1::BitmapProperties1(
D2D1_BITMAP_OPTIONS_TARGET,
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)
), &bb);
// Copy current taget's state to created bitmap
bb->CopyFromRenderTarget(0, backTarget, 0);
ID2D1Effect* blur = nullptr;
target->CreateEffect(CLSID_D2D1GaussianBlur, &blur);
blur->SetValue(D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, rad);
blur->SetInput(0, bb);
// Draw blurred result. Does nothing
backTarget->DrawImage(blur);
// Just test if bb is valid, draw
// it with some offset.
// Draws correctly
auto a = D2D1::Point2F(100, 0);
backTarget->DrawImage(bb, a);
SafeRelease(&blur);
}
inline void Render()
{
RECT rc;
GetClientRect(hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(
rc.right - rc.left,
rc.bottom - rc.top);
ID2D1BitmapRenderTarget* tar = nullptr; // Create back buffer
target->CreateCompatibleRenderTarget(&tar);
ID2D1DeviceContext* tt = nullptr;
// Get exactly back buffer as ID2D1DeviceContext*,
// because it has more draw calls, such as DrawImage()
tar->QueryInterface(&tt);
tt->CreateSolidColorBrush(
D2D1::ColorF(255, 0, 0),
&brush
);
tt->BeginDraw();
tt->Clear(D2D1::ColorF(D2D1::ColorF::Blue));
tt->SetTransform(D2D1::Matrix3x2F::Identity());
// loop through ui elements should here,
// assume we have an element with blur needed
Blur(tt, 10, RECT());
tt->EndDraw();
target->BeginDraw();
ID2D1Bitmap* bmp = nullptr;
tar->GetBitmap(&bmp);
target->DrawImage(bmp); // Draw back buffer to target
target->EndDraw();
SafeRelease(&tar);
SafeRelease(&tt);
SafeRelease(&bmp);
SafeRelease(&brush);
}
Upvotes: 1
Views: 1333
Reputation: 139177
There are multiple issues in your code, but the main reason it doesn't work is because you can't use the device context's target bitmap (GPU resource) as a source (since it's a target).
Your code doesn't check for errors (you should) so for example you don't see the error from this call:
auto hr = target->EndDraw();
which returns error D2DERR_INVALID_GRAPH_CONFIGURATION:
The solution is therefore to create an intermediary bitmap (render target), render on it, and draw that bitmap on the target device context, something like this:
// at device context init time
target->CreateCompatibleRenderTarget(&bitmapTarget);
// create blur & set bitmap as input
deviceContext->CreateEffect(CLSID_D2D1GaussianBlur, &blur);
//get bitmap from bitmap rt
bitmapTarget->GetBitmap(&bitmap);
blur->SetInput(0, bitmap);
// at render time
void Render()
{
// draw to bitmap rt
bitmapTarget->BeginDraw();
bitmapTarget->Clear(D2D1::ColorF(D2D1::ColorF::Blue));
D2D1_SIZE_F rtSize = deviceContext->GetSize();
bitmapTarget->FillRectangle(D2D1::RectF(30, 30, 100, 100), brush);
bitmapTarget->EndDraw();
// draw to dc
deviceContext->BeginDraw();
// draw bitmap + effect
deviceContext->DrawImage(blur);
deviceContext->EndDraw();
}
And here is the result:
FWIW, I have put a complete correct code here:
#include <Windows.h>
#include <stdlib.h>
#include <d2d1_1.h>
#include <d2d1helper.h>
#pragma comment(lib, "d2d1")
#pragma comment(lib, "dxguid.lib")
template<class Interface>
inline void SafeRelease(Interface** ppInterfaceToRelease)
{
if (*ppInterfaceToRelease)
{
(*ppInterfaceToRelease)->Release();
(*ppInterfaceToRelease) = NULL;
}
}
ID2D1DeviceContext* deviceContext;
ID2D1SolidColorBrush* brush;
ID2D1BitmapRenderTarget* bitmapTarget;
ID2D1Effect* blur;
ID2D1Bitmap* bitmap;
void Release()
{
SafeRelease(&bitmapTarget);
SafeRelease(&deviceContext);
SafeRelease(&brush);
SafeRelease(&blur);
SafeRelease(&bitmap);
}
void Init(HWND hwnd)
{
Release();
ID2D1Factory* factory;
D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &factory);
RECT rc;
GetClientRect(hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top);
// Create a Direct2D render deviceContext.
ID2D1HwndRenderTarget* renderTarget;
factory->CreateHwndRenderTarget(D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties(hwnd, size), &renderTarget);
renderTarget->QueryInterface(&deviceContext);
renderTarget->CreateSolidColorBrush(D2D1::ColorF(255, 0, 0), &brush);
renderTarget->CreateCompatibleRenderTarget(&bitmapTarget);
// create blur & set bitmap as input
deviceContext->CreateEffect(CLSID_D2D1GaussianBlur, &blur);
//get bitmap from bitmap rt
bitmapTarget->GetBitmap(&bitmap);
blur->SetInput(0, bitmap);
SafeRelease(&renderTarget);
SafeRelease(&factory);
}
void Render()
{
// draw to bitmap rt
bitmapTarget->BeginDraw();
bitmapTarget->Clear(D2D1::ColorF(D2D1::ColorF::Blue));
D2D1_SIZE_F rtSize = deviceContext->GetSize();
bitmapTarget->FillRectangle(D2D1::RectF(30, 30, 100, 100), brush);
bitmapTarget->EndDraw();
// draw to dc
deviceContext->BeginDraw();
// draw bitmap + effect
deviceContext->DrawImage(blur);
deviceContext->EndDraw();
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wp, lp);
}
return NULL;
}
int WINAPI WinMain(HINSTANCE hin, HINSTANCE, LPSTR, int)
{
WNDCLASS c = { NULL };
c.lpszClassName = L"GROKEN";
c.lpfnWndProc = WndProc;
c.hInstance = hin;
c.style = CS_VREDRAW | CS_HREDRAW;
c.hCursor = LoadCursor(NULL, IDC_ARROW);
c.hbrBackground = CreateSolidBrush(RGB(255, 255, 255));
RegisterClass(&c);
HDC hdcDevice = GetDC(NULL);
int xw = GetDeviceCaps(hdcDevice, HORZRES);
int yw = GetDeviceCaps(hdcDevice, VERTRES);
int cx = 500, cy = 500;
int x = xw / 2 - cx / 2, y = yw / 2 - cy / 2;
HWND hwnd = CreateWindowEx(NULL, L"GROKEN", L"asd", WS_OVERLAPPEDWINDOW | WS_VISIBLE, x, y, cx, cy, NULL, NULL, hin, 0);
ShowWindow(hwnd, SW_SHOW);
Init(hwnd);
MSG msg;
BOOL bRet;
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
Render();
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return 0;
}
Upvotes: 3