Пользователь
6,4
рейтинг
29 июля 2013 в 21:32

Разработка → OpenGL ES 3.0 в Android 4.3 — сжатие текстур ETC2 tutorial

Совсем недавно вышла новая версия Android — 4.3. Уже задолго до его релиза были утечки сперва для Galaxy S4, а потом и Nexus 4. В этих прошивках я сразу же обнаружил библиотеки для работы с OpenGL ES 3.0, что несказанно обрадовало — слухи о том, что демонстрированные еще в марте демки OpenGL ES 3.0 на HTC One работают на родных библиотеках Android, подтвердились (равно и как слухи о поддержке Bluetooth Low Energy).

И вот в пятницу вечером пришли OTA обновления одновременно на два наших устройства — Nexus 4 и Nexus 10. На Nexus 7 обновление пока 4.3 не пришло, но это нас нисклько не огорчает (почему — объясню позже). Разумеется, руки зачесались это добро опробовать.



Вступление — что нового в OpenGL ES 3.0


В новой версии OpenGL ES 3.0 появился целый ряд новых возможностей, перечислять которые не стану, о них можно узнать из документации здесь: www.khronos.org/opengles/3_X
и в кратком пресс-релизе здесь: www.khronos.org/news/press/khronos-releases-opengl-es-3.0-specification
В даной статье затронем самую простую и наиболее быструю в применении возможность OpenGL ES 3.0 — новый стандартный формат сжатия текстур ETC2.

ETC2


Формат сжатия ETC2 был разработан на основе ETC1 и его принцип работы основан на неиспользуемых в ETC1 последовательностях бит. Для того, чтобы понять гениальность того, как удалось расширить ETC1 до ETC2 стоит прочесть эти документы:
описание алгоритма сжатия: www.jacobstrom.com/publications/StromPetterssonGH07.pdf
и описание формата ETC2: www.graphicshardware.org/previous/www_2007/presentations/strom-etc2-gh07.pdf

ETC2, так же как и ETC1, работает с блоками 4х4 пикселя, но для каждого блока подбирается определенный алгоритм сжатия — обычный ETC1 или один из трех дополнительных алгоритмов.

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

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



Инициализация OpenGL ES 3.0


Для инициализации OpenGL ES 3.0 в Android 4.3 не требуется никаких дополнительных манипуляций. Следует создать обычный контекст OpenGL ES 2.0. Если GPU поддерживает OpenGL ES 3.0, то Android автоматически создаст контекст OpenGL ES 3.0, который полностью обратно совместим с OpenGL ES 2.0. То есть на Android 4.3 все приложения, использующие OpenGL ES 2.0, фактически работают с OpenGL ES 3.0. Для того, чтобы удостовериться что полученый контекст — 3.0, следует проверить строку GL_VERSION. Пример кода из исходников Android 4.3: android.googlesource.com/platform/frameworks/base/+/android-4.3_r0.9/libs/hwui/Extensions.cpp
Спасибо Romain Guy за это и другие объяснения об использовании OpenGL ES 3.0 в день релиза 4.3: plus.google.com/u/0/+RomainGuy/posts/iJmTjpUfR5E

Пример Java кода:

        protected int mOpenGLVersionMajor;
        protected int mOpenGLVersionMinor;

    String strGLVersion = GLES20.glGetString(GLES20.GL_VERSION);
        if (strGLVersion != null) {
         Scanner scanner = new Scanner(strGLVersion);
            scanner.useDelimiter("[^\\w']+");

            int i = 0;
            while (scanner.hasNext()) {
                if (scanner.hasNextInt()) {
                    if (i == 0) {
                        mOpenGLVersionMajor = scanner.nextInt();
                        i++;
                    }
                    if (i == 1) {
                        mOpenGLVersionMinor = scanner.nextInt();
                        i++;
                    }
                }
                if (scanner.hasNext()) {
                    scanner.next();
                }
            }
        }

    protected Boolean isES2() {
        return mOpenGLVersionMajor == 2;
    }

    protected Boolean isES3() {
        return mOpenGLVersionMajor == 3;
    }

nVidia


Тут пожалуй, есть смысл объяснить, почему нам неважна задержка обновления Nexus 7 до Android 4.3. Дело в том, что чипы nVidia Tegra 2/3 не поддерживают OpenGL ES 3.0. К сожалению, даже Tegra 4 его не поддерживает. nVidia просто продолжает запихивать свои десктопные решения в мобильные чипы, и их маркетинговый отдел это отстающее от жизни решение успешно проталкивает. Чего только стоит весьма нелепое оправдание этого в Tegra 4 Whitepaper, страница 11: www.nvidia.com/docs/IO/116757/Tegra_4_GPU_Whitepaper_FINALv2.pdf Они признают, что чип не поддерживает полную спецификацию ES 3.0, и открыто говорят что все равно “мы не ожидаем, что в скором времени приложения/игры будут использовать ES 3.0”. Сюрприз — Android 4.3 сам использует OpenGL ES 3.0.

