Построение экранного изображения, в принципе, довольно примитивное занятие. Если отбросить все безумные подробности, X11 и OpenGL, то дело сводится к наличию некоторого количества фрагментов памяти (буферов), имеющих разную структуру (глубина цвета, цветовое пространство, упаковка пикселов) и к механизму смешивания этих буферов. В конце концов, в контроллер дисплея попадает один или несколько буферов, представляющих растр, которые по довольно простым правилам смешиваются между собой. Скажем, в N900, таких аппаратных буферов три. Называются они планами -- графический и два видео-плана -- и по сути дела ничего, кроме этих планов в N900 и не важно. То, что имеется еще "трехмерный ускоритель", ситуацию особенно не меняет -- в конце концов, трехмерный ускоритель выдает "на гора" растровый буфер, который используется для заполнения графического плана.
Конечно, масса деталей затрудняет попытки представить все очень простым и легким. Буфера планов не используются приложениями напрямую, свое содержимое они рисуют как средствами X11, так и через OpenGL, а если приложение не работает в полноэкранном режиме, то и рисует оно не сразу на графический план, а только во вспомогательный буфер, которым пользуется композитный менеджер, чтобы смешать содержимое окон разных приложений в один графический план. Особенно хорошо это видно при просмотре application grid -- панели задач, где каждая задача показывается в виде такого маленького прямоугольника, внутри которого идет активная приложенческая жизнь.
Если содержимое окон меняется очень быстро, например, при отображении видео, у приложений остается не так-то и много вариантов. Если пропускная способность контроллера памяти и графического контроллера, а также производительности процессора достаточно для перемещения кадра за время, меньшее определенного интервала, то можно все сделать по-черному: просто представляем видео-кадр картинкой и выводим ее на экран. Проблемы с этим вариантом возникают сразу. Во-первых, модель рисования приложения через X11 предполагает, что рисует всегда сервер -- XPutImage/XShmPutImage передают изображение на сервер, а он уже доводит эту картинку до графического плана. Фактически, в идеальном случае это означает минимум одну-две копии картинки до момента, как она попадет на экран. Если вывод одного кадра занимает, скажем, 2мс, а его копирование -- 3мс, то при двух-трех копированиях мы можем добиться теоретически картинки с частотой 100Гц. Это все большое ЕСЛИ. X-протокол не гарантирует, что выполняемые действия будут произведены с абсолютной временной точностью. X-протокол работает только с изображениями в RGB-совместимых форматах, а видео-поток из себя представляет чаще всего разновидность YUV, что требует как минимум одно дополнительное копирование для преобразования цветового пространства.
Для решения этой проблемы было придумано расширение XVideo. Смысл его состоит в том, что графический ускоритель предоставляет альтернативный путь смешивания на экране видео-потока, а XVideo реализует его в рамках X-протокола. Альтернативный путь я уже упоминал выше -- те самые видео-планы. Фактически, нам говорят: "у нас тут есть еще несколько буферов размером с экран в заначке и можно рисовать на них специальным образом". В N900 таких дополнительны буферов два, а в nVidia Quadro NVS 140M, который стоит в T61, на котором я сейчас пишу этот текст, поддерживает 32 таких буфера. При выводе в эти буфера можно не конвертировать данные из YUV, вот что говорит по поводу поддерживаемых форматов NVS 140M:
maximum XvImage size: 2046 x 2046
Number of image formats: 4
id: 0x32595559 (YUY2)
guid: 59555932-0000-0010-8000-00aa00389b71
bits per pixel: 16
number of planes: 1
type: YUV (packed)
id: 0x32315659 (YV12)
guid: 59563132-0000-0010-8000-00aa00389b71
bits per pixel: 12
number of planes: 3
type: YUV (planar)
id: 0x59565955 (UYVY)
guid: 55595659-0000-0010-8000-00aa00389b71
bits per pixel: 16
number of planes: 1
type: YUV (packed)
id: 0x30323449 (I420)
guid: 49343230-0000-0010-8000-00aa00389b71
bits per pixel: 12
number of planes: 3
type: YUV (planar)
В N900 в видео-планы можно писать и RGB. В целом, путь неплохой и хотя все равно возникает как минимум одно копирование, это практически прямой путь к достижению хорошей производительности видео. Нужно только помнить, что количество буферов (портов в терминологии XVideo) ограничено и отличается от устройства к устройству, да и набор поддерживаемых форматов и дополнительных параметров тоже не стандартизирован.
Однако в случае использования XVideo получается некоторая нестыковка. Конечная картинка, которую мы видим, представляет собой смешивание данных со всех планов. Для того, чтобы смешать графический и видео-планы, дисплейной подсистеме необходимо знать, как пропускать пикселы с одного плана и брать их из другого. В XVideo для этого введено понятие color key. Приложение зарисовывает средствами X11 те пикселы в drawable, которые должны быть заменены на пикселы из видео-планов. То есть, помимо передачи каждого кадра для отрисовки командами XvPutImage/XvShmPutImage необходимо также на каждое событие, приводящее к перерисовке drawable, его перерисовывать с использованием color key. Асинхронность операций в X-протоколе может привести к тому, что рисование color key и кадра будут рассинхронизированы и пользователь увидит color key -- обычно это magenta, как единственный цвет, который отсутствует в естественном световом потоке.
Если у нас есть OpenGL и мы все равно рисуем весь интерфейс средставим OpenGL, может появиться желание вместо XVideo рисовать видеокадры как текстуры. На декстопе (и даже иногда на лаптопах и нетбуках) такое может сработать, потому что пропускная способность контроллера памяти, да и частота на которой работает графический ускоритель, вполне себе позволяет добиться 24Гц-60Гц. Но OpenGL API не рассчитано на такое использование. Во-первых, текстуры в конечном итоге должны быть в RGB-совместимом формате. Во вторых, ускорители OpenGL рассчитаны на динамически изменяемую сцены с относительно статическим набором ресурсов, среди которых и текстуры. Для эффективной работы текстуры при заливке внутрь ускорителя преобразуются во внутренний формат (twiddling). В случае видео "текстуры" у нас одноразовые, они не нужны после формирования кадра, а сцена, наоборот, статическая. Получается, что для изображения видео в рамках традиционного OpenGL мы тратим ресурсы как основного процессора, так и ускорителя на то, что потребуется лишь однажды. Если учесть, что графический ускоритель требует чаще всего DMA для перекачки данных, а при использовании системной памяти как видео-памяти (типичная ситуация для встроенных ускорителей Intel или в случае N900) придется еще озаботиться сбросами кэша памяти. Результат -- использование "в лоб" текстурирования для показа видео приводит к производительности вроде 6-15 кадров в секунду на N900.
Для решения этой проблемы производители графических трехмерных ускорителей придумали свои нестандартные расширения. Как бы они не назывались, суть их одна -- они позволяют обойти стандартный путь представления данных для текстур и прикрепления текстуры к контексту рисования для таких одноразовых кадров. Традиционное название этого процесса -- texture streaming. При этом, фактически, речь идет о том, что графический ускоритель выделяет несколько буферов для приложения и говорит: "складывай свои данные в эти буфера, а я их помогу тебе представить в виде текстур". Если мы сразу кладем наш видеокадр в такой буфер, то можем его использовать после этого в виде текстуры. Такой путь позволяет достичь хорошей производительности за счет энергопотребления. Скажем, на N900 я наблюдал отображение предварительно раскодированного потока 720p с масштабированием на полный экран (получалось 800х480 из оригинальных 1280х720) с потреблением 7-9% CPU. Энергопотребление возрастало по сравнению с XVideo на несколько процентов. Понятно, что декодирование видеопотока в этом случае займет в несколько раз больше. Вывод этого же потока через XVideo потреблял 20% CPU, то есть все же CPU мы разгружаем прилично.
Получилось довольно большое введение. Зачем оно надо -- в следующей заметке.