Книга: Графика для Windows средствами DirectDraw
Создание поверхностей
Разделы на этой странице:
Создание поверхностей
Остается лишь создать поверхности, используемые в приложении. После вызова SetDisplayMode() функция ActivateDisplayMode() вызывает еще три функции: CreateFlippingSurfaces(), StorePixelFormatData() и CreateCustomSurfaces(). Функция CreateFlippingSurfaces() создает первичную поверхность с возможностью переключения страниц. Функция StorePixelFormatData() используется для чтения и записи сведений о формате пикселей в данном видеорежиме. Эта информация может пригодиться при работе с видеорежимами High и True Color. Функция CreateCustomSurfaces() отвечает за создание и инициализацию вспомогательных поверхностей, специфических для данного приложения. Начнем с функции CreateFlippingSurfaces():
BOOL DirectDrawWin::CreateFlippingSurfaces() {
if (primsurf) primsurf->Release(), primsurf=0;
DDSURFACEDESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.dwSize = sizeof(desc);
desc.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX;
desc.dwBackBufferCount = 1;
HRESULT r=ddraw2->CreateSurface(&desc, &primsurf, 0);
if (r!=DD_OK) return FALSE;
DDSCAPS surfcaps;
surfcaps.dwCaps = DDSCAPS_BACKBUFFER;
r=primsurf->GetAttachedSurface(&surfcaps, &backsurf);
if (r!=DD_OK) return FALSE;
return TRUE;
}
Функция CreateFlippingSurfaces() вызывается при каждой инициализации нового видеорежима, поэтому ее работа начинается с освобождения ранее созданных поверхностей функцией Release(). Затем она объявляет и инициализирует экземпляр структуры DDSURFACEDESC. Эта структура описывает тип создаваемой поверхности. В соответствии с требованиями DirectDraw необходимо установить флаги для всех инициализируемых полей. В нашем случае флаги DDSD_CAPS и DDSD_BACKBUFFERCOUNT говорят о том, что мы задаем возможности поверхности (поле dwCaps) и количество вторичных буферов (поле dwBackCount). В поле dwCaps устанавливаются три флага:
• DDSCAPS_PRIMARYSURFACE
• DDSCAPS_FLIP
• DDSCAPS_COMPLEX
Флаг DDSCAPS_PRIMARYSURFACE означает, что создаваемая поверхность должна находиться в видеопамяти, а ее размеры определяются в соответствии с текущим видеорежимом. Поскольку размеры первичной поверхности зависят от видеорежима, она должна создаваться после его активизации.
Флаг DDSCAPS_FLIP сообщает DirectDraw о том, что мы собираемся выполнять переключение страниц. Переключаемые поверхности должны иметь хотя бы один вторичный буфер, так что по этому флагу DirectDraw узнает о необходимости создания вторичных буферов.
Флаг DDSCAPS_COMPLEX используется всегда, когда происходит присоединение поверхностей. В нашем случае первичная поверхность должна быть присоединена к поверхности вторичного буфера. Затем мы присваиваем полю dwBackBufferCount значение 1, показывая, что к создаваемой первичной поверхности должен быть присоединен один вторичный буфер.
Новая поверхность создается вызовом функции CreateSurface() интерфейса DirectDraw. Первым аргументом является указатель на структуру desc, а вторым - указатель на переменную DirectDrawWin::primsurf. Эта переменная объявлена защищенной (protected), поэтому мы можем использовать ее для доступа к первичной поверхности в своих программах. Третий аргумент функции CreateSurface() должен быть равен 0.
Вызов CreateSurface() создает две поверхности: первичную поверхность и вторичный буфер. Позднее указатель на вторичный буфер понадобится нам для подготовки кадров. Чтобы получить этот указатель, следует вызвать функцию GetAttachedSurface() интерфейса DirectDrawSurface и передать ей структуру DDSCAPS с описанием типа интересующей нас присоединенной поверхности. Задавая флаг DDSCAPS_BACKBUFFER, мы вызываем функцию GetAttachedSurface(), которая инициализирует переменную backsurf. Она, как и переменная primsurf, объявлена защищенной, поэтому классы, производные от DirectDrawWin, могут легко обратиться к вторичному буферу.
После того как указатели primsurf и backsurf будут инициализированы, ActivateDisplayMode() вызывает функцию StorePixelFormatData(). Эта функция с помощью функции GetPixelFormat() интерфейса DirectDrawSurface получает информацию о формате хранения цветовых RGB-составляющих для отдельных пикселей. Формат пикселей зависит от видеокарты, а иногда даже от видеорежима, так что эти сведения оказываются полезными при прямых манипуляциях с поверхностями. Функция StorePixelFormatdata() выглядит так:
BOOL DirectDrawWin::StorePixelFormatData() {
DDPIXELFORMAT format;
ZeroMemory(&format, sizeof(format));
format.dwSize=sizeof(format);
if (backsurf->GetPixelFormat(&format)!=DD_OK) {
return FALSE;
}
loREDbit = LowBitPos(format.dwRBitMask);
WORD hiREDbit = HighBitPos(format.dwRBitMask);
numREDbits=(WORD)(hiREDbit-loREDbit+1);
loGREENbit = LowBitPos(format.dwGBitMask);
WORD hiGREENbit = HighBitPos(format.dwGBitMask);
numGREENbits=(WORD)(hiGREENbit-loGREENbit+1);
loBLUEbit = LowBitPos(format.dwBBitMask);
WORD hiBLUEbit = HighBitPos(format.dwBBitMask);
numBLUEbits=(WORD)(hiBLUEbit-loBLUEbit+1);
return TRUE;
}
Структура DDPIXELFORMAT используется в функции GetPixelFormat() для получения масок, показывающих, какие биты в каждом пикселе заняты красной, зеленой и синей цветовыми составляющими. Маски точно описывают формат пикселя, но на практике работать с ними оказывается не очень удобно. Вместо того чтобы просто сохранить полученные маски, мы на основании каждой из них инициализируем два целых числа. Первое число равно позиции младшего бита цветовой составляющей, а второе — количеству бит, необходимых для ее представления. Для поверхностей True color (24- и 32-битных) цветовые составляющие всегда представляются 8 битами, но для поверхностей High color (16-битных) это число изменяется (обычно 5, но иногда 6 для зеленой составляющей).
Класс DirectDrawWin содержит шесть переменных для описания формата пикселей: loREDbit, numREDbits, loGREENbit, numGREENbits, loBLUEbit и numBLUEbits. Они используются некоторыми функциями DirectDrawWin, однако эти переменные объявлены как защищенные (protected), поэтому к ним можно обращаться и из классов, производных от DirectDrawWin. Эти переменные будут рассмотрены в главе 5.
На этом инициализация приложения подходит к концу. Функция ActivateDisplayMode() вызывает еще одну функцию, CreateCustomSurfaces(), которая создает вспомогательные поверхности, но к этому моменту инициализация DirectDraw уже завершена. Функция CreateCustomSurfaces() будет рассмотрена в следующем разделе.
Но сначала давайте подведем итоги. Приложение состоит из двух объектов, BounceWin и BounceApp. Объект BounceApp отвечает за создание объекта BounceWin, а BounceWin в свою очередь инициализирует DirectDraw. Сначала он обнаруживает все имеющиеся драйверы DirectDraw, выбирает один из них и использует его для создания экземпляра интерфейса DirectDraw2. Затем он обнаруживает видеорежимы, поддерживаемые инициализированным драйвером, выбирает один из режимов и активизирует его. Далее создается первичная поверхность с возможностью переключения страниц (и вторичным буфером) и, наконец, анализируется формат пикселей для активизированного видеорежима.
Приложение почти готово к работе, но пока у него нет графических данных. Мы подходим к следующему этапу.
Подготовка поверхностей
Последняя функция, вызываемая в ActivateDisplayMode() (см. листинг 3.2), — CreateCustomSurfaces(). Эта функция является чисто виртуальной, поэтому классы, производные от DirectDrawWin, должны реализовать ее. Функция CreateCustomSurfaces() создает вспомогательные поверхности, а также инициализирует переменные и структуры данных. В классе BounceWin эта функция выглядит так:
BOOL BounceWin::CreateCustomSurfaces() {
CString filename;
if (GetCurDisplayDepth()==8) filename="tri08.bmp";
else filename="tri24.bmp";
if (surf1) surf1->Release(), surf1=0;
surf1=CreateSurface(filename, TRUE);
if (surf1==0) {
FatalError("failed to load BMP");
return FALSE;
}
return TRUE;
}
В приложении Bounce используется одна вспомогательная поверхность, содержимое которой определяется одним из двух BMP-файлов: 8- или 24-битным. Функция использует 8-битный файл, если текущий видеорежим является 8-битным, и 24-битный файл — в противном случае. В принципе один и тот же BMP-файл можно использовать в обоих сценариях, но это неразумно. Поскольку 8-битные файлы могут состоять лишь из 256 цветов, не стоит использовать их в режимах High и True Color. В свою очередь 24-битные изображения не пользуются палитрами и могут содержать до 16 миллионов цветов. Генерация 256-цветной палитры для такого изображения становится нетривиальной задачей. Функция CreateCustomSurfaces() определяет глубину пикселей текущего видеорежима с помощью функции DirectDrawWin::GetCurDisplayDepth(). На основании полученного результата выбирается имя BMP-файла.
Затем мы проверяем указатель surf1. Если его значение отлично от нуля, поверхность освобождается, а указатель обнуляется. Это происходит из-за того, что функция CreateCustomSurfaces() может вызываться неоднократно. Функция ActivateDisplayMode() вызывает ее при активизации видеорежима, поэтому приложение, которое за время работы меняет несколько видеорежимов, несколько раз вызовет CreateCustomSurfaces(). Если поверхность создавалась ранее, приведенный код освобождает ее.
Затем мы вызываем функцию CreateSurface(), чтобы создать поверхность и загрузить в нее содержимое BMP-файла. Функция CreateSurface() есть в интерфейсе DirectDrawSurface, но в данном случае используется версия из класса DirectDrawWin. Функция CreateSurface() загружает BMP-файл и создает поверхность, размеры и содержимое которой определяются изображением, хранящимся в заданном BMP-файле. CreateSurface() возвращает указатель на созданную поверхность, если все прошло нормально, и ноль — в противном случае.
Обратите внимание на то, что функция CreateSurface() получает два аргумента. Первый из них представляет собой имя загружаемого BMP-файла. Второй аргумент показывает, нужно ли устанавливать палитру BMP-файла. Для 24-битных BMP-файлов этот аргумент игнорируется.
Функции для работы с поверхностями
Наше приложение Bounce является очень простым, поэтому функция CreateCustom Surfaces() делает не так уж много. Реальное приложение может создавать десятки и даже сотни поверхностей. Класс DirectDrawWin содержит несколько служебных функций, которые могут пригодиться при работе с поверхностями, поэтому мы ненадолго отвлечемся от приложения Bounce и рассмотрим эти функции:
LPDIRECTDRAWSURFACE CreateSurface(LPCTSTR filename, BOOL installpalette=FALSE);
LPDIRECTDRAWSURFACE CreateSurface(DWORD w, DWORD h);
BOOL LoadSurface(LPDIRECTDRAWSURFACE surf, LPCTSTR filename);
BOOL ClearSurface(LPDIRECTDRAWSURFACE surf, DWORD clr, RECT* rect=0);
BOOL ClearSurface(LPDIRECTDRAWSURFACE surf, DWORD r, DWORD g, DWORD b, RECT* rect=0);
BOOL GetSurfaceDimensions(LPDIRECTDRAWSURFACE surf, DWORD& w, DWORD& h);
Первая функция нам уже знакома. Функция CreateSurface(), получая имя BMP-файла, создает новую поверхность на основании его содержимого. Кроме того, эта функция может извлекать палитру из 8-битных файлов и назначать ее поверхности. Реализация этой функции подробно рассматривается в главе 5.
Вторая функция — CreateSurface() — создает поверхность заданных размеров. Эта функция полезна в тех случаях, когда вам нужна новая поверхность, содержимое которой не связано с BMP-файлом. Данная версия CreateSurface() реализована так:
LPDIRECTDRAWSURFACE DirectDrawWin::CreateSurface(DWORD w, DWORD h) {
DWORD bytes=w*h*(displaydepth/8);
DDSURFACEDESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.dwSize = sizeof(desc);
desc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS;
desc.dwWidth = w;
desc.dwHeight = h;
desc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY;
LPDIRECTDRAWSURFACE surf;
HRESULT r=ddraw2->CreateSurface(&desc, &surf, 0);
if (r==DD_OK) {
TRACE("CreateSurface(%d,%d) created in video memory n", w, h);
return surf;
}
desc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;
r=ddraw2->CreateSurface(&desc, &surf, 0);
if (r==DD_OK) {
TRACE("CreateSurface(%d,%d) allocated in system memory n", w, h);
return surf;
}
TRACE("CreateSurface(%d,%d) failedn", w, h);
return 0;
}
Функция CreateSurface() с помощью структуры DDSURFACEDESC описывает поверхность, размеры которой равны передаваемым параметрам w и h. Поле dwFlags показывает, какие поля структуры будут инициализироваться. Поля dwWidth и dwHeight определяют размеры поверхности, а поле dwCaps — ее возможности. Обратите внимание на флаг DDSCAPS_VIDEOMEMORY, согласно которому создаваемая поверхность должна находиться в видеопамяти.
Затем мы вызываем функцию DirectDraw CreateSurface(). В качестве первого аргумента передается указатель на структуру с описанием поверхности; если вызов окажется успешным, указатель surf будет ссылаться на созданную поверхность.
При успешном создании поверхности макрос MFC TRACE() выводит отладочное сообщение, а вы получаете указатель surf. Тем не менее раз мы явно указали, что поверхность должна находиться в видеопамяти, при нехватке последней вызов CreateSurface() может закончиться неудачно. В этом случае мы изменяем поле dwCaps, заносим в него флаг DDSCAPS_SYSTEMMEMORY и снова вызываем функцию CreateSurface(). Скорее всего, вторая попытка окажется успешной; если и на этот раз поверхность не будет создана, функция возвратит 0.
Но давайте вернемся к списку функций DirectDrawWin для работы с поверхностями. Функция LoadSurface() загружает содержимое BMP-файла в существующую поверхность. Эта функция будет часто упоминаться, когда речь пойдет о восстановлении потерянных поверхностей. Функция LoadSurface() похожа на первую версию CreateSurface() (с загрузкой BMP-файла).
Функции ClearSurface() могут использоваться для частичного заполнения поверхностей. Первая версия ClearSurface() заполняет поверхность величиной, передаваемой в качестве второго параметра. Необязательный аргумент rect определяет заполняемую прямоугольную область (если он не задан, заполняется вся поверхность). Вторая версия ClearSurface() получает в качестве аргументов RGB-составляющие и на их основании вычисляет значение, присваиваемое каждому пикселю поверхности. Из-за дополнительной работы, затрачиваемой на интерпретацию цветов, вторая версия работает медленнее первой. Первая функция ClearSurface() реализована так:
BOOL DirectDrawWin::ClearSurface(LPDIRECTDRAWSURFACE surf, DWORD clr, RECT* rect) {
if (surf==0) return FALSE;
DDBLTFX bltfx;
ZeroMemory(&bltfx, sizeof(bltfx));
bltfx.dwSize = sizeof(bltfx);
bltfx.dwFillColor = clr;
HRESULT r;
r=surf->Blt(rect, 0, 0, DDBLT_COLORFILL | DDBLT_WAIT, &bltfx);
return r==DD_OK;
}
Функция ClearSurface() получает три аргумента: указатель на заполняемую поверхность; величину, присваиваемую каждому пикселю; и необязательную структуру RECT, которая определяет заполняемую область поверхности.
После проверки указателя мы подготавливаем экземпляр структуры DDBLTFX. Полю dwFillColor присваивается величина, используемая для заполнения, а сама операция осуществляется функцией Blt() интерфейса DirectDrawSurface. Флаг DDBLT_COLORFILL сообщает Blt() о том, что вместо блиттинга выполняется цветовое заполнение.
Получившаяся функция удобна, но ей не хватает универсальности. Дело в том, что величина, применяемая для заполнения поверхности, может иметь различный смысл. Например, для палитровых поверхностей она представляет собой индекс в палитре. Без предварительной проверки палитры невозможно предсказать, какой цвет будет использоваться для заполнения. Аналогичные проблемы возникают и для беспалитровых поверхностей, поскольку конкретное значение пикселя зависит от глубины и формата пикселей. Форматы пикселей особенно часто различаются в режимах High Color, поэтому заполнение поверхности конкретным цветом превращается в нетривиальную задачу.
Вторая версия ClearSurface() получает в качестве аргументов RGB-составляющие и рассчитывает по ним конкретную величину, присваиваемую пикселям поверхности. В таком варианте функция становится более универсальной, но и работает медленнее; быстродействие особенно сильно снижается для палитровых поверхностей, потому что нужный цвет приходится искать в палитре. Код этой функции будет рассмотрен в главе 5.
Нам остается рассмотреть лишь функцию GetSurfaceDimensions(), которая получает указатель на поверхность и возвращает ее ширину и высоту. Код этой функции выглядит так:
BOOL DirectDrawWin::GetSurfaceDimensions(LPDIRECTDRAWSURFACE surf, DWORD& w, DWORD& h) {
if (surf==0) return FALSE;
DDSURFACEDESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.dwSize=sizeof(desc);
desc.dwFlags=DDSD_WIDTH | DDSD_HEIGHT;
if (surf->GetSurfaceDesc(&desc)!=DD_OK) return FALSE;
w=desc.dwWidth;
h=desc.dwHeight;
return TRUE;
}
После проверки указателя мы подготавливаем экземпляр структуры DDSURFACEDESC. Нас интересуют ширина и высота поверхности, поэтому в поле dwFlags заносятся флаги DDSD_WIDTH и DDSD_HEIGHT.
Затем мы вызываем функцию GetSurfaceDesc() интерфейса DirectDrawSurface и передаем ей указатель на структуру с описанием поверхности. Функция GetSurfaceDesc() сохраняет размеры поверхности в полях dwWidth и dwHeight. Они присваиваются переданным по ссылке переменным w и h типа DWORD, после чего функция завершается.
Все рассмотренные функции встречаются в демонстрационных программах этой книги, однако вы вовсе не обязаны пользоваться ими. Впрочем, независимо от этого вам будет полезно познакомиться с их реализацией.
- Создание файлов с блокировкой
- Создание свободно позиционируемых элементов
- 12. Лекция: Создание приложений с графическим интерфейсом пользователя.
- Создание объектов Collection
- 7.4.2.4. Создание своего первого LiveCD
- 4.2. Создание трехмерной модели и построение горизонтальной проекции детали
- 7.4.2. Создание собственного LiveCD
- Создание пользователя и группы на рабочей станции
- Создание корня документа:
- Создание многофайловой базы данных
- Создание форм с несколькими вкладками
- Создание ярлыков в специальных папках