Хотя при том что nVidia совершенно не собирается расширять текущие чипы для поддержки ES 3.0, Tegra 5 все же уже будет его поддерживать: www.ubergizmo.com/2013/07/nvidia-tegra-5-release-date-specs-news

Создание сжатых текстур


Для создания ETC2-текстур использовался инмтрумент Mali Texture Compression Tool: malideveloper.arm.com/develop-for-mali/mali-gpu-texture-compression-tool. Для получения наилучшего качества тексур использовался метод сжатия “Slow” и Error Metric “Perceptual”.

Ниже приведены изображения для сравнения качества сжатия ETC1 и ETC2, а также разница между оригинальным и сжатым изображением, усиленная в 4 раза для наглядности.

При сжатии текстуры в ETC1 заметны артефакты в виде горизонтальных (особенно хорошо видны) и вертикальных полос. При сжатии ETC2 эти артефакты практически отсутствуют.
В наших живых обоях на участках сцен, для которых качество текстуры критично, используются текстуры без сжатия. Как видно на сравнительном изображении, ETC1 вносит наиболее заметные искажения в текстуры с плавными градиентами — становятся четко видны артефакты сжатия, вызванные особенностью сжатия квадратами размером 4х4 пикселя. Поэтому для неба применяем текстуры без сжатия, а они занимают довольно много места — ведь их размер 2048х512. Сжатие в формате PVRTC также дает достаточно хорошее качество текстур, но доступно только на чипах PowerVR. Применение стандартного для ES 3.0 формата ETC2 позволило достичь приемлемого качества текстуры при сокращении объема выделенной для текстуры видеопамяти в 4 раза:
Для текстуры 2048х512:
Несжатая (16-битный цвет 565 — 2 байта на пиксель): 2*2048*512 = 2097152 // 2 МБ данных
Сжатая (16 байт — заголовок PKM): 524304-16 = 524288 // 512 кБ данных.

Загрузка ETC2 текстуры


Текстура загружается из файла .pkm. Такой же формат используется для хранения текстур ETC1. Формат заголовка описан здесь: forums.arm.com/index.php?/topic/15835-pkm-header-format

Решил не пытаться загружать ETC2 текстуры с помощью ETC1Util, так как в нем есть валидация заголовка.
Код для загрузки текстуры:


    protected int loadETC2Texture(String filename, int compression, Boolean bClamp, Boolean bHighQuality) {
        int[] textures = new int[1];
        GLES20.glGenTextures(1, textures, 0);

        int textureID = textures[0];
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureID);

        if (bHighQuality) {
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        } else {
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        }

        if (bClamp) {
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
        } else {
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
        }

        InputStream is = null;
        try {
            is = mWallpaper.getContext().getAssets().open(filename);
        } catch (IOException e1) {
            e1.printStackTrace();
        }

        try {
            byte[] data = readFile(is);

            ByteBuffer buffer = ByteBuffer.allocateDirect(data.length).order(ByteOrder.LITTLE_ENDIAN);
            buffer.put(data).position(PKM_HEADER_SIZE);

            ByteBuffer header = ByteBuffer.allocateDirect(PKM_HEADER_SIZE).order(ByteOrder.BIG_ENDIAN);
            header.put(data, 0, PKM_HEADER_SIZE).position(0);

            int width = header.getShort(PKM_HEADER_WIDTH_OFFSET);
            int height = header.getShort(PKM_HEADER_HEIGHT_OFFSET);

            GLES20.glCompressedTexImage2D(GLES20.GL_TEXTURE_2D, 0, compression, width, height, 0, data.length - PKM_HEADER_SIZE, buffer);
            checkGlError("Loading of ETC2 texture; call to glCompressedTexImage2D()");
        } catch (Exception e) {
            Log.w(TAG, "Could not load ETC2 texture: " + e);
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                // ignore exception thrown from close.
            }
        }

        return textureID;
    }

...
if (isES3()) {
    textureID = loadETC2Texture("textures/etc2/sky1.pkm", GLES30.GL_COMPRESSED_RGB8_ETC2, false, false);
} else {
    textureID = loadTexture("textures/sky1.png");
}
...

Таким образом, при наличии контекста ES 3.0 будет загружаться текстура ETC2, а в режиме ES 2.0 — обычная несжатая текстура.

