고급 RenderScript

RenderScript를 사용하는 애플리케이션은 여전히 Android VM 내에서 실행되므로 익숙한 모든 프레임워크 API에 액세스할 수 있지만 필요한 경우 RenderScript를 활용할 수 있습니다. 프레임워크와 RenderScript 런타임 간의 상호작용을 원활하게 하기 위해 중간 계층의 코드도 제공되므로 두 코드 레벨 간의 소통과 메모리 관리가 용이해집니다. 이 문서에서는 코드의 이러한 다양한 레이어와 함께, Android VM과 RenderScript 런타임 간에 메모리가 공유되는 방식에 대해 자세히 설명합니다.

RenderScript 런타임 레이어

RenderScript 코드는 잘 정의된 작은 런타임 레이어에서 컴파일되고 실행됩니다. RenderScript 런타임 API는 프로세서에서 사용할 수 있는 코어 수만큼 자동으로 확장되고 이식 가능한 고도의 연산 작업을 지원합니다.

참고: NDK의 표준 C 함수는 CPU에서 실행되도록 보장되어야 합니다. RenderScript는 다른 유형의 프로세서에서 실행되도록 설계되기 때문에 그 함수의 라이브러리에 액세스할 수 없습니다.

RenderScript 코드는 Android 프로젝트의 src/ 디렉터리에 있는 .rs 파일과 .rsh 파일에서 정의할 수 있습니다. 정의된 코드는 Android 빌드의 일부로 실행되는 llvm 컴파일러를 통해 중간 바이트 코드로 컴파일됩니다. 애플리케이션이 기기에서 실행되면 바이트 코드는 그 기기에 있는 또 다른 llvm 컴파일러에 의해 기계어 코드로 (거의 실시간) 컴파일됩니다. 기계어 코드는 기기에 최적화되고 캐시되기도 하므로 RenderScript를 사용하는 애플리케이션을 이후에 사용할 때 바이트 코드가 다시 컴파일되지 않습니다.

RenderScript 런타임 라이브러리의 몇 가지 주요 기능은 다음과 같습니다.

  • 메모리 할당 요청 기능
  • 스칼라 및 벡터 유형으로 오버로드된 다양한 일반 루틴 버전이 있는 대규모 수학 함수 모음. 원자형 산술 함수 및 비교 함수와 함께 더하기, 곱하기, 내적, 외적과 같은 연산도 가능합니다.
  • 원시 데이터 유형 및 벡터에 대한 전환 루틴, 행렬 루틴 및 날짜 및 시간 루틴
  • RenderScript 시스템을 지원하는 데이터 유형 및 구조(예: 2벡터, 3벡터, 4벡터를 정의하는 벡터 유형)
  • 로깅 함수

사용 가능한 함수에 관한 자세한 내용은 RenderScript 런타임 API 참조를 확인하세요.

반영된 레이어

반영된 레이어는 Android 프레임워크에서 RenderScript 런타임에 액세스할 수 있도록 허용하기 위해 Android 빌드 도구가 생성하는 클래스 집합입니다. 이 레이어는 RenderScript 코드에 정의된 포인터에 메모리를 할당하고 그 메모리를 사용할 수 있도록 허용하는 메서드와 생성자도 제공합니다. 다음 목록에는 반영되는 주요 구성요소가 설명되어 있습니다.

  • 생성하는 모든 .rs 파일은 ScriptC 유형의 project_root/gen/package/name/ScriptC_renderscript_filename 클래스에 생성됩니다. 이 파일은 .java 버전의 .rs 파일로, Android 프레임워크에서 호출할 수 있습니다. 이 클래스에는 .rs 파일에서 반영된 다음 항목이 들어 있습니다.
    • 비정적 함수
    • 비정적의 전역 RenderScript 변수. 접근자 메서드가 변수마다 생성되므로 Android 프레임워크에서 RenderScript 변수를 읽고 쓸 수 있습니다. 전역 변수가 RenderScript 런타임 레이어에서 초기화되면 그 값은 대응되는 Android 프레임워크 레이어 값을 초기화하는 데 사용됩니다. 전역 변수가 const로 표시되면 set 메서드가 생성되지 않습니다. 자세한 내용은 여기를 확인하세요.

    • 전역 포인터
  • structScript.FieldBase를 확장하는 project_root/gen/package/name/ScriptField_struct_name이라는 이름의 자체 클래스에 반영됩니다. 이 클래스는 struct로 구성된 배열을 나타냅니다. 이 클래스를 사용하여 이 struct의 하나 이상의 인스턴스에 사용할 메모리를 할당할 수 있습니다.

