RenderScript nâng cao

Vì ứng dụng dùng RenderScript vẫn chạy bên trong máy ảo Android nên bạn có thể truy cập tất cả API khung mà bạn đã quen thuộc, tuy vậy vẫn có thể dùng RenderScript khi phù hợp. Để tạo điều kiện tương tác giữa khung và thời gian chạy RenderScript, một lớp mã trung gian cũng hiện diện để hỗ trợ việc giao tiếp và quản lý bộ nhớ giữa hai cấp mã. Tài liệu này trình bày chi tiết hơn về các lớp mã (layer of code) này cũng như cách chia sẻ bộ nhớ giữa máy ảo Android và thời gian chạy RenderScript.

Lớp thời gian chạy RenderScript

Mã RenderScript của bạn được biên dịch và thực thi trong một lớp thời gian chạy (runtime layer) nhỏ gọn và được xác định rõ ràng. Các API thời gian chạy RenderScript hỗ trợ tính toán chuyên sâu có thể chuyển đổi và tự động mở rộng theo số lượng lõi có trên bộ xử lý.

Lưu ý: Các hàm C chuẩn trong NDK phải được đảm bảo sẽ chạy trên CPU, vậy nên RenderScript không truy cập được các thư viện này vì được thiết kế để chạy trên nhiều loại bộ xử lý.

Bạn xác định mã RenderScript trong các tệp .rs.rsh trong thư mục src/ của dự án Android. Trình biên dịch llvm chạy trong bản dựng Android biên dịch mã này sang mã byte trung gian. Khi ứng dụng chạy trên thiết bị, một trình biên dịch llvm khác trên thiết bị sẽ biên dịch (trong khi chạy) mã byte (bytecode) thành mã máy (machine code). Mã máy được tối ưu hoá cho thiết bị và được lưu vào bộ nhớ đệm, vậy nên các lần sử dụng ứng dụng hỗ trợ RenderScript tiếp theo sẽ không biên dịch lại mã byte.

Thư viện thời gian chạy RenderScript có một số tính năng chính như sau:

  • Các tính năng yêu cầu phân bổ bộ nhớ
  • Một tập hợp lớn các hàm toán học có cả phiên bản quá tải vectơ và vô hướng cho nhiều quy trình phổ biến. Có sẵn các phép tính như cộng, nhân, tích vô hướng và tích có hướng cũng như các hàm so sánh và số học nguyên tử.
  • Quy trình chuyển đổi cho các kiểu dữ liệu và vectơ nguyên gốc, quy trình ma trận, quy trình ngày và giờ
  • Các kiểu dữ liệu và cấu trúc hỗ trợ hệ thống RenderScript, chẳng hạn như các kiểu vectơ để xác định 2, 3 hoặc 4 vectơ.
  • Hàm ghi nhật ký

Xem tài liệu tham khảo về API thời gian chạy RenderScript để biết thêm thông tin về các hàm hiện có.

Lớp phản ánh

Lớp phản ánh (reflected layer) là một tập hợp lớp mà các công cụ xây dựng Android tạo ra để cho phép truy cập thời gian chạy RenderScript qua khung Android. Lớp này cũng cung cấp phương thức và hàm dựng cho phép bạn phân bổ và xử lý bộ nhớ cho con trỏ được xác định trong mã RenderScript. Danh sách sau đây mô tả các thành phần chính được phản ánh:

  • Mỗi tệp .rs mà bạn tạo sẽ được tạo trong một lớp (class) tên là project_root/gen/package/name/ScriptC_renderscript_filename thuộc loại ScriptC. Tệp này là phiên bản .java của tệp .rs mà bạn có thể gọi qua khung Android. Lớp này chứa các mục sau đây được phản ánh qua tệp .rs:
    • Hàm không tĩnh
    • Biến RenderScript toàn cục, không tĩnh. Các phương thức truy cập được tạo cho mỗi biến, do đó, bạn có thể đọc và ghi các biến RenderScript qua khung Android. Nếu một biến toàn cục được khởi tạo tại lớp thời gian chạy RenderScript, thì các giá trị đó sẽ được dùng để khởi tạo giá trị tương ứng trong lớp khung Android. Nếu biến toàn cục được đánh dấu là const, thì phương thức set sẽ không được tạo. Hãy tìm hiểu tại đây để biết thêm thông tin.

    • Con trỏ toàn cục
  • struct được phản ánh thành lớp (class) riêng tên là project_root/gen/package/name/ScriptField_struct_name, mở rộng Script.FieldBase. Lớp này đại diện cho một mảng (array) của struct, cho phép bạn phân bổ bộ nhớ cho một hoặc nhiều thực thể của struct này.

