AHI англ. ATI Handheld Interface — драйвер для чипов ATI. Присутствует в каждой прошивке для телефонов, где есть чип ATI, а это - все телефоны на базе LTE и LTE2.
Содержание
Чипы ATI
Два основных чипа, используемых в телефонах Motorola:
API драйвера для различных чипов одинаков, однако внутреннее устройство значительно различается (По словам владельцев. Лично мне неизвестно, насколько. A51)
Применение
Вывод графики через драйвер, в обход стандартных средств прошивки (см. Canvas), предоставляет больше возможностей по сравнению с последними, и, при правильном применении, работает значительно быстрее.
Если Вам нужно, чтобы вывод собственной графики был на максимально возможной скорости, как, например, для разработки динамичных игр - тогда стоит задуматься о применении средств драйвера. В остальных случаях использование Canvas будет значительно проще.
С учётом сказанного, для наиболее эффективного использования средств, предоставляемых драйвером, их следует использовать в приложении, основанном на таске.
Основные понятия и начало работы
API драйвера напоминает интерфейсом обычные графические библиотеки, как например GDI. Рассмотрим основные понятия и приёмы работы с AHI в применении к эльфописанию.
AHI разрабатывался отдельно от программных продуктов, где он используется, поэтому был спроектирован максимально универсальным. Одно из следствий такой универсальности является возможность драйвера распараллеливать работу с железом между различными "клиентами", а именно - между приложениями операционной системы и самой ОС.
Для обеспечения такой возможности используется специальный объект, с которым разработчику эльфа под драйвер придётся сталкиваться постоянно - это контекст устройства.
Контекст устройства
Контекст устройства (англ. Device Context) — структура данных, обеспечивающая эксклюзивный доступ к устройству вывода для приложения, использующего эту структуру и предотвращающая влияние других приложений на процесс работы с устройством. В ней хранятся все данные о текущем состоянии устройства, устанавливаемом приложением - владельцем контекста.
Чтобы лучше разъяснить как это работает, рассмотрим ситуацию на упрощенном примере:
- Приложения A и B работают параллельно, и хотят получить доступ к Устройству, для чего каждый из них создаёт с помощью AHI новый контекст для собственного пользования.
- Приложение A пытается изменить состояние Устройства, скажем, меняет основной цвет "кисти" на красный. А на самом деле, информация о цвете кисти попадает не сразу в Устройство, а сохраняется в контексте приложения A.
- Приложение B тоже меняет (в своём контексте) цвет кисти, но на синий.
- Теперь, Приложение B пытается вывести линию синим цветом. Для этого драйвер отсылает контекст приложения B (вместе с информацией о цвете) Устройству и подаёт команду на рисование линии текущим (синим) цветом.
- После этого, если Приложение A тоже решит нарисовать линию, ему не придётся "задумываться" о том, что Приложение B что-то там проделало с Устройством, а просто вызовет функцию рисования линии, и драйвер нарисует её красным цветом, выполнив те же процедуры с контекстом.
Если бы небыло системы контекстов, и данные сразу попадали в устройство, то после установки Приложением B цвета в синий, Приложение A тоже бы получало синие линии вместо желаемых красных.
В коде контекст описывается как тип AHIDEVCONTEXT_T и является первым параметром почти у всех функций AHI.
В ОС P2K напрямую функции драйвера не используются (кроме Java-машины), а Функции графики предоставляются через промежуточную надстройку над AHI - DAL, поэтому все приложения ОС (а точнее, система UIS), так же как и сама ОС, используют единственный системный контекст.
Для эльфов есть выбор, как получить контекст:
- Можно получить системный контекст, который использует ОС, с помощью функции DAL_GetDeviceContext:
// Это будет наш контекст. Удобнее всего объявить его глобальным AHIDEVCONTEXT_T dCtx; // Получаем системный контекст dCtx = DAL_GetDeviceContext(0);
Недостаток этого способа - необходимо помнить, что приложение не является единственным клиентом драйвера, и делать необходимые установки перед каждой операцией вывода. А так же есть вероятность появления искажений в прорисовке экрана самой системой (UIS).
- Либо можно создать новый контекст только для нужд приложения, с помощью функций ldrGetAhiDevice и AhiDevOpen:
// Это будет наш контекст. Удобнее всего объявить его глобальным AHIDEVCONTEXT_T dCtx; // Информация об устройстве AHIDEVICE_T device; // В EP2 уже есть вспомогательная функция для получения информации об устройстве device = ldrGetAhiDevice(); // Создаём контекст sts = AhiDevOpen( &dCtx, device, "Matrix", 0 );
Недостаток этого способа - если функции драйвера используются несколькими клиентами одновременно, они работают медленнее из-за необходимости постоянно переключаться между контекстами.
Поверхности
Прежде чем переходить к рассмотрению непосредственно программирования под AHI, необходимо ознакомиться с ещё одним важнейшим понятием - "поверхностями".
Поверхность (англ. Surface, "Сурфейс") — логически выделенный участок видеопамяти чипа ATI.
Как многим из вас известно, у видеочипов часто есть собственная видеопамять, причём эта видеопамять не обязательно доступна процессору напрмяую. В телефонах Motorola с чипами ATI именно такой случай.
Размер внутренней видеопамяти (располагается прямо на чипе) на W2250 и W2260 одинаков и составляет 192Kb, однако к W2260 подключен внешний модуль для расширения внутренней видеопамяти. Последняя работает медленнее внутренней, что отчасти компенсируется её значительно большим объёмом.
MCU может обращаться к видеопамяти двумя способами - через DMAC (только запись), либо через QSPI (чтение и запись).
Все растровые операции AHI производятся с участием поверхностей. Есть четыре наиболее важные поверхности, используемые в работе драйвера. Однако, последняя фраза не означает, что поверхностей именно четыре, скорее следует понимать это как статусы, присваиваемые определённым поверхностям, и при этом одна и та же поверхность(участок памяти) может принимать одновременно несколько таких статусов:
- Отображаемая поверхность ("Display Surface", "Экранная поверхность", она же "Экранный буфер")
- Это та поверхность, из которой в данный момент чип берёт информацию для непосредственного вывода на экран.
- Устанавливается с помощью функции AhiDispSurfSet, а получить можно с помощью AhiDispSurfGet
- Поверхность назначения ("Destination Surface", "Поверхность-приёмник")
- Эта поверхность является целевой для функций рисования, тоесть в неё будет попадать результат операции.
- Устанавливается функцией AhiDrawSurfDstSet
- Поверхность-источник ("Source Surface")
- Эта поверхность является источником данных для функций рисования, например картинка спрайта, который мы хотим нарисовать.
- Устанавливается функцией AhiDrawSurfSrcSet
- Поверхность кисти ("Brush Surface")
- С этой поверхностью приложения никогда не работают напрямую, но о её существовании нужно знать, чтобы проще было понимать принцип работы некоторых функций рисования - она является важным вспомогательным инструментом. Всегда монохромная (однобитная), и используется как параметр PATTERN в растровых операциях.
- Устанавливается самим драйвером, а настраивается с помощью функции AhiDrawBrushSet.
Иногда требуется создать собственную поверхность в видеопамяти, например, чтобы закешировать спрайт (вывод графики из внутренней памяти намного быстрее, чем из системной). Для этого можно использовать функции AhiSurfAlloc, AhiSurfFree, AhiSurfReuse.
Координаты
Ещё одна вещь, с которой придётся постоянно иметь дело - это точки и области экрана и их координаты.
Координатная система экрана (да и всякой поверхности, тоже!) с точки зрения чипа ATI имеет следующую конфигурацию:
- Начало отсчёта - в левом верхнем углу, отсчёт начинается с нуля
- Ось X направлена вправо
- Ось Y направлена вниз
Два основных объекта для описания координатных положений - это AHIPOINT_T и AHIRECT_T:
typedef struct { INT32 x, y; } AHIPOINT_T; typedef struct { INT32 x1, y1, x2, y2; } AHIRECT_T;
Если с первым всё понятно - просто задаёт координаты точки по x и y, то AHIRECT_T, который задаёт прямоугольную область экрана, стоит рассмотреть чуть подробнее:
- x1, y1 - координаты левого верхнего угла прямоугольника
- x2, y2 - правого нижнего
Важное замечание: в отличие от используемой в UIS системе задания такой области, здесь координаты правого нижнего угла задаются не включительно! Например, если x1 = 0, x2 = 32 - это означает, что в область попадут точки с координатами x от 0 до 31.
Чаще всего эти две структуры используются в функциях AHI примерно так:
UINT32 AhiDrawBitBlt( AHIDEVCONTEXT_T devCx, AHIRECT_T *dstRect, AHIPOINT_T *srcPt );
- AHIRECT_T указывает область на целевой поверхности, куда попадёт картинка из поверхности-источника, и именно он задаёт размер картинки.
- AHIPOINT_T указывает координаты левого верхнего угла на поверхности-источнике, откуда будет скопирована область, размерами равная размерам прямоугольника AHIRECT_T.
Цвет
Как многие знают, во многих телефонах используются дисплеи с глубиной цвета 16 бит (Покомпонентно: 5 бит на красный, 6 бит на зелёный и 5 - на синий, "RGB565"), а в настольных компьютерах - с глубиной цвета в 24 бита (по 8 бит на каждую компоненту цвета, "RGB888"). Многие привыкли к заданию цвета в 24-битном формате на настольных компьютерах, да и это намного легче, стоит только рассмотреть пример: 0xFACE8D - в 24bpp сразу видно, какая интенсивность цвета на каждой компоненте, а на 16bpp это будет 0xFE71, и ничего не понятно... Вебдизайнеры поймут.
Для облегчения задачи, предусмотрен макрос, который получает из привычного RGB888 цвета - RGB565, например:
ATI_565RGB(0xFA, 0xCE, 0x8D);
Конечно, пользоваться им необязательно для случаев "чёрного" и "белого" - это 0x0 и 0xFFFF соответственно.
Нетрудно однако подсчитать, что "белый"(0xFFFF) в RGB565 при переводе обратно в RGB888 будет уже не очень "белым", далеко не 0xFFFFFF. Это полезно иметь в виду, когда работаете с картинками в формате RGB565
Возможно, у Вас уже назрел вопрос ещё с первых строк этого раздела, "А как же 18-битные дисплеи, которые ставили, например, на E1?"
Так вот по крайней мере W2250 не имеет поддержки 18-битных поверхностей, чтобы обеспечить использование возможностей такого дисплея, так что это был не более чем маркетинговый ход. Впрочем, это же судя по всему касается и LTE2 телефонов, так как для графики там опять же используются 16-битные поверхности.
Во многих графических адаптерах прошлого (хотя, и по сей день нередко используется, например, в консолях) основным режимом был 8-битный цвет с палитрой. К моему великому сожалению, портативные чипы ATI не имеют аппаратной поддержки 8-битных палитр (8-битный цвет - возможно, только в формате RGB322, но не проверено), но есть особенность, которую можно назвать однобитной палитрой.
Однобитные поверхности занимают особое место среди прочих. А именно, при выводе однобитной картинки, мы можем задавать 16-битный цвет, который будет представлять "1", "цвет переднего плана", и цвет, представляющий "0", "цвет фона", либо сделать один из них прозрачным. Фактически, получаем палитру для однобитного изображения.
Эти два цвета также задаются отдельно для кисти (а она, как мы узнали из предыдущих разделов, тоже однобитная поверхность!) - с помощью функций AhiDrawBrushFgColorSet и AhiDrawBrushBgColorSet, и для остальных однобитных изображений - функциями AhiDrawFgColorSet и AhiDrawBgColorSet.
Инициализация
Итак, у нас теперь есть контекст, полученный одним из двух способов, указанных выше. Что дальше?
Вне зависимости от того, собственный у нас контекст или нет, нужно провести его инициализацию. Различие лишь в том, что в случае собственного контекста её достаточно провести один раз, а в случае использования системного - почти каждый раз перед вызовом какой-либо функции рисования AHI.
Перед тем, как вызвать какую-либо функцию рисования, мы должны установить необходимые для её работы параметры:
- Установить поверхность-приёмник
Часто для этого используется отображаемая поверхность, если не планируется вывод с использованием двойной буферизации.// Наш контекст, полученный ранее AHIDEVCONTEXT_T dCtx; // Наша отображаемая поверхность AHISURFACE_T sDisp; // Получим отображаемую поверхность. Достаточно сделать это один раз, ведь врядли она поменяется. AhiDispSurfGet( dCtx, &sDisp ); // Устанавливаем поверхность-приёмник AhiDrawSurfDstSet( dCtx, sDisp, 0 );
- Установить поверхность-источник
Обычно для этой роли используют либо пользовательскую поверхность с нужной к выводу картинкой, либо системную внеэкранную поверхность (см. Двойная буферизация).// Сохраним сюда системную внеэкранную поверхность AHISURFACE_T sDraw; // Получим системную внеэкранную поверхность sDraw = DAL_GetDrawingSurface( DISPLAY_MAIN ); // Устанавливаем поверхность-источник AhiDrawSurfSrcSet( dCtx, sDraw, 0 );
- Установить области вырезания для приёмника и источника
Пока что выключим их, и для этого передадим в функции AhiDrawClipDstSet и AhiDrawClipSrcSet - NULL:// Выключаем области вырезания для приёмника и источника AhiDrawClipDstSet( dCtx, NULL ); AhiDrawClipSrcSet( dCtx, NULL );
- Установить растровую операцию
Чтобы не углубляться в рамках этой статьи в растровые операции, ограничимся пока что простым правилом: Для вывода растровых изображений устанавливаем AHIROP_SRCCOPY, а для рисования цветом - AHIROP_PATCOPY. Отсановимся на последнем, так как пригодится в последующем примере.// Устанавливаем растровую операцию на применение кисти AhiDrawRopSet( dCtx, AHIROP3(AHIROP_PATCOPY) );
- Настроить кисть
Это делать не обязательно, если мы не собираемся рисовать графические примитивы. Но так как мы собираемся, то...// Установим кисть на самую обычную, сплошную (SOLID) AhiDrawBrushSet(devCx, NULL, NULL, 0x0, AHIFLAG_BRUSH_SOLID);
Конечно, некоторые из этих операций понадобится повторить перед вызовом определённых функций, с другими установками, но надеюсь это уже не составит для Вас проблем.
Примеры
Наконец, уже можно что-нибудь да и нарисовать! Не будем здесь заострять внимание на таких тривиальных вещах, как размещение последующих примеров в коде и инициализация приложения. Пускай, например, рисовать мы будем в вызове по таймеру.
Первый пример
Для первого раза нарисуем цветной графический примитив.
// Вспомогательная переменная для указания области экрана AHIRECT_T rect; // Установим цвет кисти. Этим цветом будут выводиться наши графические примитивы AhiDrawBrushFgColorSet(devCx, ATI_565RGB(0,0,255)); // Ярко-синий // Зададим прямоугольник на экране, который будет залит нашим цветом rect.x1 = 0; rect.y1 = 0; rect.x2 = 64; rect.y2 = 64; // Нарисуем прямоугольник rect AhiDrawSpans( dCtx, &rect, 1, 0);
References: AhiDrawSpans, AhiDrawBrushFgColorSet
После этого мы получим на экране синий квадрат в левом верхнем углу. Не больно-то и сложно, не так ли?
Второй пример
Теперь мы попытаемся решить задачу чуть посложнее, а именно - выведем какую-нибдь картинку. Код для загрузки картинок можно взять из библиотеки AHG, написанной tim apple, а саму картинку нужно будет подготовить особым образом.
/* Проведём инициализацию при загрузке эльфа: */ // Сюда будет загружена наша картинка. Не забудьте освободить память bitmap.image по завершению! AHIBITMAP_T bitmap; // Загружаем картинку! BMP_LoadFromFile(L"file://b/image.bmp", &bitmap); /* Теперь нарисуем картинку на экране средствами ATI */ AHIPOINT_T point; AHIRECT_T rect; // Пусть мы хотим вывести картинку в левом верхнем углу, задаём область как в предыдущем примере rect.x1 = 0; rect.y1 = 0; rect.x2 = bitmap.width; rect.y2 = bitmap.height; // И ещё - точку на исходной картинке. Мы выводим целиком, так что нули. point.x = 0; point.y = 0; // Не забываем, что в прошлый раз мы установили операцию PATCOPY, но она не подходит для вывода растра! AhiDrawRopSet( dCtx, AHIROP3(AHIROP_SRCCOPY) ); // Осталось собственно вывести картинку! AhiDrawBitmapBlt( dCtx, &rect, &point, &bitmap, NULL, 0 );
References: AhiDrawBitmapBlt, AhiDrawRopSet
Третий пример
В третьем, заключительном примере мы создадим собственную поверхность, перенесём её в видеопамять и будем работать уже из неё. Пример во многом повторяет предыдущий, но смотрите внимательнее - есть значительные отличия!
/* Проведём инициализацию при загрузке эльфа: */ // Сюда будет загружена наша картинка. Не забудьте освободить память bitmap.image по завершению! AHIBITMAP_T bitmap; // Это - размер новой поверхности AHIPOINT_T size; // Сюда мы сохраним указатель на новую поверхность AHISURFACE_T surface; // Загружаем картинку! BMP_LoadFromFile(L"file://b/image.bmp", &bitmap); /* Создадим новую поверхность - surface, по размеру картинки*/ size.x = bitmap.width; size.y = bitmap.height; AhiSurfAlloc( dCtx, &surface, &size, AHIFMT_16BPP_565, 0 ); /* Перенесём картинку на нашу новую поверхность, просто нарисовав её из системной памяти */ AHIPOINT_T point; AHIRECT_T rect; // Задаём область вывода rect.x1 = 0; rect.y1 = 0; rect.x2 = bitmap.width; rect.y2 = bitmap.height; // И точку на исходной картинке point.x = 0; point.y = 0; // Не забываем, что в прошлый раз мы установили операцию PATCOPY, но она не подходит для вывода растра! AhiDrawRopSet( dCtx, AHIROP3(AHIROP_SRCCOPY) ); // Сейчас мы хотим вывести картинку на новую поверхность. AhiDrawSurfDstSet( dCtx, surface, 0 ); // Так как выводить будем из системной памяти, поверхность-источник нам не важна. // Выводим картинку AhiDrawBitmapBlt( dCtx, &rect, &point, &bitmap, NULL, 0 ); /* Теперь мы можем работать с этой картинкой в видеопамяти */ AHIPOINT_T point; AHIRECT_T rect; // Сейчас мы хотим вывести картинку из новой поверхности на экран. AhiDrawSurfSrcSet( dCtx, surface, 0 ); AhiDrawSurfDstSet( dCtx, sDisp, 0 ); point.x = 0; point.y = 0; rect.x1 = 0; rect.y1 = 0; rect.x2 = bitmap.width; rect.y2 = bitmap.height; // Для вывода из видеопамяти используется эта функция AhiDrawBitBlt( dCtx, &rect, &point );
References: AhiDrawBitBlt, AhiSurfAlloc, AhiDrawSurfSrcSet, AhiDrawSurfDstSet, AhiDrawBitmapBlt, AhiDrawRopSet
Заключение
Итак, рассмотрев основные моменты работы с драйвером ATI и даже несколько конкретных примеров, у вас не должно возникнуть больших проблем с разработкой эльфов с его использованием. Тем более, что уже существует несколько подобных эльфов, с исходным кодом которых можно ознакомиться.
Если эта статья вам чем-то помогла - пожалуйста, отпишитесь на форуме! --Andy51 19:13, 29 августа 2009 (MSD)