Introducción a RenderScript en Android
Spanish (Español) translation by steven (you can also view the original English article)
RenderScript es un lenguaje de scripting en Android que te permite escribir renderizado gráfico de alto rendimiento y código computacional sin procesar. Obtén más información sobre RenderScript y escribe tu primera aplicación de gráficos que aproveche RenderScript en este tutorial.
Las API de RenderScript se introdujeron formalmente en el SDK de Android en el nivel de API 11 (también conocido como Android 3.0, Honeycomb). RenderScript proporciona un medio para escribir código crítico para el rendimiento que el sistema luego compila en código nativo para el procesador en el que puede ejecutarse. Esta podría ser la CPU del dispositivo, una CPU de varios núcleos o incluso la GPU. En qué se ejecuta en última instancia, depende de muchos factores que no están disponibles para el desarrollador, pero también depende de la arquitectura que admita el compilador de la plataforma interna.
Este tutorial te ayudará a comenzar con un script de renderizado simple que llamamos "Falling Snow". Es un sistema de partículas donde cada copo de nieve está representado por un punto que cae, acelerando hacia abajo en la pantalla. El viento y alguna otra aleatoriedad crean un leve efecto de remolino.
Paso 0: Comenzando
RenderScript se basa en el lenguaje de programación C. Si no estás familiarizado con C, te recomendamos que te familiarices con él antes de intentar utilizar RenderScript. Aunque RenderScript no es OpenGL, ni requiere que lo uses para renderizar gráficos, los conceptos para usarlo son similares a los conceptos de OpenGL. Por lo tanto, será útil estar familiarizado con la terminología de gráficos 3D y OpenGL.
El código fuente de este tutorial está disponible para descargar. Recomendamos usarlo para seguir con el tutorial. Los listados de los códigos en este tutorial no incluyen el contenido completo de cada archivo.
Paso 1: Creando el script de renderizado
Comencemos con el paso más detallado y trabajemos para usar el script desde una actividad típica de Android.
Crea un nuevo archivo de proyecto llamado snow.rs en tu árbol src, debajo del paquete en el que estarás trabajando. En la parte superior, define la versión de RenderScript con la que estás trabajando:
1 |
#pragma version(1)
|
A continuación, configura el paquete Java al que pertenece este script, por ejemplo:
1 |
#pragma rs java_package_name(com.mamlambo.fallingsnow)
|
Usaremos algunas funciones de la API de gráficos RenderScript, así que incluye ese encabezado:
1 |
#include "rs_graphics.rsh" |
Ahora, define dos funciones, root() e init():
1 |
int root() { |
2 |
// TBD
|
3 |
}
|
4 |
|
5 |
void init() { |
6 |
// TBD
|
7 |
}
|
La función root() será el punto de entrada de este script. El valor de retorno define si el script se ejecuta una vez (devuelve 0) o en intervalos de N milisegundos (devuelve N). Si el hardware no puede seguir el ritmo de la frecuencia solicitada, root() se ejecuta con la mayor frecuencia posible.
La función init() se llama una vez cuando se carga el script y es un buen lugar para inicializar variables y otros parámetros de estado.
Crea una variable de malla que se inicializará en el lado de Android y crea una estructura simple para contener información sobre cada copo de nieve. Mientras lo haces, crea un par de variables para mantener los valores del viento y la gravedad.
1 |
rs_mesh snowMesh; |
2 |
|
3 |
typedef struct __attribute__((packed, aligned(4))) Snow { |
4 |
float2 velocity; |
5 |
float2 position; |
6 |
uchar4 color; |
7 |
} Snow_t; |
8 |
Snow_t *snow; |
9 |
|
10 |
float2 wind; |
11 |
float2 grav; |
Inicializa el viento y la gravedad en la función init():
1 |
grav.x = 0; |
2 |
grav.y = 18; |
3 |
wind.x = rsRand(50)+20; |
4 |
wind.y = rsRand(4) - 2; |
Inicializa la nieve en su propia función:
1 |
void initSnow() { |
2 |
const float w = rsgGetWidth(); |
3 |
const float h = rsgGetHeight(); |
4 |
|
5 |
int snowCount = rsAllocationGetDimX(rsGetAllocation(snow)); |
6 |
|
7 |
Snow_t *pSnow = snow; |
8 |
for (int i=0; i < snowCount; i++) { |
9 |
pSnow->position.x = rsRand(w); |
10 |
pSnow->position.y = rsRand(h); |
11 |
|
12 |
pSnow->velocity.y = rsRand(60); |
13 |
pSnow->velocity.x = rsRand(100); |
14 |
pSnow->velocity.x -= 50; |
15 |
|
16 |
uchar4 c = rsPackColorTo8888(255, 255, 255); |
17 |
pSnow->color = c; |
18 |
pSnow++; |
19 |
}
|
20 |
}
|
Antes de continuar, hablemos de la función initSnow(). Para empezar, se obtienen el ancho y el alto del área de dibujo. Luego, el script necesita saber cuántas estructuras de copos de nieve crearemos. Para ello, obtiene las dimensiones de la asignación, a las que hace referencia el puntero de nieve. Pero, ¿dónde se inicializa el puntero y qué es una asignación? El puntero se inicializa desde el código de Android. Una asignación es uno de los medios por los cuales la memoria es administrada por el código de Android, pero utilizada por el script. Los detalles internos no son importantes en este momento. Para nuestros propósitos, podemos considerarlo como una matriz de objetos de estructura Snow_t.
El bucle itera sobre cada estructura y establece algunos valores aleatorios para que la escena inicial parezca natural.
Ahora, implementemos una función simple llamada root() que dibuja la escena sin animación. Usaremos esto para poner el resto del sistema en su lugar:
1 |
int root() { |
2 |
rsgClearColor(0.0f, 0.0f, 0.0f, 0.0f); |
3 |
rsgDrawMesh(snowMesh); |
4 |
return 0; |
5 |
}
|
Cuando guardes el script en el proyecto Eclipse, los constructores crearán automáticamente un archivo llamado snow.bc en el directorio /res/raw. Este archivo generado automáticamente no debe registrarse en el control de código fuente, ni debe modificarse. Además, algunos archivos Java se crean en la carpeta /gen. Estos son los archivos de interfaz que se utilizan para llamar al script desde Android.
Paso 2: Inicializando el script
Ahora que se creó el script, debemos inicializarlo para usarlo desde tus clases de Android. Para hacer esto, hemos creado una clase auxiliar de Java llamada SnowRS. En él, asignamos la memoria para los copos de nieve, inicializamos el script y le vinculamos la asignación de malla y copo de nieve. Esta clase también usa un objeto RenderScriptGL. Este objeto se crea en el siguiente paso como parte de la clase 'View' que crearemos.
1 |
public class SnowRS { |
2 |
|
3 |
public static final int SNOW_FLAKES = 4000; |
4 |
private ScriptC_snow mScript; |
5 |
|
6 |
protected int mWidth; |
7 |
protected int mHeight; |
8 |
protected boolean mPreview; |
9 |
protected Resources mResources; |
10 |
protected RenderScriptGL mRS; |
11 |
|
12 |
public SnowRS(int width, int height) { |
13 |
mWidth = width; |
14 |
mHeight = height; |
15 |
}
|
16 |
|
17 |
public void stop() { |
18 |
mRS.bindRootScript(null); |
19 |
}
|
20 |
|
21 |
public void start() { |
22 |
mRS.bindRootScript(mScript); |
23 |
}
|
24 |
|
25 |
public void init(RenderScriptGL rs, Resources res, boolean isPreview) { |
26 |
mRS = rs; |
27 |
mResources = res; |
28 |
mPreview = isPreview; |
29 |
mScript = (ScriptC_snow) createScript(); |
30 |
}
|
31 |
|
32 |
public RenderScriptGL getRS() { |
33 |
return mRS; |
34 |
}
|
35 |
|
36 |
public Resources getResources() { |
37 |
return mResources; |
38 |
}
|
39 |
|
40 |
public ScriptC createScript() { |
41 |
ScriptField_Snow snow = new ScriptField_Snow(mRS, SNOW_FLAKES); |
42 |
Mesh.AllocationBuilder smb = new Mesh.AllocationBuilder(mRS); |
43 |
smb.addVertexAllocation(snow.getAllocation()); |
44 |
smb.addIndexSetType(Mesh.Primitive.POINT); |
45 |
Mesh sm = smb.create(); |
46 |
|
47 |
ScriptC_snow script; |
48 |
script = new ScriptC_snow(mRS, getResources(), R.raw.snow); |
49 |
script.set_snowMesh(sm); |
50 |
script.bind_snow(snow); |
51 |
script.invoke_initSnow(); |
52 |
return script; |
53 |
}
|
54 |
}
|
En particular, veamos el método createScript(). La primera sección crea la matriz de estructura con 4.000 entradas (SNOW_FLAKES = 4000). Luego usa esto para crear un objeto de malla, usado para renderizar. La construcción de renderizado se establece en POINT, por lo que cada copo de nieve se mostrará como un píxel en la pantalla.
A continuación, inicializamos el script en sí, utilizando la entrada de recursos en bruto creada por el constructor de Eclipse. Luego asignamos la malla y la asignación de matriz en el script a través de las llamadas set_snowMesh() y bind_snow(), respectivamente. Finalmente, inicializamos la nieve con una llamada a la función initSnow() que creamos anteriormente llamando a invoke_initSnow().
El script no comienza a ejecutarse en este punto, pero se ha llamado a la función init(). Para que el script se ejecute, llama a bindRootScript() en el objeto del script, como se ve en el método start().
Paso 3: Renderizado en una vista
El SDK de Android proporciona solo el objeto que necesitamos para la salida de RenderScript: la clase RSSurfaceView. Implementa una clase llamada FallingSnowView que amplíe esta clase, así:
1 |
public class FallingSnowView extends RSSurfaceView { |
2 |
private RenderScriptGL mRSGL; |
3 |
private SnowRS mRender; |
4 |
|
5 |
public FallingSnowView(Context context) { |
6 |
super(context); |
7 |
}
|
8 |
|
9 |
@Override
|
10 |
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { |
11 |
super.surfaceChanged(holder, format, w, h); |
12 |
|
13 |
if (mRSGL == null) { |
14 |
RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig(); |
15 |
mRSGL = createRenderScriptGL(sc); |
16 |
mRSGL.setSurface(holder, w, h); |
17 |
mRender = new SnowRS(w, h); |
18 |
mRender.init(mRSGL, getResources(), false); |
19 |
mRender.start(); |
20 |
}
|
21 |
}
|
22 |
|
23 |
@Override
|
24 |
protected void onDetachedFromWindow() { |
25 |
if (mRSGL != null) { |
26 |
mRSGL = null; |
27 |
destroyRenderScriptGL(); |
28 |
}
|
29 |
}
|
30 |
}
|
El método surfaceChanged() crea un objeto RenderScriptGL a partir del SurfaceHolder pasado, según sea necesario. Luego, se crea nuestro objeto SnowRS y se inicia el renderizado.
Paso 4: Completando la aplicación
Todo está en su lugar para usar la clase FallingSnowView dentro de una clase Activity. El método onCreate() de tu clase Activity podría ser tan simple como esto:
1 |
public void onCreate(Bundle savedInstanceState) { |
2 |
super.onCreate(savedInstanceState); |
3 |
snowView = new FallingSnowView(this); |
4 |
setContentView(snowView); |
5 |
}
|