Hàm

Hàm được phản ánh trong lớp tập lệnh, nằm trong project_root/gen/package/name/ScriptC_renderscript_filename. Ví dụ: nếu bạn xác định hàm sau đây trong mã 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;
}

thì mã Java sau đây được tạo:

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);
}

Hàm không được có giá trị trả về vì hệ thống RenderScript mang tính không đồng bộ. Khi mã khung Android gọi vào RenderScript, lệnh gọi sẽ được đưa vào hàng đợi và thực thi khi có thể. Hạn chế này cho phép hệ thống RenderScript hoạt động mà không bị gián đoạn liên tục đồng thời giúp tăng mức độ hiệu quả. Nếu hàm được phép trả về giá trị, thì lệnh gọi sẽ chặn cho đến khi giá trị được trả về.

Nếu bạn muốn mã RenderScript gửi lại một giá trị tới khung Android, hãy sử dụng hàm rsSendToClient().

Biến

Biến thuộc loại được hỗ trợ sẽ được phản ánh trong lớp tập lệnh, nằm trong project_root/gen/package/name/ScriptC_renderscript_filename. Một tập hợp phương thức truy cập được tạo cho mỗi biến. Ví dụ: nếu bạn xác định biến sau đây trong mã RenderScript:

uint32_t unsignedInteger = 1;

thì mã Java sau đây được tạo:

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

public long get_unsignedInteger(){
    return mExportVar_unsignedInteger;
}
  

Cấu trúc

Cấu trúc được phản ánh thành các lớp (class) riêng, nằm trong <project_root>/gen/com/example/renderscript/ScriptField_struct_name. Lớp này đại diện cho một mảng (array) của struct và cho phép bạn phân bổ bộ nhớ cho một số lượng struct được chỉ định. Ví dụ: nếu bạn xác định cấu trúc sau:

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

thì mã sau sẽ được tạo trong 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 */);
    }
}

Mã tạo ra được cung cấp cho bạn để thuận tiện trong việc phân bổ bộ nhớ cho các cấu trúc mà thời gian chạy RenderScript yêu cầu cũng như để tương tác với struct trong bộ nhớ. Mỗi lớp (class) của struct xác định các phương thức và hàm dựng sau đây:

  • Hàm dựng quá tải cho phép bạn phân bổ bộ nhớ. Hàm dựng ScriptField_struct_name(RenderScript rs, int count) cho phép bạn xác định số lượng cấu trúc mà bạn muốn phân bổ bộ nhớ bằng tham số count. Hàm dựng ScriptField_struct_name(RenderScript rs, int count, int usages) xác định một tham số bổ sung (usages) cho phép bạn chỉ định không gian bộ nhớ của quá trình phân bổ bộ nhớ này. Có 4 khả năng liên quan đến dung lượng bộ nhớ:
    • USAGE_SCRIPT: Phân bổ trong không gian bộ nhớ của tập lệnh. Đây là dung lượng bộ nhớ mặc định nếu bạn không chỉ định dung lượng bộ nhớ.
    • USAGE_GRAPHICS_TEXTURE: Phân bổ trong không gian bộ nhớ kết cấu (texture memory space) của GPU.
    • USAGE_GRAPHICS_VERTEX: Phân bổ trong không gian bộ nhớ đỉnh (vertex memory space) của GPU.
    • USAGE_GRAPHICS_CONSTANTS: Phân bổ trong không gian bộ nhớ hằng số (constants memory space) của GPU nơi nhiều đối tượng chương trình sử dụng.

    Bạn có thể chỉ định nhiều không gian bộ nhớ bằng cách sử dụng toán tử OR một cách hợp lý. Việc này sẽ thông báo cho thời gian chạy RenderScript rằng bạn có ý định truy cập dữ liệu trong không gian bộ nhớ đã chỉ định. Ví dụ sau đây phân bổ bộ nhớ cho một loại dữ liệu tuỳ chỉnh trong cả không gian tập lệnh và không gian bộ nhớ đỉnh:

    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);
    
  • Một lớp lồng ghép tĩnh (Item) cho phép bạn tạo một thực thể của struct dưới dạng một đối tượng. Lớp lồng ghép này sẽ hữu ích nếu bạn làm việc với struct trong mã Android. Khi thao tác xong đối tượng, bạn có thể đẩy đối tượng đến bộ nhớ được phân bổ bằng cách gọi set(Item i, int index, boolean copyNow) rồi đặt Item ở vị trí mong muốn trong mảng. Thời gian chạy RenderScript tự động có quyền truy cập vào bộ nhớ mới ghi.
  • Phương thức truy cập để lấy và đặt giá trị cho mỗi trường trong một cấu trúc. Mỗi phương thức truy cập này đều có một tham số index để chỉ định struct trong mảng mà bạn muốn đọc hoặc ghi. Mỗi phương thức setter cũng có một tham số copyNow. Tham số này sẽ chỉ định việc có đồng bộ hoá ngay bộ nhớ này với thời gian chạy RenderScript hay không. Để đồng bộ hoá mọi bộ nhớ chưa được đồng bộ hoá, hãy gọi copyAll().
  • Phương thức createElement() tạo phần mô tả cho cấu trúc trong bộ nhớ. Phần mô tả này dùng để phân bổ bộ nhớ bao gồm ít nhất một phần tử.
  • resize() hoạt động giống như realloc() trong C, cho phép bạn mở rộng bộ nhớ được phân bổ trước đó, giúp duy trì các giá trị hiện tại từng được tạo.
  • copyAll() đồng bộ hoá bộ nhớ đã đặt ở cấp khung với thời gian chạy RenderScript. Khi gọi một phương thức truy cập đã đặt cho một thành viên, bạn có thể chỉ định một tham số boolean copyNow tuỳ chọn. Thao tác chỉ định true sẽ đồng bộ hoá bộ nhớ khi bạn gọi phương thức này. Nếu chỉ định sai, thì bạn có thể gọi copyAll() một lần để đồng bộ hoá bộ nhớ cho tất cả thuộc tính chưa được đồng bộ hoá.

