안드로이드 RenderScript 시작하기

안드로이드 RenderScript는 스크립팅 언어로 고성능 그래픽 렌더링 및 네이티브코드를 작성 할 수 있다. 안드로이드의 고성능앱을 제작하기 위해 필 수 적으로 선택해야 할 부분이다. 요즘들어서도 다양한 안드로이드 개발 커뮤니티에서도 RenderScript에 대해 많이 언급되고 있다.

RenderScript API는 공식적으로 API 레벨 11(안드로이드 3.0, 허니컴)에서 안드로이드 SDK에 소개 되었다.(support.v8.renderscript 라이브러리를 통해 API 레벨 8이상 지원 가능하다.) RenderScript는 프로세서 실행시 네이티브코드로 변후 실행 하는 구조로 고성능을 유지한다. 장치의 CPU, 멀티코어 CPU, GPU등에서 실행 될 수 있다. 이는 개발자가 쉽게 사용할 수 없는 여러 요인에 따라 달리 실행될 뿐만 아니라, 내부 플랫폼 컴파일러가 지원하는 아키텍처에 따라 성능이 달라진다. 컴파일과정을 좀 더 알고 싶으면 여기를 참고하자.

시작하기

RenderScript는 C언어를 기반으로 한다. 그래픽 엔더링에 사용하기위해 OpenGL은 아니지만 비슷한 개념이다. 따라서 C언어와 OpenGL및 3D 그래픽용어에 익숙한 분이라면 많은 도움이 된다.

렌더링 스크립트 만들기

렌더 스크립트 파일은 *.rs파일로 프로젝트의 패키에서 만들면 정상적으로 작동 된다. *.rs파일에 대해서 하나씩 알아보자.

렌더스크립트의 버전을 정의

#pragma version(1)

스크립트가 속한 패키지명

#pragma rs java_package_name(com.android.renderscript)

RenderScript 그래픽 API의 일부 기능을 사용할 헤더 include

#include "rs_graphics.rsh"

필수 method root(), init() 정의

int root() {
 // TBD
}
void init() {
 // TBD
}

root()함수는 스크립트의 진입시점이 되며, return 값은 밀리세컨트단위로 재호출 될시간 간격이다. 예를 들어 1이면 1ms마다 실행 된다. 이렇게 밀리세컨드는 하드웨어 성능에 따라 수행 될 수도 있고 안 될 수도 있다.

init()함수는 최초 스크립트가 로그 될때 한번만 수행며, 변수를 초기화 할 할때 사용하면 된다.

점을 포인트된 좌표로 이동하기 위해 점들의 정보를 저장하는 간단한 구조체를 만든다. 여기에는 점의 델타값, 색, 포지션정보가 들어있다.

typedef struct __attribute__((packed, aligned(4))) Point {
    float2 delta;
    float2 position;
    uchar4 color;
} Point_t;
Point_t *point;

여기서는 별도의 init()으로 초기화 하지 않는다.

점을 해당 갯수만큼 초기화 한다.

void initParticles()
{
    int size = rsAllocationGetDimX(rsGetAllocation(point));
    float width = rsgGetWidth();
    float height = rsgGetHeight();
    uchar4 c = rsPackColorTo8888(0.0f, 0.9f, 0.9f);
    Point_t *p = point;
    
    for (int i = 0; i < size; i++) {
        p->position.x = rsRand(width);
        p->position.y = rsRand(height);
        p->delta.x = 0;
        p->delta.y = 0;
        p->color = c;
        p++;
    }
    
    initialized = 1;
}

root()에서는 1ms마다 선택된 자표로 점들을 이동시킨다.

int root() {
        
    float width = rsgGetWidth();
    float height = rsgGetHeight();
    
    rsgClearColor(0.f, 0.f, 0.f, 1.f);
    
    int size = rsAllocationGetDimX(rsGetAllocation(point));
    Point_t *p = point;

    for (int i = 0; i < size; i++) {
        float diff_x = gTouchX - p->position.x;
        float diff_y = gTouchY - p->position.y;
        float acc = 50.f/(diff_x * diff_x + diff_y * diff_y);
        float acc_x = acc * diff_x;
        float acc_y = acc * diff_y;
        p->delta.x += acc_x;
        p->delta.y += acc_y;
        
        p->position.x += p->delta.x;
        p->position.y += p->delta.y;
            
        p->delta.x *= 0.96;
        p->delta.y *= 0.96;
        
        if (p->position.x > width) {
            p->position.x = 0;
        } else if (p->position.x < 0) {
            p->position.x = width;
        }
        
        if (p->position.y > height) {
            p->position.y = 0;
        } else if (p->position.y < 0) {
            p->position.y = height;
        }
        
        p++;
    }    
    
    rsgDrawMesh(partMesh);
    
    return 1;
}

이렇게 점들에 대해서 생성및 초기화 후 root()에서 ms당 포지션이동을 통해 .rs파일을 작성 해보았다. 파일을 저장하면 빌더가 자동으로 /res/raw에 .bc라는 파일이 생성된다.(자동빌드 설정 일경우.) 또한, 일부 Java파일을 숨겨친체로 패키지내에 생서된다. 이 Java파일은 스크립트와 Java의 호출을 위한 인터페이스로 사용된다.  