Разумеется, для доступа к классу GLES30 нужно задать android:targetSdkVersion=«18» в манифесте приложения и target=android-18 в project.properties.

Результат


В приложении разница между несжатой текстурой (без искажений) и ETC2 не заметна:


Приложение доступно по ссылке: play.google.com/store/apps/details?id=org.androidworks.livewallpapercarfree

Заключение


Мы всегда стараемся использовать новые возможности каждой новой версии Android для оптимизации производительности и расширения возможностей. В частности, обои поддерживают и работу в режиме заставки — daydream. Хоть статейка получилась и недостаточно объемная, но надеюсь что наш скромный опыт в использовании ETC2 может кому-нибудь пригодится для оптимизации своих приложений.

P.S.

Если кто-то ставил себе утекшую прошивку 4.3 для Samsung Galaxy S4, пожалуйста проверьте работу этого приложения на этом устройстве.
@diamond3
карма
22,0
рейтинг 6,4
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (17)

  • +2
    Т.е. не видать мне этой красоты на Nexus 7? Странно что в телефон они железо лучше ставят, чем на планшете.
    • –2
      Планшет старее, к тому же очень дешевый.
  • 0
    Nexus 4, Android 4.3.
    Хотя и не люблю «живые» обои из-за энергопотребления, с эстетический, а в данном случае скорее с технической точки зрения, обои выглядят приятно.
    Лагов или фризов не заметил. Все плавно. На экранах позиционируется правильно.
    Нашел небольшое баглишко — ярлык приложения на рабочий стол добавился, но в основном списке приложений я его не нашел. Отображается только через список приложений в настройках.
    • 0
      это не приложение а дополнение к живым обоям. настраивается в режиме выбора обоев.
  • +3
    Вот так новость, первый раз расстроился, что у меня Nexus 7(
    • +1
      Что не так в моём комменте? До сего момента считал, что Нексусы — это передовые устройства, которые можно не менять хотя бы пару лет. Девайс используется и для игр тоже. А в данной ситуации получается, что нельзя будет оценить ES 3.0.
      • +2
        Нексусы, может, и ОК, и дело не в том, что, как говорит iSeiryu «в телефон они железо лучше ставят, чем на планшете», а в том, что тегра (что первая, что вторая, что третья) — по всем фронтам так себе железка, которую упрямо толкает отдел маркетинга нвидиа. В первых двух теграх не было работы с инструкциями NEON (из-за чего обычные приложения нещадно тормозили), в третьей тегре они пожмотничали на поддержку OpenGL 3.0, хотя прототипы Mali и Adreno того же поколения имели полную поддержку 3.0 (справедливости ради, стоит отметить, что Adreno 320 имеет какие-то ограничения на 3.0, но тут надо долго гуглить и искать пруфы). К тому же Tegra 3 — достаточно старый камень, а сам по себе Nexus 7 на рынке уже чуть больше года (а третья тегра существовала уже на тот момент), так что отсутствие передовых фич вполне объяснимо. Остаётся надеяться, что нвидиа перестанет жадничать или что от неё отвернутся производители. Новый Nexus 7 как раз на Qualcomm'e с Adreno на борту.
        • 0
          Да-да, видел, вот и задумываюсь всерьёз о том, что пора менять девайс. Повёлся на маркетинг в своё время.
  • +11
    Думаю они допилят драйвера тегры 3\4 под OpenGL ES 3.0, эти чипы не держат только ETC\ETC2 и FP32, первое можно декодировать в понятный для тегры формат на лету внутри драйвера, второе в принципе не сильно важно, разве что вы умножаете model и view матрицы в шейдере, но тогда у вас возникли бы всякие подёргивания и тд куда раньше чем вам реально понадобилось бы FP32. Даже наоборот — тегра держала MRT и PBO когда остальные еще плясали вокруг OpenGL ES 2.0.

    Большинство вещей из OpenGL ES 3.0 можно сделать на банальных чипах SGX 543 и подобных, просто драйверописатели ну очень ленивые, один и тот же чип (SGX 543) имеет разный функционал в разных устройствах: в PS Vita есть MRT (хотя там нету OpenGL вообще, там свой GAPI), а в устройствах на базе Apple A5 и iOS до 7 нету, как это понимать? PBO в мобильных системах делается вообще очень просто, ведь зачастую RAM и VRAM это один и тот же чип памяти и PBO это просто отдать поинтер из памяти. В iOS есть даже PBO-подобный функционал через CoreVideo, но сделать расширение для OpenGL ES? Неее, это они не могут\это им лень. Так что тегра в этом свете даже молодец — они первые реализовали критически важные расширения когда остальные ждали новой волны GPU чтобы поднять свой зад и сделать наконец-то штуки из OpenGL ES 3.0, в старых чипах наверно этого и не будет, ведь зачем портить жизнь маркетологам? Новые ГПУ ведь надо же как-то толкать.

    После опыта разработки под PS Vita я очень сердит на всех разработчиков драйверов под мобильные системы. Под витой я мог красиво получать C поинтеры на объекты, буфера, текстуры, и потом читать и писать их как захочу, мог формировать буфер команд гпу и потом просто отсылать уже сформированый буфер, имел всякие плюшки типа профилировщика и анализатора шейдеров, имел тот же MRT, имел по пиксельный дебаг как DirectX — можно было поставить брекпоинт на пиксель и посмотреть что туда рисовалось. А под iOS\Android я не имею вообще ничего, только под iOS я внезапно могу посмотреть буфер OpenGL команд и абстрактные метрики «я сожрало 50 мб видеопамяти». Единственное что выделилось на фоне этого уг — NVidia, со своим NVidia Nsight и NVidia PerfHud ES, подобного софта тупо нет ни для SGX чипов, ни для Mali, ни для Adreno. А нужная для оптимизации информация есть только у Unity3D, потому что они договорились со всеми, подписали NDA и похоже имеют доступ к архитектуре разных гпу и способах оптимизации.

    Потому что считаю крики «NVidia не может в OpenGL ES 3.0» не больше чем маркетинговым ходом.
    • +1
      но тогда у вас возникли бы всякие подёргивания и тд куда раньше чем вам реально понадобилось бы FP32
      Где-то в юнити на это жаловались…

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

      Под витой я мог красиво получать C поинтеры на объекты, буфера, текстуры, и потом читать и писать их как захочу
      Потому, что на вите одно железо на весь жизненный цикл — ты оптимизировал свою игру под айПад, вышел айПад 2 и она там уже не работает, вышла иОС 4 — и оно там не работает. Молчу уже про андроиды — там и с высокоуровневым АПИ все не работает =). На мобильниках же железо каждые пол года меняется, еще и выходят новые ОС, которые ломают старый функционал.

      З.Ы. Под PVR есть профиллировщик шейдеров и USSE, на iOS так же есть профиллировщик ОГЛ, есть довольно полезные вещи.
      • +3
        Извините, где там на Android с новыми версиями все ломается? Большинство приложений сейчас (или по крайней мере до недавнего времени) идут для 2.3+. Да и на iOS на обратную совместимость никто не жалуется, вроде бы.
        • 0
          Где я писал, что на Android с новыми версиями все ломается?
          А вот c iOS такое было на моей памяти раз — с выходом iOS 5. Эпл поменяли что-то в компиляторе шейдеров и в итоге у всех умников, которые забыли в вершинном шейдере указать precision qualifier упала производительность раза в 2-4.
          • 0
            Вот как раз про название системы не было ни слова.
            На мобильниках же железо каждые пол года меняется, еще и выходят новые ОС, которые ломают старый функционал.

            Вот я и позволил себе смелость отнести это высказывание в частности и к Android. Про iOS не скажу ничего — я устройства с ней даже в руках почти не держал.
        • +1
          Вы совсем не о том думаете. Будет ломаться пусть не от версии Андроида к версии, а от версии вендоровской прошивки к другой версии, от девайса к девайсу, от чипсета к чипсету. От стоковой прошивки к какому-нить Цианогену. Так что не, не надо пожалуй на Андроиде голые Сишные поинтеры и самостоятельно рисовать очередь команд — это всё прикольные хардкорные консольные фишки, но ровно под один-два девайса. Как правильно написал jimon должны быть вменяемые расширения для этого под всё что умеет чип хардово.
          • 0
            Насчет аппаратов и чипсетов ничего не могу сказать — опыта переноса приложений нет. А вот ICS⇔JB переносы (при помощи Titanium Backup) происходят с появлением минимальных глюков типа ошибок в цветах темы приложения, сток⇔MIUI тоже переносится все идеально (в рамках одного устройства)
      • 0
        было бы хорошо что бы USSE еще и асм показывал, только циклы — может быть не достаточно

        PS Vita — это очень спец. железо,
        как и PS3, xbox360 etc — свои расширения (включая HW) и драйвера и модели использования,

        хорошо что нет этого зоопарка на iOS, Android.
        • 0
          было бы хорошо что бы USSE еще и асм показывал, только циклы — может быть не достаточно
          Шейдер профайлер этого действительно не умеет. Но умеет USSE asm компилятор! К сожалению его нельзя свободно скачать, но можно получить отправив запрос в Imagination

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