Михаил Краснов - Графика DirectX в Delphi
// Завершение работы. Выполняем переключение поверхностей
Result := g_pDisplay.Present;
Выглядит код немного непривычно, но радует своей лаконичностью. Надеюсь, вы сейчас испытываете светлое чувство ясности понимания того, что скрыто за этой краткостью кода.
Самостоятельно разберите, как выглядит код восстановления потерянных поверхностей.
Вам не стоит обижаться на меня, что мы не начали сразу же писать подобный код, потому что, напоминаю, нам все равно не удастся уберечься от углубления в дебри, стоит только попробовать решить мало-мальски сложные задачи.
Вот первый пример такой задачи, проект каталога Ех02 - развитие предыдущего: те же мечущиеся логотипы, но желтоватые кресты их со временем меняют цвет. Пример также является моим переложением учебной программы из SDK.
Используется палитровый режим, поэтому добавилась переменная знакомого нам типа IDIRECTDRAWPALETTE. Для загрузки ее задействованы соответствующие методы главного объекта:
// Загружаем палитру из растра
hr := g_pDisplay.CreatePaletteFromBitmap(g_pDDPal, imageBmp);
if FAILED(hr) then ErrorOut (hr, 'CreatePaletteFromBitmap');
// Задаем палитру для экрана
hr := g_pDisplay.SetPalette(g_pDDPal);
if FAILED(hr) then ErrorOut (hr, 'SetPalette');
Для смены оттенков, образующих палитры, используются все знакомые нам действия: сохранение составляющих во вспомогательном массиве и циклическое изменение цветовых весов некоторых из них. В примере циклически модифицируются элементы палитры, отвечающие за оттенки желтого цвета. Все эти действия никак с появлением нового подхода не упростились, и этот код заметно выделяется среди прочего.
Обратите внимание, что разработчики рекомендуют синхронизировать обновление палитры с вертикальной разверткой экрана, чем я пренебрег в своем первом примере на тему цветовой анимации. Действие это аналогично использованию флага WAIT при блиттинге и запирании поверхности, но записывается вот так:
hr:=g_pDisplay.GetDirectDraw.WaitForVerticalBlank(DDWAITVB_BLOCKBEGIN,0);
if(FAILED(hr)) then ErrorOut (hr, 'WaitForVerticalBlank');
Результат будет равен константе E_NOTIMPL, если такая синхронизация аппаратно не поддерживается. Карты с отсутствием данной поддержки сейчас редко встречаются, но вот аппаратная поддержка следующего рассматриваемого нами приема на "устаревших" картах может и отсутствовать.
Гамма-контроль используется для обеспечения цветовых переходов в непалитровых режимах и управляет яркостью изображения. Примером создания fade-эффекта в 32-битном режиме является проект каталога Ех03. Здесь появилась переменная специального типа, связанного с гамма-контролем:
g_pGarnmaControl: IDIRECTDRAWGAMMACONTROL;
Поскольку не каждая видеокарта поддерживает эту возможность, необходимо определиться, возможна ли корректная работа приложения:
function TfrmDD.HasGammaSupport : BOOL;
var
ddcaps : TDDCAPS; // Структура описания возможностей драйвера
begin
ZeroMemory(@ddcaps, sizeof(ddcaps));
ddcaps.dwSize := sizeof(ddcaps);
// Получаем список возможностей
g_pDisplay.GetDirectDraw.GetCaps(@ddcaps, nil);
// Поддерживается ли гамма-контроль аппаратно?
if(ddcaps.dwCaps2 and DDCAPS2_PRIMARYGAMMA) <> 0
then Result := TRUE
else Result := FALSE;
end;
В этом примере при отсутствии аппаратной поддержки приложение завершает работу. В принципе этого можно не делать. Не должно возникать исключений в работе приложений при отсутствии аппаратной поддержки такой возможности. Просто на экране по ходу работы не будет заметно никаких изменений.
В примере на экране рисуются три красивые полосы, образованные плавным переходом черного цвета в каждый из тройки чистых цветов. Как это осуществляется, разберите самостоятельно. Чтобы полосы равномерно заполняли экран при любых установках, необходимо получить данные о формате пиксела, для чего предназначен метод поверхности (объекта типа
CSurface) GetBitMasklnfo.
Проект, располагающийся в каталоге Ех04, отличается от предыдущего тем, что вместо полос на экран выводится система мечущихся логотипов.
Для задания текущей яркости служит целочисленная переменная:
g_lGammaRamp : Longlnt = 256;
Работа по осуществлению гамма-контроля очень похожа на работу с палитрой:
function TfrmDD.UpdateGammaRamp : HRESULT;
var
hr : HRESULT;
ddgr : TDDGAMMARAMP; // Набор значений яркости чистого цвета dwGamma : WORD; iColor : Integer;
begin
ZeroMemory(@ddgr, sizeof (ddgr));
// Получаем текущие значения яркостей
hr := g_pGammaControl.GetGanimaRamp(0, ddgr);
if(FAILED(hr)) then begin
Result := hr;
Exit
end;
dwGamma := 0;
// Последовательно наращиваем яркость цветовых составляющих
for iColor := 0 to 255 do begin
ddgr.red[iColor] := dwGamma;
ddgr.green[iColor] := dwGamma;
ddgr.blue[iColor] := dwGamma;
dwGamma := dwGamma + g_lGammaRamp;
end;
// Устанавливаем текущую "палитру"
hr := g_pGainmaControl. SetGammaRamp (0, ddgr) ;
if(FAILED(hr)) then begin
Result := hr;
Exit
end;
Result := S_OK;
end;
Привожу еще один вариант использования модуля DDuti8 (проект каталога Ех05) - иллюстрацию непосредственной работы с пикселами поверхности. Здесь таким способом подготавливаются поверхности спрайтов. Пример рассчитан на работу в 16-битном режиме и использует указатели PWORD. Принципиально ничего нового в коде не появилось. Библиотека DDUtil8 ничего не изменила в этой части, поэтому не стану подробно разбирать код. Только обращаю ваше внимание на то, что этот пример, подобно предыдущему, корректно работает с любым форматом пиксела, поскольку опирается на присутствующие битовые маски.
Надеюсь, такого беглого знакомства с библиотекой DDUtil8 для вас оказалось достаточным для того, чтобы получить представление о ней.
Вернемся к обычному для этой книги подходу, лишенному объектной ориентированности. Рассмотрим следующий пример - проект, располагающийся в каталоге Ех06. Пример можно отнести к разряду классических, он является моей интерпретацией программы stretch.cpp из DirectX 6.0 SDK. Это оконное приложение, на экране выводится образ вращающегося в пространстве тора (рис. 4.1).
Мультфильм намеренно подготовлен таким образом, чтобы создать у зрителя иллюзию трехмерной графики. На самом деле последовательно выводятся отдельные кадры с изображением различных фаз поворота тора.
Размер отдельного кадра 64x64 пиксела. Все кадры записаны в одно растровое изображение donut.bmp в шесть рядов по десять кадров. Растр загружается на поверхность FDDSImage. При перерисовке экрана на первичную поверхность выводится прямоугольник очередной фазы поворота тора:
function TfrmDD.UpdateFrame : HRESULT;
var
rcRect : TRECT;
begin
Inc (Frames);
ThisTickCount := GetTickCount;
if ThisTickCount - LastTickCount > TimeDelay then begin
FPS : = PChar ('FPS = ' + Format('%6.2f,
[Frames * 1000 / (ThisTickCount - LastTickCount)])); Caption := FPS; Frames := 0;
// Наращиваем текущий кадр; всего 60 кадров
CurrentFrame := (CurrentFrame + 1) mod 61;
// Прямоугольник очередного кадра; "shl 6" равносильно " * 64" SetRect (rcRect,
(CurrentFrame mod 10) shl 6,
(CurrentFrame div 10) shl 6,
(CurrentFrame mod 10 + 1) shl 6,
(CurrentFrame div 10 + 1) shl 6);
LastTickCount := GetTickCount;
end;
// Вывод кадра на первичную поверхность
Result := FDDSPrimary.Blt(OrcDest, FDDSImage, @rcRect,
DDBLT_WAIT, nil) ;
end;
Принципиально этот пример отличается от примеров предыдущей главы только тем, что при каждой перерисовке кадра выводится не вся вторичная поверхность, а только ее фрагмент.
Для оптимизации код, связанный с определением позиции окна вывода на экране, я перенес в обработчик onResize формы. Для корректного поведения при перемещении окна этот же код вызывается в ловушке соответствующего сообщения WM_MOVE:
procedure TfrmDD.WindowMove (var Msg: TWMMove); // Перемещение окна
begin
FormResize (nil); // Определение нового положения окна
end;
procedure TfrmDD.FormResize(Sender: TObject);
var
p : TPoint;
begin
p.X := 0;
p.Y := 0;
Windows.ClientToScreen(Handle, p);
Windows.GetClientRect(Handle, rcDest);
OffsetRect(rcDest, p.X, p.Y);
end;
Вдогонку рассмотренному примеру привожу проект каталога Ех07, идею которого я также позаимствовал из набора примеров SDK. В данном примере эмулируется наложение выводимого тора с содержимым рабочего стола, наподобие одной из программ предыдущей главы. Пример не очень хорош и предложен скорее "для массовости". Здесь содержимое экрана копируется только один раз, при запуске приложения. Поэтому при изменении подлинного фона тора возникает ощущение некорректности работы приложения. Если же копировать подложку при каждом обновлении фазы поворота тора, то вместе с фоном копируется изображение самого тора, оставшееся с предыдущего вывода. Попробуйте развить этот пример. Из него может получиться занятная программа, если изменения во всех кадрах будут находиться только в пределах первоначального силуэта.
Хранитель экрана
В этом разделе мы начнем работу по созданию оригинального хранителя экрана, попробуем реализовать наши полученные знания в проекте, имеющем не только познавательную ценность. Также мы решим для себя вопрос, возможно ли в принципе создать на Delphi приложение, использующее DirectDraw со множеством образов, выполняющееся с удовлетворительной скоростью.
Работа нашего хранителя экрана будет заключаться в том, что по экрану станут проплывать рыбки и полупрозрачные пузырьки воздуха (рис. 4.2).