Обычно программисты сами про себя думают, что они самая передовая и продвинутая часть общества, что они несут только прогресс и процветание человечеству. Отчасти это правда, но отчасти и нет. Иногда программисты создают хаос и цифровой мусор. Цифрового мусора становится все больше и иногда он порождает материальный мусор или вполне осязаемые траты средств, времени и энергии.
Прежде всего хотелось бы пояснить, что я имею ввиду под термином «цифровой мусор». Не знаю, может кто-то уже использовал раньше эту фразу. Цифровой мусор — это вредная информация, которая создается в процессе деятельности человека и вообще-то её накопление мешает дальше жить, так что она требует переработки и иногда уничтожения. К сожалению, программисты по натуре склонны к архивированию, размножению-тиражированию цифрового мусора. Я хочу об этом поговорить.
Итак, цифровой мусор — это устаревшая или неактуальная, вредная, вводящая в заблуждение или просто бесполезная информация. Хотя нередко бывает и другое: современная, автоматически генерируемая скриптами информация, но при этом абсолютно не верная и вводящая в заблуждение. Чтоб далеко не ходить за примером, вот сайт CNN Business, где выкладываются биржевые сводки и прогнозы:
Видимо программисты CNN программировали — программировали, да не выпрограммировали. Где-то в их скриптах смешались рубли с долларами и в результате такой вроде бы уважаемый ресурс выдает прогноз по акциям Яндекс рост на 9223% за год. Я конечно рад за Яндекс (у него сейчас тяжелые времена), но вот интересно, были ли случаи, чтобы инвесторы и читатели CNN поймались на эту удочку? Ну бывает, что в программах случаются баги, чтож… вот такой цифровой мусор. И, кстати, такая же точно байда у CNN приключилась с акциями китайской Baidu. Видимо баг связанный с несколькими валютами в одном рассчете.
Есть и другие примеры цифрового мусора. Бывает, что программисты, к примеру, создают популярную блог платформу вроде pikabu, где каждый желающий вываливает на читателей зачастую бессмысленный треш и угар. Возможно многие со мной не согласятся и скажут, что это не мусор, но развлекательный контент. Хотя… даже читатели Пикабу уже согласны, что не все хорошо в датском королевстве:
Вот старались программисты, старались, душу в сайт вкладывали, а получилось то, что получилось.
Отдельный вид цифрового мусора представляют собой битые ссылки. Это прямо беда современного интернета. Куда не пойди — везде битые ссылки. Вот даже на хабре, запустил xenu для проверки сайта и вот пожалуйста, они есть и их много:
Зачастую битые ссылки появляются со временем даже в статьях, которые ну очень хороши, но автор ссылается на сторонний ресурс, который либо стал временно недоступным либо вовсе прекратил свое существование. Да что там хабр! Даже в моем персональном FPGA блоге marsohod.org я уже устал бороться с этим злом. То компания Интел купит Альтеру и многие ссылки перестают работать, так как у них там происходит внутренняя трансформация. То MIPSfpga становится собственностью другой компании Wave Computing и это опять порождает битые ссылки в связи со сменой домена и т.д. Иногда ссылки добавляют читатели с комментариями, но со временем часть из этих ссылок становится битыми и администратор ресурса ничего с этим не может поделать, ну разве что удалять такие коммментарии, но тогда потеряется связность обсуждения…
Однако, поскольку Хабр — это все таки довольно узко профессиональный ресурс, то дальше я постараюсь перейти к более конкретным примерам, как цифровой мусор созданный одними программистами мешает жить другим программистам.
Я сейчас пытаюсь написать кросс-платформенную мультимедийную программу, она должна работать на ПК с архитектурой x86_64 и на одноплатниках ARM вроде Raspberry Pi3, Rock64 и возможно даже OrangePi PC. Я посмотрел на возможности всего этого разношерстного оборудования и решил, что вероятно единственный способ сделать совместимое ПО, которое бы единообразно работало и там и сям — это использование OpenGL ES 2.0. Поскольку одноплатники ARM обычно имеют не очень быстрый процессор, то принципиальный вопрос состоит в том, чтобы использовать аппаратное ускорение для декодирования видео и GPU для рендеринга. Сказать по правде, для этого проекта мне пришлось изучать OpenGL ES 2.0 с нуля.
Однако, на этом пути мне пришлось столкнуться с трудностями, о которых я и не догадывался. Вот начну с самого неприятного. У меня уже был одноплатник Raspberry Pi3 и я программируя его понял, что это весьма странное устройство. В Pi3 видеоподсистема скажем так не очень «стандартная». Чтобы написать программу с использованием OpenGL ES для Pi3 я должен:
- нестандартным образом создавать окно для рендеринга, тут для этого используется так называемый DispmanX API;
- линковать программу со специальными библиотеками Broadcom /opt/vc/lib/libbrcmGLESv2.so и прочими.
Но не смотря на эти странности, вроде бы работает. Правда присутствует очень небольшой список расширений OpenGL ES 2.0, что тоже расстраивает. После этого я подумал, что вероятно, на других платах с более распространенным GPU таких проблем быть не должно. Я подумал, что GPU Mali должен быть хорошим выбором, ведь это ARM, а он везде. Я разузнал, что:
- GPU Mali-400 присутствует в SOC Allwinner H3 на плате OrangePi PC;
- GPU Mali-450 есть в SOC Allwinner H5 на плате OrangePi PC2;
- Так же GPU Mali-450 есть в SOC Rockchip RK3328.
Раздобыв эти платы я с энтузиазмом начал с ними разбираться и только тут я понял, сколько цифрового мусора мне нужно прочитать, разобрать, выбросить, чтобы продвинуться хоть на один маленький шаг вперед.
Начнем с того, что официальные сборки Ubuntu, Debian, Raspbian существуют для OpangePi и их можно скачать, но пока вы их не скачаете да не попробуете вы не узнаете, что ни о каком ускорении видео декодирования и ни о какой поддержке GPU там речи не идет. То есть официальные сборки ОС для плат OpangePi — это цифровой мусор. Более того, вы можете перейти на их страницу Github github.com/orangepi-xunlong можете взять скрипты сборки ОС и попробовать сами собрать нужную вам ОС. Теперь-то, после многих потерянных дней, я знаю точно, что даже и не соберется. Потому, что скрипты пытаются сделать download некоторых файлов с битых ссылок. Получается, что и на github у них лежит цифровой мусор, который ссылается на несуществующие ресурсы битыми ссылками. Бинго.
Включаем план B. Есть такой проект Armbian, который выдает свои собственные сборки для различных ARM плат. Сколько нужно потратить времени, чтобы перепробовать различные сборки Armbian на своих платах и понять, что укорение декодирования видео и GPU рендеринг не работает и здесь? (Примечание: на Rock64 Mali GPU и аппаратное декодирование видео работают в Armbian после установки специального MediaPack).
Перерыв тонны цифрового мусора в гугле я нашел на форуме инструкцию, как запустить GPU рендеринг на OrangePi PC. Повторив описанные в инструкции действия я смог получить OpenGL ES рендеринг через GPU Mali 400 на апельсинке OrangePi PC. На OrangePi PC2 эта же инструкция не сработала, так как Armbian для OrangePi PC2 оказался другой версии и даже другой архитектуры, AARCH64.
Пока я все это делал и погружался в тему, я узнал, что:
- драйвера Mali GPU от ARM в общем являются закрытыми драйверами. ARM отдает только исходники драйвера ядра для Mali Utgard вот здесь
И при этом ARM сообщает, что это не полный стек драйверов:
Some of these components are being made available under the GPLv2 licence. This page provides access to the source packages from which loadable kernel modules can be built.
Note that these components are not a complete driver stack. To build a functional OpenGL ES or OpenVG driver you need access to the full source code of the Mali GPU DDK, which is provided under the standard Arm commercial licence to all Mali GPU customers. For a complete integration of the Mali GPU DDK with the X11 environment refer to the Integration Guide supplied with the Mali GPU DDK.
При этом ARM на этой же странице желает нам «Happy hacking!». Так и хочется срифмовать здесь hacking с другим словом… но не буду — у нас же здесь приличное общество.
- usermode библиотеки для mali распространяются (или не распространяются) производителем чипа в виде блобов, то есть бинарников. У каждого производителя они могут быть свои, и может еще быть конфликт версий и тогда работать не будет, но вы не узнаете почему не работает.
Хочу еще обратить внимание на казалось бы незначительный факт. На странице Open Source Mali Utgard GPU Kernel Drivers есть упоминание так называемого драйвера UMP:
The UMP kernel device driver handles:
Access to allocated UMP memory through a secure ID. This enables memory to be shared across different applications, drivers and hardware components to facilitate zero-copy operations
The physical address information required to set up an MMU or MPU table
A method to map UMP memory into CPU address space, to enable reading and writing
Так вот это все на сегодняшний день есть чистейший цифровой мусор, который вводит в заблуждение: в современном ядре Linux никаких драйверов UMP для GPU похоже не требуется. А ведь разработчик (вроде меня) прочитав эту фразу может потратить на изучение UMP день, два, неделю или две… Сегодня же, как я понимаю, большинство программистов переходят от использования UMP к DMABUF.
Ну ладно я… а сколько программистов в мире занимаются этим самым «happy hacking» связанным с драйверами ARM Mali? Кто-то ковыряется в драйвере ядра, кто-то пытается сделать Mali действительно доступным через open source проект Lima…
Итак, в голове складывается картинка:
- есть opensource проект драйвера ядра Mali GPU для Allwinner чипов на github, github.com/mripard/sunxi-mali Это драйвер ядра. И тут же есть некоторые usermode OpenGLESv2 блобы для чипов Allwinner;
- К нему нужно еще собрать драйвер X11 github.com/mripard/xf86-video-armsoc
Вот хотелось бы подробнее остановиться на драйвере mali для ядра на github от mripard (Maxime Ripard). Он работает. Я смог собрать и запустить его и на Mali GPU-400 в OrangePi PC и на Mali GPU-450 в OrangePi PC2. Однако, есть один момент о котором очень трудно догадаться. Нигде в репозитории github.com/mripard/sunxi-mali вы не найдете с каким именно ядром требуется собирать этот драйвер. Получается очень тонкая грань, когда исходники вроде бы рабочие, но без дополнительной нигде не указанной информации вы не сможете ими воспользоваться. Проблема в том, что требуется взять не только конкретную версию ядра линукса, но и приложить к нему вполне определенные патчи ядра. Я эти патчи раздобыл многими часами копаясь в исходниках сборщика Armbian. То есть для большинства новых программистов — этот гитхаб репозиторий с драйвером ядра Mali — просто цифровой мусор, но для посвященных в таинство секретных патчей — это ценный артифакт.
Без патчей ядра Linux 4.20
- github.com/armbian/build/blob/master/patch/kernel/sunxi-next/0005-drm-gem-cma-Export-with-handle-allocator.patch
- github.com/armbian/build/blob/master/patch/kernel/sunxi-next/0006-drm-sun4i-Add-GEM-allocator.patch
драйвер Mali на Allwinner SOC работать не будет.
Следующий пример цифрового мусора — это некачественная документация по программированию. Только не бейте меня, но я считаю, что документация по OpenGLES ужасная.
Предположим, мое мультимедийное приложение должно часто перезагружать текстуру в GPU. Пусть у меня есть в памяти CPU битмап, который я переношу в текстуру в памяти GPU. Допустим изменился только прямоугольный фрагмент битмапа и мне нужно сделать update только этой прямоугольной области в GPU Texture. Как это сделать?
В спецификации OpenGL ES есть функция:
void glTexSubImage2D( GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLsizei width,
GLsizei height,
GLenum format,
GLenum type,
const GLvoid * data);
Отсюда понятно, какой прямоугольник будет обновляться внутри текстуры, но не понятно, как задавать stride и координаты для исходного битмапа. Читаем документацию дальше и видим, что недостающие параметры можно передать через функцию glPixelStorei().
Пишем все по документации:
glPixelStorei( GL_UNPACK_ROW_LENGTH, stride);
glPixelStorei( GL_UNPACK_SKIP_PIXELS, offset_x);
glPixelStorei( GL_UNPACK_SKIP_ROWS, offset_y);
glTexSubImage2D( target, 0, offset_x, offset_y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
И это не работает!
Начинаем искать решение в интернете. Конечно же сразу находим на stackoverflow:
Вот смотрю на решение из stackoverflow, оно отмечено зеленой галочкой, то есть кому-то помогло. Однако, зеленая галочка на решении в stackoverflow не означает, что
1) решение правильное;
2) что может работать в любых условиях.
Я смотрю на это решение из stackoverflow и на свое решение — они одинаковы, но у меня не работает.
И знаете что? Можно до дыр по сто раз перечитывать страницы документации по glPixelStorei() и glTexSubImage2D() — все равно не поймете в чем дело. Потому что вот такая документация. А на самом деле проблема в том, что для такого копирования произвольного прямоугольника нужно специальное расширение OpenGL ES. На разных платформах оно может быть, а может и не быть. В Stackoverflow на той странице ничего такого никто не написал, не предупредил, не поставил акцент. То есть получается вроде бы и решение описано, но оно верно только отчасти при некоторых условиях, о которых умалчивается. В Stackoverflow такого полно и с каждым годом все больше и больше.
Проверить наличие специального расширения OpenGL ES для частичного апдейта текстуры можно вот так:
const char* extensions = glGetString(GL_EXTENSIONS);
if (strstr(extensions, "GL_EXT_unpack_subimage"))
has_unpack_subimage = true;
Возвращаясь к документации OpenGL ES…
Как из чтения документации про функцию glPixelStorei() можно догадаться о существовании расширения GL_EXT_unpack_subimage? Сколько времени теряет новичок в OpenGL ES пока не набьет себе шишек на такой ерунде? Что стоило написать в документации в каких случаях glPixelStorei() работает, а в каких нет? Но понятно, что на самом деле новичок может и не столкнуться с этой проблемой если пишет просто для ПК. У него скорее всего будет современная Linux с современными драйверами и экстеншн есть и код сразу заработает. А вот когда начнешь портировать свою программу на разные одноплатники raspberry/orange/rock, вот тогда и столкнешься с проблемой. Такие дела.
Должен сказать, что в результате многочисленных экспериментов я для себя понял, что функция glTexSubImage2D() довольно медленная. Сейчас я использую другой метод динамической загрузки изображений в текстуры — это DMABUF. Процессором пишу изображение в специальным образом выделенную память, а DMA переносит его в память GPU в текстуру. Толковой документации по этой теме я не нашел. Есть только бестолковая. Хорошо, что на Github, кроме мусора если очень долго рыться можно отыскать и толковые вещи. Например, вот пример использования DMABUF: github.com/OtherCrashOverride/mali_test
С небольшими переделками запускается и на ПК и на других платформах.
Мне видится большая проблема в том, что количество репозиториев в github растет быстрыми темпами и найти что-то нужное становится все сложнее и сложнее. То же самое и stackoverflow.
Для фичи DMABUF требуется наличие расширения OpenGL ES EGL_EXT_image_dma_buf_import. К сожалению, в Raspberry Pi3 этого расширения нет, и тут придется использовать жутко тормознутую glTexSubImage2D(). Однако, DMABUF поддерживается в Rock64, OrangePi PC, OrangePi PC2 ну и в x86_64 платформах конечно. Еще, как оказалось, в Raspberry Pi4 вполне настоящий OpenGL ES 3.0. И в Pi4 работает DMABUF.
К сожалению, я не до конца разобрался с синхронизацией GPU рендеринга и DMABUF. Так как DMA работает асинхронно ко всем другим устройствам, то можно получить неприятный эффект рваных кадров-текстур. Как я уже писал, толковой документации не нашел, но есть много мусора, разбирая который часто вынужденно тратишь очень много времени.
Например, статья Sharing CPU and GPU buffers on Linux. Вроде бы она про то, что мне нужно, про DMABUF, но, во-первых, приведенные примеры в статье какие-то не полные, по ним невозможно создать работающее приложение. Во-вторых, приведенные в статье примеры неработающие и даже не компилирующиеся. Возможно автор пытался продемонстрировать «псевдокод», но тогда в чем смысл его статьи? Это созданный программистом цифровой мусор.
Смотрите его пример из Cache Control:
Ну что это такое? Как в ioctl передается параметр? Как структуре присваивается константа? Мне пришлось потом еще пол часа смотреть заголовки ядра, чтобы понять как правильно передать параметры в ioctl. Почему бы не написать в статье изначально все правильно? Должно быть так:
struct dma_buf_sync sync_args;
sync_args.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE;
ioctl( DmabufFd, DMA_BUF_IOCTL_SYNC, &sync_args );
memcpy( DmabufPtr, pixels, width*height*4 );
sync_args.flags = DMA_BUF_SYNC_END;
ioctl( DmabufFd, DMA_BUF_IOCTL_SYNC, &sync_args );
Это тоже псевдокод, но он хотя бы будет компилироваться без ошибок.
Итак, разобрав огромные завалы мусора при поиске в Google, Github, Stackoverflow, на различных форумах я в конце концов смог написать приложение, которое вполне сносно работает используя для рендеринга OpenGL ES 2.0 используя DMABUF и на разных платформах. Но, увы. Слишком сложно это разрабатывать. Слишком много противоречивой, не верной, не полной информации, конфликты версий, веб-страницы документации без выходных параметров (ну хотя бы дата и номер версии о которой идет речь). На этом пути слишком много цифрового мусора. И я думаю дальше будет хуже, ведь github радостно рапортует о взрывном росте числа контрибьютеров и репозиториев, появляются новые протоколы, стандарты, языки, фреймворки, библиотеки. А старое не уходит — оно здесь же рядом, уже закешировано гуглом и яндексом, готовое вывалиться на нас при поиске.
PS:
- я в полной мере осознаю, что эта статья так же добавляет в мир ИТ технологий немного нового цифрового мусора, который кого-то введет в заблуждение или заставит потратить время там, где его не нужно тратить;
- я призываю всех нас однажды провести субботник и отредактировать и обновить свои старые статьи, вычищать битые ссылки и всячески бороться с цифровым мусором.