Статьи

Начало работы с RenderScript на Android

RenderScript — это язык сценариев на Android, который позволяет писать высокопроизводительный графический рендеринг и необработанный вычислительный код. Узнайте больше о RenderScript и напишите свое первое графическое приложение, которое использует RenderScript, в этом руководстве.

API-интерфейсы RenderScript были официально представлены в Android SDK в API Level 11 (он же Android 3.0, Honeycomb). RenderScript предоставляет средства для написания критичного для производительности кода, который система впоследствии компилирует в собственный код для процессора, на котором она может работать. Это может быть процессор устройства, многоядерный процессор или даже графический процессор. То, на чем он работает, зависит от многих факторов, которые недоступны для разработчика, но также зависят от того, какую архитектуру поддерживает компилятор внутренней платформы.

Этот урок поможет вам начать с простого скрипта рендеринга, который мы называем «Падающий снег». Это система частиц, в которой каждая снежинка представлена ​​точкой, которая падает и ускоряется вниз по экрану. Ветер и некоторые другие случайности создают мягкий закрученный эффект.


RenderScript основан на языке программирования C. Если вы не знакомы с C, рекомендуем сначала ознакомиться с ним, прежде чем пытаться использовать RenderScript. Хотя RenderScript не является OpenGL и не требует его использования для визуализации графики, концепции его использования аналогичны концепциям OpenGL. Таким образом, знакомство с терминологией OpenGL и 3D-графики поможет.

Открытый исходный код этого руководства доступен для скачивания . Мы рекомендуем использовать его, чтобы следовать. Перечни кодов в этом руководстве не включают в себя все содержимое каждого файла.


Давайте начнем с самого подробного шага и приступим к использованию сценария в типичном действии Android.

Создайте новый файл проекта с именем snow.rs в своем дереве src, под пакетом, в котором вы будете работать. Вверху определите версию RenderScript, с которой вы работаете:

1
#pragma version(1)

Затем установите пакет Java, к которому принадлежит этот скрипт, например:

1
#pragma rs java_package_name(com.mamlambo.fallingsnow)

Мы будем использовать некоторые функции из графического API RenderScript, поэтому включите этот заголовок:

1
#include «rs_graphics.rsh»

Теперь определим две функции, root () и init ():

1
2
3
4
5
6
7
int root() {
 // TBD
}
 
void init() {
 // TBD
}

Функция root () будет точкой входа в этот скрипт. Возвращаемое значение определяет, будет ли скрипт выполняться один раз (возвращать 0) или с интервалом в N миллисекунд (возвращать N). Если оборудование не справляется с запрошенной частотой, то root () запускается так часто, как может.

Функция init () вызывается один раз при загрузке скрипта и является хорошим местом для инициализации переменных и других параметров состояния.

Создайте переменную сетки, которая будет инициализироваться на стороне Android, и создайте простую структуру для хранения информации о каждой снежинке. Пока вы это делаете, создайте пару переменных для хранения значений ветра и гравитации.

01
02
03
04
05
06
07
08
09
10
11
rs_mesh snowMesh;
 
typedef struct __attribute__((packed, aligned(4))) Snow {
    float2 velocity;
    float2 position;
    uchar4 color;
} Snow_t;
Snow_t *snow;
 
float2 wind;
float2 grav;

Инициализируйте ветер и гравитацию в функции init ():

1
2
3
4
grav.x = 0;
grav.y = 18;
wind.x = rsRand(50)+20;
wind.y = rsRand(4) — 2;

Инициализируйте снег в своей собственной функции:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
void initSnow() {
    const float w = rsgGetWidth();
    const float h = rsgGetHeight();
     
    int snowCount = rsAllocationGetDimX(rsGetAllocation(snow));
     
    Snow_t *pSnow = snow;
    for (int i=0; i < snowCount; i++) {
        pSnow->position.x = rsRand(w);
        pSnow->position.y = rsRand(h);
         
        pSnow->velocity.y = rsRand(60);
        pSnow->velocity.x = rsRand(100);
        pSnow->velocity.x -= 50;
         
        uchar4 c = rsPackColorTo8888(255, 255, 255);
        pSnow->color = c;
        pSnow++;
    }
}

