рефераты конспекты курсовые дипломные лекции шпоры

Реферат Курсовая Конспект

DDLOCK_READONLY и DDLOCK_WRITEONLY

DDLOCK_READONLY и DDLOCK_WRITEONLY - раздел Образование, Состоит из следующих компонентов Указываются, Если Из Памяти Будет Производиться Только Чтение Или Наоборот, Б...

указываются, если из памяти будет производиться только чтение или наоборот, будет идти только запись. Обычно в использовании этих флагах нет необходимости, но их можно применять для контроля содержимого поверхности, например, чтобы по ошибке не записать что-нибудь в память, для записи не предназначенной. Также они могут использоваться в видеорежимах "Mode X".

Остальные флаги применяются очень редко, их описание можно прочитать в справочной службе SDK.

Заблокировав поверхность, можно узнать о её формате. Для этого, собственно, и передавалась структура TDDSURFACEDESC2. Поля dwWidth, dwHeight содержат размер поверхности в пикселях, ddpfPixelFormat - формат самого пикселя (о нём уже говорилось в предыдущих статьях). Наиболее востребованные поля - это lpSurface, оно предоставляет нам указатель на первый бит поверхности (визуально это верхний левый угол поверхности), и поле lPitch - это шаг поверхности. В предыдущих примерах шаг поверхности уже использовался, но не был рассмотрен мною детально. Теперь настала пора это сделать.

Точное определение шага таково: шаг - это расстояние в байтах между адресом, который представляет собой начало растра и адресом, который представляет собой начало следующей строки растра. Естественно, шаг поверхности DirectDraw меняется в зависимости от текущего графического режима.

Например, в режиме 32 бит на один пиксель приходится по 4 байта, и тогда, если ширина поверхности 100 пикселей, теоретически шаг должен составлять 100 * 4 = 400 байт. В режиме 16 бит шаг такой поверхности должен составлять 200 байт.

Следует учитывать, что часто реальный размер шага может не совпадать с расчётным. Для ускорения копирования данных видеоадаптер может создать поверхность с шагом, большим, чем это необходимо( например, некоторые видеоадаптеры создают поверхности, ширина которых кратна четырём). Такая дополнительная память носит название кэша. Так что нельзя считать, что шаг - это просто ширина растра, умноженная на количество БАЙТ на пиксель. К счастью, нам нет дела, сколько конкретный видеоадаптер выделяет памяти под кэш - необходимо знать только шаг поверхности и количество бит на пиксель в текущем видеорежиме.

Почему так важен шаг? Дело в том, что, хе-хе, в памяти компьютера нет всяких там прямоугольников, которые представляют поверхности DirectDraw. Весь растр поверхности, для простоты, можно представить в виде одной длинной макаронины. Длина такой макаронины равна шагу поверхности, умноженному на высоту поверхности, или, точнее говоря, на количество строк в растре. Если шаг поверхности - ровно 400 байт, а в растре - 100 строк, то такая поверхность займёт в памяти 40 000 байт, или ~ 39,1 Кб.

Вот как это можно представить графически:


Рис. 2. Представление поверхности DirectDraw в памяти.

А теперь представьте, что мы хотим нарисовать на поверхности вертикальную линию. Из рисунка видно, что пиксели в таком случае будут разбросаны в памяти на большом расстоянии друг от друга. Чтобы правильно нарисовать точку на поверхности, необходимо пользоваться следующей формулой:

x * bpp + y * lPitch, где x, y - координаты пикселя на поверхности;lPitch - шаг поверхности;bpp - количество бит на пиксель.

Левое от плюса выражение предназначено для установки на нужный пиксель в ряду, правое - на нужный ряд поверхности. Надеюсь, теперь вы имеете более или менее чёткое представление о формате поверхностей DirectDraw.
Рассмотрим пример Demo2.

Оговорка:
для работы примера необходимо подготовить вручную файл wallpaper.bmp размером 640x480, желательно с красочной картинкой. Для остальных примеров его потребуется скопировать в соответствующие каталоги вместе с файлом sprite.bmp. Я не сделал этого по причине экономии места в архиве.

