Undocumented BlurBehind makes WM_NCHITTEST slow - winapi

Note, below is about Windows 10. There is same functionality for Windows 7, but code a bit different
If to apply undocumented blurring just WM_POPUP|WM_VISIBLE window's background feature, as shown below:
void BlurBehind(HWND hwnd)
{
struct ACCENTPOLICY
{
int na;
int nf;
int nc;
int nA;
};
struct WINCOMPATTRDATA
{
int na;
PVOID pd;
ULONG ul;
};
int error = 0;
const HINSTANCE hm = LoadLibrary(L"user32.dll");
if (hm)
{
typedef BOOL(WINAPI* pSetWindowCompositionAttribute)(HWND, WINCOMPATTRDATA*);
const pSetWindowCompositionAttribute SetWindowCompositionAttribute = (pSetWindowCompositionAttribute)GetProcAddress(hm, "SetWindowCompositionAttribute");
if (SetWindowCompositionAttribute)
{
ACCENTPOLICY policy = { 4, 0, 155, 0 };
WINCOMPATTRDATA data = { 19, &policy,sizeof(ACCENTPOLICY) };
SetWindowCompositionAttribute(hwnd, &data);
error = (GetLastError());
}
FreeLibrary(hm);
}
}
int WINAPI WinMain(HINSTANCE hin, HINSTANCE, LPSTR, int)
{
WNDCLASS c = {NULL};
c.lpszClassName = L"Example";
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);
HWND hwnd = CreateWindowEx(NULL, L"Example", L"Example", WS_POPUP | WS_VISIBLE, 0, 0, 1000, 600, NULL, NULL, hin, 0);
MSG msg;
while(GetMessage(&msg, NULL, NULL, NULL))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
And, handling WM_NCHITTEST message like this (again, window doesn't have default bar):
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
{
switch (message)
{
case WM_NCHITTEST:
{
RECT r;
GetWindowRect(hwnd, &r);
auto cp = MAKEPOINTS(lp);
int dyn = 61;
const LRESULT result = DefWindowProc(hwnd, message, wp, lp);
if (result == HTCLIENT)
{
if (cp.x <= r.right - dyn && cp.y <= r.top + 31)
return HTCAPTION;
else
{
if (r.right - cp.x < 20 && r.bottom - cp.y < 20)
return HTBOTTOMRIGHT;
else if (r.right - cp.x < 20)
return HTRIGHT;
else if (cp.x - r.left < 20 && r.bottom - cp.y < 20)
return HTBOTTOMLEFT;
else if (r.bottom - cp.y < 20)
return HTBOTTOM;
else if (cp.x - r.left < 20)
return HTLEFT;
else
{
SetClassLong(hwnd, GCL_HCURSOR, (LONG)LoadCursor(NULL, IDC_ARROW));
return result;
}
}
else
{
SetClassLong(hwnd, GCL_HCURSOR, (LONG)LoadCursor(NULL, IDC_ARROW));
return result;
}
}
else
return result;
}
break;
default:
return DefWindowProc(hwnd, message, wp, lp);
}
return 1;
}
Then HT_ events will be slow, laggy. Window will not respond as fast, as cursor moves, so you can, trying to move window, shake your mouse for a while, release it, and window still will be follow the path. So there is some delay.
If not to apply blur, all events, like moving, resizing occurs initially, instantly following mouse cursor.
Perhaps, someone, who used this undocumented features also faced this issue, and know the solution?
I found this trick (didn't tested yet) for dragging window, but I also need to resize it.
Update
So, updating my question with minimal example, I explored, that lags disappear, if to remove completely WM_PAINT case from WndProc. In WM_PAINT currently draws some direct2d stuff, but problem was with GDI+ also earlier. Probably, laggs appears, because message handling in Windows is synchronous, so it waits till stuff is drawn. But why there is such a big difference between switching blur, I don't know.
Also, I found, that I call render function twice: in WM_PAINT and in WM_SIZE (because in WM_SIZE I recreate direct2d render target).
Currently I removed render call in WM_PAINT at all, left only ValidateRect call, and things seems to be better, though during resize it still a bit laggy.
Update 2
Well, I just did not do anything but now it laggs again, even with empty WM_PAINT and WM_SIZE. If I leave WM_NCHITTEST alone at all in WndProc, it still laggs during moving or resizing window.
Perhaps, earlier, when I wrote that there are no laggs, I disabled blur and forget about that.
The example, I provided is representative, so can anyone check, do they have same behaviour (at least if You don't have core i9 and 3080 gpu, probably there will not be difference)?
Update 3
Seems, that earlier lags disappeared not because I accidently turned off blur in code, but because of the battery on my Surface Go was low, and Windows enabled charge economy mode. When that mode is enabled, Windows turns off Transparency effects switcher in Settings->Colors
If manually switch off Transparency effects in settings lags also disappears.
But the most interesting thing is that "Transparency effect", specifically blur is not gone for custom Window, although for other uwp apps, start menu, etc. is.

Related

WinApi, disabling all controls in a rect area

Is there an api to disable all controls in a rect area?
I am trying to write something like that:
GetClientRect(hWnd, rect);
DisableControls(rect);
GetClientRect gets the client area (left and top are always 0), you need to use GetWindowRect to figure out where a child window is.
All child controls are in the client area and you can just disable the parent window and all children will also stop accepting input.
It does seem a little contrived to disable based on a rectangle instead of a list of known controls but I suppose it might be useful in some cases.
static BOOL CALLBACK DisableChildrenInRectProc(HWND hWnd, LPARAM Param)
{
RECT *pParentRect = (RECT*) Param, r, ir;
if (GetWindowRect(hWnd, &r) && IntersectRect(&ir, &r, pParentRect))
{
EnableWindow(hWnd, FALSE);
}
return TRUE;
}
HWND hWnd = ...
RECT r;
GetWindowRect(hWnd, &r);
r.bottom = r.top + (r.bottom - r.top) / 2; // In this example, only disable controls in the top half.
EnumChildWindows(hWnd, DisableChildrenInRectProc, (LPARAM) &r);
I don't believe there is one. Use EnumChildWindows, GetWindowRect and some coordinate mapping via ScreenToClient. For overlap testing you could use IntersectRect.

AdjustWindowRectEx() and GetWindowRect() give wrong size with WS_OVERLAPPED

I want to calculate the full size of a window before opening it. I'm using AdjustWindowRectEx() to achieve this. My code looks like this for a window with a client size of 640x480:
wrect.left = 0;
wrect.top = 0;
wrect.right = 640;
wrect.bottom = 480;
AdjustWindowRectEx(&wrect, WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX, FALSE, 0);
This returns the following values:
left: -3
top: -22
right: 643
bottom: 483
However, when opening the window using CreateWindowEx() and passing
wrect.right - wrect.left
wrect.bottom - wrect.top
as the window size, the window's size physical size actually turns out to be 656x515 pixels. Still, GetWindowRect() returns 646x505, i.e. the same dimensions as returned by AdjustWindowRectEx() but as I said, when I take a screenshot of the desktop and measure the window's size using a paint program, its physical size is actually 656x515 pixels. Does anybody have an explanation for this?
The client size is alright, it's 640x480 but it looks like the border size is calculated wrongly because the border uses more pixels than calculated by AdjustWindowRectEx() and GetWindowRect().
I'm on Windows 7.
EDIT:
Is this downvoted because the title of the question is misleading? As stated in the MSDN autodocs, WS_OVERLAPPED is not supported by AdjustWindowRectEx(). So is there any other way to calculate the dimensions of a WS_OVERLAPPED window? Using WS_OVERLAPPEDWINDOW instead is not a solution because it sets the WS_THICKFRAME and WS_MAXIMIZEBOX which I don't want.
Here is some test code now which shows the problem. You can see that the client size is alright but the window's physical size is larger than what is returned by GetWindowRect().
#include <stdio.h>
#include <windows.h>
#define CLASSNAME "Test"
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wcx;
RECT wrect;
HWND hWnd;
char tmpstr[256];
memset(&wcx, 0, sizeof(WNDCLASSEX));
wcx.cbSize = sizeof(WNDCLASSEX);
wcx.style = CS_HREDRAW|CS_VREDRAW;
wcx.lpfnWndProc = WindowProc;
wcx.hInstance = hInstance;
wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
wcx.hbrBackground = GetStockObject(BLACK_BRUSH); // important! otherwise a borderless window resize will not be drawn correctly
wcx.lpszClassName = CLASSNAME;
RegisterClassEx(&wcx);
wrect.left = 0;
wrect.top = 0;
wrect.right = 640;
wrect.bottom = 480;
AdjustWindowRectEx(&wrect, WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX, FALSE, 0);
hWnd = CreateWindowEx(0, CLASSNAME, "Test", WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX|WS_OVERLAPPED, 0, 0, wrect.right - wrect.left, wrect.bottom - wrect.top, NULL, NULL, hInstance, NULL);
ShowWindow(hWnd, SW_SHOWNORMAL);
GetWindowRect(hWnd, &wrect);
sprintf(tmpstr, "%d %d %d %d\n", wrect.left, wrect.top, wrect.right, wrect.bottom);
AllocConsole();
WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), tmpstr, strlen(tmpstr), NULL, NULL);
GetClientRect(hWnd, &wrect);
sprintf(tmpstr, "%d %d %d %d\n", wrect.left, wrect.top, wrect.right, wrect.bottom);
WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), tmpstr, strlen(tmpstr), NULL, NULL);
Sleep(10000);
DestroyWindow(hWnd);
UnregisterClass(CLASSNAME, hInstance);
return 0;
}
GetWindowRect api doesn't give the correct size:
Due to compatability requirements, certain metrics are reported in such a way that they're not consistent with what is actually drawn on the screen when Aero Glass (more accurately, "Windows Vista Aero") is enabled. This sort of approach is needed in order to change the look of the system for the vast majority of apps for which this isn't an issue.However, there's been a recent change in the system which will be coming out in Vista RC1 that will return the correct rendered value from GetWindowRect() for executables that are linked with "winver = 6.0". This allows new and newly-linked applications to get the "correct" values from GetWindowRect().
GetWindowRect on non-resizable windows under Aero Glass
Use instead DwmGetWindowAttribute to get the correct size:
DwmGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, &wrect, sizeof(wrect));
If AdjustWindowRect doesn't work, just try to create the Window and adjust the size after the window is created.
// Create the window with a nearly correct size
RECT rect;
rect.left = 0;
rect.top = 0;
rect.right = 640;
rect.bottom = 480;
AdjustWindowRectEx(&rect, WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX, FALSE, 0);
// try
hWnd = CreateWindowEx(0, CLASSNAME, "Test", WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX|WS_OVERLAPPED, 0, 0, rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, hInstance, NULL);
// Get the size that is really required and adjust
RECT rectCreated;
GetWindowRect(hWnd, &rectCreated);
rect.right += rectCreated.right-rectcreated.left;
rect.bottom += rectCreated.bottom-rectcreated.top;
// Resize to let the window fir the inner client aea
SetWindowPos(hWnd,NULL,0,0,rect.right-rect.left,rect.bottom-rect.top,SWP_NOMOVE|SWP_NOZORDER);
// Show it.
ShowWindow(hWnd, SW_SHOWNORMAL);
Code is not tested. Hopefully I have no bugs or syntax errors in it.