스크립트 초기화

스크립트가 생성되어 안드로이드 클래스 내에서 사용하기 위해 초기화 해야한다. 이 스크립트를 호출하기위한 helper를 만들어 보자.

package com.android.sample;

import android.content.res.Resources;
import android.renderscript.*;

public class GravityRS {
    public static final int PART_COUNT = 10000; // Count of particles

    public GravityRS() {
    }

    private Resources mRes;
    private RenderScriptGL mRS;
    private ScriptC_gravity mScript;

    public void init(RenderScriptGL rs, Resources res, int width, int height) {
        mRS = rs;
        mRes = res;

        ProgramFragmentFixedFunction.Builder pfb = new ProgramFragmentFixedFunction.Builder(rs);
        pfb.setVaryingColor(true);
        rs.bindProgramFragment(pfb.create());
        
        ScriptField_Point points = new ScriptField_Point(mRS, PART_COUNT);
        
        Mesh.AllocationBuilder smb = new Mesh.AllocationBuilder(mRS);
        smb.addVertexAllocation(points.getAllocation());
        smb.addIndexSetType(Mesh.Primitive.POINT);
        Mesh sm = smb.create();

        mScript = new ScriptC_gravity(mRS, mRes, R.raw.gravity);
        mScript.set_partMesh(sm);
        mScript.bind_point(points);
        mRS.bindRootScript(mScript);
        
        mScript.invoke_initParticles(); // Initialize Particles
    }

    public void newTouchPosition(float x, float y, float pressure, int id) {
        mScript.set_gTouchX(x);
        mScript.set_gTouchY(y);
    }
}

ScriptField_Point는 .rs의 point구조체를 생성하며, set_partMesh는 .rs의 partMesh변수에 sm을 대입하는 것이다. 또한 bind_point는 points주소를 대입하는 것이다.

set_xxxxx(변수명)(값) 일경우 해당 변수에 값을 대입.

bind_xxxx(변수명)(주소값) 일경우 주소를 대입 하는 것이다.

invoke_xxxx()는 해당 함수를 수행한다.

위에서는 점의 갯수를 넘겨 initParticles()를 실행하면 해당 갯수만큼의 배열이 초기화 된다. 초기화 후 시작을 하기위해서는 bindRootScript()를 실행 하게되면 root()함수를 타게 되어 화면에 그려지기 시작 할 것이다.

View에 보이기

안드로이드 SDK에서 RenderScrpit출력에 필요한 RSSurfaceView를 제공한다.

public class GravityView extends RSSurfaceView {

    public GravityView(Context context) {
        super(context);
    }

    private RenderScriptGL mRS;
    private GravityRS mRender;

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        super.surfaceChanged(holder, format, w, h);
        if (mRS == null) {
            RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig();
            mRS = createRenderScriptGL(sc);
            mRS.setSurface(holder, w, h);

            mRender = new GravityRS();
            mRender.init(mRS, getResources(), w, h);
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        if (mRS != null) {
            mRS = null;
            destroyRenderScriptGL();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev)
    {
        mRender.newTouchPosition(ev.getX(0), ev.getY(0), ev.getPressure(0), ev.getPointerId(0));        
        return true;
    }
}

RSSurfaceView를 확장 하여 surfaceChanged()에서 SurfaceHolder를 이용하여 RenderSrciptGL개체를 생성한다. 그리고 아까전에 만들어 놓은 스크립트 helper인 GravityRS 개체를 생성된 후 렌더링이 시작된다.

이렇게 View를 만들었기 때문에 Layout에서 자유자제로 불러와서 사용 하면 된다.

이 소스코드는 아래 SVN을 통해서 받을 수 있다.

SVN: http://renderscript-examples.googlecode.com/svn/trunk/Gravity

실제 작동 화면

v34ad4YYFccIhSS6dA69sh6

결론

위의 예제는 점 3만개를 해당 포인터 지점으로 이동하는 것이다. SDK의 일반 View를 통해서 엄두도 못내는 속도로 RenderScript는 충분히 처리하고 있는 것을 볼 수 있다. 약간 복한것 같지만 실제로 C언어와 약간의 OpenGL을 접해본 개발자라면 아주 익숙할 것 이며, RenderScrpit를 통해서 멋진 어플리케이션을 만들 수 있다는것에 얼마나 흥미로운지 생각 해보길 바란다.

“안드로이드 RenderScript 시작하기”에 대한 4개의 생각

  1. 코드를 실행 해보고 싶은데요
    window 7 에 이클립스로 하려고 하는데 렌더스크립트 구문들은 다 오류가 나는데

    조금 구체적인 설명을 얻을수 있을까요?

  2. android studio에서 해봤는데..
    아래와 같이 에러나네요???
    뭐가 잘못되었을까요??

    android-renderscript-compiler: dyld: Library not loaded: @rpath/libclang.dylib
    android-renderscript-compiler: Referenced from: /Users/kmkwak/dev/android/sdk/build-tools/19.0.0/llvm-rs-cc
    android-renderscript-compiler: Reason: image not found

댓글 남기기