Why does my window lag when I run multiple instances of it? - c

I created a Win32 window app that moves around the screen occasionally, sort of like a pet. As it moves, it switches between 2 bitmaps to show 'animation' of it moving. The implementation involves multiple WM_TIMER messages: One timer moves the window, another changes the bitmap and windows region (to only display the bitmap without the transparent parts) as it is moving, and another changes the direction the window moves.
The window runs perfectly smoothly by itself, but when I open multiple instances, the animations and movements start to lag - it is not so noticeable at 2 windows, but 3 instances and above causes every single window to start lagging very noticably. The movement and animations are choppy and even freeze occasionaly.
I have tried removing portions of the code to pinpoint the cause of the issue, and apparently this only occurs when a section of the following code is put in (I have marked it out with comments):
HBITMAP hBitMap = NULL;
BITMAP infoBitMap;
hBitMap = LoadBitmap(GetModuleHandle(NULL), IDB_BITMAP2);
if (hBitMap == NULL)
{
MessageBoxA(NULL, "COULD NOT LOAD PET BITMAP", "ERROR", MB_OK);
}
HRGN BaseRgn = CreateRectRgn(0, 0, 0, 0);
HDC winDC = GetDC(hwnd);
HDC hMem = CreateCompatibleDC(winDC);
GetObject(hBitMap, sizeof(infoBitMap), &infoBitMap);
HDC hMemOld = SelectObject(hMem, hBitMap);
COLORREF transparentCol = RGB(255, 255, 255);
for (int y = 0; y < infoBitMap.bmHeight; y++) //<<<< THIS SECTION ONWARDS
{
int x, xLeft, xRight;
x = 0;
do {
xLeft = xRight = 0;
while (x < infoBitMap.bmWidth && (GetPixel(hMem, x, y) == transparentCol))
{
x++;
}
xLeft = x;
while (x < infoBitMap.bmWidth && (GetPixel(hMem, x, y) != transparentCol))
{
x++;
}
xRight = x;
HRGN TempRgn;
TempRgn = CreateRectRgn(xLeft, y, xRight, y + 1);
int ret = CombineRgn(BaseRgn, BaseRgn, TempRgn, RGN_OR);
if (ret == ERROR)
{
MessageBoxA(NULL, "COMBINE REGION FAILED", "ERROR", MB_OK);
}
DeleteObject(TempRgn);
}
while (x < infoBitMap.bmWidth);
}
SetWindowRgn(hwnd, BaseRgn, TRUE); //<<<<---- UNTIL HERE
BitBlt(winDC, 0, 0, infoBitMap.bmWidth, infoBitMap.bmHeight, hMem, 0, 0, SRCCOPY);
SelectObject(hMem, hMemOld);
DeleteDC(hMem);
ReleaseDC(hwnd, winDC);
The commented section is the code I use to eliminate the transparent parts of the bitmap from being displayed in the window client region. It is run every time the app changes bitmap to display animation.
The app works perfectly fine if I remove that code, so I suspect this is causing the issue. Does someone know why this section of code causes lag, and ONLY with multiple instances open? Is there a way to deal with this lag?

You're iterating over each pixel in each update (correct me if I'm wrong.) which is a fairly slow process (relatively.)
A better option would be to use something like this: https://stackoverflow.com/a/3970218/19192256 to create a mask color and simply use masking to remove the transparent pixels.

creating multiple regions and concatenating them is a very slow and resource/cpu-intensive operation. Instead, use ExtCreateRegion() to create a single region from an array of rectangles.
Alternatively, forget using a region at all. Simply display your bitmap on the window normally and fill in the desired areas of the window with a unique color that you can make transparent using SetLayeredWindowAttributes(), as described in #Substitute's answer.

Related

SDL, how to fix key input lag [duplicate]

