Когда я ещё программировал графику под DOS, единственным способом получить большую скорость было использование прямого доступа к видеопамяти. Это было достаточно просто, потому что видеопамять находилась по определённому адресу, а использование прерываний для вывода графики очень сильно тормозило систему. В Windows запрещён прямой доступ, поэтому тут есть проблемы. Но в DirectDraw уже заложили возможность прямого доступа и мы сейчас с ним познакомимся.
В некоторых случаях, прямой доступ к видеопамяти - единственный способ повысить производительность системы. Хотя DirectDraw достаточно быстрая библиотека, она всё же расходует некоторое процессорное время зря. Когда ты обращаешся к видеопамяти напрямую, вывод всегда происходит быстрее.
Для получения прямого доступа к поверхности, нужно вызвать её метод Lock. Этот метод блокирует поверхность и позволяет получить к ней доступ. Во время блокировки поверхности происходить и блокировка системы, поэтому желательно блокировать на небольшые промежутки времени и после использования сразу разблокировывать. У него 4 параметра:
Область поверхности, к которой мы хотим получить доступ. Тип параметра - указатель на TRect. Если здесь указать nil , то доступ будет получен ко всей области.
Структура типа TDDSURFACEDESC2, куда будет записана инфа о поверхности.
Флаги. Можно указывать следующие:
DDLOCK_NOSYSLOCK - не блокировать систему (игнорируется при получении доступа к первичной поверхности).
DDLOCK_READONLY - заблокировать только для чтения.
DDLOCK_SURFACEMEMORYPTR - указывает на то, что надо получить правильный указатель на верхний угал области поверхности.
DDLOCK_WAIT - Если сейчас блокировка невозможна, то подождать, пока поверхность не осободится.
DDLOCK_WRITEONLY - блокировать только для записи.
Последний параметр не используется и должен быть равен 0.
После использования поверхности, нужно вызвать метод Unlock. У этого метода только один параметр - область поверхности, которая должна быть разблокирована. Желательно разблокировать ту же область, что и блокировалась.
После вызова метода Lock, адрес памяти, где находятся данные поверхности находится в свойстве lpSurface структуры, переданной в качестве второго параметра. Вот тут наверно нужно будет написать реальный пример, а потом разбирать его.
Давай напишем процедуру PutPixel, которая будет выводить на экран один пиксел в видеопамять.
procedure TForm1.PutPixel(X, Y: Integer; R, G, B : Byte);
var
desc : TDDSURFACEDESC2;
Value:Word;
begin
ZeroMemory (@desc, SizeOf(desc)); //Обнуляю структуру
desc.dwSize := SizeOf(desc); //Заполняю размер
value:=B or (G shl 5) or (R shl 11); //Вычисляю цвет
FImageSurface.Lock (nil, desc, DDLOCK_WAIT, 0); //Блокировка
PWord(Integer(desc.lpSurface) + Y * desc.lPitch + X*2)^ := Value;
FImageSurface.Unlock (nil);//Разблокировка
end;
Первым делом я обнуляю структуру desc и в поле dwSize заношу её размер.
Дальше я вычисляю цвет, который мне нужно занести в память. Вот тут начинается самое интересное. Вычисление цвета зависит от текущего видеорежима и выбранной глубины цветов.
Если ты используешь разрешение в 8 бит, то есть максимум можно отобразить 256 цветов и то благодаря палитре, то никаких проблем. У тебя в памяти, каждый байт - это указатель на цвет в палитре, т.е. картинка 8х8 будет выглядеть так:
Тут каждое число означает какой-то цвет. Если мы используем режим TrueColor, то в этом режиме каждые три рядом стоящих байта дают цвет. Это значит, что наша табличка должна расширится в ширину в три раза. В этом случае, каждые три байта будут давать цвет (RGB).
Ну а самое интересное происходит при 16-битном цвете, когда в два байта нужно засунуть три цвета. Как это сделать, если два байта - это 16 бит, а 16 на 3 не делится? Очень просто - в этом случае очень часто используется режим 5-6-5, где 5 бит под красный цвет, 6 бит под зелёный и 5 бит под голубой. Можно переключиться на режим 5-5-5, где всем цветам будет отдано одинаковое количество бит, но мы будем рассматривать 5-6-5. Так как 16-битный режим самый сложный, я решил использовать в своём примере именно его.
Чтобы получить результирующий цвет и засунуть его в переменную Value (которая имеет тип WORD или два байта), мне нужно сдвинуть красный цвет на 11 бит влево, зелёный на 5 бит и синий прибавить как есть.
Теперь о том, как записывать в память. Чтобы записать значение цвета в левый верхний угол, ножно присвоить его по адресу указанному в desc.lpSurface, то есть вот сюда - PWord(Integer(desc.lpSurface))^ . Я привожу указатель к типу PWord, потому что записывать буду значение типа Word. Если бы я записывал только байт, то приводил бы к PByte - PByte(Integer(desc.lpSurface))^ .
Чтобы вычеслить строку, в которую надо записать я умножаю значение Y на длинну строки - Y * desc.lPitch. В desc.lPitch как раз и находится длинна строки картинки. Чтобы вычислить Х позицию, я умножаю Х на 2, потому что каждое значение Х позиции состоит из 2 байт.
Ну и наконец в этой процедуре FImageSurface - это поверхность типа IDirectDrawSurface7, в которую я загружу картинку. Именно эту поверхность я блокирую и получаю к ней доступ.
Вот и всё. Чуть позже я напишу пример анимации с использованием прямого доступа к памяти. К этой же статье я прилагаю пример, в котором на экран выводится изображение (я писал этот пример в статье "Delphi+DirectX7,8. Улучшенный вывод на экран") и поверх него случайным образом рисуються точки случайного цвета. Скрин ты можешь видеть где-то выше в этой статье.