changeset 18635:4e8238f64e46 draft

(svn r23482) -Change: [Win32] Move re-painting the window and doing palette animation into a separate thread.
author michi_cc <michi_cc@openttd.org>
date Sat, 10 Dec 2011 16:54:46 +0000
parents 45e8cda75fba
children 3a4c1613d80b
files src/video/win32_v.cpp
diffstat 1 files changed, 174 insertions(+), 48 deletions(-) [+]
line wrap: on
line diff
--- a/src/video/win32_v.cpp
+++ b/src/video/win32_v.cpp
@@ -19,6 +19,8 @@
 #include "../core/math_func.hpp"
 #include "../core/random_func.hpp"
 #include "../texteff.hpp"
+#include "../thread/thread.h"
+#include "../progress.h"
 #include "win32_v.h"
 #include <windows.h>
 
@@ -27,6 +29,7 @@
 	HBITMAP dib_sect;
 	void *buffer_bits;
 	HPALETTE gdi_palette;
+	RECT update_rect;
 	int width;
 	int height;
 	int width_org;
@@ -45,17 +48,25 @@
 uint _codepage;
 #endif
 
+/** Whether the drawing is/may be done in a separate thread. */
+static bool _draw_threaded;
+/** Thread used to 'draw' to the screen, i.e. push data to the screen. */
+static ThreadObject *_draw_thread = NULL;
+/** Mutex to keep the access to the shared memory controlled. */
+static ThreadMutex *_draw_mutex = NULL;
+/** Should we keep continue drawing? */
+static volatile bool _draw_continue;
+/** Local copy of the palette for use in the drawing thread. */
+static Palette _local_palette;
+
 static void MakePalette()
 {
-	LOGPALETTE *pal;
-	uint i;
-
-	pal = (LOGPALETTE*)alloca(sizeof(LOGPALETTE) + (256 - 1) * sizeof(PALETTEENTRY));
+	LOGPALETTE *pal = (LOGPALETTE*)alloca(sizeof(LOGPALETTE) + (256 - 1) * sizeof(PALETTEENTRY));
 
 	pal->palVersion = 0x300;
 	pal->palNumEntries = 256;
 
-	for (i = 0; i != 256; i++) {
+	for (uint i = 0; i != 256; i++) {
 		pal->palPalEntry[i].peRed   = _cur_palette.palette[i].r;
 		pal->palPalEntry[i].peGreen = _cur_palette.palette[i].g;
 		pal->palPalEntry[i].peBlue  = _cur_palette.palette[i].b;
@@ -64,6 +75,10 @@
 	}
 	_wnd.gdi_palette = CreatePalette(pal);
 	if (_wnd.gdi_palette == NULL) usererror("CreatePalette failed!\n");
+
+	_cur_palette.first_dirty = 0;
+	_cur_palette.count_dirty = 256;
+	_local_palette = _cur_palette;
 }
 
 static void UpdatePalette(HDC dc, uint start, uint count)
@@ -72,9 +87,9 @@
 	uint i;
 
 	for (i = 0; i != count; i++) {
-		rgb[i].rgbRed   = _cur_palette.palette[start + i].r;
-		rgb[i].rgbGreen = _cur_palette.palette[start + i].g;
-		rgb[i].rgbBlue  = _cur_palette.palette[start + i].b;
+		rgb[i].rgbRed   = _local_palette.palette[start + i].r;
+		rgb[i].rgbGreen = _local_palette.palette[start + i].g;
+		rgb[i].rgbBlue  = _local_palette.palette[start + i].b;
 		rgb[i].rgbReserved = 0;
 	}
 
@@ -164,6 +179,7 @@
 		/* mark all palette colors dirty */
 		_cur_palette.first_dirty = 0;
 		_cur_palette.count_dirty = 256;
+		_local_palette = _cur_palette;
 
 		BlitterFactoryBase::GetCurrentBlitter()->PostResize();
 
@@ -328,56 +344,113 @@
 	return true; // the request succedded
 }
 
