النص البرمجي RenderScript المتقدّم

بما أنّ التطبيقات التي تستخدم RenderScript لا تزال تعمل داخل جهاز Android الافتراضي، يمكنك الوصول إلى جميع واجهات برمجة تطبيقات إطار العمل المألوفة لك، ولكن يمكنك استخدام RenderScript عند الاقتضاء. لتسهيل هذا التفاعل بين إطار العمل ووقت تشغيل RenderScript، يتم أيضًا توفير طبقة وسيطة من الرمز لتسهيل الاتصال وإدارة الذاكرة بين مستويَي الرمز. يقدم هذا المستند المزيد من التفاصيل حول هذه الطبقات المختلفة من الرموز وكذلك كيفية مشاركة الذاكرة بين جهاز Android الافتراضي ووقت التشغيل RenderScript.

طبقة وقت تشغيل RenderScript

يتم تجميع رمز RenderScript وتنفيذه في طبقة وقت تشغيل مدمجة ومحددة جيدًا. توفّر واجهات برمجة التطبيقات لوقت تشغيل RenderScript دعمًا لإجراء العمليات الحسابية المكثّفة التي تكون قابلة للنقل وقابلة للتوسّع تلقائيًا لتلائم عدد النوى المتوفّرة على المعالج.

ملاحظة: يجب ضمان تشغيل وظائف C العادية في NDK على وحدة معالجة مركزية، لذلك لا يمكن لـ RenderScript الوصول إلى هذه المكتبات، لأنّ RenderScript مصمم للتشغيل على أنواع مختلفة من المعالجات.

يمكنك تحديد رمز RenderScript في ملفَّي .rs و.rsh في دليل src/ لمشروع Android الخاص بك. يتم تجميع الرمز إلى رمز بايت وسيط من خلال برنامج تجميع llvm الذي يتم تشغيله كجزء من إصدار Android. عند تشغيل تطبيقك على أحد الأجهزة، يتم تجميع رمز البايت (في الوقت المناسب) إلى رمز الجهاز عن طريق برنامج تجميع llvm آخر متوفّر على الجهاز. يتم تحسين رمز الجهاز للجهاز ويتم أيضًا تخزينه مؤقتًا، لذا لا تؤدي الاستخدامات اللاحقة للتطبيق المتوافق مع RenderScript إلى إعادة تجميع رمز البايت.

تتضمن بعض الميزات الرئيسية لمكتبات وقت تشغيل RenderScript ما يلي:

  • ميزات طلب تخصيص الذاكرة
  • هي مجموعة كبيرة من الدوال الحسابية تتضمّن نُسخًا ذات أعداد كبيرة ومتّجهة تحمل طابعًا زائدًا من العديد من الإجراءات الروتينية الشائعة. تتوفّر عمليات مثل الجمع والضرب وناتج الضرب النقطي وحاصل الضرب التبادلي، بالإضافة إلى وظائف الحساب الذري والمقارنة.
  • سلاسل إجراءات الإحالات الناجحة لأنواع البيانات الأساسية والمتجهات، وإجراءات المصفوفة، وسلاسل إجراءات التاريخ والوقت
  • أنواع البيانات وبنياتها المتوافقة مع نظام RenderScript، مثل أنواع المتجهات لتحديد متجهين أو ثلاثة أو أربعة متجهات.
  • دوال التسجيل

راجِع مرجع واجهة برمجة التطبيقات لوقت تشغيل RenderScript للحصول على مزيد من المعلومات عن الدوال المتاحة.

طبقة منعكسة

الطبقة المنعكسة هي مجموعة من الفئات التي تنشئها أدوات إصدار Android للسماح بالوصول إلى وقت تشغيل RenderScript من إطار عمل Android. توفر هذه الطبقة أيضًا طرقًا وأساليب إنشائية تتيح لك تخصيص مؤشرات الماوس المحددة في التعليمات البرمجية RenderScript والعمل عليها. توضّح القائمة التالية المكوّنات الرئيسية التي تنطبق على المحتوى:

  • يتم إنشاء كل ملف .rs تنشئه في فئة باسم project_root/gen/package/name/ScriptC_renderscript_filename من النوع ScriptC. هذا الملف هو نسخة .java من ملف .rs الخاص بك، ويمكنك الاتصال به من إطار عمل Android. وتتضمّن هذه الفئة العناصر التالية المعروضة في ملف .rs:
    • الدوال غير الثابتة
    • متغيّرات RenderScript عمومية غير ثابتة. يتم إنشاء طرق الموصّل لكل متغيّر، بحيث يمكنك قراءة متغيّرات RenderScript وكتابتها من إطار عمل Android. في حال إعداد متغيّر عمومي في طبقة وقت تشغيل RenderScript، يتم استخدام هذه القيم لإعداد القيم المقابلة في طبقة إطار عمل Android. إذا تم وضع علامة const على المتغيّرات العمومية، لن يتم إنشاء الطريقة 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 مصمَّم ليكون غير متزامن. عندما يستدعي رمز إطار عمل Android في RenderScript، يتم وضع المكالمة في قائمة الانتظار ويتم تنفيذها إن أمكن. ويسمح هذا الشرط لنظام RenderScript بالعمل بدون مقاطعة مستمرة وزيادة الفعالية. إذا تم السماح للدوال بالحصول على قيم إرجاع، فسيتم حظر الاستدعاء حتى يتم إرجاع القيمة.

إذا كنت تريد من رمز RenderScript إرسال قيمة مرة أخرى إلى إطار عمل 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 الطرق ودوال الإنشاء التالية:

  • هناك دوالَّ تحميل زائدة تسمح لك بتخصيص الذاكرة. وتتيح لك الدالة الإنشائية ScriptField_struct_name(RenderScript rs, int count) تحديد عدد البنى التي تريد تخصيص ذاكرة لها باستخدام المعلَمة count. تحدد الدالة الإنشائية ScriptField_struct_name(RenderScript rs, int count, int usages) معلَمة إضافية، وهي usages، تتيح لك تحديد مساحة الذاكرة لتخصيص الذاكرة هذا. هناك أربعة احتمالات لمساحة الذاكرة:
    • USAGE_SCRIPT: يتم تخصيص هذا الحقل في مساحة ذاكرة النص البرمجي. هذه هي مساحة الذاكرة التلقائية إذا لم يتم تحديد مساحة ذاكرة.
    • USAGE_GRAPHICS_TEXTURE: لتخصيص مساحة ذاكرة المظهر السطحي لوحدة معالجة الرسومات
    • USAGE_GRAPHICS_VERTEX: يتم تخصيص مساحة الذاكرة الرأسية لوحدة معالجة الرسومات.
    • USAGE_GRAPHICS_CONSTANTS: يتم تخصيص مساحة الذاكرة الثابتة لوحدة معالجة الرسومات التي تستخدمها كائنات البرنامج المختلفة.

    يمكنك تحديد مساحات ذاكرة متعددة باستخدام عامل التشغيل 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 الوصول تلقائيًا إلى الذاكرة المكتوبة حديثًا.
  • طُرق الموصّل للحصول على قيم كل حقل وضبطها في بنية معيّنة. تحتوي كل طريقة من طرق الموصّل هذه على معلَمة index لتحديد struct في المصفوفة التي تريد القراءة أو الكتابة فيها. تتضمن كل طريقة setter أيضًا معلَمة copyNow تحدد ما إذا كانت ستتم مزامنة هذه الذاكرة في الحال مع وقت تشغيل RenderScript. لمزامنة أي ذكرى لم تتم مزامنتها، يمكنك الاتصال بـ "copyAll()".
  • تنشئ الطريقة createElement() وصفًا للبنية في الذاكرة. ويُستخدَم هذا الوصف لتخصيص ذاكرة تتألف من عنصر واحد أو عدة عناصر.
  • تعمل السمة resize() إلى حد كبير مثل realloc() في لغة C، ما يسمح لك بتوسيع الذاكرة التي سبق تخصيصها والحفاظ على القيم الحالية التي تم إنشاؤها في السابق.
  • يعمل copyAll() على مزامنة الذاكرة التي تم ضبطها على مستوى إطار العمل مع وقت تشغيل RenderScript. عند استدعاء طريقة موصّل المجموعة في أحد الأعضاء، تتوفّر معلَمة copyNow منطقية اختيارية يمكنك تحديدها. ويؤدي تحديد true إلى مزامنة الذاكرة عند استدعاء الطريقة. إذا حدّدت القيمة 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 ربط الذاكرة التي تم تخصيصها في جهاز Android الافتراضي بوقت تشغيل RenderScript (لا يمكنك تخصيص ذاكرة في ملف .rs). لمزيد من المعلومات، راجِع العمل باستخدام الذاكرة المخصّصة.

واجهات برمجة التطبيقات لتخصيص الذاكرة

لا تزال التطبيقات التي تستخدم RenderScript تعمل على جهاز Android الافتراضي. أمّا رمز RenderScript الفعلي، فيعمل محليًا ويحتاج إلى الوصول إلى الذاكرة المخصصة في الجهاز الافتراضي الذي يعمل بنظام التشغيل Android. لتنفيذ ذلك، يجب إرفاق الذاكرة التي تم تخصيصها في الجهاز الافتراضي بوقت تشغيل RenderScript. تسمح هذه العملية، التي تُسمى "الربط"، لوقت تشغيل RenderScript بالعمل بسلاسة مع الذاكرة التي يطلبها، ولكن لا يمكن تخصيصها بشكلٍ صريح. النتيجة النهائية هي في الأساس نفس الشيء كما لو كنت استدعيت malloc في C. وتتمثل الميزة الإضافية في قدرة جهاز Android الافتراضي على تنفيذ عمليات جمع البيانات غير الضرورية بالإضافة إلى مشاركة الذاكرة مع طبقة وقت تشغيل RenderScript. لا يكون الربط ضروريًا إلا للذاكرة المخصصة ديناميكيًا. يتم تلقائيًا إنشاء ذاكرة مخصّصة بشكل ثابت للتعليمة البرمجية RenderScript في وقت التجميع. انظر الشكل 1 للحصول على مزيد من المعلومات حول كيفية تخصيص الذاكرة.

لتوفير نظام تخصيص الذاكرة هذا، هناك مجموعة من واجهات برمجة التطبيقات التي تتيح لجهاز Android الافتراضي تخصيص الذاكرة وتقديم وظائف مشابهة لاستدعاء malloc. تصف هذه الفئات بشكل أساسي كيفية تخصيص الذاكرة وكذلك تنفيذ التخصيص. لفهم آلية عمل هذه الصفوف بشكل أفضل، من الضروري التفكير فيها في ما يتعلق باستدعاء malloc بسيط قد يبدو على الشكل التالي:

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

يمكن تقسيم استدعاء malloc إلى جزأين: حجم الذاكرة التي يتم تخصيصها (sizeof(int)) وعدد وحدات الذاكرة التي يجب تخصيصها (10). يوفر إطار عمل Android فئات لهذين الجزأين بالإضافة إلى فئة لتمثيل malloc.

تمثّل الفئة Element الجزء (sizeof(int)) من الطلب malloc وتتضمّن خلية واحدة من عملية تخصيص الذاكرة، مثل قيمة عائمة واحدة أو بنية. وتتضمن الفئة Type السمة Element ومقدار العناصر المطلوب تخصيصها (10 في المثال). ويمكنك اعتبار Type مصفوفة من Element. تنفّذ الفئة Allocation عملية تخصيص الذاكرة الفعلية استنادًا إلى عنصر Type معيّن وتمثل الذاكرة المخصّصة الفعلية.

في معظم الحالات، لن تحتاج إلى استدعاء واجهات برمجة التطبيقات لتخصيص الذاكرة هذه مباشرةً. تنشئ فئات الطبقات المنعكسة رمزًا لاستخدام واجهات برمجة التطبيقات هذه تلقائيًا وكل ما عليك فعله لتخصيص الذاكرة هو استدعاء دالة إنشاء معرَّفة في إحدى فئات الطبقات المنعكسة ثم ربط الذاكرة الناتجة Allocation بـ RenderScript. هناك بعض الحالات التي قد تحتاج فيها إلى استخدام هذه الفئات مباشرةً لتخصيص الذاكرة بنفسك، مثل تحميل صورة نقطية من مورد أو عندما تريد تخصيص ذاكرة للمؤشرات للأنواع الأساسية. يمكنك التعرّف على طريقة إجراء ذلك في القسم تخصيص ذاكرة وربطها في RenderScript. يصف الجدول التالي فئات إدارة الذاكرة الثلاث بمزيد من التفصيل:

نوع كائن Android الوصف
Element

يصف العنصر خلية واحدة من عملية تخصيص الذاكرة ويمكن أن يكون له شكلان: أساسي أو معقد.

يحتوي العنصر الأساسي على مكون واحد من البيانات لأي نوع بيانات RenderScript صالح. تشمل الأمثلة على أنواع بيانات العناصر الأساسية قيمة float واحدة أو متجه float4 أو لون RGB-565 فردي.

تحتوي العناصر المعقدة على قائمة بالعناصر الأساسية ويتم إنشاؤها من struct التي تعلن عنها في رمز RenderScript. على سبيل المثال، يمكن أن تحتوي عملية التخصيص على عدة struct مرتّبة بالترتيب في الذاكرة. ويتم اعتبار كل بنية كعنصر خاص بها بدلاً من كل نوع بيانات ضمن هذه البنية.

Type

النوع هو نموذج لتخصيص الذاكرة ويتكون من عنصر وسمة واحدة أو أكثر. وهو يصف تنسيق الذاكرة (في الأساس مصفوفة من Elements) ولكن لا يخصّص الذاكرة للبيانات التي تصفها.

ويتكون النوع من خمسة أبعاد: X وY وZ وLOD (مستوى التفاصيل) والوجوه (لخريطة مكعبة). يمكنك ضبط الأبعاد "س" و"ص" و"ع" على أي قيمة عدد صحيح موجب ضمن قيود الذاكرة المتاحة. يكون تخصيص البُعد الواحد له بُعد X أكبر من الصفر، في حين أن السمتين Y وZ تساوي صفرًا للإشارة إلى عدم توفّرهما. على سبيل المثال، يُعتبر تخصيص س=10، ص=1 ثنائي الأبعاد وسَ=10، ص=0 بُعدًا واحدًا. يعتبر سمتا LOD وFaces قيمتين منطقية للإشارة إلى وجودهما أم لا.

Allocation

يوفّر التخصيص الذاكرة للتطبيقات استنادًا إلى وصف الذاكرة التي تمثّلها علامة Type. ويمكن أن تتوفّر الذاكرة المخصّصة في العديد من مساحات الذاكرة بشكلٍ متزامن. إذا تم تعديل الذاكرة في مساحة واحدة، يجب إجراء مزامنة واضحة للذاكرة، بحيث يتم تعديلها في جميع المساحات الأخرى التي توجد فيها.

يتم تحميل بيانات التخصيص بإحدى الطريقتين الأساسيتين: النوع "تم تحديد النوع" و"النوع غير المحدد". في المصفوفات البسيطة، هناك دوال copyFrom() تأخذ مصفوفة من نظام Android وتنسخها إلى مخزن ذاكرة الطبقة الأصلية. وتسمح الصيغ التي لم يتم وضع علامة لها لنظام Android بالنسخ إلى صفائف البُنى لأنها لا تتيح استخدام البُنى. على سبيل المثال، إذا كانت هناك عملية تخصيص تمثّل صفيفًا من عدد n عائم، يمكن نسخ البيانات المضمّنة في مصفوفة float[n] أو مصفوفة byte[n*4].

استخدام الذاكرة

يتم تخصيص ذاكرة للمتغيرات العامة غير الثابتة التي تُعلِن عنها في RenderScript في وقت التجميع. يمكنك استخدام هذه المتغيّرات مباشرةً في رمز RenderScript بدون الحاجة إلى تخصيص ذاكرة لها على مستوى إطار عمل Android. ويمكن لطبقة إطار عمل Android أيضًا الوصول إلى هذه المتغيّرات من خلال طرق الموصّل المتاحة التي يتم إنشاؤها في فئات الطبقة المنعكسة. في حال إعداد هذه المتغيّرات في طبقة وقت تشغيل RenderScript، يتم استخدام هذه القيم لإعداد القيم المقابلة في طبقة إطار عمل Android. إذا تم وضع علامة على المتغيرات العامة كثابتة، لن يتم إنشاء طريقة set. يمكنك الاطّلاع هنا على مزيد من التفاصيل.

ملاحظة: إذا كنت تستخدم بُنى RenderScript معينة تحتوي على مؤشرات، مثل rs_program_fragment وrs_allocation، يجب الحصول على عنصر من فئة إطار عمل Android المقابلة أولاً ثم استدعاء الطريقة set لهذه البنية لربط الذاكرة بوقت تشغيل RenderScript. ولا يمكنك معالجة هذه البنى مباشرةً في طبقة وقت تشغيل RenderScript. لا ينطبق هذا التقييد على البنى التي يحددها المستخدم والتي تحتوي على مؤشرات، لأنه لا يمكن تصديرها إلى فئة طبقة منعكسة في المقام الأول. يظهر خطأ في برنامج التجميع إذا حاولت الإعلان عن بنية عامة غير ثابتة تحتوي على مؤشر.

يتيح RenderScript أيضًا استخدام مؤشرات الماوس، ولكن يجب تخصيص الذاكرة بشكل صريح في رمز إطار عمل Android. عندما تُعلن عن مؤشر عمومي في ملف .rs، يتم تخصيص ذاكرة من خلال فئة الطبقة المنعكسة المناسبة وربط تلك الذاكرة بطبقة RenderScript الأصلية. يمكنك التفاعل مع هذه الذاكرة من طبقة إطار عمل Android وكذلك من طبقة RenderScript التي توفّر لك المرونة في تعديل المتغيّرات في الطبقة الأكثر ملاءمةً.

تخصيص ذاكرة ديناميكية وربطها في 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:

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، يمكنك قراءة الذاكرة وكتابتها من مستوى إطار عمل Android باستخدام الطريقتين get وset لهذا المؤشر. في طبقة وقت تشغيل RenderScript، يمكنك القراءة والكتابة في الذاكرة باستخدام المؤشرات كالمعتاد، ويتم نشر التغييرات مرة أخرى في طبقة إطار عمل 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;

يمكنك أيضًا قراءة القيم وكتابتها في المؤشر على طبقة إطار عمل 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 في كل مرة تجري فيها تغييرًا إلى قيمة.