Con trỏ

Con trỏ (pointer) toàn cục được phản ánh trên lớp tập lệnh, nằm trong project_root/gen/package/name/ScriptC_renderscript_filename. Bạn có thể khai báo con trỏ đến struct hoặc loại RenderScript bất kỳ được hỗ trợ, nhưng struct không được chứa con trỏ hoặc mảng lồng ghép. Ví dụ: nếu bạn xác định các con trỏ sau cho structint32_t

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

Point_t *touchPoints;
int32_t *intPointer;

thì mã Java sau đây được tạo:

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;
}
  

Thay vì phương thức set(), hệ thống sẽ tạo một phương thức get và một phương thức đặc biệt có tên bind_pointer_name. Phương thức bind_pointer_name cho phép bạn liên kết bộ nhớ đã phân bổ trong máy ảo Android với thời gian chạy RenderScript (bạn không thể phân bổ bộ nhớ trong tệp .rs). Để biết thêm thông tin, hãy xem nội dung Xử lý bộ nhớ được phân bổ.

API phân bổ bộ nhớ

Ứng dụng dùng RenderScript vẫn chạy được trong máy ảo Android. Tuy nhiên, mã RenderScript thực tế vốn vẫn chạy và cần truy cập vào bộ nhớ được phân bổ trong máy ảo Android. Để làm được việc này, bạn phải đưa bộ nhớ được phân bổ trong máy ảo vào thời gian chạy RenderScript. Quá trình này (gọi là liên kết) cho phép thời gian chạy RenderScript xử lý liền mạch bộ nhớ mà nó yêu cầu nhưng không thể phân bổ rõ ràng. Kết quả cuối cùng về cơ bản giống như khi bạn gọi malloc trong C. Có một lợi ích nữa là máy ảo Android có thể tiến hành thu thập rác cũng như chia sẻ bộ nhớ bằng lớp thời gian chạy RenderScript. Quy trình liên kết chỉ cần thiết đối với bộ nhớ được phân bổ động. Bộ nhớ được phân bổ tĩnh sẽ được tạo tự động cho mã RenderScript của bạn tại thời điểm biên dịch. Xem Hình 1 để biết thêm thông tin về cách diễn ra quy trình phân bổ bộ nhớ.

