Графика для Windows библиотека программиста средствами DirectDraw

       

Поток ввода



Поток ввода обладает более узкой специализацией по сравнению с основным потоком. Он должен делать следующее:
  • обнаруживать ввод от мыши;
  • обновлять курсор;
  • синхронизироваться с основным потоком;
  • обрабатывать сигнал завершения, полученный от основного потока.
Для получения ввода от мыши могут применяться две схемы: опрос и оповещение. Опрос плохо подходит для нашего случая, потому что поток ввода постоянно остается активным, даже если пользователь не работает с мышью. С другой стороны, если поток ввода блокируется до поступления новых данных от мыши, он почти не расходует лишнего процессорного времени. С помощью имеющегося в DirectInput механизма оповещения можно заблокировать поток ввода до тех пор, пока DirectInput не сообщит о поступлении новых данных.
После получения сигнала поток ввода извлекает новые данные и обновляет курсор одним из двух способов, рассмотренных выше. Независимо от того, какой способ будет использован, обновление курсора необходимо синхронизировать с основным потоком, чтобы потоки не пытались обратиться к первичной поверхности одновременно.
Наконец, поток ввода отвечает за свое завершение. При получении сигнала от основного потока он должен прекратить работу.


Если не считать двух вспомогательных функций, весь поток ввода реализован в виде одной функции. Функция MouseThread() приведена в листинге 7.5.
Листинг 7.5. Функция MouseThread()