В примере используется мышь для перемещения спрайта. Клавишами <- и -> можно менять прозрачность спрайта от 0 до 100. За вывод прозрачного спрайта отвечает функция BltTrans(). Она вызывает другие три функции - это DDGetPixel(), DDMixerPixels() и DDSetPixel(). Эти функции расположены в модуле ddutils.pas. Функции получают и передают целые значения цветов, их составляющие по-прежнему вырезаются с помощью сдвиговых операций внутри самих функций.

Пример рассчитан на работу в 24- или 32-битном режимах. Для простоты код написан для последнего. Если вам понадобится работать в 24-битном режиме, задайте его в строке инициализации DirectDraw, и измените выражение

dwPos := x * 4 + ddsd2.lPitch * y;наdwPos := x * 3 + ddsd2.lPitch * y;

так как надо сдвигать указатель только на три байта для каждого пикселя. Для работы в режиме 16 бит надо переписать выражения с указателями, но я боялся усложнить и без того немаленькие примеры, поэтому оставил эту работу тому, кто этим заинтересуется.

А теперь о неприятном. Запустите пример и посмотрите, какая частота кадров получается, если спрайт имеет произвольную прозрачность... У-у-у, негусто! Хочется даже сказать, что частота очень низкая, даже удручающе низкая. Это скорее какое-то слайд-шоу. Если кто-то только читает эту статью и ещё не скачал примеры, наберусь храбрости и скажу, что на акселераторе GeForce 2 MX 400 частота кадров со 120 падает до... 7-8 в секунду. К тому же и на более быстродействующих акселераторах ситуация вряд ли изменится к лучшему. В чём причина таких "тор-р-р-мозов"?

Поначалу автор статьи даже грешил на Delphi. Дело в том, что здесь применяется работа с указателями не на уровне функций API, а на уровне операторов языка. Этот момент мог обещать серьёзные проблемы, так как бытует мнение, что частые операции с указателями в Delphi производятся медленнее, чем в компиляторах С/С++. Здесь именно такая ситуация. Подсчитаем. Растр имеет размерность 200x200 пикселей. В сумме это 40 000 пикселей. На каждый пиксель - по три байта (подразумевается режим 32 бит, но так как работа идёт только с тремя составляющими, будем считать по три). Используется три операции - чтение пикселя с дополнительного буфера, чтение пикселя со спрайта и запись смикшированного пикселя в дополнительный буфер. Операции с микшированием в счёт не идут. Итого 9 * 40 000 = 360 000 операций с указателями на один спрайт. Это довольно много даже для современных компьютеров, поэтому подозревать Delphi причины были. Для проверки я написал аналогичное приложение на языке С++, используя компилятор Microsoft Visual C+ 6.0. И что же? Приложение дало такой же fps, разницу составляли случайные отклонения.

Я решил попробовать различные флаги при блокировке поверхностей, использовал блокировку отдельной области, но всё напрасно. Смена кадров по-прежнему происходила очень медленно.

Похоже, виноват сам механизм доступа к пикселям поверхностей DirectDraw. Автор хочет поделиться секретом - никакие книжки по DirectX не помогут вам досконально разобраться в нём, так как зачатую авторы пишут о том, чего сами не пробовали сделать, а просто передрали это из файла справки или других источников. Так вот, если кто-то будет говорить вам, что пользуясь методами IDirectDrawSurface7.Lock() и IDirectDrawSurface7.Unlock() вы получаете прямой доступ к видеопамяти, не верьте этому. Вообще, обычным способом из программы н-е-в-о-з-м-о-ж-н-о получить прямой доступ к видеопамяти, все операции с данными можно осуществить только в системной памяти ПК. Непосредственный доступ к видеопамяти "на борту" акселератора осуществляет только сервер DirectDraw, в данном случае библиотека ddraw.dll через драйверы DirectX и драйверы видеоакселератора.

После ряда тестов стал ясен внутренний механизм блокировки поверхностей. Вот его очертания: получив указания заблокировать поверхность, DirectDraw проверяет, где она расположена. Располагаться поверхность может в видеопамяти (что желательно), или в системной памяти. Во втором случае мы действительно сразу же можем писать в эту память из программы. Но если место локации - видеопамять, то в этом случае начинаются проблемы. По-видимому, DirectDraw копирует часть или всю память поверхности в системную память, а по команде разблокировать поверхность - назад, в видеопамять (хотя эти утверждения могут быть далеки от истины). Во всяком случае, в примере Demo2 осуществляется блокировка сразу двух поверхностей, расположенных по умолчанию в видеопамяти - дополнительному буферу и самому спрайту.