Để hỗ trợ hệ thống phân bổ bộ nhớ này, sẽ có một tập hợp API cho phép máy ảo Android phân bổ bộ nhớ và cung cấp chức năng tương tự cho lệnh gọi malloc. Về cơ bản, các lớp (class) này mô tả cách phân bổ bộ nhớ, đồng thời cũng thực hiện việc phân bổ. Để hiểu rõ hơn về cách thức hoạt động của các lớp này, bạn nên nghĩ về mối tương quan giữa các lớp này với một lệnh gọi malloc đơn giản có thể có dạng như sau:

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

Lệnh gọi malloc có thể được chia thành hai phần: kích thước của bộ nhớ được phân bổ (sizeof(int)) và số lượng đơn vị bộ nhớ cần được phân bổ (10). Khung Android cung cấp các lớp cho hai phần này cũng như một lớp đại diện cho chính malloc.

Lớp Element đại diện cho phần (sizeof(int)) của lệnh gọi malloc và đóng gói một ô trong quá trình phân bổ bộ nhớ, chẳng hạn như một giá trị số thực duy nhất hoặc một cấu trúc. Lớp Type đóng gói Element và số lượng phần tử để phân bổ (trong ví dụ của chúng ta là 10). Bạn có thể coi Type là một mảng của Element. Lớp Allocation thực hiện quá trình phân bổ bộ nhớ thực dựa trên một Type đã cho và đại diện cho bộ nhớ thực được phân bổ.

Trong hầu hết trường hợp, bạn không cần trực tiếp gọi API phân bổ bộ nhớ này. Các lớp được phản ánh sẽ tạo mã để tự động sử dụng các API này và tất cả những gì bạn cần làm để phân bổ bộ nhớ là gọi một hàm dựng được khai báo ở một trong các lớp được phản ánh rồi liên kết bộ nhớ kết quả Allocation với RenderScript. Trong một số trường hợp, bạn nên trực tiếp sử dụng các lớp này để tự phân bổ bộ nhớ, chẳng hạn như khi tải một bitmap qua một tài nguyên hoặc khi bạn muốn phân bổ bộ nhớ cho con trỏ đến các kiểu nguyên gốc. Bạn có thể xem cách thực hiện trong phần Phân bổ và liên kết bộ nhớ với RenderScript. Bảng sau đây mô tả chi tiết về 3 lớp quản lý bộ nhớ:

Loại đối tượng Android Nội dung mô tả
Element

Phần tử mô tả một ô của quá trình phân bổ bộ nhớ và có thể có 2 dạng: cơ bản hoặc phức tạp.

Một phần tử cơ bản chứa một thành phần dữ liệu của mọi kiểu dữ liệu RenderScript hợp lệ. Có thể kể đến một số kiểu dữ liệu phần tử cơ bản như một giá trị float đơn, một vectơ float4 hoặc một màu RGB-565.

Các phần tử phức tạp chứa một danh sách phần tử cơ bản và được tạo từ các struct mà bạn khai báo trong mã RenderScript. Ví dụ: một quy trình phân bổ có thể chứa nhiều struct được sắp xếp theo thứ tự trong bộ nhớ. Mỗi cấu trúc được xem là một phần tử riêng, thay vì mỗi kiểu dữ liệu trong cấu trúc đó.

Type

Kiểu (type) là một mẫu phân bổ bộ nhớ và bao gồm một phần tử cùng một hoặc nhiều phương diện. Kiểu mô tả bố cục của bộ nhớ (về cơ bản là một mảng của Element) nhưng không phân bổ bộ nhớ cho dữ liệu mà nó mô tả.

Mỗi kiểu bao gồm 5 phương diện: X, Y, Z, LOD (cấp chi tiết) và bề mặt (face) (của một bản đồ khối). Bạn có thể đặt các phương diện X, Y, Z thành giá trị số nguyên dương bất kỳ trong giới hạn của bộ nhớ còn trống. Một phương diện X đơn được phân bổ cho một phương diện X lớn hơn 0, còn phương diện Y và Z bằng 0 thể hiện việc không hiện diện. Ví dụ: hoạt động phân bổ x=10, y=1 được coi là hai phương diện và x=10, y=0 được coi là một phương diện. Các phương diện LOD và Bề mặt là giá trị boolean để cho biết việc hiện diện hay không hiện diện.

Allocation