함수

함수는 project_root/gen/package/name/ScriptC_renderscript_filename에 있는 스크립트 클래스 자체에 반영됩니다. 예를 들어 RenderScript 코드에서 다음 함수를 정의하는 경우

void touch(float x, float y, float pressure, int id) {
    if (id >= 10) {
        return;
    }

    touchPos[id].x = x;
    touchPos[id].y = y;
    touchPressure[id] = pressure;
}

다음 자바 코드가 생성됩니다.

public void invoke_touch(float x, float y, float pressure, int id) {
    FieldPacker touch_fp = new FieldPacker(16);
    touch_fp.addF32(x);
    touch_fp.addF32(y);
    touch_fp.addF32(pressure);
    touch_fp.addI32(id);
    invoke(mExportFuncIdx_touch, touch_fp);
}

RenderScript 시스템이 비동기식으로 설계되기 때문에, 함수는 반환 값을 가질 수 없습니다. Android 프레임워크 코드가 RenderScript를 호출하면 호출은 대기 상태에 놓이고 가능한 경우 실행됩니다. 이 제한 덕분에 RenderScript 시스템이 장시간 중단 없이 작동하고 효율성이 높아집니다. 함수가 반환 값을 갖도록 허용된 경우 호출은 값이 반환될 때까지 차단됩니다.

RenderScript 코드가 값을 Android 프레임워크로 다시 전송하도록 하려면 rsSendToClient() 함수를 사용하세요.

변수

지원되는 유형의 변수는 project_root/gen/package/name/ScriptC_renderscript_filename에 있는 스크립트 클래스 자체에 반영됩니다. 접근자 메서드 집합이 변수마다 생성됩니다. 예를 들어 RenderScript 코드에서 다음 변수를 정의하는 경우

uint32_t unsignedInteger = 1;

다음 자바 코드가 생성됩니다.

private long mExportVar_unsignedInteger;
public void set_unsignedInteger(long v){
    mExportVar_unsignedInteger = v;
    setVar(mExportVarIdx_unsignedInteger, v);
}

public long get_unsignedInteger(){
    return mExportVar_unsignedInteger;
}
  

구조체

구조체는 <project_root>/gen/com/example/renderscript/ScriptField_struct_name에 있는 자체 클래스에 반영됩니다. 이 클래스는 struct로 구성된 배열을 나타냅니다. 이 클래스를 사용하여, 지정된 수의 struct에 사용할 메모리를 할당할 수 있습니다. 예를 들어, 다음과 같이 구조체를 정의하는 경우

typedef struct Point {
    float2 position;
    float size;
} Point_t;

그러면 ScriptField_Point.java에 다음 코드가 생성됩니다.

package com.example.android.rs.hellocompute;

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

  /**
  * @hide
  */
public class ScriptField_Point extends android.renderscript.Script.FieldBase {

    static public class Item {
        public static final int sizeof = 12;

        Float2 position;
        float size;

        Item() {
            position = new Float2();
        }
    }

    private Item mItemArray[];
    private FieldPacker mIOBuffer;
    public static Element createElement(RenderScript rs) {
        Element.Builder eb = new Element.Builder(rs);
        eb.add(Element.F32_2(rs), "position");
        eb.add(Element.F32(rs), "size");
        return eb.create();
    }

    public  ScriptField_Point(RenderScript rs, int count) {
        mItemArray = null;
        mIOBuffer = null;
        mElement = createElement(rs);
        init(rs, count);
    }

    public  ScriptField_Point(RenderScript rs, int count, int usages) {
        mItemArray = null;
        mIOBuffer = null;
        mElement = createElement(rs);
        init(rs, count, usages);
    }