I am making a game where a nozzle of a tank rotates around when space is pressed to shoot enemies. However, right in the beginning when the space is pressed, it seems to stop for a few milliseconds and then continues without any problems. How can I make it so that the rotations is smooth and consistent as soon as the space is pressed, right from the start? Here is a minimal reproducible example:
#include "SDL.h"
#include <iostream>
class Nozzle
{
public:
void draw(SDL_Renderer* renderer, int cx, int cy, int l)
{
float x = ((float)cos(angle) * l) + cx;
float y = ((float)sin(angle) * l) + cy;
SDL_RenderDrawLine(renderer, cx, cy, (int)x, (int)y);
}
void plusAngle(float a)
{
angle += a;
}
private:
float angle = 0.0f;
};
int main(int argc, char* argv[])
{
SDL_Window* window = SDL_CreateWindow("RGame", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1200, 600, false);
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
SDL_Event event;
Nozzle nozzle;
bool running = true;
while (running)
{
while (SDL_PollEvent(&event))
{
if (event.type == SDL_QUIT)
running = false;
if (event.type == SDL_KEYDOWN)
{
if (event.key.keysym.sym == SDLK_SPACE)
nozzle.plusAngle(0.1f);
}
}
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderClear(renderer);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
nozzle.draw(renderer, 600, 300, 70);
SDL_RenderPresent(renderer);
}
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
if (event.type == SDL_KEYDOWN)
{
if (event.key.keysym.sym == SDLK_SPACE)
nozzle.plusAngle(0.1f);
}
This is not how you do controls in a game.
If you open a text editor and hold a key, you'll see one letter being typed, then, after a delay, a steady stream of the same repeated letter. And SDL does the same thing, it gives you fake repeated "key down" events in this manner. This is normally used for editing text, not for game controls. (Those repeated events are marked by event.key.repeat == true).
What you should do is to create something like bool space_key_down, set it to true when you get SDL_KEYDOWN for the space key, and to false when you get SDL_KEYUP for the same key. Then, outside of the event loop, if the variable is set, you rotate your nozzle.
Or you can use SDL_GetKeyboardState. SDL does this thing automatically for every key, and you can access the list of flags it maintains using this function.
Also, while we're at it, you normally don't want to use keycodes (.sym == SDLK_SPACE) for game controls. Prefer scancodes (.scancode == SDL_SCANCODE_SPACE). The difference only becomes apparent on exotic layouts (e.g. AZERTY): keycodes represent the letters printed on the keycaps, while scancodes represent physical key locations. For example, on AZERTY you want to use ZQSD instead of WASD. If you use scancodes, it will happen automatically (SDL_SCANCODE_W will represent Z, and so on).
scaling rotation with frame-rate isn't really what I am looking for. Its a different problem
You need to solve this problem too. If you don't want the rotation speed to depend on FPS (bad thing), you must either mutliply the rotation angle by the frame length (it works, but it's easy to make mistakes this way), or make sure your game logic runs the fixed amount of times per second regardless of the FPS (I prefer this solution). See Fix Your Timestep!.

Scaled Layers in GDI

Original question
Basically, I have two bitmaps, and I want to put one behind the other, scaled down to half its size.
Both are centered, and are of the same resolution.
The catch is that I want to put more than one bitmap on this back layer eventually, and want the scaling to apply to the whole layer and not just the individual bitmap.
My thought is I would use a memory DC for the back layer, capture its contents into a bitmap of its own and use StretchBlt to place it in my main dc
The code I have right now doesn't work, and I can't make sense of it, let alone find anyone who had done this before for direction.
My variables at the moment are as follows
hBitmap - back bitmap
hFiller - front bitmap
hdc - main DC
ldc - back DC(made with CreateCompatibleDC(hdc);)
resh - width of hdc
resv - height of hdc
note that my viewport origin is set to the center
--this part above is solved, with the one major issue being that it does not keep the back layers...
Revised Question
Here's my code. Everything works as intended except for the fact that the layers do not properly stack. They seem to erase what is underneath or fill it with black.
For the record this is a direct copy of my code. I explain sections of it but there is nothing missing between the code blocks.
case WM_TIMER:
{
switch(wParam)
{
case FRAME:
If any position or rotation values have changed, the following section of code clears the screen and prepares it to be rewritten
if(reload == TRUE){
tdc = CreateCompatibleDC(hdc);
oldFiller = SelectObject(tdc,hFiller);
GetObject(hFiller, sizeof(filler), &filler);
StretchBlt(hdc, 0-(resh/2), 0-(resv/2), resh, resv, tdc, 0, 0, 1, 1, SRCCOPY);
SelectObject(tdc,oldFiller);
DeleteDC(tdc);
if(turn == TRUE){
xForm.eM11 = (FLOAT) cos(r/angleratio);
xForm.eM12 = (FLOAT) sin(r/angleratio);
xForm.eM21 = (FLOAT) -sin(r/angleratio);
xForm.eM22 = (FLOAT) cos(r/angleratio);
xForm.eDx = (FLOAT) 0.0;
xForm.eDy = (FLOAT) 0.0;
SetWorldTransform(hdc, &xForm);
}
This is the part that only partially works. At a distance of 80 my scale value will make my bitmap 1 pixel by 1 pixel, so I consider this my "draw distance"
It scales properly, but the layers do not stack, as I mentioned above
for(int i=80;i>1;i--){
tdc = CreateCompatibleDC(hdc);
tbm = CreateCompatibleBitmap(hdc, resh, resv);
SelectObject(tdc, tbm);
BitBlt(tdc, 0-(resh/2), 0-(resv/2), resh, resv,hdc,0,0,SRCCOPY);
//drawing code goes in here
ldc = CreateCompatibleDC(hdc);
oldBitmap = SelectObject(ldc,hBitmap);
StretchBlt(tdc,(int)(angleratio*atan((double)128/(double)i)),0,(int)(angleratio*atan((double)128/(double)i)),(int)(angleratio*atan((double)128/(double)i)),ldc,0,0,128,128,SRCCOPY);
SelectObject(ldc,oldBitmap);
DeleteDC(ldc);
BitBlt(hdc, 0, 0, resh, resv, tdc, 0, 0, SRCCOPY);
DeleteObject(tbm);
DeleteDC(tdc);
}
reload = FALSE;
}
This section below just checks for keyboard input which changes the position or rotation of the "camera"
This part works fine and can be ignored
if(GetKeyboardState(NULL)==TRUE){
reload = TRUE;
if(GetKeyState(VK_UP)<0){
fb--;
}
if(GetKeyState(VK_DOWN)<0){
fb++;
}
if(GetKeyState(VK_RIGHT)<0){
lr--;
}
if(GetKeyState(VK_LEFT)<0){
lr++;
}
if(GetKeyState(0x57)<0){
p++;
}
if(GetKeyState(0x53)<0){
p--;
}
}
break;
}
}
break;