Прежде чем мы продолжим, давайте поговорим о функции initSnow (). Для начала, ширина и высота области рисования извлекаются. Затем скрипт должен знать, сколько структур снежинок мы создадим. Это достигается путем получения размеров распределения, на которое ссылается указатель снега. Но где указатель инициализируется и каково распределение? Указатель инициализируется из кода Android. Выделение — это одно из средств, с помощью которого память управляется кодом Android, но используется сценарием. Внутренние детали не важны в настоящее время. Для наших целей мы можем думать об этом как о массиве объектов структуры Snow_t.

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

Теперь давайте реализуем простую функцию root (), которая рисует сцену без анимации. Мы будем использовать это для установки остальной части системы:

1
2
3
4
5
int root() {
    rsgClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    rsgDrawMesh(snowMesh);
    return 0;
}

Когда вы сохраняете файл проекта скрипта в Eclipse, сборщики автоматически создают файл с именем snow.bc в каталоге / res / raw. Этот автоматически сгенерированный файл не должен быть возвращен в систему контроля версий и не должен изменяться. Кроме того, некоторые файлы Java создаются в папке / gen. Это файлы интерфейса, используемые для вызова скрипта из Android.


Теперь, когда скрипт создан, нам нужно инициализировать его для использования в ваших классах Android. Для этого мы создали вспомогательный класс Java под названием SnowRS. В нем мы выделяем память для снежинок, инициализируем скрипт и привязываем к нему сетку и снежинки. Этот класс также использует объект RenderScriptGL. Этот объект создается на следующем шаге как часть класса View, который мы создадим.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class SnowRS {
 
    public static final int SNOW_FLAKES = 4000;
    private ScriptC_snow mScript;
 
    protected int mWidth;
    protected int mHeight;
    protected boolean mPreview;
    protected Resources mResources;
    protected RenderScriptGL mRS;
     
    public SnowRS(int width, int height) {
        mWidth = width;
        mHeight = height;
    }
 
    public void stop() {
        mRS.bindRootScript(null);
    }
 
    public void start() {
        mRS.bindRootScript(mScript);
    }
     
    public void init(RenderScriptGL rs, Resources res, boolean isPreview) {
        mRS = rs;
        mResources = res;
        mPreview = isPreview;
        mScript = (ScriptC_snow) createScript();
    }
     
    public RenderScriptGL getRS() {
        return mRS;
    }
     
    public Resources getResources() {
        return mResources;
    }
     
    public ScriptC createScript() {
        ScriptField_Snow snow = new ScriptField_Snow(mRS, SNOW_FLAKES);
        Mesh.AllocationBuilder smb = new Mesh.AllocationBuilder(mRS);
        smb.addVertexAllocation(snow.getAllocation());
        smb.addIndexSetType(Mesh.Primitive.POINT);
        Mesh sm = smb.create();
 
        ScriptC_snow script;
        script = new ScriptC_snow(mRS, getResources(), R.raw.snow);
        script.set_snowMesh(sm);
        script.bind_snow(snow);
        script.invoke_initSnow();
        return script;
    }
}

В частности, давайте посмотрим на метод createScript (). Первый раздел создает структурный массив с 4000 записей (SNOW_FLAKES = 4000). Затем он использует это для создания объекта сетки, используемого для рендеринга. Конструкция рендеринга установлена ​​в POINT, поэтому каждая снежинка будет отображаться в виде пикселя на экране.

Далее мы инициализируем сам скрипт, используя необработанную запись ресурса, созданную Eclipse Builder. Затем мы назначаем сетку и размещение массива в сценарии с помощью вызовов set_snowMesh () и bind_snow () соответственно. Наконец, мы инициализируем снег вызовом функции initSnow (), которую мы создали ранее, вызывая invoke_initSnow ().

Сценарий не запускается в этот момент, но была вызвана функция init (). Чтобы запустить скрипт, вызовите bindRootScript () для объекта скрипта, как показано в методе start ().


Android SDK предоставляет только тот объект, который нам нужен для вывода RenderScript: класс RSSurfaceView. Реализуйте класс с именем FallingSnowView, который расширяет этот класс, например:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class FallingSnowView extends RSSurfaceView {
    private RenderScriptGL mRSGL;
    private SnowRS mRender;
     
    public FallingSnowView(Context context) {
        super(context);
    }
 
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        super.surfaceChanged(holder, format, w, h);
 
        if (mRSGL == null) {
            RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig();
            mRSGL = createRenderScriptGL(sc);
            mRSGL.setSurface(holder, w, h);
            mRender = new SnowRS(w, h);
            mRender.init(mRSGL, getResources(), false);
            mRender.start();
        }
    }
     
    @Override
    protected void onDetachedFromWindow() {
        if (mRSGL != null) {
            mRSGL = null;
            destroyRenderScriptGL();
        }
    }
}