Как бы то ни было, причина именно в этом - слишком медленном доступе к поверхности, если она расположена в видеопамяти. Подтвердилось выражение "Всегда считайте данные в видеокарте, как доступные только для записи" http://www.gamedev.ru/coding/20518.shtml

Что из этого следует? А то, что ВСЕ поверхности DirectDraw надо создавать в системной памяти. Так как производятся операции с дополнительным буфером, первичную поверхность и буфер тоже надо разместить там. К сожалению, всё это тоже приведёт к замедлению смены кадров, но уже менее значительному. Некоторые видеоадаптеры способны на более эффективный обмен данными с поверхности в системной памяти за счет использования прямого доступа (DMA).

Всё вышесказанное можно проверить, посмотрев на работу примера Demo3. Наблюдается среднее значение fps в штатном режиме (когда спрайт непрозрачен) и снижение её до приемлемого значения при операциях с поверхностями. Функция CreateSurface() теперь создаёт все поверхности в системной памяти.

Думаю, даже если вы неудовлетворены результатами, данный пример - отличная возможность познакомиться на практике с "оптимизацией в компьютерной графике". Что же здесь можно оптимизировать?

Давайте подсчитаем. Если в спрайте 40 000 пикселей, и на каждый пиксель - по шесть сдвиговых операций shr, то получается 240 000 операций. Это немало, поэтому целесообразно переписать функции DDGetPixel(), DDSetPixel() и DDMixerPixels() с использованием непосредственно трёх составляющих пикселя, а не разбирать-собирать каждый раз исходные цвета. Это и делано в примере Demo3. Многие программисты объявляют для этого собственные структуры вроде

type PRGB = ^TRGB; TRGB = record r: byte; g: byte; b: byte; end;

Однако я прибегнул к стандартной структуре RGBTRIPLE из модуля windows.pas.

Хорошо, но всё ли это? Нет! Если кто не знает, могу сообщить, что на вызов внешних функций или метода класса-интерфейса тратится определённое процессорное время. Для приложений, сгенерированных разными компиляторами, это время различно, но в любом случае оно всегда может стать существенным. Смотрите - в нашем случае из цикла делается вызов четырёх функций для каждого пикселя, т. е. в общей сумме внешние функции вызываются 160 000 раз за кадр. К тому же кто сказал, что спрайт размером 200x200 - предел мечтаний или спрайт должен быть один? Поэтому целесообразно все операции из внешних функций перенести внутрь, в тело цикла - что и сделано в примере Demo31. Функция BltTrans() немного выросла в объёме, но зато снижение fps при переходе из "штатного" режима в режим с прозрачностью снизилось (сравните с примером Demo3).

В принципе, можно сделать последний шаг - переписать выражения

// извлекаем цвет с поверхности заднего буфераprd := pbyte(longword(ddsd2d.lpSurface) + (dwPosD));pgd := pbyte(longword(ddsd2d.lpSurface) + (dwPosD + 1));pbd := pbyte(longword(ddsd2d.lpSurface) + (dwPosD + 2)); // извлекаем цвет с поверхности спрайтаprs := pbyte(longword(ddsd2s.lpSurface) + (dwPosS));pgs := pbyte(longword(ddsd2s.lpSurface) + (dwPosS + 1));pbs := pbyte(longword(ddsd2s.lpSurface) + (dwPosS + 2)); // микшируем цвета (результат сразу же отображается в памяти)prd^ := round( prd^ * v + prs^ * (1 - v) );pgd^ := round( pgd^ * v + pgs^ * (1 - v) );pbd^ := round( pbd^ * v + pbs^ * (1 - v) );

на языке Ассемблер - однако это, по расчётам, в данном случае даст совсем небольшой прирост в производительности. Я вовсе не умаляю значение Ассемблера, часто он просто необходим как воздух, но в этом случае может отдохнуть.

