Решение проблемы с нехваткой памяти при загрузке текстур в Android
Как известно, в популярной нынче мобильной ОС Android есть фатальный недостаток — это язык Java. Более того, каждому приложению выделяется кусок памяти размером от 32-х мегабайт до бесконечности в зависимости от желания производителя мобильного телефона.
Если вы делаете большое приложение с использованием OpenGLES и пачкой больших текстур размером 2048×2048 (а это 16 мегабайт для формата RGBA), то памяти для загрузки у вас не будет. После загрузки второй-третьей, пятой текстуры с помощью BitmapFactory
приложение упадет с OutOfMemoryError
, что есть очень плохо.
Хочу поделиться решением этой проблемы. Она состоит из двух пунктов: загружать изображения из PNG с помощью libpng и исключить работу с большими блоками данных из Java части.
Решение первой части и исходники для прямой загрузки изображения из PNG файла можно найти на stackoverflow.com.
Для работы с памятью создадим вот такой Java класс:
package com.example.memory;
import java.nio.IntBuffer;
public class MemoryManager {
public static native IntBuffer create(int sizeInBytes);
public static native void release(IntBuffer buffer);
}
И нативную часть (язык cpp):
#include <jni.h>
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jobject JNICALL Java_com_example_memory_MemoryManager_create(
JNIEnv *env,
jclass cls,
jint sizeInBytes)
{
// Выделяем блок памяти
void *buf = malloc(sizeInBytes);
// Создаем буфер
return env->NewDirectByteBuffer(buf, 0);
}
JNIEXPORT void JNICALL Java_com_example_memory_MemoryManager_release(
JNIEnv *env,
jclass cls,
jobject buffer)
{
// Освобождаем память
free(env->GetDirectBufferAddress(buffer));
}
#ifdef __cplusplus
}
#endif
Далее алгоритм работы такой:
- Читаем высоту и ширины изображения из первых 24 байт PNG (подробности в статье на хабре: Получаем тип и размеры изображения без скачивания его целиком, используя Python)
- Выделяем
IntBuffer
с помощьюMemoryManager
размером W*H*4 (32bit RGBA, 4 байта на пиксель) - Считываем в него PNG файл
- Загружаем эти данные в текстуру с помощью
glTexImage2d
- Освобождаем
IntBuffer
вMemoryManager
Честно говоря, я слабо понимаю — почему в нативе вызывается функция NewDirectByteBuffer
, а в Java приходит IntBuffer
, а не ByteBuffer
. Загадка какая-то.
UPD: Если вы решили пойти стандартным путем с загрузкой Bitmap
и GLUtil.texImage2D
, то не забывайте вызывать bitmap.recycle()
после загрузки изображения в текстуру, дабы освободить нативную память.
Ностальгия
Никогда не обращал внимания, что там происходит на фоне. А там оказывается красота!
Отдельно музыку можно послушать тут.
Социальные нормы потребления электричества
Отличную идею придумало правительство во главе с незабвенным нашим Дмитрием Анатольевичем — социальные нормы потребления электричества. Сама идея проста как дважды два — смотрим среднее потребление электричества по региону, берем за него текущую цену, а остальное, что народ нажжет, в полтора-четыре раза больше.
По замыслу этих славных товарищей, бесконечно далеких от народа, большая часть людей будет получать электричество по обычной цене, а остальные за тройной счет. Ибо нефиг электричество тратить, мало его осталось, на всех не хватает. Не ты, чувак, и не твои предки электростанции строили, а лично Чубайс собака в поте лица вкалывал по 30 часов в сутки сразу после войны под чутким руководством Сталина, затем Хрущева, а после Брежнева с Горбачевым.
Как определять пайку каждому человеку российскому, чтоб не обидно никому было — непонятно. Идея-то пришла, а палец, откуда они эту идею высосали, кончился, видимо. Напрягли тогда тугой череп власть держащие и решили поставить ЭКСПЕРИМЕНТ над народом: установить норму потребления электричества в 50-70КВт на пару месяц в самых нищих районах России-матушки и посмотреть кто сколько потребляет.
Задумайтесь на пару секунд: что будет делать не самый богатый народ, чтоб за эту норму не выскочить ненароком?
ПРАВИЛЬНО! Он будет экономить на всем, вплоть до зажигания лучин и сидения в полной темноте по вечерам.
Представим, что будет дальше: эксперимент прошел, наверх ушла бумага о потреблении электричества в данном регионе. Правительство посмотрит на отчет и подумает — АГА, тут потребление электричества всего 10КВт на душу. Почешет задумчиво нимб и сделает 10КВт для всей России нормой. Ловко, а? Ну это же профит в чистом виде!
Поэтому, уважаемые жители тех регионов, где проводят сейчас этот эксперимент — жгите как можно больше электричества! Я вас очень прошу! Остальные регионы будут вам только благодарны.
Основы библиотеки PIXI.js
Библиотека PIXI.js предназначена для вывода 2D графики с помощью javascript. Она может работать с двумя типами рендеров: Canvas и WebGL.
В этой библиотеке вся сцена представлена деревом элементов, которые присоединены к объекту типа Stage
. Это root для всех элементов сцены. Отрисовка начинается с него и проходит по всем детям этого объекта в прямом порядке.
Базовый «кирпичик» сцены — объект типа DisplayObject
. Он содержит: 2D позицию, поворот, масштаб, прозрачность, цвет.
Набор элементов типа DisplayObject можно объединить с помощью DisplayObjectContainer
. Это наследник от класса DisplayObject с поддержкой добавления и удаления детей.
Примитивное создание сцены PIXI.js выглядит так:
function createScene() {
var stage = new PIXI.Stage();
var container = new PIXI.DisplayObjectContainer();
var object = new PIXI.DisplayObject();
container.addChild(object);
stage.addChild(container);
return stage;
}
Класс DisplayObject, по сути, бесполезно использовать напрямую, он не содержит никакой визуальной информации. Самый часто используемый класс на базе DisplayObject — это класс Sprite
. Он представляет собой наследника от DisplayObject и добавляет к нему текстуру и выбор режима отрисовки этой текстуры.
Для задания текстуры используется класс BaseTexture
. Это другой базовый «кирпичик» библиотеки PIXI.js. Он является простой оберткой над изображением. Содержит в себе размер этого изображения, ссылку на источник и загруженные данные типа Image.
Таким образом создание простой сцены теперь выглядит так:
function createScene() {
var stage = new PIXI.Stage();
var texture = new PIXI.BaseTexture("image.png");
var sprite = new PIXI.Sprite(texture);
stage.addChild(sprite);
return stage;
}
Следующим по списку, но не по значению, идет класс Texture
. Он содержит в себе ссылку на BaseTexture и прямоугольник, который задает видимую область BaseTexture.
С помощью класса Texture можно создавать такой полезный объект как текстурный атлас. Для этого необходимо загрузить изображение с помощью BaseTexture, а затем создать на его основе множество мелких объектов типа Texture со ссылкой на базовую текстуру и прямоугольник. Таким образом достигается уменьшение объема используемой памяти и увеличение скорости работы.
function createTextures() {
// загружаем изображение размером 1024x1024
var atlas = new PIXI.BaseTexture("atlas.png");
// левая верхняя часть размером 512x512
var textureLT = new PIXI.Texture(atlas, new Rectangle(0, 0, 512, 512);
// правая верхняя часть размером 512x512
var textureRT = new PIXI.Texture(atlas, new Rectangle(512, 0, 512, 512);
// левая нижняя часть размером 512x512
var textureLB = new PIXI.Texture(atlas, new Rectangle(0, 512, 512, 512);
// правая нижняя часть размером 512x512
var textureRB = new PIXI.Texture(atlas, new Rectangle(512, 512, 512, 512);
}
В текстурный атлас при желании можно сохранить всю сцену, которая будет рисоваться за один проход WebGL рендера.
На этой радостной ноте закончу описание PIXI.js. Спасибо за внимание. Как всегда ваши комментарии и угрозы вопросы вы можете оставлять прямо здесь.