DirectX Графика в проектах Delphi

       

Оконные приложения



Вы, должно быть, уже привыкли к тому, что наши примеры работают в полноэкранном режиме. Обычно оконные приложения создаются в DirectDraw только в случае крайней необходимости, т. к. слишком многое мы теряем при его использовании. Главная потеря здесь - скорость работы приложения.

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

Для оконных приложений нельзя использовать переключение страниц или двойную буферизацию.
По своей сути, оконные приложения похожи на наши самые первые примеры, с выводом кружка на поверхности окна. Точно так же первичной поверхностью является весь рабочий стол экрана, и приложение должно отслеживать положение окна.
Рассмотрим простейший пример, располагающийся в каталоге Ех25. Работа его совсем проста, в пределах окна выводится хорошо нам знакомый растр с горным пейзажем.
Свойство Borderstyle формы приняло теперь свое обычное значение bssizeabie, удалены единственный компонент и все, связанное с курсором. Не можем мы также здесь задавать параметры экрана и устанавливать исключительный уровень кооперации, поскольку для оконных приложений задается обычный уровень доступа:

hRet := FDD.SetCooperativeLevel(Handle, DDSCL_NORMAL);

Появился обработчик перерисовки окна, в котором определяем текущее положение окна приложения и выводим на него масштабированный растр:

procedure TfrmDD.FormPaint(Sender: TObject);
var
rcDest : TRECT;
p : TPOINT; // Вспомогательная точка для определения положения окна begin
р.Х := 0;
p.Y := 0;
// Находим положение на экране точки левого верхнего угла
// клиентской части окна приложения
Windows.ClientToScreen(Handle, p);
// Получаем прямоугольник размерами клиентской части окна
Windows.GetClientRect(Handle, rcDest);
OffsetRect(rcDest, p.X, p.Y); // Сдвигаем прямоугольник на р.Х, p.Y
if Failed (FDDSPrimary.Blt (@rcDest, FDDSBackGround, nil,
DDBLT_WAIT, nil)) // Выводим растр then RestoreAll;
end;


Перед именами некоторых процедур я указал, что имеются в виду процедуры именно модуля windows. Если для вас привычнее использовать аналогичные методы формы, то эти строки можете записать так:

р := ClientToScreen(р); rcDest := GetClientRect;

Хоть в рассматриваемом примере и приходится следовать рекомендациям разработчиков, пытаясь восстанавливать все поверхности в случае неудачи при воспроизведении, но сделано это большей частью формально. Если по ходу приложения поменять установки экрана, то оно не сумеет восстановить первичную поверхность. Это не является недостатком конкретно нашего примера. Точно так же ведут себя все оконные приложения, использующие DirectDraw.
Если вы внимательно посмотрите на работу приложения, то должны заметить, как плохо масштабируется картинка при изменении размеров окна. Для более удовлетворительной работы обработчик этого события должен вызывать код перерисовки окна.
Оконное приложение может рисовать в любом месте рабочего стола. Малейшая ошибка в коде приведет к очень некрасивым результатам. Обычно такие приложения ограничивают область вывода, для чего используется объект класса IDirectDrawClipper.
Посмотрим проект каталога Ех2б, в коде модуля которого появилась переменная FDDCiipper такого типа. В начале и конце работы приложения ее значение, как принято, устанавливается в nil.
Сразу после создания первичной поверхности формируется этот объект, ответственный за отсечение области вывода, и присоединяется к области вывода:

// Создание объекта отсечения
hRet := FDD.CreateClipper(0, FDDCiipper, nil);
if Failed (hRet) then ErrorOut(hRet, 'CreateClipper FAILED');
// Определяем окно, связанное с отсечением области вывода
hRet := FDDCiipper.SetHWnd(0, Handle);
if Failed (hRet) then ErrorOut(hRet, 'SetHWnd FAILED');
// Устанавливаем объект отсечения для первичной поверхности
hRet := FDDSPrimary.SetClipper(FDDClipper);
if Failed (hRet) then ErrorOut(hRet, 'SetClipper FAILED^);

Можете для проверки работы отсечения удалить в коде строку с вызовом offsetRect, чтобы принудить приложение воспроизводить за границами своей клиентской области. Картинка окажется искаженной, но приложение уже не испортит вид рабочего стола.
Одно небольшое замечание. В программах DirectX SDK можно обнаружить, что объект отсечения не удаляется по окончании работы, я же делаю это в моих примерах намеренно. Легко проверить это: объект, связанный с отсечением, имеет по окончании работы значение, отличное от nil, а в таком случае лучше будет явным образом освобождать память, занятую им. Также иногда можно встретить, что эта переменная присваивается nil сразу после присоединения к первичной поверхности.
Важно подчеркнуть, что при использовании отсечения нельзя применять для вывода на первичную поверхность метод BitFast.
Следующий пример, проект каталога Ех27, продолжает тему оконных приложений, отличается он от предыдущего пользовательским курсором (рис. 3.13).







Рис. 3.13. Фрагмент работы примера с использованием буферизации в оконном приложении

Буферизацию приходится организовывать самостоятельно, для экономии памяти вспомогательная поверхность создается при каждом изменении размеров окна:

procedure TfrmDD.FormResize(Sender: TObject);
var
hRet : HRESULT;
ddsd : TDDSurfaceDesc2;
begin
if Assigned(FDDSBack) then FDDSBack := nil;
ZeroMemory(@ddsd, SizeOf(ddsd));
with ddsd do begin
dwSize := SizeOf(ddsd);
dwFlags := DDSD_CAPS or DDSDJiEIGHT or DDSD_WIDTH;
ddsCaps.dwCaps := DDSCAPS_OFFSCREENPLAIN;
dwWidth := ClientWidth; // Размеры совпадают с текущими размерами
dwHeight := ClientHeight; // окна
end;
hRet := FDD.CreateSurface(ddsd, FDDSBack, nil);
if Failed(hRet) then ErrorOut(hRet, 'Create Back Surface');
FormPaint (nil);
end;

Обратите внимание, что в этом примере пользовательским курсором можно указать на любую точку клиентской области окна. Для отсечения нужной части поверхности образа (ее размер 32x32 пиксела) объявлена переменная rcMouse типа TRECT. При перемещении курсора вблизи границы окна оставляем для воспроизведения только часть образа:

procedure TfrmDD.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var
wrkl, wrkJ : Integer;
begin
mouseX := X;
if X < ClientWidth - 32
then wrkl := 32 // По Х помещается весь растр
else wrkl := ClientWidth - X; // Воспроизводить только часть образа
mouseY := Y;
if Y < ClientHeight - 32
then wrkJ := 32 // По Y помещается весь растр
else wrkJ := ClientHeight - Y; // Воспроизводить только часть образа
SetRect (rcMouse, 0, 0, wrkl, wrkJ); // Итоговый прямоугольник образа
FormPaint (nil); // Принудительно перерисовываем окно
end;

При перерисовке окна метод BitFast приходится использовать только для вывода растрового изображения курсора:

procedure TfrmDD.FormPaint(Sender: TObject);
var
rcDest, wrkRect : TRECT;
p : TPOINT;
begin
p.X := 0;
p.Y := 0;
Windows.ClientToScreen(Handle, p);
Windows.GetClientRect(Handle, rcDest);
OffsetRect(rcDest, p.X, p.Y);
SetRect (wrkRect, 0, 0, ClientWidth, ClientHeight);
//На вспомогательную поверхность помещаем растровое изображение фона
if Failed (FDDSBack.Blt (SwrkRect, FDDSBackGround, nil,
DDBLT^WAIT, nil}) then if Failed (RestoreAll) then Exit;
// Поверх фона размещаем растровое изображение курсора
if Failed (FDDSBack.BltFast (mouseX, mouseY, FDDSImage, @rcMouse,
DDBLTFAST_WAIT or DDBLTFAST_SRCCOLORKEY))
then if Failed (RestoreAll) then Exit;
// Копируем содержимое вспомогательной поверхности на первичную
if Failed (FDDSPrimary.Blt (@rcDest, FDDSBack, nil, DDBLT__WAIT, nil))
then if Failed (RestoreAll) then Exit;
end;



Для отключения отображения курсора в этом примере прибегнем к альтернативному способу: воспользуемся процедурой showcursor. В начале работы вызовем ее с аргументом False. Однако с курсором осталась связанной одна проблема, возникающая при нахождении его в области заголовка и в пределах рамки окна, когда пользовательский курсор мы отобразить уже не можем, а системный отключен. Полного решения данной проблемы достичь нелегко, если включать курсор в ловушке сообщения WM_NCMOUSEMOVE, возникающего при нахождении курсора за пределами клиентской части окна, то результат может получиться неустойчивым, придется все равно отслеживать возвращение курсора в окно.
Самое простое решение - управлять видимостью курсора в обработчике OnMouseMove, включать его при нахождении курсора вблизи границ окна. Но для хорошего функционирования алгоритма надо либо беспрерывно перерисовывать окно, либо добиться более высокой скорости работы с мышью.
Вскользь я уже говорил, что для поверхностей можно принудительно устанавливать одинаковый формат пиксела. Посмотрим на примере проекта каталога Ех28, как это сделать. Здесь введена переменная Pixel Format типа TDDPixelFormat; после создания первичной поверхности заносим ее формат в данную переменную:

ZeroMemory(@PixelFormat, SizeOf(PixelFormat));
PixelFormat.dwSize := SizeOf(PixelFormat);
// Получаем формат пиксела
hRet := FDDSPrimary.GetPixelFormat(PixelFormat);
if Failed (hRet) then ErrorOut(hRet, 'GetPixelFormat');

При создании вспомогательной поверхности явно устанавливаем ее формат пиксела:

ZeroMemory(@ddsd, SizeOf(ddsd));
with ddsd do begin
dwSize := SizeOf(ddsd);
// Добавился новый флаг
dwFlags := DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH or DDSD_PIXELFORMAT;
ddsCaps.dwCaps := DDSCAPS_OFFSCREENPLAIN;
ddpfPixelFormat := PixelFormat; // Устанавливаем формат поверхности
dwWidth := ClientWidth;
dwHeight := ClientHeight;
end;

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


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