    private void copyToArray(Item i, int index) {
        if (mIOBuffer == null) mIOBuffer = new FieldPacker(Item.sizeof * getType().getX()/* count
        */);
        mIOBuffer.reset(index * Item.sizeof);
        mIOBuffer.addF32(i.position);
        mIOBuffer.addF32(i.size);
    }

    public void set(Item i, int index, boolean copyNow) {
        if (mItemArray == null) mItemArray = new Item[getType().getX() /* count */];
        mItemArray[index] = i;
        if (copyNow)  {
            copyToArray(i, index);
            mAllocation.setFromFieldPacker(index, mIOBuffer);
        }
    }

    public Item get(int index) {
        if (mItemArray == null) return null;
        return mItemArray[index];
    }

    public void set_position(int index, Float2 v, boolean copyNow) {
        if (mIOBuffer == null) mIOBuffer = new FieldPacker(Item.sizeof * getType().getX()/* count */);
        if (mItemArray == null) mItemArray = new Item[getType().getX() /* count */];
        if (mItemArray[index] == null) mItemArray[index] = new Item();
        mItemArray[index].position = v;
        if (copyNow) {
            mIOBuffer.reset(index * Item.sizeof);
            mIOBuffer.addF32(v);
            FieldPacker fp = new FieldPacker(8);
            fp.addF32(v);
            mAllocation.setFromFieldPacker(index, 0, fp);
        }
    }

    public void set_size(int index, float v, boolean copyNow) {
        if (mIOBuffer == null) mIOBuffer = new FieldPacker(Item.sizeof * getType().getX()/* count */);
        if (mItemArray == null) mItemArray = new Item[getType().getX() /* count */];
        if (mItemArray[index] == null) mItemArray[index] = new Item();
        mItemArray[index].size = v;
        if (copyNow)  {
            mIOBuffer.reset(index * Item.sizeof + 8);
            mIOBuffer.addF32(v);
            FieldPacker fp = new FieldPacker(4);
            fp.addF32(v);
            mAllocation.setFromFieldPacker(index, 1, fp);
        }
    }

    public Float2 get_position(int index) {
        if (mItemArray == null) return null;
        return mItemArray[index].position;
    }

    public float get_size(int index) {
        if (mItemArray == null) return 0;
        return mItemArray[index].size;
    }

    public void copyAll() {
        for (int ct = 0; ct < mItemArray.length; ct++) copyToArray(mItemArray[ct], ct);
        mAllocation.setFromFieldPacker(0, mIOBuffer);
    }

    public void resize(int newSize) {
        if (mItemArray != null)  {
            int oldSize = mItemArray.length;
            int copySize = Math.min(oldSize, newSize);
            if (newSize == oldSize) return;
            Item ni[] = new Item[newSize];
            System.arraycopy(mItemArray, 0, ni, 0, copySize);
            mItemArray = ni;
        }
        mAllocation.resize(newSize);
        if (mIOBuffer != null) mIOBuffer = new FieldPacker(Item.sizeof * getType().getX()/* count */);
    }
}