Данный пример - наглядный урок того, как поступают программисты в области современной компьютерной графики. Обычно всё сначала пишется в виде отдельных функций, но в итоге "последней-последней" оптимизации большинство критичного кода за несколько шагов собирается воедино. Как видите, даже в нашем простом случае это дало свои плоды, в гораздо более крупных циклах это может дать ещё более значительный прирост в скорости, вернее, "снимет тормоза".

Можно было бы закончить сей, надеюсь, интересный рассказ, если бы скорость была бы более высокой. Но половина (или меньше) от возможного - это плохо. За что же мы платим деньги, покупая навороченный акселератор? Из-за этих соображений я устроил себе долгий думушник, вот его результаты - проект Demo4.

Если доступ к поверхности, расположенной в видеопамяти, очень медленный, необходимо избегать его. Для этого нужно создать все поверхности, с которыми мы будем иметь дело, в системной памяти. Но вот загвоздка - это крайнё нежелательно для дополнительного буфера.

Я сделал так. В системной памяти создаётся спрайт и специально для него так называемый спрайт-буфер таких же размеров. Для чего он нужен? Просто на него копируются данные из дополнительного буфера, а именно - та его область, куда предполагается вывести прозрачный спрайт. К счастью, современные видеоакселераторы, как оказалось, выполняют эту операцию достаточно быстро, невзирая на то, что приходится читать данные из видеопамяти. Теперь можно спокойно блокировать обе поверхности - спрайт и спрайт-буфер и заниматься микшированием пикселей. Результат операций (довольно быстрых) отображается в спрайт-буфере, после чего мы его разблокируем и копируем на дополнительный буфер.

Вот и всё! Запустите пример и понаблюдайте за производительностью. Кстати, привожу таблицу fps для всех четырёх примеров:

Пример Fps, 640x480 GeForce 2 MX 400 S3 Trio 3D/2X Demo2 126 6-7 85 5-4 Demo3 95 60 23 15 Demo31 95 72 23 18-19 Demo4 126 125 83 18-19  

Видеоакселератор GeForce 2 работал в режиме 32 бит, S3 Trio 3D/2X - в режиме 24 бит. Первые значения - это fps в стандартном режиме, соседние - fps при прорисовке с прозрачностью.

Как видно, последнее усовершенствование никак не отразилось на "ветеране", похоже, это его максимум, существенно поднялась лишь частота смены кадров в штатном режиме. А вот GeForce порадовал сполна. Поэтому последний алгоритм вполне можно рекомендовать для реализации полупрозрачных спрайтов.

В заключение хочется сказать, что непосредственные операции с пикселями - это очень мощный инструмент для реализации качественной графики. Ведь здесь можно пойти ещё дальше. Усовершенствуйте сами функцию BltTrans() - можно сделать так, что пиксели заданного цвета не выводились (аналог цветовых ключей), пиксели другого цвета можно выводить непрозрачными... Да мало ли что! Таким образом можно реализовать, например, поворот спрайта на угол, разные волновые эффекты (как в DelphiX), эффект линзы...

Во всяком случае, если понадобятся прозрачные спрайты, DelphiX уже не является абсолютно необходимым.

Пишем DirectX-движок

 

Вернуться к разделу Hello, World! Автор Виктор Кода, дата публикации 11 октября 2002г.

Привет всем, кто интересуется DirectX!

Темой этого урока я решил сделать написание т. н. "движка". Полагая, что всегда существует определённый контингент людей, только начавших изучать мультимедийные и игровые технологии, и плавающие в загадочных понятиях, объясню, что такое "движок" и на каком бензине он работает.

"Движок" - это перевод от английского "engine" - т. е. двигатель. На данный момент термин является общепризнанным, поэтому далее я буду придерживаться именно его (хотя само слово не очень стыкуется с правилами русского языка).

Сначала небольшой экскурс в историю. Если кто помнит, в конце 80-х - начале 90-х годов игры не были такими огромными и сложными, каковыми они являются теперь. В те далёкие времена на экстишках и эйтишках в офисах гоняли разве что кошек и цветные кубики из "Тетриса". По объему кода, такие игры, естественно, не идут ни в какое сравнение с теми, что пишутся в наши дни, и поэтому программировались по-иному. Как? Обычно весь код такой программы писался за один "присест" и отвечал за всё - и за графику, и за звук (пищание), и за клавиатуру, и за AI, в общем за всё, что требовалось для воссоздания игрового процесса.