WM_NCHITTEST not changing mouse cursor

I have a Layered Window (WS_EX_LAYERED) that implements a custom NCHITTEST and NCCALCSIZE to make the client rect of my window the same as the window rect. My window sizes and paints correctly; I can return HTBOTTOM from WM_NCHITTEST when the cursor is near the bottom edge of my window to cause a vertical-resize-window-from-the-bottom type action. However, I am not getting the vertical resize cursor. Is there way a do fix this without having to implement WM_SETCURSOR and test the pointer's position vs the edges of the windows?
Here's a snippet of my code:
case WM_NCCALCSIZE:
// Bypass DefWindowProc, so the Window Rect == Client Rect
return 0;
case WM_NCHITTEST: {
RECT w;
::GetWindowRect(hwnd, &w);
// Compare the mouse X/Y vs the rect of the window to detect
// resizing from the bottom edges
int r = HTCLIENT;
int x = GET_X_LPARAM(lParam);
int y = GET_Y_LPARAM(lParam);
if (w.bottom - y < 10) {
// If I was not using NCHITTEST, I should get a verticle resize pointer here
if (x - w.left < 10)
r = HTBOTTOMLEFT;
else if (w.right - x < 10)
r = HTBOTTOMRIGHT;
else
r = HTBOTTOM;
}
return r;
}
You need to handle the WM_SETCURSOR message - the low-order word of lParam specifies the hit-test code.
For instance,
case WM_SETCURSOR:
switch (LOWORD(lParam))
{
case HTBOTTOM:
SetCursor(LoadCursor(0, IDC_SIZENS));
return 0;
}
}
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);

