Re: Память под Surface
От: IgorK Россия  
Дата: 02.08.02 08:52
Оценка: 18 (2)
Здравствуйте dronA, Вы писали:

A>Приветствую всех,


A>Вопрос по использованию DirectX не полноэкранный режиме.

A>Есть два PrimySurface BackSurface. Изменеие размера окна должно сооветсвенно отражаться на памяти под эти Surfaces. Как правильно это делать? Какой способ предлагается DirectX ? Длина и ширина поверхности определяется формой окна. Что сообственно поставить в обработчике окна для первой и второй поверхности.

Интересный вопрос. Интересен он тем, что все достаточно просто, но в MSDN описание использования DirectDraw для оконнных не полноэкранных приложений несколько размазанно :). Давай разберем все по полочкам. Итак:

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


LPDIRECTDRAW7 pDD;

DirectDrawCreateEx(NULL, (VOID**)&pDD, IID_IDirectDraw7, NULL);

// устанавливаем режим кооперации
m_pDD->SetCooperativeLevel( hWnd, DDSCL_NORMAL );



В результате мы получаем обьек DirectDraw в неэксклюзивном режиме (DDSCL_NORMAL).
Далее, для того чтобы можно было куда-нибудь рисовать, мы должны создать Primary Surface — основную поверхность, которую непосредственно будет наблюдать пользователь. Тут появляется еще одна особенность, связанная с архитектурой API — создать основную поверхность с размером, отличающимся от текущего разрешения экрана нельзя. Значит, мы должны создать Primary Surface размером текущего разрешения. Создание выглядит так:

LPDIRECTDRAWSURFACE7 pPriSurface; // наша основная повехность
DDSURFACEDESC2 ddsd; // структура, описывающая поверхность

ZeroMemory( &ddsd, sizeof( ddsd ) );
ddsd.dwSize         = sizeof( ddsd );
ddsd.dwFlags        = DDSD_CAPS;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

pDD->CreateSurface(&ddsd, &pPriSurface, NULL);


Здесь видно, что при создании поверхности мы не указываем ее размеры.
Далее — хуже :). Так как поверхность на весь экран, то нам нужно нужно ограничить прорисовку до размеров и положения нашего окна. Делается это в два этапа:

1. Нам нужно создать так называемый клиппер (Clipper). Клипперы в DirectDraw достаточно широкопрофильная весчь. Вкратце — клипперы ограничивают вывод изображения на поверхность, создавая область на поверхности внутри которой данные могут изменяться, а за ее (областью) пределами — нет. Простой пример — у нас есть поверхность размером 200х200, мы хотим, чтобы в результате действий с этой поверхностью изображение изменялось только в области [(0, 0) : (99, 99)], для этого можно создать клиппер в виде прямоугольника 100х100 установленным в позицию (0, 0). Допутим, после этого мы копируем (Blt) на нашу поверхность в точку (50, 50) поверхность размером 100х100 — в результате будет отрисовано только в области [(0, 0) : (99, 99)]. Короче: Clipper — это обрезатель :).
Как нам может он понадобиться: у клиппера есть метод SetHWnd, одним из параметров которого является хендл окна. С помощью вызова этого метода мы привязываем клиппер к окну и, при перемещении или изменении размеров окна клиппер будет сам пересчимтывать свое положение и размеры. Применение клиппера в нашем случае не даст "замусоривать" часть экрана, где не находится наше окно. Клиппер нужно создать один раз и привязать его к нашей основной поверхности:

LPDIRECTDRAWCLIPPER pClipper;

// Создаем клиппер
pDD->CreateClipper(0, &pcClipper, NULL); // количество ссылок на Clipper становится = 1
pcClipper->SetHWnd( 0, hWnd ); // Присваиваем ему хендл нашего окна

// устанавливаем этот клиппер для основоной поверхности
pPriSurface->SetClipper(pClipper); // количество ссылок на Clipper становится = 2

// лично нам больше этот клиппер не нужен, а pPriSurface теперь имеет свою копию указателя
// на обьект клиппера, поэтому:
pcClipper->Release(); // количество ссылок на Clipper опять становится = 1


2. Второй этап заключается в том, чтобы отследить то место экрана куда нам в дальнейшем делать blit. Здесь все очень просто — так как BltFast нельзя делать на поверхность, которая имеет clipper, нам ничего не остается кроме просто Blt (если еще конечно BltBatch, но это просто пакетный Blt), а у Blt мы можем указать место и размер (RECT) куда конкретно на поверхность мы хотим скопировать изображение. Рассчитать эту зону вывода можно так:

RECT rc;
GetClientRect(hWnd, &rc); 
ClientToScreen(hWnd, (POINT*)&rc);
ClientToScreen(hWnd, (POINT*)&rc+1);


При перемещении окна или изменении его размеров нужно все пересчитать заново.

Теперь про back buffer. Back buffer в нашем случае это внеэкранная поверхность (offscreen surface). Создается так:

LPDIRECTDRAWSURFACE7 pBackBuffer;

ddsd.dwFlags        = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;    
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
ddsd.dwWidth        = dwWidth; //  размер в пикселах
ddsd.dwHeight       = dwHeight; 

m_pDD->CreateSurface( &ddsd, &pBackBuffer, NULL);



Для получения нормальной картинки размер копируемой части из back buffer`а должен совпадать с размером отображаемого изображения. Иначе будет наблюдаться так называемый stretch — растяжение/сжатие картинки.

Выводы по твоему вопросу:

1. Изменение размеров окна никак не влияет на количесво памяти для primary surface и back buffer. Ничего само по себе не перевыделяется.

2. При изменении размера окна возможен stretch если мы не увиличиваем размер копируемой области из back buffer. Иногда это выглядит прикольно :) Особенно, если карточка при растяжении делает билинейную фильтрацию.

3. Если размер окна изменяемый и необходимо при увеличении окна копировать большую часть бекбуффера, чтобы картинка не "тянулась" — надо создать сразу большой бэкбуффер :). Пересоздание бэкбуффера на каждое изменение размера окна — это слишком сильно :)

Надеюсь теперь все стало ясно и понятно :shuffle:

ps: вот меня прорвало :))
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.