К сожалению, принцип "написать все, затем откомпилировать и это работает" проходит только для программ определенного объема, каковыми и были игры пятнадцателетней давности. Повторное использование написанного таким образом кода весьма сомнительно, и в лучшем случае вам придётся переделывать только его половину.
Вообще, одновременно с пропорциональным усложнением программ идёт пропорциональное же абстрагирование программиста от первоначального кода. Сейчас уже невозможно, например, написать полномасштабную программу на Ассемблере (вернее можно, но никто этим заниматься не будет). На помощь программисту пришли многочисленные API и библиотеки, созданные трудом многих тысяч других программистов.

Движок - это ещё один уровень абстрагирования. Можно сказать, что движок - это небольшая ОС в рамках самой игры, которая отвечает за низкоуровневые операции. В маленьких движках (если не движочках) такие функции обычно отвечают за общение с "железом" - т. е. вывод данных на экран, звуковое оформление и другие подобные вещи. Теперь программиста не интересует механизм вывода, например, текста на экран - он командует PrintText() - и все хлопоты по выводу текста берёт на себя движок. Конечно, код в движке базируется на каком-либо API, и использует его методы для реализации своих "идей". В сущности, любой API - например, DirectX - это тоже в своём роде движок, так как предоставляет нам более высокоуровневую надстройку над низкоуровневыми операциями, реализованными в DLL. Проблема в том, что такие API довольно сложны по причине их гибкости, и обеспечивают только базовые функции. Цель движка - собрать рутинные последовательности команд в один вызов. Из этого, между прочем, следует, что конкретный движок пишется под конкретный жанр игр.

Написание движков в настоящее время стало отдельным этапом разработки. Многие профессиональные фирмы уже не пишут движки самостоятельно, а приобретают их у других фирм, которые занимаются исключительно этой областью программирования. Такие движки представляют собой весьма сложные программы, они тщательно документируются и комплектуются демонстрационными примерами.

Обычно серьёзные движки отвечают не только за работу с графикой, звуком и т.д., а реализуют ещё и специфичные функции, востребованные играми жанра. Однако мы ещё не настолько "круты", что бы делать подобные вещи, поэтому займемся пока простейшими.

Я надеюсь, теперь вам понятно, что написание кода следует разбивать на отдельные этапы и постепенно реализовывать их. Движок - один из таких этапов. Реализовав этот уровень, можно приступать к написанию, скажем, самой игры. Вообще, невозможно писать игровую логику программы и одновременно заниматься поверхностями DirectDraw. Поэтапная разработка способствует тому, что проект в конце-концов будет доведён до конца, а не брошен на полпути.
Перейдём от слов к делу. Итак, с чего начать? Прежде всего надо определиться со структурой движка. Для меня это был самый сложный этап, им пренебрегают очень многие. Я на собственном опыте убедился, что если не думать над структурой, вместо цельной программы получается лапша из функций и структур. Особенно тяжеловато приходится в первый раз, когда за плечами только демонстрационные примеры и кое-какие наброски. Впрочем, иногда и лапша полезна (с точки зрения "ой, какой ужас вышел").

Меня удивляют люди, которые пишут большие программы без применения модульности. Например, почти вся реализация игры Donuts из DirectX SDK 7 размещена в одном файле. Лично у меня моментально пропало желание разбирать такую программу после того, как я пару раз повозил ползунок редактора кода туда-сюда. Подобных примеров много - взять тот же DelphiX. Всё "спихнуто" в пару файлов, и разобрать что-либо в этой лапше не представляется возможным (вообще, большие модули в Object Pascal - это болезнь самого языка, немного позже я дам объяснение этому факту).

