summaryrefslogtreecommitdiff
path: root/src/video
diff options
context:
space:
mode:
authormichi_cc <michi_cc@openttd.org>2011-12-10 16:54:46 +0000
committermichi_cc <michi_cc@openttd.org>2011-12-10 16:54:46 +0000
commit7099faba28b1f35f766d2e720ec9a59284854c79 (patch)
tree6a251ffe1213de8125fd8292c22f5ad293d2f146 /src/video
parenta0f3649c1abe7b831f083366e2fd91206bf23091 (diff)
downloadopenttd-7099faba28b1f35f766d2e720ec9a59284854c79.tar.xz
(svn r23482) -Change: [Win32] Move re-painting the window and doing palette animation into a separate thread.
Diffstat (limited to 'src/video')
-rw-r--r--src/video/win32_v.cpp220
1 files changed, 173 insertions, 47 deletions
diff --git a/src/video/win32_v.cpp b/src/video/win32_v.cpp
index 93961e476..ed774fd66 100644
--- 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 @@ static struct {
HBITMAP dib_sect;
void *buffer_bits;
HPALETTE gdi_palette;
+ RECT update_rect;
int width;
int height;
int width_org;
@@ -45,17 +48,25 @@ static Dimension _bck_resolution;
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 @@ static void MakePalette()
}
_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 @@ static void UpdatePalette(HDC dc, uint start, uint count)
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 @@ static void ClientSizeChanged(int w, int h)
/* 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 @@ bool VideoDriver_Win32::MakeWindow(bool full_screen)
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;
-
- 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();
+ case WM_ENTERSIZEMOVE:
+ in_sizemove = true;
+ break;
- 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 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);
- case Blitter::PALETTE_ANIMATION_NONE:
- break;
+ /* Mark the window as updated, otherwise Windows would send more WM_PAINT messages. */
+ ValidateRect(hwnd, NULL);
+ _draw_mutex->SendSignal();
+ } else {
+ PAINTSTRUCT ps;
- default:
- NOT_REACHED();
- }
- _cur_palette.count_dirty = 0;
+ BeginPaint(hwnd, &ps);
+ PaintWindow(ps.hdc);
+ EndPaint(hwnd, &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);
return 0;
- }
case WM_PALETTECHANGED:
if ((HWND)wParam == hwnd) return 0;
@@ -679,7 +752,7 @@ static void RegisterWndClass()
if (!registered) {
HINSTANCE hinst = GetModuleHandle(NULL);
WNDCLASS wnd = {
- 0,
+ CS_OWNDC,
WndProcGdi,
0,
0,
@@ -815,6 +888,8 @@ const char *VideoDriver_Win32::Start(const char * const *parm)
MarkWholeScreenDirty();
+ _draw_threaded = GetDriverParam(parm, "no_threads") == NULL && GetDriverParam(parm, "no_thread") == NULL && GetCPUCoreCount() > 1;
+
return NULL;
}
@@ -841,6 +916,7 @@ static void CheckPaletteAnim()
{
if (_cur_palette.count_dirty == 0) return;
+ _local_palette = _cur_palette;
InvalidateRect(_wnd.main_wnd, NULL, FALSE);
}
@@ -851,6 +927,32 @@ void VideoDriver_Win32::MainLoop()
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 @@ void VideoDriver_Win32::MainLoop()
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)