Hoạt động phân bổ cung cấp bộ nhớ cho các ứng dụng dựa trên nội dung mô tả về bộ nhớ mà Type biểu thị. Bộ nhớ được phân bổ có thể tồn tại đồng thời trong nhiều không gian bộ nhớ. Nếu bộ nhớ được sửa đổi trong một không gian, bạn phải đồng bộ hoá một cách rõ ràng để bộ nhớ đó được cập nhật trong tất cả không gian khác hiện có.

Dữ liệu phân bổ được tải lên theo một trong hai cách chính: đánh dấu loại và bỏ đánh dấu loại. Đối với các mảng đơn giản, có các hàm copyFrom() lấy một mảng trên hệ thống Android rồi sao chép mảng đó vào kho lưu trữ bộ nhớ của lớp gốc (native layer memory store). Các biến thể chưa đánh dấu cho phép hệ thống Android sao chép nhiều mảng cấu trúc vì hệ thống không hỗ trợ cấu trúc. Ví dụ: nếu có một hoạt động phân bổ là một mảng dấu phẩy động n (n float), thì dữ liệu có trong mảng float[n] hoặc một mảng byte[n*4] có thể được sao chép.

Xử lý Memory

Các biến toàn cục, không tĩnh mà bạn khai báo trong RenderScript được phân bổ bộ nhớ tại thời điểm biên dịch. Bạn có thể trực tiếp xử lý các biến này trong mã RenderScript mà không cần phân bổ bộ nhớ cho chúng ở cấp khung Android. Lớp khung Android cũng có quyền truy cập vào các biến này qua các phương thức truy cập đã cung cấp được tạo trong các lớp phản ánh. Nếu các biến này được khởi tạo tại lớp thời gian chạy RenderScript, thì các giá trị đó sẽ được dùng để khởi tạo các giá trị tương ứng trong lớp khung Android. Nếu các biến toàn cục được đánh dấu là const, thì phương thức set sẽ không được tạo. Hãy tìm hiểu tại đây để biết thêm thông tin.

Lưu ý: Nếu bạn đang sử dụng một số cấu trúc RenderScript có chứa con trỏ (pointer), chẳng hạn như rs_program_fragmentrs_allocation, thì bạn phải lấy một đối tượng của lớp khung Android tương ứng trước rồi mới gọi phương thức set cho cấu trúc đó để liên kết bộ nhớ với thời gian chạy RenderScript. Bạn không thể thao tác trực tiếp trên những cấu trúc này tại lớp thời gian chạy RenderScript. Quy định hạn chế này không áp dụng cho các cấu trúc do người dùng xác định và có chứa các con trỏ, vì ngay từ đầu những cấu trúc này đã không xuất được sang loại lớp phản ánh. Hệ thống sẽ tạo lỗi biên dịch nếu bạn cố gắng khai báo một cấu trúc tĩnh toàn cục có chứa con trỏ.

RenderScript cũng hỗ trợ con trỏ, nhưng bạn phải phân bổ rõ ràng bộ nhớ trong mã khung Android. Khi khai báo một con trỏ chung trong tệp .rs, bạn sẽ phân bổ bộ nhớ thông qua lớp phản ánh phù hợp và liên kết bộ nhớ đó với lớp RenderScript gốc. Bạn có thể tương tác với bộ nhớ này qua lớp khung trong Android cũng như qua lớp RenderScript, lớp này giúp bạn linh hoạt sửa đổi các biến trong lớp phù hợp nhất.

Phân bổ và liên kết bộ nhớ động với RenderScript

Để phân bổ bộ nhớ động, bạn cần gọi hàm khởi tạo của lớp Script.FieldBase, đây là cách phổ biến nhất. Một cách khác là tạo Allocation theo cách thủ công, việc này cần thiết cho những phần như con trỏ kiểu nguyên gốc. Để dễ dàng thực hiện, bạn nên sử dụng hàm khởi tạo lớp Script.FieldBase mỗi khi có thể. Sau khi yêu cầu phân bổ bộ nhớ, hãy gọi phương thức bind phản ánh của con trỏ để liên kết bộ nhớ được phân bổ với thời gian chạy RenderScript.

Ví dụ sau đây phân bổ bộ nhớ cho cả con trỏ kiểu nguyên gốc (intPointer) và con trỏ cho một cấu trúc (touchPoints). Mã này cũng liên kết bộ nhớ với 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);

   ...
}