생성된 코드는 RenderScript 런타임에서 요청한 구조체에 사용할 메모리를 할당하고 메모리 내 struct와 상호작용할 수 있는 유용한 수단이 됩니다. 각 struct의 클래스는 다음 메서드와 생성자를 정의합니다.

  • 메모리 할당을 허용하는 오버로드된 생성자. ScriptField_struct_name(RenderScript rs, int count) 생성자를 사용하면 count 매개변수를 사용하여 메모리를 할당할 구조체 수를 정의할 수 있습니다. ScriptField_struct_name(RenderScript rs, int count, int usages) 생성자는 이 메모리 할당의 메모리 공간을 지정할 수 있는 추가 매개변수 usages를 정의합니다. 4개의 메모리 공간이 있을 수 있습니다.
    • USAGE_SCRIPT: 스크립트 메모리 공간에 할당됩니다. 메모리 공간을 지정하지 않는 경우 기본 메모리 공간입니다.
    • USAGE_GRAPHICS_TEXTURE: GPU의 텍스처 메모리 공간에 할당됩니다.
    • USAGE_GRAPHICS_VERTEX: GPU의 정점 메모리 공간에 할당됩니다.
    • USAGE_GRAPHICS_CONSTANTS: 다양한 프로그램 객체가 사용하는 GPU의 상수 메모리 공간에 할당됩니다.

    비트별 OR 연산자를 사용하여 메모리 공간을 여러 개 지정할 수 있습니다. 메모리 공간을 지정하면 지정된 메모리 공간의 데이터에 액세스하려고 할 때 이 사실이 RenderScript 런타임에 통지됩니다. 다음은 스크립트와 정점 메모리 공간의 맞춤 데이터 유형에 사용할 메모리를 할당하는 예입니다.

    Kotlin

    val touchPoints: ScriptField_Point = ScriptField_Point(
            myRenderScript,
            2,
            Allocation.USAGE_SCRIPT or Allocation.USAGE_GRAPHICS_VERTEX
    )
    

    Java

    ScriptField_Point touchPoints = new ScriptField_Point(myRenderScript, 2,
            Allocation.USAGE_SCRIPT | Allocation.USAGE_GRAPHICS_VERTEX);
    
  • 정적 중첩 클래스 Item을 사용하면 struct의 인스턴스를 객체 형태로 생성할 수 있습니다. 이 중첩 클래스는 Android 코드에서 struct를 사용하는 것이 합리적일 경우 유용합니다. 객체 조정이 끝났으면, set(Item i, int index, boolean copyNow)를 호출하고 Item을 원하는 배열 위치에 설정하여 객체를 할당된 메모리에 푸시할 수 있습니다. RenderScript 런타임은 새로 작성된 메모리에 자동으로 액세스할 수 있습니다.
  • 구조체의 각 필드 값을 가져오고 설정하는 접근자 메서드. 각 접근자 메서드에는 읽고 쓰는 대상이 되는 배열에 struct를 지정하는 index 매개변수가 있습니다. 각 setter 메서드에는 이 메모리를 RenderScript 런타임과 즉시 동기화할지를 지정하는 copyNow 매개변수도 있습니다. 동기화되지 않은 메모리를 동기화하려면 copyAll()을 호출하세요.
  • createElement() 메서드는 메모리 내 구조체에 관한 설명을 작성합니다. 이 설명은 하나 이상의 요소로 구성된 메모리를 할당하는 데 사용됩니다.
  • resize()는 C의 realloc()와 많이 유사합니다. 이 메서드를 사용하면 이전에 할당된 메모리를 확장하고 이전에 생성된 현재 값을 유지할 수 있습니다.
  • copyAll()은 프레임워크 수준에서 설정된 메모리를 RenderScript 런타임에 동기화합니다. 멤버에 설정된 접근자 메서드를 호출하면 copyNow 부울 매개변수(선택사항)를 지정할 수 있습니다. true를 지정하면 이 메서드를 호출할 때 메모리가 동기화됩니다. false를 지정하면 copyAll()을 한 번 호출할 수 있고, 아직 동기화되지 않은 모든 속성과 관련하여 메모리가 동기화됩니다.

포인터

전역 포인터는 project_root/gen/package/name/ScriptC_renderscript_filename에 있는 스크립트 클래스 자체에 반영됩니다. struct 또는 지원되는 RenderScript 유형의 포인터 선언이 가능하지만 struct는 포인터 또는 중첩 배열을 포함할 수 없습니다. 예를 들어 다음과 같이 structint32_t의 포인터를 정의하는 경우

typedef struct Point {
    float2 position;
    float size;
} Point_t;

Point_t *touchPoints;
int32_t *intPointer;

다음 자바 코드가 생성됩니다.

private ScriptField_Point mExportVar_touchPoints;
public void bind_touchPoints(ScriptField_Point v) {
    mExportVar_touchPoints = v;
    if (v == null) bindAllocation(null, mExportVarIdx_touchPoints);
    else bindAllocation(v.getAllocation(), mExportVarIdx_touchPoints);
}

public ScriptField_Point get_touchPoints() {
    return mExportVar_touchPoints;
}

private Allocation mExportVar_intPointer;
public void bind_intPointer(Allocation v) {
    mExportVar_intPointer = v;
    if (v == null) bindAllocation(null, mExportVarIdx_intPointer);
    else bindAllocation(v, mExportVarIdx_intPointer);
}

public Allocation get_intPointer() {
    return mExportVar_intPointer;
}
  

