Михаил Краснов - Графика DirectX в Delphi
Вспомогательная поверхность создана и заполнена растром размером 256x256 пикселов. Среди аргументов операции блиттинга присутствуют структуры типа TRECT, задающие местоположение в принимающей поверхности и копируемую область. Поэтому код обработчика перерисовки окна дополнился переменными dstRect и srcRect типа TRECT. Заполняем их поля с помощью API-функции setRect:
SetRect (dstRect, 100, 100, 356, 356); // Для принимающей поверхности
SetRect (srcRect, 0, 0, 256, 256); // Для источника
Теоретически эта операция также может привести к провалу. Для важных приложений рекомендую здесь анализировать возвращаемое булево значение. К тому же соглашусь, что в данном примере оптимальным решением было бы использование глобальных переменных, заполняемых один раз, а не при каждой перерисовке окна. Просто код в таком виде удобнее читать, а перерисовка не станет производиться интенсивно.
Канву для вывода растра не используем, делаем теперь все традиционным для DirectDraw способом:
while True do begin // Возможно, придется производить неоднократно
hRet := FDDSPrimary.Blt (SdstRect, FDDSImage, @srcRect, DDBLT_WAIT,
nil); // Собственно блиттинг
if hRet = DDERR_SURFACELOST then begin // Поверхность потеряна
if Failed (RestoreAll) then Exit; // Пытаемся восстановить
end else Break; // Или все прошло успешно, или неустранимая ошибка
end;
Потеря любой поверхности является верным признаком того, что надо восстанавливать все поверхности. Поэтому каждый раз в случае ошибки обращаемся к пользовательской функции RestoreAll:
function TfrmDD.RestoreAll : HRESULT; begin
Result := DD_FALSE; // Определяемся с результатом // Пытаемся восстановить первичную поверхность
if Succeeded (FDDSPrimary._Restore) then begin
// Пытаемся восстановить вторичную поверхность
if Failed (FDDSImage._Restore) then Exit;
Result := DD_OK; // Все прошло успешно
end;
end;
Нажав комбинацию клавиш <Alt>+<Tab>, переключитесь с этого приложения, а затем верните ему фокус. Если восстановление поверхностей прошло успешно, вы увидите картинку с пейзажем. Но если это получилось с вашей картой, совсем не обязательно, что это произойдет и с другими. На иных компьютерах пользователи в такой ситуации могут получить бессмысленный узор. Согласно рекомендациям разработчиков, поверхности, содержащие растр, при восстановлении должны заново заполняться.
Если это окно из рассматриваемого примера у вас восстанавливается без потерь, можете двигаться дальше. Если же у вас картинка при восстановлении портится, функцию восстановления исправьте следующим образом:
function TfrmDD.RestoreAll : HRESULT; var
hRet : HRESULT; begin
hRet := FDDSPrimary._Restore;
if Succeeded (hRet) then begin hRet := FDDSImage._Restore;
if Failed (hRet} then begin Result := hRet;
Exit;
end;
// Перезагружаем на поверхность содержимое растра Result := DDReLoadBitmap(FDDSImage, imageBMP);
end else Result := hRet;
end;
Теперь мы можем узнать смысл первых трех аргументов метода Bit поверхности. Первый из них - указатель на структуру типа TRECT, задающую местоположение и размер области, в которую происходит копирование. Второй параметр - поверхность источника. Третий аргумент - указатель на структуру типа TRECT, задающую местоположение и размер области, из которой происходит копирование.
Флагом задаем константу DDBLT_WAIT, не комбинацию значений. Дополнительные параметры пока не указываем, поэтому последний аргумент метода устанавливаем в nil.
Пример простой, но очень важный. Осмыслим изученное. Естественным для DirectDraw способом воспроизведения является блиттинг. На вспомогательных поверхностях размещаем нужные нам образы, а в определенный момент времени копируем требуемые области с одной поверхности на другую, в простейшем случае - со вспомогательных поверхностей на первичную, связанную с экраном.
Вторичных поверхностей создают столько, сколько требуется приложению. Разработчик сам решает, что и где ему располагать, но здесь надо учесть небольшую тонкость: если видеокарта имеет малый размер памяти, то вторичную поверхность не получится создать размером больше первичной. Может быть, это происходит только с конкретными картами, но я действительно встречался с такой ситуацией.
Поверхностей вы можете создавать сколько угодно, но чем их меньше, тем быстрее будет осуществляться блиттинг. Поэтому лучше не плодить множество поверхностей, а, в идеальном случае, располагать все образы на одной большой поверхности.
В общем случае операция копирования блоков из системной памяти в видеопамять осуществляется медленнее, чем из видеопамяти в видеопамять. Поэтому образы, выводимые наиболее часто, старайтесь размешать в видеопамяти, а при ее нехватке те из образов, которые редко будут появляться, например заставки или меню, следует размещать в системной памяти.
Поддержка AGP если и стирает разницу в скоростях обмена, то незначительно. Если бы скорости работы с видеопамятью и системной памятью сосовпадали или были близки друг к другу, не было бы нужды производителям карт выпускать модели, различающиеся размером видеопамяти, а пользователям оставалось лишь наращивать размер системной памяти.
Для поверхности, создаваемой в видеопамяти, надо использовать комбинацию флагов DDSCAPS_OFFSCREENPLAIN or DDSCAPSJ/IDEOMEMORY. и наоборот, флаг DDSCAPS_SYSTEMMEMORY указывает, что поверхность должна располагаться в системной памяти.
Метод GetAvailablevidMem главного объекта DirectDraw позволяет выяснить, сколько видеопамяти осталось в распоряжении приложения.
При нехватке видеопамяти операция создания поверхности завершится провалом, код соответствующей ошибки - DDERR_OUTOFVIDEOMEMORY. В этом случае необходимо поменять флаг и заново попытаться создать поверхность.
Теперь обсудим вопросы масштабирования растров. Вспомогательная функция копирования растра поддерживает масштабирование. Задайте размер вторичной поверхности больше, чем размер используемого растра, например, так:
ddsd.dwWidth := wrkBitmap.Width * 2;
Теперь при воспроизведении мы увидим картинку, растянутую вдоль экрана, но не всю, а только ее половину. Для того чтобы вывести ее целиком, надо изменить значение поля Right структуры dstRect:
SetRect (dstRect, 100, 100, 100 + 256 * 2, 356);
Попробуем перемещать картинку по экрану (проект каталога Ех08). Переменная ift хранит текущее значение смещения картинки, на это значение опираемся при заполнении полей структуры dstRect:
SetRect (dstRect, 1ft, 100, 1ft + 256, 356);
Форма дополнилась обработчиком нажатия клавиши: демонстрационные программы, использующие DirectDraw, традиционно должны завершать работу при нажатии клавиши <Esc> или <F12>. Добавилась также обработка нажатий клавиш управления курсором:
case Key of
VK_ESCAPE, VK_F12 : begin // Традиция для DirectDraw
Close; Exit;
end;
VK_LEFT : begin // Клавиша "стрелка влево"
Dec (1ft, 1); // Уменьшаем 1ft
FormPaint (nil); // Перерисовываем экран end;
VK_RIGHT : begin // Клавиша "стрелка вправо"
Inc (1ft, 1); // Увеличиваем 1ft
FormPaint (nil); // Перерисовываем экран
end;
end;
Обратите внимание, что для перерисовки окна метод Refresh не годится, иначе сквозь экран будет проглядывать мелькнувшее окно приложения. Картинка движется с малым шагом с целю убедить вас, что если хоть один пиксел растра не помещается на первичную поверхность, не воспроизводится ничего.
Также наверняка вам бросится в глаза то, что картинка появляется медленно на экране, при ее воспроизведении вы можете увидеть мельтешение черных полос. Пока старайтесь не обращать на это внимания. В будущем мы устраним это - такого не должно быть в наших серьезных проектах.
Сейчас сделайте следующее. Запишите строку, задающую параметры области вывода так:
SetRect (dstRect, 1ft, 100, 1ft + 512, 356);
Картинка выводится растянутой, из чего делаем важный вывод: метод Bit поверхности поддерживает операцию масштабирования. Удобное для нас свойство, им можно пользоваться, чтобы задавать в качестве фона растровое изображение любого размера. Для этого измените ту же строку вот так:
SetRect (dstRect, 0, 0, ClientWidth, ClientHeight);
Теперь код, заполняющий первичную поверхность черным цветом, можно просто удалить. Он не нужен, его выполнение только отнимает драгоценное время.
Плохо в получающемся примере то, что размер растра используется в нем дважды: при задании размеров первичной поверхности и при задании области вывода. Здесь нас ждет хорошая новость: во втором случае можно ничего не указывать, по умолчанию будет использоваться вся поверхность, и строку блиттинга можно записать так:
hRet := FDDSPrimary.Blt (SdstRect, FDDSImage, nil, DDBLT_WAIT, nil);
To есть третий параметр равен nil. Обязательно проверьте это, все должно работать как следует.
С точки зрения оптимизации лучше явно задавать размер копируемой поверхности.
Протестируйте работу программы при переключении и восстановлении и, если картинка пейзажа теряется, скорректируйте код функции RestoreAli.
Я воспользуюсь случаем, чтобы посвятить вас в еще одну важную тему: в любой момент времени мы можем получить информацию обо всех свойствах поверхности, в том числе и о ее размерах. Для этого предназначен метод поверхности GetSurfaceDesc.