how can I redraw only the rectangle in my app

I'm using Visual Studios Win32 to develop a control app for an ARINC 629 bus. It works fine for an event-driven action such as a mouse click, but I need it to update in real-time when I'm receiving data over the ARINC 629 bus.
I've learned that Win32 is not the best environment for graphics that need to be continuously updated, but my app is working fairly well except for some flicker.
It seems most of the suggestions on how to reduce flicker don't address when a function is called continuously.
Double-buffering does not seem to help much.
In this case, if I could just redraw the rectangle and not the entire client area, I could reduce or eliminate the flicker.
Here is a code snippet:
void twirling_baton_left_channel(HDC hdc, HWND hWnd, unsigned int left_chan_data_receive)
{
LPRECT lpRect;
RECT rect_chan_one;
lpRect = &rect_chan_one;
lpRect->left = 0;
lpRect->top = 0;
lpRect->right = 0x01ba;
lpRect->bottom = 0x01c6;
static unsigned int wait_to_redraw_left_chan = 0;
status_baton_one.left = 0xb2;
status_baton_one.top = 0x28;
status_baton_one.right = 0x11c;
status_baton_one.bottom = 0x4b;
/* draw the running indicator */
if(wait_to_redraw_left_chan > 5)
{
RedrawWindow(hWnd, lpRect, NULL, RDW_INVALIDATE); //RedrawWindow((HWND)hdc, NULL, NULL, RDW_ERASE);
wait_to_redraw_left_chan = 0;
}
wait_to_redraw_left_chan++;
//SelectObject(ps.hdc, GetStockObject(DC_BRUSH));
if(left_chan_data_txrx)
{
SetDCBrushColor(hdc, RGB(0, 255, 0)); // green
Rectangle(hdc, status_baton_one.left, status_baton_one.top, status_baton_one.left+30, status_baton_one.top+20);
}
else
{
SetDCBrushColor(hdc, RGB(128,128,128)); //grey
Rectangle(hdc, status_baton_one.left, status_baton_one.top, status_baton_one.left+30, status_baton_one.top+20);
}
}
I'm trying to use the RedrawWindow() command to only redraw the box defined by the 'Rectangle' function, but it causes the entire client area to be redrawn, which causes a flicker since it is being called continuously.

Xlib: Showing the webcam using PIxmap and XDrawPoint is too slow... how can I improve?