+/** Do palette animation and blit to the window. */
+static void PaintWindow(HDC dc)
+{
+	HDC dc2 = CreateCompatibleDC(dc);
+	HBITMAP old_bmp = (HBITMAP)SelectObject(dc2, _wnd.dib_sect);
+	HPALETTE old_palette = SelectPalette(dc, _wnd.gdi_palette, FALSE);
+
+	if (_cur_palette.count_dirty != 0) {
+		Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
+
+		switch (blitter->UsePaletteAnimation()) {
+			case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND:
+				UpdatePalette(dc2, _local_palette.first_dirty, _local_palette.count_dirty);
+				break;
+
+			case Blitter::PALETTE_ANIMATION_BLITTER:
+				blitter->PaletteAnimate(_local_palette);
+				break;
+
+			case Blitter::PALETTE_ANIMATION_NONE:
+				break;
+
+			default:
+				NOT_REACHED();
+		}
+		_cur_palette.count_dirty = 0;
+	}
+
+	BitBlt(dc, 0, 0, _wnd.width, _wnd.height, dc2, 0, 0, SRCCOPY);
+	SelectPalette(dc, old_palette, TRUE);
+	SelectObject(dc2, old_bmp);
+	DeleteDC(dc2);
+}
+
+static void PaintWindowThread(void *)
+{
+	/* First tell the main thread we're started */
+	_draw_mutex->BeginCritical();
+	_draw_mutex->SendSignal();
+
+	/* Now wait for the first thing to draw! */
+	_draw_mutex->WaitForSignal();
+
+	while (_draw_continue) {
+		/* Convert update region from logical to device coordinates. */
+		POINT pt = {0, 0};
+		ClientToScreen(_wnd.main_wnd, &pt);
+		OffsetRect(&_wnd.update_rect, pt.x, pt.y);
+
+		/* Create a device context that is clipped to the region we need to draw.
+		 * GetDCEx 'consumes' the update region, so we may not destroy it ourself. */
+		HRGN rgn = CreateRectRgnIndirect(&_wnd.update_rect);
+		HDC dc = GetDCEx(_wnd.main_wnd, rgn, DCX_CLIPSIBLINGS | DCX_CLIPCHILDREN | DCX_INTERSECTRGN);
+
+		PaintWindow(dc);
+
+		/* Clear update rect. */
+		SetRectEmpty(&_wnd.update_rect);
+		ReleaseDC(_wnd.main_wnd, dc);
+
+		/* Flush GDI buffer to ensure drawing here doesn't conflict with any GDI usage in the main WndProc. */
+		GdiFlush();
+
+		_draw_mutex->WaitForSignal();
+	}
+
+	_draw_mutex->EndCritical();
+	_draw_thread->Exit();
+}
+
 static LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
 {
 	static uint32 keycode = 0;
 	static bool console = false;
+	static bool in_sizemove = false;
 
 	switch (msg) {
 		case WM_CREATE:
 			SetTimer(hwnd, TID_POLLMOUSE, MOUSE_POLL_DELAY, (TIMERPROC)TrackMouseTimerProc);
 			break;
 
-		case WM_PAINT: {
-			PAINTSTRUCT ps;
-			HDC dc, dc2;
-			HBITMAP old_bmp;
-			HPALETTE old_palette;
+		case WM_ENTERSIZEMOVE:
+			in_sizemove = true;
+			break;
 
-			BeginPaint(hwnd, &ps);
-			dc = ps.hdc;
-			dc2 = CreateCompatibleDC(dc);
-			old_bmp = (HBITMAP)SelectObject(dc2, _wnd.dib_sect);
-			old_palette = SelectPalette(dc, _wnd.gdi_palette, FALSE);
-
-			if (_cur_palette.count_dirty != 0) {
-				Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
-
-				switch (blitter->UsePaletteAnimation()) {
-					case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND:
-						UpdatePalette(dc2, _cur_palette.first_dirty, _cur_palette.count_dirty);
-						break;
+		case WM_EXITSIZEMOVE:
+			in_sizemove = false;
+			break;
 
-					case Blitter::PALETTE_ANIMATION_BLITTER:
-						blitter->PaletteAnimate(_cur_palette);
-						break;
-
-					case Blitter::PALETTE_ANIMATION_NONE:
-						break;
+		case WM_PAINT:
+			if (!in_sizemove && _draw_mutex != NULL && !HasModalProgress()) {
+				/* Get the union of the old update rect and the new update rect. */
+				RECT r;
+				GetUpdateRect(hwnd, &r, FALSE);
+				UnionRect(&_wnd.update_rect, &_wnd.update_rect, &r);
 
-					default:
-						NOT_REACHED();
-				}
-				_cur_palette.count_dirty = 0;
-			}
+				/* Mark the window as updated, otherwise Windows would send more WM_PAINT messages. */
+				ValidateRect(hwnd, NULL);
+				_draw_mutex->SendSignal();
+			} else {
+				PAINTSTRUCT ps;
 
-			BitBlt(dc, 0, 0, _wnd.width, _wnd.height, dc2, 0, 0, SRCCOPY);
-			SelectPalette(dc, old_palette, TRUE);
-			SelectObject(dc2, old_bmp);
-			DeleteDC(dc2);
-			EndPaint(hwnd, &ps);
+				BeginPaint(hwnd, &ps);
+				PaintWindow(ps.hdc);
+				EndPaint(hwnd, &ps);
+			}
 			return 0;
-		}
 
 		case WM_PALETTECHANGED:
 			if ((HWND)wParam == hwnd) return 0;
@@ -679,7 +752,7 @@
 	if (!registered) {
 		HINSTANCE hinst = GetModuleHandle(NULL);
 		WNDCLASS wnd = {
-			0,
+			CS_OWNDC,
 			WndProcGdi,
 			0,
 			0,
@@ -815,6 +888,8 @@
 
 	MarkWholeScreenDirty();
 
+	_draw_threaded = GetDriverParam(parm, "no_threads") == NULL && GetDriverParam(parm, "no_thread") == NULL && GetCPUCoreCount() > 1;
+
 	return NULL;
 }
 
@@ -841,6 +916,7 @@
 {
 	if (_cur_palette.count_dirty == 0) return;
 
+	_local_palette = _cur_palette;
 	InvalidateRect(_wnd.main_wnd, NULL, FALSE);
 }
 
@@ -851,6 +927,32 @@
 	uint32 last_cur_ticks = cur_ticks;
 	uint32 next_tick = cur_ticks + MILLISECONDS_PER_TICK;
 
+	if (_draw_threaded) {
+		/* Initialise the mutex first, because that's the thing we *need*
+		 * directly in the newly created thread. */
+		_draw_mutex = ThreadMutex::New();
+		if (_draw_mutex == NULL) {
+			_draw_threaded = false;
+		} else {
+			_draw_mutex->BeginCritical();
+			_draw_continue = true;
+
+			_draw_threaded = ThreadObject::New(&PaintWindowThread, NULL, &_draw_thread);
+
+			/* Free the mutex if we won't be able to use it. */
+			if (!_draw_threaded) {
+				_draw_mutex->EndCritical();
+				delete _draw_mutex;
+				_draw_mutex = NULL;
+			} else {
+				DEBUG(driver, 1, "Threaded drawing enabled");
+
+				/* Wait till the draw mutex has started itself. */
+				_draw_mutex->WaitForSignal();
+			}
+		}
+	}
+
 	_wnd.running = true;
 
 	CheckPaletteAnim();
@@ -900,26 +1002,50 @@
 
 			if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
 
+#if !defined(WINCE)
+			/* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
+			GdiFlush();
+#endif
+
+			/* The game loop is the part that can run asynchronously.
+			 * The rest except sleeping can't. */
+			if (_draw_threaded) _draw_mutex->EndCritical();
 			GameLoop();
+			if (_draw_threaded) _draw_mutex->BeginCritical();
 
 			if (_force_full_redraw) MarkWholeScreenDirty();
 
-#if !defined(WINCE)
-			GdiFlush();
-#endif
 			_screen.dst_ptr = _wnd.buffer_bits;
 			UpdateWindows();
 			CheckPaletteAnim();
 		} else {
-			Sleep(1);
 #if !defined(WINCE)
+			/* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
 			GdiFlush();
 #endif
+
+			/* Release the thread while sleeping */
+			if (_draw_threaded) _draw_mutex->EndCritical();
+			Sleep(1);
+			if (_draw_threaded) _draw_mutex->BeginCritical();
+
 			_screen.dst_ptr = _wnd.buffer_bits;
 			NetworkDrawChatMessage();
 			DrawMouseCursor();
 		}
 	}
+
+	if (_draw_threaded) {
+		_draw_continue = false;
+		/* Sending signal if there is no thread blocked
+		 * is very valid and results in noop */
+		_draw_mutex->SendSignal();
+		_draw_mutex->EndCritical();
+		_draw_thread->Join();
+
+		delete _draw_mutex;
+		delete _draw_thread;
+	}
 }
 
 bool VideoDriver_Win32::ChangeResolution(int w, int h)