Drawing (too slow) in WM_PAINT causes flicker?

I want to draw a lot of lines in the WM_PAINT message handler with the following code.
//DrawLine with double buffering
LRESULT CALLBACK CMyDoc::OnPaint(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
std::vector<Gdiplus::Point> points;
std::vector<Gdiplus::Point>::iterator iter1, iter2;
HDC hdc, hdcMem;
HBITMAP hbmScreen, hbmOldBitmap;
PAINTSTRUCT ps;
RECT rect;
hdc = BeginPaint(hWnd, &ps);
//Create memory dc
hdcMem = CreateCompatibleDC(hdc);
GetClientRect(hWnd, &rect);
hbmScreen = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
hbmOldBitmap = (HBITMAP)SelectObject(hdcMem, hbmScreen);
//Fill the rect with white
FillRect(hdcMem, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
//Draw the lines
Gdiplus::Graphics graphics(hdcMem);
Gdiplus::Pen blackPen(Gdiplus::Color(255, 0, 0));
points = m_pPolyLine->GetPoints();
for (iter1 = points.begin(); iter1 != points.end(); iter1++) {
for (iter2 = iter1 + 1; iter2 != points.end(); iter2++)
graphics.DrawLine(&blackPen, *iter1, *iter2);
}
//Copy the bitmap from memory dc to the real dc
BitBlt(hdc, 0, 0, rect.right, rect.bottom, hdcMem, 0, 0, SRCCOPY);
//Clean up
SelectObject(hdcMem, hbmOldBitmap);
DeleteObject(hbmScreen);
DeleteDC(hdcMem);
EndPaint(hWnd, &ps);
return 0;
}
However, if the size of points exceed 20, the client rect just flicker. I think the reason is that Gdiplus::DrawLines is too slow.
Is there any method to solve the flicker problem?
Thanks.
The flickering may be caused by slow painting as well as by other things. In general, try/ensure the following:
Try to not rely on WM_ERASEBKGND message, i.e. return non-zero, or specify NULL in WNDCLASS::hbrBackground if possible. Often the paint method paints all the background of the dirty region, then there is no need to do the erasing.
If you need the erasing, it can often be optimized so that WM_ERASEBKGND returns non-zero, and the paint method then ensures the "erasing" by also painting areas not covered by the regular painted contents, if PAINTSTRUCT::fErase is set.
If reasonably possible, write the paint method so that it does not repaint the same pixels in one call. E.g. to make blue rect with red border, do not FillRect(red), then repainting inner part of it with FillRect(blue). Try to paint each pixel once as much as reasonably possible.
For complex controls/windows, the paint method may often be optimized to easily skip a lot of painting outside the dirty rect (PAINTSTRUCT::rcPaint) by proper organizing the control data.
When changing the control state, invalidate only the minimal required region of the control.
If it is not top-level window, consider using CS_PARENTDC. If your paint method does not rely on system setting clipping rectangle to the client rect of the control, this class style will lead to a somewhat better performance.
If you see the flickering on control/window resizing, consider to not using CS_HREDRAW and CS_VREDRAW. Instead invalidate the relevant parts of the control in WM_SIZE manually. This often allows to invalidate only smaller parts of the control.
If you see the flickering on control scrolling, do not invalidate whole client, but use ScrollWindow() and invalidate only small area which exposes the new (scrolled-in) content.
If everything above fails, then use double buffering.
Use double buffer. It is an offen problem with Win32 C++ application and specifically with OnPaint function and DC facilities.
Here is a few links to help you check if everything is fine with YOUR implementation of double buffer: Flicker Free Drawing In MFC and SO question "Reduce flicker with GDI+ and C++"
If your lines happen to extend outside the bounds of the DC (Graphics), Win32/GDI+ is painfully slow at clipping. Like, up to two orders of magnitude slower than rolling your own clipping function. Here is some C# code that implements Liang/Barsky - I scrounged this up from an old library that was originally in C++ 20 years ago. Should be easy enough to port back.
If your lines can extend beyond the client rectangle, call ClipLine(rect, ...) on your points before handing them off to Graphics::DrawLine.
private static bool clipTest(double dp, double dq, ref double du1, ref double du2)
{
double dr;
if (dp < 0.0)
{
dr = dq / dp;
if (dr > du2)
{
return false;
}
else if (dr > du1)
{
du1 = dr;
}
}
else
{
if (dp > 0.0)
{
dr = dq / dp;
if (dr < du1)
{
return false;
}
else if (dr < du2)
{
du2 = dr;
}
}
else
{
if (dq < 0.0)
{
return false;
}
}
}
return true;
}
public static bool ClipLine(Rectangle clipRect, ref int x1, ref int y1, ref int x2, ref int y2)
{
double dx1 = (double)x1;
double dx2 = (double)x2;
double dy1 = (double)y1;
double dy2 = (double)y2;
double du1 = 0;
double du2 = 1;
double deltaX = dx2 - dx1;
double deltaY;
if (clipTest(-deltaX, dx1 - clipRect.Left, ref du1, ref du2))
{
if (clipTest(deltaX, clipRect.Right - dx1, ref du1, ref du2))
{
deltaY = dy2 - dy1;
if (clipTest(-deltaY, dy1 - clipRect.Top, ref du1, ref du2))
{
if (clipTest(deltaY, clipRect.Bottom - dy1, ref du1, ref du2))
{
if (du2 < 1.0)
{
x2 = DoubleRoundToInt(dx1 + du2 * deltaX);
y2 = DoubleRoundToInt(dy1 + du2 * deltaY);
}
if (du1 > 0.0)
{
x1 = DoubleRoundToInt(dx1 + du1 * deltaX);
y1 = DoubleRoundToInt(dy1 + du1 * deltaY);
}
return x1 != x2 || y1 != y2;
}
}
}
}
return false;
}
The problem is I have not handle WM_ERASEBKGND message by myself.

Proper animation in Direct3D 10

I'm a beginner in d3d development and I'm stuck on my first issue. I've created a little program that draws a terrain with random heights in each vertex and a light that moves from left to right (plus basic keyboard management). Problem is the animated light is very skippy, input recognition fails pretty often and cpu usage is 50% constant (I have a dual core cpu). I thought that this issue was caused by incorrect throttling, but even after fixing the code i still have problems. My main loop is
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
HWND wndHandle;
int width = 640;
int height = 480;
wndHandle = InitWindow(hInstance, width, height);
InitDirect3D(wndHandle, width, height);
DInputInit(hInstance, wndHandle);
SceneSetUp();
MSG msg = {0};
QueryPerformanceFrequency((LARGE_INTEGER *) &perf_cnt);
time_count = perf_cnt / fixedtic;
QueryPerformanceCounter((LARGE_INTEGER *) &next_time);
while (WM_QUIT != msg.message)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
continue;
}
HandleInput();
QueryPerformanceCounter((LARGE_INTEGER *) &cur_time);
if (cur_time>next_time) {
Render();
next_time += time_count;
if (next_time < cur_time)
next_time = cur_time + time_count;
}
}
ShutdownDirect3D();
return (int)msg.wParam;
}
while the Render function is
void Render()
{
QueryPerformanceCounter((LARGE_INTEGER *) &curtime);
timespan = (curtime - last_time) * time_factor;
last_time = curtime;
model.pD3DDevice->ClearRenderTargetView(model.pRenderTargetView, D3DXCOLOR(0.0f, 0.0f, 0.0f, 0.0f));
lightpos.x = (float)cos(ang) * 256.0f;
ang += timespan * 0.5;
lpvar->SetFloatVector((float *) lightpos);
D3DXMatrixLookAtLH(&V, &model.campos, new D3DXVECTOR3(0.0f, 0.0f, 0.0f), new D3DXVECTOR3(0.0f, 1.0f, 0.0f));
D3DXMATRIX VP = V*P;
camvar->SetFloatVector((float *)model.campos);
ViewProjection->SetMatrix((float *) &VP);
for(UINT p=0; p < techniqueDescription.Passes; ++p)
{
pTechnique->GetPassByIndex(p)->Apply(0);
model.pD3DDevice->DrawIndexed(numIndices, 0, 0);
}
model.pSwapChain->Present(0, 0);
}
Any help is appreciated
Remove the continue from after the Dispatch message. As it stads if you have a shed load of messages queued up (say you move the mouse over the window) then you will end up wasting time processing those messages instead of rendering.
It would also be usual to do the following to process all the messages in a one'r:
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
It would be interesting to see what sort of input you are handling too as you appear to be handling the input more regularly than you are rendering.
In general, though, its better to render constantly and simply multiply any movement code by the time since the last frame to smooth things out. This way you'd work out what the movement for a given second. So if you want to press a key and rotate at 1 rotation per second then you would simply set the rotation increment to (2 * pi) / sinceLastFrame ...
The reason your CPU time is at 50% is because you are, effectively, sitting in a loop. You don't give up your timeslice at any point. You simply spin round the loop handling input, probably many more times per second than rendering occurs.
Ok, I nailed it and I think I deserve a punch in my face. I was creating a reference device instead of a hardware device

Resources