DWORD CursorWin::MouseThread(LPVOID p) { TRACE("starting mouse thread\n" ); CursorWin* win=(CursorWin*)p; while(TRUE) { CMultiLock mlock( (CSyncObject**)mouse_event, 2 ); DWORD event=mlock.Lock( INFINITE, FALSE ); if (event-WAIT_OBJECT_0==quit_event_index) { TRACE("got quit message: quitting mouse thread\n"); return 0; } critsection.Lock(); oldcurx=curx; oldcury=cury; BOOL buffer_empty=FALSE; while (!buffer_empty) { DIDEVICEOBJECTDATA data; DWORD elements=1; if (mouse==0) { TRACE("invalid pointer: quitting mouse thread\n"); return 0; } HRESULT r=mouse->GetDeviceData( sizeof(data), &data, &elements, 0 ); if (r==DI_OK && elements==1) { static MouseClickData mc; switch(data.dwOfs) { case DIMOFS_X: curx+=data.dwData; break; case DIMOFS_Y: cury+=data.dwData; break; case DIMOFS_BUTTON0: if (data.dwData & 0x80) { mc.x=curx; mc.y=cury; mc.button=0; mouseclickqueue.AddHead( mc ); } break; case DIMOFS_BUTTON1: if (data.dwData & 0x80) { mc.x=curx; mc.y=cury; mc.button=1; mouseclickqueue.AddHead( mc ); } break; } } else buffer_empty=TRUE; } if (curx<0) curx=0; if (cury<0) cury=0; if (curx>=screen_width-cursor_width) curx=screen_width-cursor_width-1; if (cury>=screen_height-cursor_height) cury=screen_height-cursor_height-1; if (curx==oldcurx && cury==oldcury) { //----- обновление курсора не требуется ------ goto nevermind; } else if (abs(curx-oldcurx) >= cursor_width || abs(cury-oldcury) >= cursor_height) { //----- простой случай: прямоугольники нового // и старого курсора не перекрываются ----- win->UpdateCursorSimpleCase( curx, cury, oldcurx, oldcury ); } else { //----- сложный случай: прямоугольники нового // и старого курсора перекрываются ----- win->UpdateCursorComplexCase( curx, cury, oldcurx, oldcury ); } nevermind:; critsection.Unlock(); } TRACE("leaving mouse thread\n"); return 0; };

Функция MouseThread() имеет один параметр — значение, передаваемое функции AfxBeginThread() при создании потока (см. листинг 7.3). Мы передавали указатель this, поэтому сейчас сможем присвоить его значение указателю на класс CursorWin (переменная win). В функции MouseThread() указатель win будет использоваться для доступа к членам класса CursorWin.
Функция MouseThread() в цикле выполняет блокировку по двум событиям. Класс CMultiLock позволяет блокироваться как по событиям от мыши, так и по событию завершения потока. Фактическая блокировка выполняется функцией CMultiLock::Lock(). По умолчанию функция Lock() блокирует поток до установки всех (в данном случае - двух) заданных событий. Мы изменяем это поведение и передаем FALSE в качестве второго аргумента Lock(), показывая тем самым, что функция должна снимать блокировку при установке хотя бы одного из этих событий.
Когда любое из двух событий переходит в установленное состояние, функция Lock() завершается, и мы проверяем код возврата. Если выясняется, что было установлено событие завершения потока (обозначенное константой quit_event_index), мы выходим из функции MouseThread(), тем самым завершая поток. В противном случае активизация потока вызвана событием мыши, поэтому мы переходим к обработке новых данных.
Однако сначала необходимо захватить критическую секцию с помощью объекта critsection. Для получения данных нам придется обращаться к очереди событий от кнопок мыши и к первичной поверхности, поэтому выполнение этого кода следует синхронизировать с основным потоком.
Мы в цикле получаем данные от объекта DirectInputDevice, представляющего мышь, с помощью функции GetDeviceData(). Если получены данные о перемещении мыши, происходит обновление переменных curx и cury. Если получены данные о нажатии кнопок, они заносятся в очередь событий.
Когда цикл получения данных завершается (поскольку в буфере не остается элементов), мы проверяем переменные curx и cury и убеждаемся, что курсор не вышел за пределы экрана (вместо того чтобы писать код частичного отсечения курсора, мы выбираем простой путь и требуем, чтобы курсор всегда полностью оставался на экране).
Наконец, мы проверяем новое положение курсора. Если перемещение курсора не обнаружено, критическая секция освобождается, а объект CMultiLock снова используется для блокировки по обоим событиям. Если курсор переместился в другое положение, мы вызываем одну из двух функций обновления курсора в зависимости от того, перекрывается ли старая область курсора с новой. Если области перекрываются, вызывается функция UpdateCursorComplexCase(); в противном случае вызывается функция UpdateCursorSimpleCase().
Начнем с более простой функции UpdateCursorSimpleCase() (см. листинг 7.6).
Листинг 7.6. Функция UpdateCursorSimpleCase()

BOOL CursorWin::UpdateCursorSimpleCase(int curx, int cury, int oldcurx, int oldcury) { RECT src; HRESULT r; //------ Блиттинг 1: стирание старого курсора ---------- r=primsurf->BltFast( oldcurx, oldcury, cursor_under, 0, DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 1 failed\n"); CheckResult(r); } //------ Блиттинг 2: сохранение области под новым курсором ------ src.left=curx; src.top=cury; src.right=curx+cursor_width; src.bottom=cury+cursor_height; r=cursor_under->BltFast( 0, 0, primsurf, &src, DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 2 failed\n"); CheckResult(r); } //------ Блиттинг 3: рисование нового курсора ---------- r=primsurf->BltFast( curx, cury, cursor, 0, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 3 failed\n"); CheckResult(r); } return TRUE; }

С помощью трех последовательных вызовов функции BltFast() интерфейса DirectDrawSurface, функция UpdateCursorSimpleCase() стирает существующий курсор, сохраняет область под новым курсором и рисует новый курсор.
В UpdateCursorComplexCase() функция BltFast() вызывается пять раз. Два дополнительных блиттинга предназначены для копирования обновляемой части первичной поверхности на вспомогательную поверхность (cursor_union) и обратно. Функция UpdateCursorComplexCase() приведена в листинге 7.7.
Листинг 7.7. Функция UpdateCursorComplexCase()

BOOL CursorWin::UpdateCursorComplexCase(int curx, int cury, int oldcurx, int oldcury) { RECT src; HRESULT r; int unionx=min(curx, oldcurx); int uniony=min(cury, oldcury); int unionw=max(curx, oldcurx)-unionx+cursor_width; int unionh=max(cury, oldcury)-uniony+cursor_height; //----- Блиттинг 1: копирование объединяющего прямоугольника // во вспомогательный буфер -------- src.left=unionx; src.top=uniony; src.right=unionx+unionw; src.bottom=uniony+unionh; r=cursor_union->BltFast( 0, 0, primsurf, &src, DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 1 failed\n"); CheckResult(r); } //------ Блиттинг 2: стирание старого курсора // во вспомогательном буфере --------- r=cursor_union->BltFast( oldcurx-unionx, oldcury-uniony, cursor_under, 0, DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 2 failed\n"); CheckResult(r); } //------ Блиттинг 3: сохранение области под новым курсором ----- src.left=curx-unionx; src.top=cury-uniony; src.right=src.left+cursor_width; src.bottom=src.top+cursor_height;r r=cursor_under->BltFast( 0, 0, cursor_union, &src, DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 3 failed\n"); CheckResult(r); } //------ Блиттинг 4: рисование нового курсора // во вспомогательном буфере --------- r=cursor_union->BltFast( curx-unionx, cury-uniony, cursor, 0, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 4 failed\n"); CheckResult(r); } //------- Блиттинг 5: копирование вспомогательного буфера // на первичную поверхность -------- src.left=0; src.top=0; src.right=unionw; src.bottom=unionh; r=primsurf->BltFast( unionx, uniony, cursor_union, &src, DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 5 failed\n"); CheckResult(r); } return TRUE; }

Пользуясь одной из этих двух функций, поток ввода обновляет курсор. При этом удается избежать мерцания и разрушения текущего изображения на первичной поверхности.


Содержание раздела