locked
WM_PAINT & Update Flicker RRS feed

  • Question

  • Hi,

    Does anyone have ideas on how to eliminate flicker in update region defined in call to WM_PAINT?  Calling the WM_PAINT case on 50 ms interrupt right now.  I would find it hard to believe the routine has any difficulty whatsoever completing in this interval.  One of the suggestions I saw from another thread was to force the update region not to erase so I added the RDW_NOERASE qualifier in the call to redraw window.  Little to no difference.  I have no background understanding of how this actually renders.  I.e. does the entire update region get buffered during code and calculations and effectively replace existing image once that is complete or is this more of a real time redraw where the update region is wiped and each of these elements is drawn individually sequentially until complete?

    //interrupt trigger at 50 ms
    case WM_TIMER:
    		
    		if (wParam == SCREEN_REFRESH_TIMER) {
    			RedrawWindow(hWnd, &updateRegion, NULL, RDW_INVALIDATE | RDW_UPDATENOW | RDW_NOERASE);
    		}
    
    //jumping forward to paint case
    
    
    case WM_PAINT:  
    		{
    			RECT parentWindowSize;
    			RECT TextWindow;
    			BOOL fixedGraphScale = TRUE;
    			HBRUSH hbr;										//brush for shading scope window
    			WCHAR xMinBuffer[10];
    			WCHAR xMaxBuffer[10];
    			float graphMax = returnMaxAmplitude();			//check max and min from retrieved array for dynamic scaling
    			float graphMin = returnMinAmplitude();
    			if (fixedGraphScale == TRUE) {					//otherwise use constant values
    				graphMax = 84;
    				graphMin = 71;
    			}
    			int GraphXLeft = 20;							//set default graph frame @ 600x400
    			int GraphXRight = 620;
    			int GraphYTop = 50;
    			int GraphYBottom = 450;
    			RECT scopeWindow;
    			scopeWindow.left = 20;
    			scopeWindow.right = 620;
    			scopeWindow.top = 50;
    			scopeWindow.bottom = 450;
    			
    
    			float nextX, nextY;
    			float span = graphMax - graphMin + 2;
    			float pixelsPerEGU = (GraphYBottom - GraphYTop) / span;
    			float pixelsPerXUNIT = (GraphXRight - GraphXLeft) / float(dataArraySize - 1);		//calculate pixels per X axis unit
    			swprintf_s(xMinBuffer, 10, L"%f", graphMin - 1.0);			//min value in sample array
    			swprintf_s(xMaxBuffer, 10, L"%f", graphMax + 1.0);			//max value in sample array
    
    			PAINTSTRUCT ps;
                HDC hdc = BeginPaint(hWnd, &ps);
    			char sampleValue[10];
    			size_t convertedChars = 0;
    			WCHAR displayValue[10];
    			//swprintf_s(displayValue, 2, L"%c", testPicRead);
    			mbstowcs_s(&convertedChars, displayValue, 10, testPicRead, _TRUNCATE);   //string pos 4 -> to WCHAR pos 2
    			
    			hbr = CreateSolidBrush(RGB(0,0,0));											//shade scope window background
    			SelectObject(hdc, hbr);
    			FillRect(hdc, &scopeWindow, hbr);
    
    			//Rectangle(hdc, GraphXLeft, GraphYTop, GraphXRight, GraphYBottom);				//draw graph boundary
    			SelectObject(hdc, GetStockObject(DC_PEN));
    			SetTextColor(hdc, RGB(0, 255, 0));
    			SetBkColor(hdc, RGB(0, 0, 0));
    			TextWindow.left = GraphXLeft + 50; TextWindow.right = GraphXLeft + 150; TextWindow.top = GraphYBottom -30; TextWindow.bottom = GraphYBottom - 5;		//trace 1 label
    			DrawText(hdc, displayValue, 10, &TextWindow, DT_LEFT | DT_SINGLELINE | DT_VCENTER);
    			TextWindow.left = GraphXLeft + 5; TextWindow.right = GraphXLeft + 40; TextWindow.top = GraphYBottom - 30; TextWindow.bottom = GraphYBottom - 5;		//bottom Y axis label
    			DrawText(hdc, xMinBuffer, 4/*numDigits(returnMinAmplitude())*/, &TextWindow, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
    			TextWindow.left = GraphXLeft + 5; TextWindow.right = GraphXLeft + 40; TextWindow.top = GraphYTop + 5; TextWindow.bottom = GraphYTop + 25;				//top Y axis label
    			DrawText(hdc, xMaxBuffer, 4/*numDigits(returnMaxAmplitude())*/, &TextWindow, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
    
    			
    			SetDCPenColor(hdc, RGB(0, 255, 0));						//Draw first trace
    			MoveToEx(hdc, GraphXRight, GraphYTop + ((graphMax + 1 - CH1Array[0])*pixelsPerEGU), NULL);
    			//LineTo(hdc, 200, 300);
    			
    			for (int i = 0; i < dataArraySize; i++) {				
    				nextX = GraphXRight - i * (pixelsPerXUNIT);
    				nextY = GraphYTop + ((graphMax + 1 - CH1Array[i])*(pixelsPerEGU));
    				LineTo(hdc, nextX, nextY);
    				//SelectObject(hdc, GetStockObject(GRAY_BRUSH));
    				//if ((i != 0) && (i != dataArraySize - 1)) {
    				//	Ellipse(hdc, nextX - 2, nextY + 2, nextX + 2, nextY - 2);
    				//}
    			}
    			
    			DeleteObject(hdc);
    			DeleteObject(hbr);
                EndPaint(hWnd, &ps);
            }
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
        return 0;
    }


    Graphics post update.

    Sunday, June 7, 2020 8:52 PM

Answers

  • As a bit of a hint, why do you think high performance gaming applications use interfaces like IDXGISwapChain to double buffer?

    One of the most common ways to reduce flicker is to implement double buffering. For GDI this involves creating an off screen bitmap in a compatible DC, drawing to this offscreen bitmap, the "back buffer" and then BitBlt to the window's DC. If you only want to update part of the image then you can either keep this "back buffer" around and incrementally update it, or you copy the contents of the window to the "back buffer" using BitBlt and then draw over that.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    • Edited by Darran Rowe Monday, June 8, 2020 9:02 AM
    • Marked as answer by JigglyBit Wednesday, June 10, 2020 1:46 AM
    Monday, June 8, 2020 9:01 AM

All replies

  • Hello JigglyBit,

    The presented code lost some information like definition of CH1Array etc. Could you show a mini, complete and reproducible sample? So I can do a further investigation.

    Best regards,

    Rita


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.


    Monday, June 8, 2020 3:10 AM
  • As a bit of a hint, why do you think high performance gaming applications use interfaces like IDXGISwapChain to double buffer?

    One of the most common ways to reduce flicker is to implement double buffering. For GDI this involves creating an off screen bitmap in a compatible DC, drawing to this offscreen bitmap, the "back buffer" and then BitBlt to the window's DC. If you only want to update part of the image then you can either keep this "back buffer" around and incrementally update it, or you copy the contents of the window to the "back buffer" using BitBlt and then draw over that.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    • Edited by Darran Rowe Monday, June 8, 2020 9:02 AM
    • Marked as answer by JigglyBit Wednesday, June 10, 2020 1:46 AM
    Monday, June 8, 2020 9:01 AM
  • As Darran Rowe said, use double-buffering (I often use  BeginBufferedPaint in GDI)

    (you must draw/blit only once to the destination DC)

    and as Rita Han said, your code is not compilable (and has apparently memory leaks (incorrect selection/deletion of GDI objects)

    and depending on how the parts of background are filled, flickering can also be eliminated by handling WM_ERASEBKGND


    • Edited by Castorix31 Monday, June 8, 2020 9:40 AM
    Monday, June 8, 2020 9:38 AM
  • Hi Rita,

    I have data collection on CH1Array set up on 20 ms periodicity as follows.  It is reading from a serial port and the array is 300 floating point elements in length.  Based on introduction of sleep() statements in code it looks like this is executing magnitudes of order quicker than the call to refresh the screen graphic.  

    bool PICRead(char* returnValue) {
    	BOOL wasRead = 0;
    	DWORD dwBytesRead;
    	DWORD lastError;
    
    	HANDLE serialHandle;				// Open serial port
    	serialHandle = CreateFile(L"\\\\.\\COM4", GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
    	if (serialHandle == INVALID_HANDLE_VALUE) {
    		return 0;
    	}
    
    	DCB serialParams = { 0 };
    	serialParams.DCBlength = sizeof(serialParams);
    	GetCommState(serialHandle, &serialParams);
    	serialParams.BaudRate = 19200;
    	serialParams.ByteSize = DATABITS_8;
    	serialParams.StopBits = ONESTOPBIT;
    	serialParams.Parity = PARITY_NONE;
    	SetCommState(serialHandle, &serialParams);
    
    	// Set timeouts
    	COMMTIMEOUTS timeout = { 0 };
    	timeout.ReadIntervalTimeout = 50;
    	timeout.ReadTotalTimeoutConstant = 50;
    	timeout.ReadTotalTimeoutMultiplier = 50;
    	timeout.WriteTotalTimeoutConstant = 50;
    	timeout.WriteTotalTimeoutMultiplier = 50;
    	SetCommTimeouts(serialHandle, &timeout);
    
    	wasRead = ReadFile(serialHandle, returnValue, sizeof(returnValue), &dwBytesRead, NULL);
    	
    	//lastError = GetLastError();				//debug logging if necessary
    	CloseHandle(serialHandle);
    		
    	return wasRead;
    }
    
    bool CH1QueuePush(char* nextElement) {					//add newest data sample to queue
    	float nextElementFloat;
    	nextElementFloat = atof(nextElement);
    	for (int iterate = dataArraySize- 1; iterate >= 1; iterate--) {   //shift CH1 array by one element
    		CH1Array[iterate] = CH1Array[iterate - 1];
    	}
    	CH1Array[0] = nextElementFloat;
    	return 0;
    }

    I am reading the additional comments about potential memory leaks and double buffering.  The buffering is foreign language to me so I think I may be in a bit over my head with my original question.  New to this.  The only observation I can make is that it appears that every time I declare an object in case WM_PAINT I also delete it before exiting the case.  I compiled and ran this as a .exe outside of debug mode and I consumed a relatively constant 1.2 MB of RAM with it.  I would think that if I had some kind of grotesque memory leak (especially due to periodicity of interrupt refresh calls - 30 to 50 ms testing) I would have seen RAM utilization growing (very) significantly after 10 or so minutes of running it but I did not.  Hovered right at 1.2 MB.  But I very well could be wrong.  Going to research buffering situation more as well.  If you would like the rest of the code the program is only about 450 lines.


    Graphics post update.

    Tuesday, June 9, 2020 3:58 AM
  • Hi Darran,

    In short, I didn't think.  I was unaware that double buffering was an option.  (This is my first attempt at a "high speed" graphical rendition stab with this platform).  I am performing an order of N1 calculations 300 iterations in length + some spare change in the WM_PAINT.  My main confusion is does WM_PAINT actually in itself buffer the render commands while processing through this loop and simply swap the graphic out at the end of it all, or does the image basically dissolve the second the invalidate command is sent, effectively sit real time on pause until the code in WM_PAINT is executed, then update the new rendition?  This could easily explain (any) refresh latency, especially as magnitude of background processing tasks increases.  If this is the case I see huge value in utilizing the 99% of the time this program thread is effectively sitting idle in recreating a new image then simply using the WM_PAINT to swap it out.

    I am far from there in terms of my understanding of how to do this though.  But agreed, if this is an option it seems like a very good one.  I just need an example of how to accomplish the outcome.  Will hit the forums and see what I can dig up with what you mentioned above.  Thank you for the pointer.  And if you have any quick and dirty actual code sample for me to browse through I would much appreciate it.


    Graphics post update.

    Tuesday, June 9, 2020 4:31 AM
  • One interesting bit of code to help you understand how Windows draws to a DC is:

    	case WM_PAINT:
    	{
    		HBRUSH red_brush = nullptr;
    		HBRUSH green_brush = nullptr;
    		HBRUSH blue_brush = nullptr;
    		HBRUSH old_brush = nullptr;
    
    		red_brush = CreateSolidBrush(RGB(255, 0, 0));
    		green_brush = CreateSolidBrush(RGB(0, 255, 0));
    		blue_brush = CreateSolidBrush(RGB(0, 0, 255));
    
    		PAINTSTRUCT ps{};
    		HDC dc = BeginPaint(wnd, &ps);
    
    		old_brush = reinterpret_cast<HBRUSH>(SelectObject(dc, red_brush));
    		Rectangle(dc, 20, 20, 450, 450);
    		Sleep(200);
    		SelectObject(dc, green_brush);
    		Rectangle(dc, 40, 40, 470, 470);
    		Sleep(200);
    		SelectObject(dc, blue_brush);
    		Rectangle(dc, 60, 60, 490, 490);
    		Sleep(200);
    		SelectObject(dc, red_brush);
    		Rectangle(dc, 80, 80, 510, 510);
    		Sleep(200);
    		SelectObject(dc, green_brush);
    		Rectangle(dc, 100, 100, 530, 530);
    		Sleep(200);
    		SelectObject(dc, blue_brush);
    		Rectangle(dc, 120, 120, 550, 550);
    		Sleep(200);
    		SelectObject(dc, old_brush);
    
    		EndPaint(wnd, &ps);
    
    		DeleteObject(red_brush);
    		DeleteObject(green_brush);
    		DeleteObject(blue_brush);
    
    		return DefWindowProcW(wnd, msg, wparam, lparam);
    	}

    Notice how after each draw operation, the thread sleeps for 200ms? Because of how slow this is, you actually see each rectangle being drawn. So hopefully this should answer the question about Windows buffering drawing. To modify this to use buffered painting, you could use the buffered painting features of uxtheme.dll. This is quite easy:

    	case WM_PAINT:
    	{
    		HBRUSH red_brush = nullptr;
    		HBRUSH green_brush = nullptr;
    		HBRUSH blue_brush = nullptr;
    		HBRUSH old_brush = nullptr;
    		HBRUSH white_brush = nullptr;
    		RECT client_rect{};
    		BP_PAINTPARAMS params{sizeof(BP_PAINTPARAMS), BPPF_ERASE, nullptr, nullptr};
    
    		red_brush = CreateSolidBrush(RGB(255, 0, 0));
    		green_brush = CreateSolidBrush(RGB(0, 255, 0));
    		blue_brush = CreateSolidBrush(RGB(0, 0, 255));
    		white_brush = CreateSolidBrush(RGB(255, 255, 255));
    		GetClientRect(wnd, &client_rect);
    
    		HRGN client = CreateRectRgn(client_rect.left, client_rect.top, client_rect.right, client_rect.bottom);
    
    		PAINTSTRUCT ps{};
    		HDC dc = BeginPaint(wnd, &ps);
    		HDC buffered_dc = nullptr;
    		HPAINTBUFFER bp = BeginBufferedPaint(dc, &client_rect, BPBF_DIB, &params, &buffered_dc);
    
    		
    		old_brush = reinterpret_cast<HBRUSH>(SelectObject(buffered_dc, white_brush));
    		PaintRgn(buffered_dc, client);
    		Sleep(200);
    		SelectObject(buffered_dc, red_brush);
    		Rectangle(buffered_dc, 20, 20, 450, 450);
    		Sleep(200);
    		SelectObject(buffered_dc, green_brush);
    		Rectangle(buffered_dc, 40, 40, 470, 470);
    		Sleep(200);
    		SelectObject(buffered_dc, blue_brush);
    		Rectangle(buffered_dc, 60, 60, 490, 490);
    		Sleep(200);
    		SelectObject(buffered_dc, red_brush);
    		Rectangle(buffered_dc, 80, 80, 510, 510);
    		Sleep(200);
    		SelectObject(buffered_dc, green_brush);
    		Rectangle(buffered_dc, 100, 100, 530, 530);
    		Sleep(200);
    		SelectObject(buffered_dc, blue_brush);
    		Rectangle(buffered_dc, 120, 120, 550, 550);
    		Sleep(200);
    		SelectObject(buffered_dc, old_brush);
    
    		EndBufferedPaint(bp, TRUE);
    		EndPaint(wnd, &ps);
    
    		DeleteObject(white_brush);
    		DeleteObject(red_brush);
    		DeleteObject(green_brush);
    		DeleteObject(blue_brush);
    
    		return DefWindowProcW(wnd, msg, wparam, lparam);
    	}

    This requires that you include uxtheme.h and link to uxtheme.lib. The extra draw, PaintRgn is used because the buffered painting likes to clear the drawing region to transparent black and not white. So take that into consideration with regards to the WM_ERASEBKGND message.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    Tuesday, June 9, 2020 6:33 PM
  • Thank you.  I played around simply with copying device contexts and BitBlt() and was able to get this functioning perfectly.  Your explanation and the reasoning behind it now makes a lot of sense to me with your exaggerated test case and after playing around with it for a half hour.  Zero flicker.  Jitter in graphics but that is due to interrupt timing I need to tune and could probably use a smoothing algorithm, but for purposes of this post, mission accomplished.  So thanks again for steering me in the right direction!

    Graphics post update.

    Wednesday, June 10, 2020 1:50 AM