Как стать автором
Обновить

OpenGL ES 3.0 в Android 4.3 — сжатие текстур ETC2

Время на прочтение 6 мин
Количество просмотров 72K
Совсем недавно вышла новая версия 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, пожалуйста проверьте работу этого приложения на этом устройстве.
Теги:
Хабы:
+54
Комментарии 17
Комментарии Комментарии 17

Публикации

Истории

Работа

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн