RenderScript מתקדם

מכיוון שאפליקציות שמשתמשות ב-RenderScript עדיין פועלות ב-VM של Android, יש לכם גישה לכל ממשקי ה-API של framework שאתם מכירים, אבל אתם יכולים להשתמש ב-RenderScript במקרים הרלוונטיים. כדי לאפשר את האינטראקציה הזו בין ה-framework וזמן הריצה של RenderScript, גם שכבת ביניים של קוד קיימים כדי להקל על התקשורת וניהול הזיכרון בין שתי רמות הקוד. במסמך הזה יש פירוט נוסף על הנושאים האלה בשכבות קוד שונות וגם האופן שבו הזיכרון משותף בין ה-VM של Android זמן ריצה של RenderScript.

שכבת זמן ריצה של RenderScript

קוד RenderScript שלך עבר הידור בשכבה קומפקטית ומוגדרת היטב של סביבת זמן ריצה. ממשקי ה-API של זמן הריצה של RenderScript מציעים תמיכה ב עיבוד נתונים אינטנסיבי שניתן לנייד וניתן להתאמה באופן אוטומטי את כמות הליבות הזמינות במעבד.

הערה: הפונקציות הסטנדרטיות של C ב-NDK חייבות להיות מובטח שהוא יפעל במעבד (CPU), כך ש-RenderScript לא יוכל לגשת לספריות האלה, כי RenderScript נועד לפעול בסוגים שונים של מעבדים.

הגדרת קוד RenderScript שלך ב-.rs ו-.rsh קבצים בספרייה src/ של פרויקט Android. הקוד עובר לבייטקוד (bytecode) מתווך באמצעות מהדר (compiler) llvm שפועל כחלק מגרסת build של Android. כשהבקשה פועל במכשיר, והבייטקוד עובר הידור (בדיוק בזמן) לקוד מכונה על ידי משתמש אחר מהדר (compiler) llvm שנמצא במכשיר. קוד המכונה עבר אופטימיזציה במכשיר וגם במטמון, כך ששימושים עתידיים באפליקציה שהופעל בה RenderScript לא להדר מחדש את הבייטקוד.

אלה כמה מהתכונות העיקריות של ספריות זמן הריצה של RenderScript:

  • תכונות הבקשה להקצאת זיכרון
  • אוסף גדול של פונקציות מתמטיות עם גרסאות עם עומס יתר בהקלדת וקטורים ובסקלריות מתרחישים נפוצים רבים. פעולות כמו הוספה, כפל, מכפלה ומוצר חוצות-מוצרים וגם פונקציות אריתמטיות והשוואה אטומיות.
  • תרחישים של המרות עבור וקטורים וסוגי נתונים פרימיטיביים, תרחישים של מטריצות ותאריך ושעה תרחישים
  • סוגי נתונים ומבנים שיתמכו במערכת RenderScript, כמו סוגי וקטורים הגדרה של וקטורים של שניים, שלושה או ארבעה.
  • פונקציות רישום ביומן

מידע נוסף על הפונקציות הזמינות זמין בחומר העזר בנושא API של זמן ריצה של RenderScript.

שכבה מוחזרת

השכבה שמשתקפת היא קבוצה של מחלקות שכלי ה-build של Android יוצרים כדי לאפשר גישה לסביבת זמן הריצה של RenderScript מה-framework של Android. השכבה הזאת מספקת גם שיטות ובנאים שמאפשרים לכם להקצות ולעבוד עם זיכרון עבור מצביעים שמוגדרים בקוד RenderScript. הרשימה הבאה מתארת את הרכיבים שמשתקפים:

  • כל קובץ .rs שיוצרים נוצר לכיתה בשם project_root/gen/package/name/ScriptC_renderscript_filename מתוך מקלידים ScriptC. הקובץ הזה הוא גרסת .java של את הקובץ .rs, שאפשר לקרוא לו מה-framework של Android. הכיתה הזו מכילה את הפריטים הבאים שמשתקפים מקובץ .rs:
    • פונקציות לא סטטיות
    • משתנים גלובליים לא סטטיים של RenderScript. אביזר לכל משתנה נוצרות שיטות, כך שאפשר לקרוא לכתוב את המשתנים של RenderScript מ-Android . אם משתנה גלובלי מאותחל בשכבת זמן הריצה של RenderScript, הערכים האלה משמשים כדי אתחול הערכים התואמים ב-framework של Android בשכבת זרימת הנתונים. אם משתנים גלובליים מסומנים כ-const, אז ה-method set שנוצר. חיפוש כאן אפשר לקבל פרטים נוספים.

    • מצביעים גלובליים
  • struct משתקף במחלקה משלו בשם project_root/gen/package/name/ScriptField_struct_name, שנמשך עד Script.FieldBase. המחלקה מייצגת מערך של 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;
}

לאחר מכן נוצר קוד ה-Java הבא:

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 מתוכננת אסינכרוני. כשקוד framework של Android שולח קריאה ל-RenderScript, הקריאה נמצאת בתור ו לבצע אותו כשאפשר. ההגבלה הזו מאפשרת למערכת RenderScript לפעול ללא קבוע את ההפרעה לו, ולהגביר את היעילות. אם לפונקציות הייתה הרשאה להחזיר ערכים, הקריאה תיחסם עד שהערך יוחזר.

אם רוצים שקוד RenderScript ישלח ערך חזרה ל-framework של Android, צריך להשתמש ב- rsSendToClient() מותאמת אישית.

משתנים

משתנים מהסוגים הנתמכים נכללים במחלקה של הסקריפט עצמה, שנמצאת ב- project_root/gen/package/name/ScriptC_renderscript_filename קבוצה של רכיבי גישה נוצרות שיטות לכל משתנה. לדוגמה, אם תגדירו את המשתנה הבא קוד RenderScript שלך:

uint32_t unsignedInteger = 1;

לאחר מכן נוצר קוד ה-Java הבא:

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 מגדירה את השיטות ואת הבנאים הבאים:

  • עומס יתר במבנים שמאפשרים לכם להקצות זיכרון. constructor של ScriptField_struct_name(RenderScript rs, int count) מאפשר כדי להגדיר את מספר המבנים שרוצים להקצות להם זיכרון count. ה-constructor של ScriptField_struct_name(RenderScript rs, int count, int usages) מגדיר פרמטר נוסף, usages, מאפשרת לציין את נפח הזיכרון של הקצאת הזיכרון. יש ארבעה מקום לזיכרון האפשרויות:
    • 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, בצורת אובייקט. המחלקה הפנימית הזו שימושית אם יותר הגיוני לעבוד באמצעות struct בקוד של Android. כשמסיימים לתמרן את האובייקט, אפשר לדחוף את האובייקט לזיכרון שהוקצה על ידי קריאה ל-set(Item i, int index, boolean copyNow) והגדרת Item למיקום הרצוי של המערך. לזמן הריצה של RenderScript יש גישה אוטומטית לזיכרון החדש שנכתב.
  • methods של רכיבי גישה כדי לקבל ולהגדיר את הערכים של כל שדה במבנה. כל אחד מאלה ל-methods של רכיב הגישה יש פרמטר index שמציין את struct המערך שרוצים לקרוא או לכתוב בו. לכל שיטת הגדרה יש גם פרמטר copyNow שמציין אם לסנכרן את הזיכרון הזה באופן מיידי לסביבת זמן הריצה של RenderScript. כדי לסנכרן זיכרון שלא סונכרן, צריך להתקשר copyAll()
  • השיטה createElement() יוצרת תיאור של המבנה בזיכרון. הזה משמש להקצאת זיכרון שמורכב מרכיב אחד או יותר.
  • resize() פועל כמו realloc() ב-C, ומאפשר להרחיב את הזיכרון שהוקצה בעבר, תוך שמירה על הערכים הנוכחיים נוצר.
  • הפונקציה copyAll() מסנכרנת את הזיכרון שהוגדר ברמת ה-framework עם זמן ריצה של RenderScript. כשמבצעים קריאה ל-method של רכיב גישה מוגדר אצל חבר, יש אפשרות פרמטר בוליאני copyNow שאפשר לציין. ציון true מסנכרן את הזיכרון כשקוראים ל-method. אם מציינים את הערך False, אפשר לקרוא לפונקציה copyAll() פעם אחת, והיא מסנכרנת את הזיכרון מאפיינים שעדיין לא סונכרנו.

מצביעים

מצביעים גלובליים משתקפים במחלקה של הסקריפט עצמה, שנמצאת ב- project_root/gen/package/name/ScriptC_renderscript_filename שלך יכול להצהיר על מצביעים ל-struct או לכל אחד מסוגי RenderScript הנתמכים, אבל struct לא יכול להכיל מצביעים או מערכים מקוננים. לדוגמה, אם תגדיר המצביעים הבאים לstruct ולint32_t

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

Point_t *touchPoints;
int32_t *intPointer;

לאחר מכן נוצר קוד ה-Java הבא:

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

שיטת get ושיטה מיוחדת בשם bind_pointer_name (במקום השיטה set()) נוצרות. השיטה bind_pointer_name מאפשרת לקשר את הזיכרון שמוקצית ב-VM של Android לזמן הריצה של RenderScript (לא ניתן להקצות בזיכרון שלך בקובץ .rs). מידע נוסף זמין בעבודה עם זיכרון מוקצה.

ממשקי API להקצאת זיכרון

אפליקציות שמשתמשות ב-RenderScript עדיין פועלות ב-VM של Android. עם זאת, קוד RenderScript בפועל רץ באופן טבעי צריכה גישה לזיכרון שהוקצה ב-VM של Android. כדי לעשות את זה, צריך לצרף את הזיכרון שהוקצה ל-VM לסביבת זמן הריצה של RenderScript. הזה שנקרא 'קישור', מאפשר לסביבת זמן הריצה של RenderScript לעבוד בצורה חלקה עם הזיכרון בקשות אך לא יכולה להקצות באופן מפורש. התוצאה הסופית היא למעשה אותה תוצאה שנקראה malloc בשפת C. היתרון הנוסף הוא שהמכונה הווירטואלית של Android יכולה לבצע איסוף אשפה וגם לשתף את הזיכרון עם שכבת זמן הריצה של RenderScript. קישור נדרש רק לזיכרון שמוקצה באופן דינמי. באופן סטטי הזיכרון שהוקצה נוצר באופן אוטומטי לקוד ה-RenderScript בזמן הקומפילציה. ראו איור 1 לקבלת מידע נוסף על האופן שבו מתבצעת הקצאת זיכרון.

כדי לתמוך במערכת הקצאת הזיכרון הזו, יש קבוצה של ממשקי API שמאפשרים ל-VM של Android להקצות זיכרון ולהציע פונקציונליות דומה לזו של קריאה ל-malloc. הכיתות האלה לתאר בעיקר את האופן שבו צריך להקצות את הזיכרון וגם לבצע את ההקצאה. לשיפור להבין איך הכיתות האלה פועלות, כדאי לחשוב עליהן ביחס שיחת malloc שיכולה להיראות כך:

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

אפשר לחלק את הקריאה malloc לשני חלקים: גודל הזיכרון שמוקצה (sizeof(int)), וגם את מספר היחידות של הזיכרון שיש להקצות (10). במסגרת Android יש סיווגים לשני החלקים האלה וגם מחלקה שתייצג את malloc עצמה.

המחלקה Element מייצגת את החלק (sizeof(int)) של הקריאה malloc ומציין תא אחד בהקצאת זיכרון, למשל תא יחיד ערך מסוג מספר ממשי (float) או מבנה. המחלקה Type כוללת את Element ואת מספר הרכיבים להקצאה (10 בדוגמה שלנו). אפשר לחשוב על Type בתור מערך של Element. המחלקה Allocation מבצעת את הפעולה הקצאת הזיכרון על סמך Type נתון, ומייצגת את הזיכרון שהוקצה בפועל.

ברוב המקרים, אין צורך להפעיל ישירות את ממשקי ה-API האלה להקצאת זיכרון. השכבה שמשתקפת הכיתה מפיקה קוד לשימוש אוטומטי בממשקי ה-API, וכל מה שצריך לעשות כדי להקצות זיכרון הוא שמוצהר באחד מסיווגי השכבות המשתקפים ואז מקשרים מהזיכרון שנוצר Allocation ל-RenderScript. יש מצבים מסוימים שבהם תרצו להשתמש בכיתות האלה ישירות כדי להקצות זיכרון למשל, טעינת מפת סיביות ממשאב או כשרוצים להקצות זיכרון לסמנים מסוגים פרימיטיביים. אפשר לראות איך לעשות זאת הקצאה וקישור של זיכרון לקטע RenderScript. בטבלה הבאה מתוארים בצורה מפורטת יותר שלושת הסוגים של ניהול הזיכרון:

סוג אובייקט Android תיאור
Element

רכיב מתאר תא אחד של הקצאת זיכרון ויכול להיות בשתי צורות: בסיסית או הוא מורכב.

רכיב בסיסי מכיל רכיב יחיד של נתונים מכל סוג נתונים חוקי של RenderScript. דוגמאות לסוגי נתונים בסיסיים של אלמנטים כוללות ערך יחיד של float, וקטור float4 או בצבע RGB-565 יחיד.

אלמנטים מורכבים כוללים רשימה של אלמנטים בסיסיים והם נוצרים מתוך הנכסים מסוג 'struct' שהצהרת עליהם בקוד RenderScript. לדוגמה: הקצאה יכול להכיל כמה פרמטרים של struct שמסודרים בזיכרון לפי הסדר. כל מבנה נחשב רכיב מסוים, ולא לכל סוג נתונים בתוך אותו רכיב.

Type

סוג הוא תבנית להקצאת זיכרון שמורכבת מרכיב אחד או יותר מאפיינים. הוא מתאר את פריסת הזיכרון (בעיקרון מערך של Element), אבל לא מקצה את הזיכרון לנתונים שהוא מכיל שמתארת.

סוג מורכב מחמישה מאפיינים: X, Y, Z, LOD (רמת פירוט) ופנים (של קובייה מפה). אפשר להגדיר את המימדים X,Y,Z לכל ערך של מספר שלם חיובי במסגרת ומגבלות של הזיכרון הזמין. להקצאת מאפיין יחיד יש מאפיין X של גדול מאפס ואילו המאפיינים Y ו-Z הם אפס כדי לציין שהם לא קיימים. עבור לדוגמה, הקצאה של x=10, y=1 נחשבת לדו-ממדית ו-x=10, y=0 הוא מוגדר כחד ממדי. המאפיינים LOD ו-Faces הם בוליאניים כדי לציין שהם קיימים או לא קיים.

Allocation

הקצאה מספקת את הזיכרון לאפליקציות על סמך תיאור של הזיכרון שמיוצג על ידי Type. הזיכרון שהוקצה יכול להופיע ב- הרבה מרחבי זיכרון בו-זמנית. אם הזיכרון משתנה במרחב משותף אחד, צריך להגדיר אותו לסנכרן את הזיכרון כך שהוא יתעדכן בכל המרחבים האחרים שבהם הוא קיים.

ההעלאה של נתוני הקצאה מתבצעת באחת משתי דרכים עיקריות: הסוג מסומן והסוג לא מסומן. במערכים פשוטים יש פונקציות copyFrom() שלוקחות מערך במערכת Android ומעתיקים אותה למאגר הזיכרון של השכבה המקורית. הווריאנטים שלא סומנו מאפשרים שימוש שמערכת Android תעתיק מערכים של מבנים כי היא לא תומכת מבנים. לדוגמה, אם יש הקצאה שהיא מערך של מספרים צפים, הנתונים אפשר להעתיק במערך float[n] או מערך byte[n*4].

איך לעבוד עם הזיכרון

משתנים גלובליים לא סטטיים שמצהירים עליהם ב-RenderScript מקבלים זיכרון בזמן ההידור. אפשר לעבוד עם המשתנים האלה ישירות בקוד RenderScript בלי שתצטרכו להקצות את הזיכרון ברמת ה-framework של Android. לשכבת ה-framework של Android יש גם גישה למשתנים האלה באמצעות ה-methods של רכיב ה-accessor, שנוצרות במחלקות השכבות משתקפות. אם המשתנים האלה בשכבת זמן הריצה של RenderScript, הערכים האלה משמשים לאתחול בשכבת המסגרת של Android. אם משתנים גלובליים מסומנים כקבועים, השיטה set מסמן לא נוצרו. כאן אפשר למצוא פרטים נוספים.

הערה: אם אתם משתמשים במבנים מסוימים של RenderScript שמכילים סמנים, כמו rs_program_fragment ו-rs_allocation, עליך לקבל אובייקט קודם המחלקה המתאימה של Android framework ואז קוראים לשיטה set בשביל זה כדי לקשר את הזיכרון לסביבת זמן הריצה של RenderScript. אי אפשר לשנות את המבנים האלה ישירות בשכבת זמן הריצה של RenderScript. ההגבלה הזו לא רלוונטית למבנים שהוגדרו על ידי המשתמש מכילים מצביעים, כי אי אפשר לייצא אותם למחלקה של שכבה מוחזרת מלכתחילה. תיווצר שגיאת מהדר אם תנסו להצהיר על גרסה גלובלית לא סטטית שמכיל את מצביע העכבר.

ל-RenderScript יש גם תמיכה בסמנים, אבל צריך להקצות את הזיכרון באופן מפורש. קוד מסגרת של Android. כשמצהירים על מצביע גלובלי בקובץ .rs, להקצות זיכרון באמצעות מחלקה המתאימה של השכבה הבאה, ולחבר את הזיכרון הזה לשכבת ה-RenderScript. אפשר להשתמש בזיכרון הזה משכבת ה-framework של Android וגם לשכבת RenderScript, שמציעה את הגמישות לשנות את המשתנים או שכבות מתאימות.

הקצאה וקישור של זיכרון דינמי ל-RenderScript

כדי להקצות זיכרון דינמי, צריך לקרוא ל-constructor של הכיתה Script.FieldBase, זו הדרך הנפוצה ביותר. חלופה היא ליצור Allocation באופן ידני, שנדרש עבור דברים כמו מצביעי טיפוס פרימיטיבי. אתם צריכים להשתמש ב-constructor של מחלקה Script.FieldBase כשהדבר אפשרי, כדי לשמור על פשטות. לאחר קבלת הקצאת זיכרון, קוראים ל-method 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 framework.

זיכרון שמוקצה באופן סטטי מגיע עם הגבלת תקשורת חד-כיוונית ברמת זמן הריצה של RenderScript. כשקוד RenderScript משנה את הערך של משתנה, הוא לא הועברה לשכבת ה-framework של Android למטרות יעילות. הערך האחרון שמוגדר מ-framework של Android תמיד מוחזר במהלך קריאה ל-get . עם זאת, כשקוד framework של Android משנה משתנה, אפשר להעביר את השינוי הזה אל שזמן הריצה של RenderScript סונכרן באופן אוטומטי או מסתנכרן במועד מאוחר יותר. אם צריך לשלוח נתונים מסביבת זמן הריצה של RenderScript לשכבת ה-framework של Android, אפשר להשתמש הפונקציה rsSendToClient() כדי להתגבר על המגבלה הזו.

כשעובדים עם זיכרון שמוקצה באופן דינמי, השינויים בשכבת זמן הריצה של RenderScript מופצים בחזרה לשכבת ה-framework של Android, אם שיניתם את הקצאת הזיכרון באמצעות המצביע המשויך אליה. שינוי אובייקט בשכבת ה-framework של Android מופץ באופן מיידי את השינוי בחזרה ל-RenderScript בשכבת זמן הריצה.

קריאה וכתיבה למשתנים גלובליים

הקריאה וכתיבה למשתנים גלובליים היא תהליך פשוט. אפשר להשתמש ב-methods של רכיב הגישה ברמת ה-framework של Android, או להגדיר אותם ישירות בקוד RenderScript. חשוב לזכור שבכל פעם השינויים שמבצעים בקוד RenderScript לא מופצים חזרה לשכבת המסגרת של Android (חיפוש כאן לקבלת פרטים נוספים).

לדוגמה, בהינתן המבנה הבא שהוצהר בקובץ בשם rsfile.rs:

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

Point_t point;

אפשר להקצות ערכים למבנה כמו זה ישירות ב-rsfile.rs. הערכים האלה אינם הוחזרו לרמת ה-framework של Android:

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

אפשר להקצות ערכים למבנה בשכבת ה-framework של 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);

אפשר לקרוא את הערכים בשכבת ה-framework של Android עם הקוד הבא. חשוב לזכור הפונקציה מחזיר ערך רק אם הוגדר ערך ברמת ה-framework של Android. יתקבל מצביע null חריג אם מגדירים את הערך רק ברמת זמן הריצה של 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());

קריאה וכתיבה של מיקום הסמן של משתמשים אחרים

בהנחה שהזיכרון הוקצה ברמת ה-framework של Android וקשור לזמן הריצה של RenderScript, אפשר לקרוא ולכתוב זיכרון ברמת ה-framework של Android באמצעות השיטות get ו-set לאותו מצביע. בשכבה של סביבת זמן הריצה של RenderScript, אפשר לקרוא ולכתוב לזיכרון באמצעות מצביעים כרגיל, והשינויים מופצים. בחזרה לשכבת ה-framework של Android, בניגוד לזיכרון שמוקצה באופן סטטי.

לדוגמה, בהינתן הסמן הבא ל-struct בקובץ בשם rsfile.rs:

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;

אפשר גם לקרוא ולכתוב ערכים לסמן בשכבת ה-framework של 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 בכל פעם שאתם מבצעים שינוי בערך.