Вывод очевиден - разработку программ необходимо вести с помощью модулей. В каждом модуле следует разместить только те функции и процедуры, которые выполняют узкий круг задач, т. е. разместить их по смыслу. Из личного опыта замечено, что желательно доводить разработку отдельного модуля до степени "готовности" приблизительно процентов этак на 60-80%. Иначе при огромном количестве "недоначататых" модулей начнётся настоящая "беготня" вокруг закладок (в случае с Delphi), и ба-альшие проблемы с отладкой кода. Только обеспечив необходимую функциональность одной части задачи, можно переходить к следующей. Естественно, всегда потом оказывается необходимым что-то исправить или дополнить в уже написанном коде, но сделать это будет гораздо легче.

Итак, перейдём к "разбору полётов". Движок (если его вообще можно так назвать), который я выставляю на всеобщее обозрение - мой первый опыт в этой области. Подумав немного, я решил написать его без применения объектно-ориентированного программирования. Хорошо это или плохо? Лично я считаю, что ООП - это хорошо, но оно не всегда востребовано. По моему сугубо личному мнению, в программах вроде игр и движках для них процедурное программирование - ничуть не устаревший инструмент, а применение ООП - неоправдано. Правильно построенная, хорошо структурированная программа легка для понимания и последующей модификации, свободная от классов, загадок наследования и ошибок полиморфизма...

Сколько я придумывал название своему движку - словами не передать. В конце концов устоялось название Simple DirectX Interface - сокращённо SDI. По примеру многочисленных библиотек (например, OpenGL) все функции движка, которые предназначены для вызова из внешней программы, начинаются с префикса "sdi", а те, что предназначены для внутреннено использования - без него.

На данный момент я реализовал только графичекую составляющую. Остальные части планируется написать немного позже. Это облегчит начинающим первое знакомство со структурой программы. Графическая часть размещается в 10 модулях, остальные выполняют различные вспомогательные работы.

Ниже дано перечисление модулей и краткое описание каждого:

– Конец работы –

Эта тема принадлежит разделу:

Состоит из следующих компонентов

Введение... Фанаты игр часто встречаются с аббревиатурой quot DirectX quot На упаковках... Компоненты DirectX обеспечивают не только прямой доступ к устройствам компьютера они избавляют программиста от...

Если Вам нужно дополнительный материал на эту тему, или Вы не нашли то, что искали, рекомендуем воспользоваться поиском по нашей базе работ: DDLOCK_READONLY и DDLOCK_WRITEONLY

Что будем делать с полученным материалом:

Если этот материал оказался полезным ля Вас, Вы можете сохранить его на свою страничку в социальных сетях:

Все темы данного раздела:

DirectShow
используется в мультимедиа-технологиях - можно выполнить высококачественое воспроизведение или захват видео и звука. Распространённость использования компоненты не слишком велика Все компо

Виктор Кода
Смотрите по теме : DirectX для начинающих.Часть вторая DirectX для начинающих. Часть третья. Считывание и запись Цикл лекция JINX'а Direc

Небольшое отступление
Прошёл месяц с тех пор как я написал первую часть ( http://www.delphikingdom.com/helloworld/directx.htm ) статьи по использованию DirectX в среде Delphi. У меня накопилось ещё несколько примеров, к

Почему я не рекомендую использовать DelphiX
Хочется поделиться с новичками своим мнением по поводу компонетов DelphiX и почему я не рекомендую их использовать. С одной стороны, DelphiX - это удобно – нет необходимости выполнять утом

DDLOCK_NOSYSLOCK
данный флаг указывает на то, что приложение пытается получить прямой доступ к поверхности без остановки стандартных механизмов Win32. По умолчанию при блокировке все действия ОС приостанавливаются

E_string.pas
- две функции - ltos() и ltoc() для преобразования типа longint к строке string или pchar соответственно. Базируются на процедуре str() из модуля system.pas. Это здорово сокращает объём исполняемог

Несколько слов о недоработках.
Первое: пример MainExample в окне в видеорежимах HighColor или TrueColor на всех компьютерах с видеокартами GeForce2 MX 400, где я его тестировал, почему-то работает некорректно. Н

Хотите получать на электронную почту самые свежие новости?
Education Insider Sample
Подпишитесь на Нашу рассылку
Наша политика приватности обеспечивает 100% безопасность и анонимность Ваших E-Mail
Реклама
Соответствующий теме материал
  • Похожее
  • Популярное
  • Облако тегов
  • Здесь
  • Временно
  • Пусто
Теги