I want to open the webcam and show the video with Xlib.
So, I open the webcam, get the image, and I do something like:
for(x = 0; x < webcamX; x++){
for(y = 0; y < webcamY; y++){
pixel = GetCameraPixel(x, y);
XDrawPoint(dpy, pixmap, pixel, x, y);
}
}
XCopyArea(dpy, pixmal, window, ....);
but, calling XDrawPoint is too slow.
I tried also with XImage, but this time XPutPixel and XPutImage are both slows (using Pixmap is faster than XImage)
I think the problem is that doing XDrawPoint 640 * 480 times makes a lot of requests to the X server. The same when doing XPutImage, sending a 640 * 480 image is a lot.. isn't it?
So, forgetting about the webcam, imagine I want to do a game, an animation, a video player, or whatever, only with XLib.. is it possible? I am sure that drawing pixel by pixel is not efficient, so how can I do it well?
All the information I found on the internet is to use Pixmap or XImage, it is better than drawing on the window itself, but it's not enought.
For example, the xscreensavers show a very nice animation in full screen (and I think they only use Xlib), how can they make the animation smoothly?
Thanks.
Well, it was my fault I think.
Using XImage is the best way, it is very fast and smooth when drawing pixel by pixel something like the webcam example.
But, I was creating the XImage using the XYPixmap, and it was too slow, I changed to ZPixmap and everything is perfect!
So, the solution I used for the webcam is:
image = XCreateImage(dpy, DefaultVisual(dpy, DefaultScreen(dpy)), DefaultDepth(dpy, DefaultScreen(dpy)), ZPixmap, 0, imagedata, webcamX, webcamY, 32, 0);
/* ....*/
for(x = 0; x < webcamX; x++){
for(y = 0; y < webcamY; y++){
pixel = GetCameraPixel(x, y);
XPutPixel(image, x, y, pixel);
}
}
XPutImage(dpy, window, gc, image, 0, 0, 0, 0, webcamX, webcamY);
Create an XImage using the ZPixmap format
Draw in that XImage (using XPutPixel in my case)
Copy the XImage to the window (using XPutImage)
I think that using XYPixmap was too slow because X needed to do some kind of conversion thus making the process very slow

Replacing desktop wallpaper / draw on the desktop

I'd like to do some custom drawing to my windows desktop such that it appears to replace the desktop background (wallpaper). My first try was to get a DC for desktopListView and draw to it:
IntPtr desktopDC = GetWindowDC(desktopListView);
Graphics g = Graphics.FromHwnd(desktopDC); //<-- fails on out of memory error
I then tried to create a NativeWindow and capture the WM_PAINT message by assigning the native window's handle to the desktop and do my own drawing, but I was unable to see any messages to the desktop.
Ideally I'd like to do this in WPF and not windows forms at all. Any clue how to create a WPF window that I can draw to that sits beneath the desktop icons but on top of the wallpaper such that it ignores any mouse messages and the desktop continues to work normally?
If you get the window handle of the desktop, you can create a new window and add your own custom window as a child of that. Putting it behind the list view may give you the result you need, though I'm not 100% sure how well the transparency will work.
Found some code - Most of what you need is in the first part if you don't need to deal with multiple screens that change shape.
public void SetDesktopWindows()
{
Thread.Sleep(0);
while (this.Count < Screen.AllScreens.Length)
{
OrangeGuava.Desktop.DesktopWindow.DesktopControl dtc = new OrangeGuava.Desktop.DesktopWindow.DesktopControl();
User32.SetParent(dtc.Handle, User32.FindWindow("ProgMan", null));
this.Add(dtc);
}
int minx = 0;
int miny = 0;
foreach (Screen screen in Screen.AllScreens)
{
if (screen.Bounds.Left < minx) minx = screen.Bounds.Left;
if (screen.Bounds.Top < miny) miny = screen.Bounds.Top;
}
for (int i = Screen.AllScreens.Length; i < Count; i++)
{
OrangeGuava.Desktop.DesktopWindow.DesktopControl dtc = (OrangeGuava.Desktop.DesktopWindow.DesktopControl)this[i];
dtc.Hide();
}
for (int i = 0; i < Screen.AllScreens.Length; i++)
{
OrangeGuava.Desktop.DesktopWindow.DesktopControl dtc = (OrangeGuava.Desktop.DesktopWindow.DesktopControl)this[i];
dtc.DeviceId = i.ToString();
Rectangle r = Screen.AllScreens[i].WorkingArea;
r.X -= minx;
r.Y -= miny;
dtc.SetBounds(r.X, r.Y, r.Width, r.Height);
dtc.displaySettingsChanged(null, null);
}
}
I've done this by having my window respond to the WM_WINDOWPOSCHANGING message by setting WINDOWPOS.hWndInsertAfter = HWND_BOTTOM. This says to the OS: make sure my window is underneath all other windows, and makes it appear as though your window is glued to the desktop.

Resources