Paso 5: Animando la nieve
¡Pero espera! Eso es solo una imagen estática. No es muy interesante, ¿verdad? Regresemos al archivo snow.rs y editemos la función root(). Para una simulación simple de estilo pseudo-físico, querrás iterar sobre cada copo de nieve y aplicar su velocidad y viento actuales a su posición. Luego, ajusta la velocidad según la aceleración de la gravedad. Por último, comprueba si ha caído nieve de la parte inferior de la pantalla. El nivel de complejidad y la eficiencia de la codificación aquí afectarán el tipo de velocidad de fotogramas que obtendrás en última instancia. Intentamos mantenerlo realmente simple para este tutorial.
Esto es lo que hemos hecho:
1 |
int root() { |
2 |
// Clear to the background color
|
3 |
rsgClearColor(0.0f, 0.0f, 0.0f, 0.0f); |
4 |
|
5 |
// time since last update
|
6 |
float dt = min(rsGetDt(), 0.1f); |
7 |
|
8 |
// dimens
|
9 |
float w = rsgGetWidth(); |
10 |
float h = rsgGetHeight(); |
11 |
|
12 |
int snowCount = rsAllocationGetDimX(rsGetAllocation(snow)); |
13 |
|
14 |
Snow_t *pSnow = snow; |
15 |
|
16 |
for (int i=0; i < snowCount; i++) { |
17 |
|
18 |
pSnow->position.x += ((pSnow->velocity.x +wind.x) * dt); |
19 |
pSnow->position.y += ((pSnow->velocity.y +wind.y) * dt); |
20 |
|
21 |
if (pSnow->position.y > h) { |
22 |
pSnow->position.y = 0; |
23 |
pSnow->position.x = rsRand(w); |
24 |
pSnow->velocity.y = rsRand(60); |
25 |
}
|
26 |
|
27 |
pSnow->velocity.x += (grav.x)*dt; |
28 |
pSnow->velocity.y += (grav.y)*dt; |
29 |
|
30 |
pSnow++; |
31 |
}
|
32 |
rsgDrawMesh(snowMesh); |
33 |
|
34 |
if (rsRand(32) == 1) { |
35 |
wind.x = 0-wind.x; |
36 |
}
|
37 |
|
38 |
return 30; |
39 |
}
|
Y aquí está en movimiento:
Paso 5: Temas avanzados
Este tutorial acaba de arañar la superficie de lo que RenderScript puede hacer (Jaja, ¿entiendes? ¿superficie?). Puedes utilizar un script de procesamiento de RenderScript para aplicar efectos gráficos a mapas de bits. Puedes agregar sombreadores para aprovechar el hardware de gráficos del dispositivo para dibujar la escena de manera diferente. Puedes configurar transformaciones para dibujar en un espacio 3D. Puedes configurar texturas para dibujar. Puedes hacer mucho más con RenderScript.
Si bien RenderScript es más limitante que usar OpenGL ES en el área de renderizado 3D, la adición de RenderScript de solo computación agrega algunas capacidades de bienvenida. Dibujar una escena 3D rápida usando RenderScript puede ser más eficiente, en cuanto a codificación, que usar OpenGL. El uso de RenderScript para cálculos pesados o manipulación de imágenes puede ser más rápido de desarrollar y funcionar mejor que soluciones NDK similares (debido a la distribución automática entre núcleos de hardware). A diferencia de cuando se desarrolla con el NDK de Android, no tienes que preocuparte por la arquitectura de hardware subyacente.
La principal desventaja de RenderScript es la falta de portabilidad del código existente. Si ya tienes código OpenGL o funciones computacionales en C que deseas aprovechar en tus aplicaciones de Android, es posible que desees seguir con el NDK de Android.
Conclusión
Este tutorial te ha dado una idea del uso de RenderScript con tus aplicaciones de Android. Aprendiste los conceptos básicos de escritura e inicialización de un script y renderizado en la pantalla. Todo esto se hizo en el contexto de un sistema de partículas simple que simula copos de nieve del tamaño de un píxel.
¡Cuéntanos en los comentarios sobre qué aplicaciones geniales has hecho en RenderScript!
Acerca de los autores
Los desarrolladores móviles Lauren Darcey y Shane Conder han sido coautores de varios libros sobre el desarrollo de Android: un libro de programación en profundidad titulado Desarrollo de aplicaciones inalámbricas en Android y Sams Teach Yourself Desarrollo de aplicaciones para Android en 24 horas. Cuando no están escribiendo, pasan su tiempo desarrollando software móvil en su empresa y brindando servicios de consultoría. Puedes comunicarte con ellos por correo electrónico a androidwirelessdev+mt@gmail.com, a través de su blog en androidbook.blogspot.com y en Twitter @androidwireless.