set() 메서드 대신, get 메서드 및 bind_pointer_name이라는 이름의 특수 메서드가 생성됩니다. bind_pointer_name 메서드를 사용하면 Android VM에 할당된 메모리를 RenderScript 런타임에 바인딩할 수 있습니다(.rs 파일에는 메모리를 할당할 수 없음). 자세한 내용은 할당된 메모리 사용을 참고하세요.

메모리 할당 API

RenderScript를 사용하는 애플리케이션은 종전처럼 Android VM에서 실행됩니다. 하지만 실제 RenderScript 코드가 기본적으로 실행되고 Android VM에 할당된 메모리에 액세스해야 합니다. 이를 위해 VM에 할당된 메모리를 RenderScript 런타임에 연결하는 작업이 필요합니다. 바인딩이라는 이 프로세스를 통해 RenderScript 런타임은 요청하긴 하지만 명시적으로 할당할 수 없는 메모리를 문제없이 사용할 수 있습니다. 최종 결과는 C에서 malloc를 호출한 것과 근본적으로 동일합니다. 또한 Android VM이 가비지 컬렉션을 실행하고 RenderScript 런타임 레이어와 메모리를 공유할 수 있다는 이점도 있습니다. 바인딩은 동적으로 할당된 메모리에만 필요합니다. 정적으로 할당된 메모리는 컴파일 시 RenderScript 코드용으로 자동으로 생성됩니다. 메모리가 할당되는 방식에 대한 자세한 내용은 그림 1을 참고하세요.

이 메모리 할당 시스템을 지원하기 위해, Android VM에 메모리 할당을 허용하고 malloc 호출과 유사한 기능을 제공하는 API 집합이 있습니다. 이러한 클래스는 기본적으로 메모리 할당 방법을 설명하고 할당을 실행하기도 합니다. 이 클래스를 다음과 같은 간단한 malloc 호출과 관련지어 생각하면 클래스의 작동 방식을 더 잘 이해할 수 있습니다.

array = (int *)malloc(sizeof(int)*10);

malloc 호출은 할당되는 메모리의 크기(sizeof(int))와 할당되어야 할 메모리의 유닛 수(10)라는 두 부분으로 나눌 수 있습니다. Android 프레임워크는 이 두 부분을 위한 클래스와 함께, malloc 자체를 나타내기 위한 클래스도 제공합니다.

Element 클래스는 malloc 호출의 sizeof(int) 부분을 나타내고, 메모리 할당의 한 셀(예: 단일 부동 소수점 값 또는 구조체)을 캡슐화합니다. Type 클래스는 Element와 할당할 요소 개수(이 예에서는 10)를 캡슐화합니다. TypeElement로 구성된 배열로 생각할 수 있습니다. Allocation 클래스는 지정된 Type에 따라 실제 메모리를 할당하고, 실제 할당된 메모리를 나타냅니다.

대부분의 경우 이러한 메모리 할당 API를 직접 호출할 필요는 없습니다. 반영된 레이어 클래스가 이러한 API를 자동으로 사용하기 위한 코드를 생성합니다. 메모리를 할당하려면 반영된 레이어 클래스 중 하나에 선언된 생성자를 호출한 다음 결과 메모리 Allocation을 RenderScript에 바인딩하기만 하면 됩니다. 리소스에서 비트맵을 로드하거나 원시 유형의 포인터에 사용할 메모리를 할당하려는 경우 등 이러한 클래스를 바로 사용하여 메모리를 직접 할당하려는 경우가 있을 수 있습니다. 이 방법은 메모리 할당 및 RenderScript에 메모리 바인딩 섹션에서 확인할 수 있습니다. 다음 표에서는 세 가지 메모리 관리 클래스를 보다 자세히 설명합니다.

Android 객체 유형 설명
Element

요소는 메모리 할당의 한 셀을 설명하고 기본 또는 복합이라는 두 가지 형식을 취할 수 있습니다.

기본 요소는 유효한 RenderScript 데이터 유형의 데이터로 구성된 단일 구성요소를 포함합니다. 기본 요소 데이터 유형에는 단일 float 값, float4 벡터, 단일 RGB-565 색 등이 있습니다.

복합 요소는 기본 요소의 목록을 포함하고, RenderScript 코드에 선언한 struct에서 만들어집니다. 예를 들어 할당은 메모리에 순서대로 정렬된 여러 개의 struct를 포함할 수 있습니다. 각 구조체는 구조체 내의 각 데이터 유형 대신 자체 요소로 간주됩니다.

Type

유형은 메모리 할당 템플릿으로, 하나의 요소와 하나 이상의 차원으로 구성됩니다. 유형은 메모리의 레이아웃(기본적으로 Element로 구성된 배열)을 설명하지만, 설명하는 데이터에 사용할 메모리를 할당하지는 않습니다.

유형은 X, Y, Z, LOD(Level Of Detail), Faces(큐브 맵에 해당) 등 5가지 차원으로 구성됩니다. X, Y, Z 차원은 사용 가능한 메모리의 제한 내에서 양의 정수 값으로 설정할 수 있습니다. 단일 차원 할당의 경우 X 차원은 0보다 큰 반면, Y 및 Z 차원은 존재하지 않음을 나타내는 0입니다. 예를 들어, x=10, y=1인 할당은 2차원으로 간주되고, x=10, y=0인 할당은 1차원으로 간주됩니다. LOD 및 Faces 차원은 존재 여부를 나타내는 부울입니다.

Allocation

할당은 Type으로 표현되는 메모리 설명을 기반으로 애플리케이션에 사용할 메모리를 제공합니다. 할당된 메모리는 여러 메모리 공간에 동시에 존재할 수 있습니다. 메모리가 한 공간에서 수정되는 경우 그 메모리를 명시적으로 동기화해야 합니다. 그래야 그 메모리가 있는 다른 모든 공간에서 메모리가 업데이트됩니다.

할당 데이터는 선택됨 유형과 선택되지 않음 유형이라는 두 가지 기본 방법 중 하나로 업로드됩니다. 간단한 배열의 경우 Android 시스템에서 배열을 가져와 기본 레이어 메모리 저장소에 복사하는 copyFrom() 함수가 있습니다. 선택되지 않은 변형을 사용하면 Android 시스템이 구조체를 지원하지 않으므로 복사 시 구조체 배열을 덮어쓸 수 있습니다. 예를 들어, 배열이 n개 부동 소수점으로 구성된 할당이 있는 경우 float[n] 배열 또는 byte[n*4] 배열에 포함된 데이터를 복사할 수 있습니다.

메모리 사용

RenderScript에 선언하는 비정적 전역 변수는 컴파일 시 메모리가 할당됩니다. 이러한 변수는 Android 프레임워크 수준에서 변수에 사용할 메모리를 할당할 필요 없이 RenderScript 코드에서 직접 사용할 수 있습니다. Android 프레임워크 레이어는 제공된 접근자 메서드 중 반영된 레이어 클래스에서 생성된 메서드를 사용하여 이러한 변수에 액세스할 수도 있습니다. 이러한 변수가 RenderScript 런타임 레이어에서 초기화되면 그 값은 Android 프레임워크 레이어의 대응되는 값을 초기화하는 데 사용됩니다. 전역 변수가 const로 표시되면 set 메서드가 생성되지 않습니다. 자세한 내용은 여기를 확인하세요.

참고: 포인터를 포함하는 특정 RenderScript 구조체(예: rs_program_fragmentrs_allocation)를 사용하는 경우, Android 프레임워크 클래스의 객체를 먼저 얻은 다음 그 구조체에 set 메서드를 호출하여 메모리를 RenderScript 런타임에 바인딩해야 합니다. 그 구조체를 RenderScript 런타임 레이어에서 직접 조작할 수 없습니다. 이 제한은 포인터를 포함하는 사용자 정의 구조체에는 적용되지 않는데, 그러한 구조체는 반영된 레이어 클래스로 먼저 내보낼 수 없기 때문입니다. 포인터를 포함하는 비정적 전역 구조체를 선언하려고 하면 컴파일러 오류가 발생합니다.