Метод surfaceChanged () создает объект RenderScriptGL из переданного в SurfaceHolder при необходимости. Затем создается наш объект SnowRS и начинается рендеринг.


Все готово для использования класса FallingSnowView в классе Activity. Метод onCreate () вашего класса Activity может быть таким простым:

1
2
3
4
5
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    snowView = new FallingSnowView(this);
    setContentView(snowView);
}

Но ждать! Это просто статичное изображение. Не очень интересно, правда? Вернемся к файлу snow.rs и отредактируем функцию root (). Для некоторого простого моделирования в стиле псевдофизики вы захотите перебрать каждую снежинку и применить ее текущую скорость и ветер к ее положению. Затем настройте скорость на основе ускорения силы тяжести. Наконец, проверьте, не выпал ли снег с нижней части экрана. Уровень сложности и эффективность кодирования здесь будут влиять на то, какую частоту кадров вы в конечном итоге получите. Мы постарались сделать это действительно просто для этого урока.

Вот что мы сделали:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
int root() {
    // Clear to the background color
    rsgClearColor(0.0f, 0.0f, 0.0f, 0.0f);
 
    // time since last update
    float dt = min(rsGetDt(), 0.1f);
     
    // dimens
    float w = rsgGetWidth();
    float h = rsgGetHeight();
     
    int snowCount = rsAllocationGetDimX(rsGetAllocation(snow));
     
    Snow_t *pSnow = snow;
     
    for (int i=0; i < snowCount; i++) {
         
        pSnow->position.x += ((pSnow->velocity.x +wind.x) * dt);
        pSnow->position.y += ((pSnow->velocity.y +wind.y) * dt);
 
        if (pSnow->position.y > h) {
            pSnow->position.y = 0;
            pSnow->position.x = rsRand(w);
            pSnow->velocity.y = rsRand(60);
        }
         
        pSnow->velocity.x += (grav.x)*dt;
        pSnow->velocity.y += (grav.y)*dt;
 
        pSnow++;
    }
    rsgDrawMesh(snowMesh);
 
    if (rsRand(32) == 1) {
        wind.x = 0-wind.x;
    }
     
    return 30;
}

И вот оно в движении:


Этот урок только что показал, что может делать RenderScript (Ха-ха, понимаешь? Поверхность?). Вы можете использовать вычислительный скрипт RenderScript для применения графических эффектов к растровым изображениям. Вы можете добавить шейдеры, чтобы использовать графическое оборудование устройства, чтобы нарисовать сцену по-другому. Вы можете настроить преобразования для рисования в трехмерном пространстве. Вы можете настроить текстуры для рисования. С RenderScript вы можете сделать гораздо больше.

Хотя RenderScript более ограничен, чем использование OpenGL ES в области 3D-рендеринга, добавление RenderScript только для вычислений добавляет некоторые полезные возможности. Рисование быстрой трехмерной сцены с использованием RenderScript может быть более эффективным с точки зрения кодирования, чем использование OpenGL. Использование RenderScript для сложных вычислений или манипуляций с изображениями может быстрее разрабатываться и работать лучше, чем аналогичные решения NDK (из-за автоматического распределения по аппаратным ядрам). В отличие от разработки с Android NDK, вам не нужно беспокоиться о базовой аппаратной архитектуре.

Основным недостатком RenderScript является отсутствие переносимости существующего кода. Если у вас уже есть код OpenGL или вычислительные функции в C, которые вы хотите использовать в своих приложениях для Android, вы можете просто придерживаться Android NDK.


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

Дайте нам знать, какие классные приложения RenderScript вы создаете в комментариях!

Разработчики мобильных приложений Лорен Дарси и Шейн Кондер являются соавторами нескольких книг по разработке Android: углубленная книга по программированию под названием « Разработка беспроводных приложений для Android» и « Самс научи себя разрабатывать приложения для Android за 24 часа» . Когда они не пишут, они тратят свое время на разработку мобильного программного обеспечения в своей компании и оказание консультационных услуг. С ними можно связаться по электронной почте [email protected] , через их блог на androidbook.blogspot.com и в Twitter @androidwireless .

Купить Android-разработку беспроводных приложений, 2-е издание Купить Sam's Teach Yourself для Android-разработки приложений в течение 24 часов Код Мамламбо в Код-Каньоне