Đọc và ghi vào bộ nhớ

Bạn có thể đọc và ghi vào bộ nhớ được phân bổ động và tĩnh ở cả thời gian chạy RenderScript và lớp khung Android.

Bộ nhớ được phân bổ tĩnh đi kèm với hạn chế giao tiếp một chiều ở cấp thời gian chạy RenderScript. Khi mã RenderScript thay đổi giá trị của một biến, biến đó sẽ không giao tiếp trở lại lớp khung Android để đảm bảo hiệu suất. Giá trị cuối cùng được đặt qua khung Android luôn được trả về trong lệnh gọi phương thức get. Tuy nhiên, khi mã khung Android sửa đổi một biến, thay đổi đó có thể được thông báo tự động hoặc được đồng bộ hoá vào thời gian chạy RenderScript sau này. Nếu cần gửi dữ liệu từ thời gian chạy RenderScript tới lớp khung Android, bạn có thể sử dụng hàm rsSendToClient() để khắc phục hạn chế này.

Khi xử lý bộ nhớ được phân bổ động, mọi thay đổi ở lớp thời gian chạy RenderScript sẽ được truyền trở lại lớp khung Android nếu bạn đã sửa đổi hoạt động phân bổ bộ nhớ bằng cách sử dụng con trỏ liên kết với lớp đó. Thao tác sửa đổi một đối tượng ở lớp khung Android sẽ ngay lập tức truyền thay đổi đó trở lại lớp thời gian chạy RenderScript.

Đọc và ghi vào biến toàn cục

Hoạt động đọc và ghi vào các biến toàn cục là một quá trình đơn giản. Bạn có thể sử dụng các phương thức truy cập ở cấp khung Android hoặc đặt trực tiếp các phương thức này trong mã RenderScript. Xin lưu ý rằng mọi thay đổi bạn thực hiện trong mã RenderScript sẽ không được truyền trở lại lớp khung Android (xem tại đây để biết thêm thông tin).

Ví dụ: giả sử cấu trúc sau đây được khai báo trong tệp tên là rsfile.rs:

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

Point_t point;

Bạn có thể trực tiếp gán các giá trị cho cấu trúc như vậy ngay trong rsfile.rs. Các giá trị này không được truyền trở lại cấp khung Android:

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

Bạn có thể gán giá trị cho cấu trúc trong lớp khung Android như sau. Các giá trị này được truyền trở lại cấp thời gian chạy RenderScript một cách không đồng bộ:

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);

Bạn có thể đọc các giá trị trong mã RenderScript như sau:

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

Bạn có thể đọc các giá trị trong lớp khung Android bằng mã sau đây. Hãy lưu ý rằng mã này chỉ trả về một giá trị nếu bạn đặt giá trị ở cấp khung Android. Bạn sẽ nhận được trường hợp ngoại lệ cho con trỏ rỗng (null) nếu chỉ đặt giá trị ở cấp thời gian chạy RenderScript:

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());

Đọc và ghi con trỏ toàn cục

Giả sử bộ nhớ đã được phân bổ ở cấp khung Android và liên kết với thời gian chạy RenderScript, bạn có thể đọc và ghi bộ nhớ qua cấp khung Android bằng cách sử dụng các phương thức getset cho con trỏ đó. Trong lớp thời gian chạy RenderScript, bạn có thể dùng các con trỏ để đọc và ghi vào bộ nhớ như bình thường. Các thay đổi sẽ được truyền trở lại lớp khung Android, khác với bộ nhớ phân bổ tĩnh.

Ví dụ: giả sử cho con trỏ sau vào struct trong tệp tên là rsfile.rs:

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

Point_t *point;

Giả sử bạn đã phân bổ bộ nhớ ở lớp khung Android, bạn có thể truy cập các giá trị trong struct như bình thường. Mọi thay đổi bạn thực hiện cho cấu trúc thông qua biến con trỏ của cấu hình đó đều tự động có sẵn cho lớp khung Android:

Kotlin

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

Java

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

Bạn cũng có thể đọc và ghi các giá trị vào con trỏ ở lớp trong khung 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);

Sau khi bộ nhớ đã được liên kết, bạn không cần liên kết lại bộ nhớ với thời gian chạy RenderScript mỗi khi thay đổi một giá trị.