RenderScript는 포인터도 지원하지만, Android 프레임워크 코드에서 메모리가 명시적으로 할당되어야 합니다. .rs 파일에서 전역 포인터를 선언할 경우, 반영된 적절한 레이어 클래스를 통해 메모리를 할당하고 그 메모리를 기본 RenderScript 레이어에 바인딩할 수 있습니다. 또한 RenderScript 레이어는 물론, Android 프레임워크 레이어에서도 이 메모리와 상호작용할 수 있기 때문에, 가장 적절한 레이어에서 변수를 수정할 수 있는 유연성을 갖게 됩니다.

동적 메모리를 할당하고 RenderScript에 바인딩

동적 메모리를 할당하는 가장 일반적인 방법은 Script.FieldBase 클래스의 생성자를 호출하는 것입니다. 또 다른 방법은 Allocation을 수동으로 생성하는 것인데, 이 방법에서는 원시 유형 포인터 같은 여러 항목이 요구됩니다. 가능하다면 간결함을 위해 Script.FieldBase 클래스 생성자를 사용해야 합니다. 메모리 할당을 얻은 후 포인터의 반영된 bind 메서드를 호출하여 할당된 메모리를 RenderScript 런타임에 바인딩하세요.

아래 예에서는 원시 유형 포인터 intPointer와 구조체 포인터 touchPoints에 사용할 메모리를 할당하는 방법을 보여줍니다. 또한 RenderScript에 메모리를 바인딩하는 방법도 보여줍니다.

Kotlin

private lateinit var myRenderScript: RenderScript
private lateinit var script: ScriptC_example
private lateinit var resources: Resources

public fun init(rs: RenderScript, res: Resources) {
    myRenderScript = rs
    resources = res

    // allocate memory for the struct pointer, calling the constructor
    val touchPoints = ScriptField_Point(myRenderScript, 2)

    // Create an element manually and allocate memory for the int pointer
    val intPointer: Allocation = Allocation.createSized(myRenderScript, Element.I32(myRenderScript), 2)

    // create an instance of the RenderScript, pointing it to the bytecode resource
    script = ScriptC_point(myRenderScript/*, resources, R.raw.example*/)

    // bind the struct and int pointers to the RenderScript
    script.bind_touchPoints(touchPoints)
    script.bind_intPointer(intPointer)

   ...
}

Java

private RenderScript myRenderScript;
private ScriptC_example script;
private Resources resources;

public void init(RenderScript rs, Resources res) {
    myRenderScript = rs;
    resources = res;

    // allocate memory for the struct pointer, calling the constructor
    ScriptField_Point touchPoints = new ScriptField_Point(myRenderScript, 2);

    // Create an element manually and allocate memory for the int pointer
    intPointer = Allocation.createSized(myRenderScript, Element.I32(myRenderScript), 2);

    // create an instance of the RenderScript, pointing it to the bytecode resource
    script = new ScriptC_example(myRenderScript, resources, R.raw.example);

    // bind the struct and int pointers to the RenderScript
    script.bind_touchPoints(touchPoints);
    script.bind_intPointer(intPointer);

   ...
}

메모리 읽기 및 쓰기

RenderScript 런타임과 Android 프레임워크 레이어에서 정적으로 할당된 메모리와 동적으로 할당된 메모리를 읽고 쓸 수 있습니다.

정적으로 할당된 메모리에는 RenderScript 런타임 수준에서 단방향 통신 제한이 있습니다. RenderScript 코드가 변수 값을 변경하면 효율을 위해 변경사항은 Android 프레임워크 레이어에 다시 전달되지 않습니다. Android 프레임워크에서 설정된 마지막 값이 get 메서드를 호출하는 동안 항상 반환됩니다. 그러나 Android 프레임워크 코드가 변수를 수정하는 경우 변경사항은 RenderScript 런타임에 자동으로 전달되거나 나중에 동기화될 수 있습니다. RenderScript 런타임에서 Android 프레임워크 레이어로 데이터를 전송해야 하는 경우 rsSendToClient() 함수를 사용하면 이 제한을 적용받지 않습니다.

동적 할당 메모리를 사용하는 경우 연결된 포인터를 사용하여 메모리 할당을 수정하면 RenderScript 런타임 레이어에서 수정된 사항이 Android 프레임워크 레이어로 다시 전파됩니다. Android 프레임워크 레이어에서 객체를 수정하면 변경사항이 즉시 RenderScript 런타임 레이어에 다시 전파됩니다.

전역 변수 읽기 및 쓰기

전역 변수를 읽고 쓰는 과정은 간단합니다. Android 프레임워크 수준에서 접근자 메서드를 사용하거나 이 메서드를 RenderScript 코드에서 직접 설정할 수 있습니다. 염두에 두어야 할 점은 RenderScript 코드에서 변경한 사항은 Android 프레임워크 레이어에 다시 전파되지 않는다는 것입니다(자세한 내용은 여기 참고).

예를 들어, rsfile.rs라는 이름의 파일에 다음 구조체가 선언된 경우

typedef struct Point {
    int x;
    int y;
} Point_t;

Point_t point;

다음과 같이 rsfile.rs에서 직접 구조체에 값을 할당할 수 있습니다. 값은 Android 프레임워크 수준에 다시 전파되지 않습니다.

point.x = 1;
point.y = 1;

다음과 같이 Android 프레임워크 레이어에서 구조체에 값을 할당할 수 있습니다. 값은 RenderScript 런타임 수준에 비동기식으로 다시 전파됩니다.

Kotlin

val script: ScriptC_rsfile = ...

...

script._point = ScriptField_Point.Item().apply {
    x = 1
    y = 1
}

Java

ScriptC_rsfile script;

...

Item i = new ScriptField_Point.Item();
i.x = 1;
i.y = 1;
script.set_point(i);

다음과 같이 RenderScript 코드의 값을 읽을 수 있습니다.

rsDebug("Printing out a Point", point.x, point.y);

다음 코드를 사용하여 Android 프레임워크 레이어의 값을 읽을 수 있습니다. 염두에 두어야 할 점은 Android 프레임워크 수준에서 값이 설정된 경우에만 이 코드가 값을 반환한다는 것입니다. RenderScript 런타임 수준에서만 값을 설정하는 경우 null 포인터 예외가 발생합니다.

Kotlin

Log.i("TAGNAME", "Printing out a Point: ${mScript._point.x} ${mScript._point.y}")
println("${point.x} ${point.y}")

Java

Log.i("TAGNAME", "Printing out a Point: " + script.get_point().x + " " + script.get_point().y);
System.out.println(point.get_x() + " " + point.get_y());

전역 포인터 읽기 및 쓰기

메모리가 Android 프레임워크 수준에서 할당되었고 RenderScript 런타임에 바인딩되었다고 가정하면, 포인터에 get 메서드와 set 메서드를 사용하여 Android 프레임워크 수준에서 메모리를 읽고 쓸 수 있습니다. RenderScript 런타임 레이어에서는 포인터가 있는 메모리를 정상적으로 읽고 쓸 수 있으며, 정적으로 할당된 메모리에서와 달리 변경사항이 Android 프레임워크 레이어에 다시 전파됩니다.

예를 들어 rsfile.rs라는 이름의 파일에 다음과 같은 struct 포인터가 지정된 경우

typedef struct Point {
    int x;
    int y;
} Point_t;

Point_t *point;

Android 프레임워크 레이어에 이미 메모리를 할당했다고 가정하면 struct의 값에 정상적으로 액세스할 수 있습니다. 포인터 변수를 통해 구조체에 변경한 모든 사항은 Android 프레임워크 레이어에 자동으로 사용 가능합니다.

Kotlin

point[index].apply {
    x = 1
    y = 1
}

Java

point[index].x = 1;
point[index].y = 1;

Android 프레임워크 레이어에서도 포인터 값을 읽고 쓸 수 있습니다.

Kotlin

val i = ScriptField_Point.Item().apply {
    x = 100
    y = 100
}
val p = ScriptField_Point(rs, 1).apply {
    set(i, 0, true)
}
script.bind_point(p)

p.get_x(0)            //read x and y from index 0
p.get_y(0)

Java

ScriptField_Point p = new ScriptField_Point(rs, 1);
Item i = new ScriptField_Point.Item();
i.x=100;
i.y = 100;
p.set(i, 0, true);
script.bind_point(p);

p.get_x(0);            //read x and y from index 0
p.get_y(0);

메모리가 일단 바인딩되면, 값을 변경할 때마다 RenderScript 런타임에 메모리를 다시 바인딩할 필요는 없습니다.