نظرة عامة على RenderScript

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

لبدء استخدام RenderScript، هناك مفهومان رئيسيان يجب فهمهما:

  • اللغة نفسها هي لغة مشتقة من C99 لكتابة حوسبة عالية الأداء الرمز. تصف كتابة Kernel في RenderScript وكيفية استخدامها لكتابة نواة الحوسبة.
  • تُستخدَم control API لإدارة موارد RenderScript منذ عمرها. التحكم في تنفيذ النواة. يتوفر بثلاث لغات مختلفة: Java وC++ على Android NDK، ولغة النواة المشتقّة من C99 نفسها. استخدام RenderScript من لغة ترميز Java يصف نص RenderScript أحادي المصدر العنصر الأول والثالث. على التوالي.

كتابة نواة RenderScript

توجد نواة RenderScript عادةً في ملف .rs في الدليل <project_root>/src/rs؛ كل ملف في .rs يسمى script. يحتوي كل نص برمجي على مجموعة النواة والدوال والمتغيرات الخاصة به. يمكن للنص تحتوي على:

  • بيان براغما (#pragma version(1)) الذي يفصح عن إصدار لغة النواة في RenderScript المستخدَمة في هذا النص البرمجي. وفي الوقت الحالي، الرقم 1 هو القيمة الصالحة الوحيدة.
  • يشير ذلك المصطلح إلى إعلان براغما (#pragma rs java_package_name(com.example.app)). تعلن عن اسم الحزمة لفئات Java المنعكسة من هذا النص البرمجي. لاحظ أن ملف .rs يجب أن يكون جزءًا من حزمة التطبيق، وليس في مشروع المكتبة.
  • لا يتم توفير أي دوال قابلة للاستدعاء أو أكثر. الدالة القابلة للاستدعاء هي RenderScript واحد يتضمّن سلسلة تعليمات يمكنك استدعاؤها من رمز Java باستخدام وسيطات عشوائية. غالبًا ما تكون هذه مفيدة الإعداد الأولي أو العمليات الحاسوبية التسلسلية داخل مسار معالجة أكبر.
  • لا يتم إدخال أي عبارات عامة أو أكثر. يشبه النص البرمجي العمومي المتغير العمومي في C. يمكنك الوصول إلى المتغيرات العمومية للنص البرمجي من رمز Java، وتُستخدم غالبًا لتمرير المَعلمات إلى RenderScript. النواة. يمكنك الاطّلاع على شرح مفصَّل للنصوص العامة هنا.

  • صفر أو أكثر من نواة الحوسبة. النواة الحاسوبية هي دالة أو مجموعة دوال يمكنك توجيه وقت تشغيل RenderScript لتنفيذها بالتوازي عبر مجموعة من البيانات. هناك نوعان من الحوسبة النواة: نواة التعيين (تُعرف أيضًا باسم نواة لكلّ) وتخفيض النواة.

    نواة التعيين هي دالة متوازية تعمل على مجموعة من Allocations بالأبعاد نفسها. يتم تنفيذه تلقائيًا مرة واحدة لكل إحداثي في تلك الأبعاد. يتم استخدامها عادةً (ولكن ليس حصريًا) تحويل مجموعة من المدخلات Allocations إلى الناتج Allocation واحد Element في الوقت.

    • في ما يلي مثال على نواة ربط بسيطة:

      uchar4 RS_KERNEL invert(uchar4 in, uint32_t x, uint32_t y) {
        uchar4 out = in;
        out.r = 255 - in.r;
        out.g = 255 - in.g;
        out.b = 255 - in.b;
        return out;
      }

      وفي معظم الجوانب، يُعد هذا مطابقًا لمعيار C الأخرى. السمة RS_KERNEL المطبَّقة على دالة النموذج الأوّلي للدالة أن الدالة هي نواة تعيين RenderScript بدلاً من دالة قابلة للاستدعاء. تتم تعبئة الوسيطة in تلقائيًا استنادًا إلى تم تمرير الإدخال Allocation إلى تشغيل النواة. تشير رسالة الأشكال البيانية الوسيطات x وy هي التي تمت مناقشتها أدناه. تكون القيمة التي يتم إرجاعها من النواة تلقائيًا في المكان المناسب في الناتج Allocation. يتم تشغيل هذه النواة تلقائيًا على مستوى الإدخال بالكامل Allocation، مع تنفيذ دالة kernel مرة واحدة لكل Element في Allocation.

      قد تحتوي نواة الربط على إدخال Allocations واحد أو أكثر، أو إخراج واحد Allocation، أو كليهما. تشير رسالة الأشكال البيانية عمليات التأكّد من وقت تشغيل RenderScript للتأكّد من تطابق جميع تخصيصات المدخلات والمخرجات وأن هناك أنواع Element للمدخل والمخرجات تتطابق التخصيصات مع النموذج الأولي للنواة؛ إذا تعذَّر إجراء أي من عمليتي التحقق، فإن RenderScript يطرح استثناء.

      ملاحظة: قبل الإصدار Android 6.0 (المستوى 23 من واجهة برمجة التطبيقات)، قد تحتاج نواة التعيين إلى لا تحتوي على أكثر من إدخال واحد Allocation.

      إذا كنت بحاجة إلى إدخال أو إخراج أكثر Allocations من في النواة (kernel)، يجب ربط هذه الكائنات بـ rs_allocation نصوص برمجية عمومية. ويتم الوصول إليه من خلال دالة kernel أو دالة قابلة للاستدعاء عبر rsGetElementAt_type() أو rsSetElementAt_type().

      ملاحظة: RS_KERNEL عبارة عن وحدة ماكرو يتم تحديدها تلقائيًا بواسطة RenderScript لتيسير الأمر عليك:

      #define RS_KERNEL __attribute__((kernel))
      

    نواة الاختزال هي مجموعة من الدوال التي تعمل على مجموعة من المدخلات. Allocations بالأبعاد نفسها. بشكل افتراضي، يتم تنفيذ وظيفة المركم مرة واحدة لكل التنسيق في تلك الأبعاد. ويتم استخدامه عادةً (وليس حصريًا) لـ "تقليل" CANNOT TRANSLATE مجموعة من المدخلات Allocations إلى واحدة

    • وفي ما يلي مثال لعملية الخفض البسيطة kernel تضيف Elements من الإدخال:

      #pragma rs reduce(addint) accumulator(addintAccum)
      
      static void addintAccum(int *accum, int val) {
        *accum += val;
      }

      تتكوّن نواة الاختزال من دالة واحدة أو أكثر من الدوال المكتوبة من قِبل المستخدم. تُستخدم #pragma rs reduce لتعريف النواة (kernel) من خلال تحديد اسمها. (addint، في هذا المثال) وأسماء الدوال وأدوارها أعلى النواة (الدالة accumulator addintAccum، في هذه مثال). يجب أن تكون جميع هذه الدوال static. نجد أن نواة الاختزال دائمًا تتطلب الدالة accumulator، قد يكون له أيضًا دوال أخرى، اعتمادًا على على ما تريد أن تفعله النواة.

      يجب أن تعرض دالة تجميع النواة المستندة إلى الاختزال void ويجب أن تتضمّن على الأقل وسيطتين. الوسيطة الأولى (accum، في هذا المثال) هي مؤشر إلى عنصر بيانات المركم والثاني (val، في هذا المثال) هو يتم ملؤه تلقائيًا استنادًا إلى الإدخال Allocation الذي تم تمريره إلى إطلاق النواة. يتم إنشاء عنصر بيانات المركم من خلال وقت تشغيل RenderScript. في افتراضيًا، يتم إعدادها على صفر. يتم تشغيل هذه النواة تلقائيًا على مستوى الإدخال بالكامل Allocation، مع تنفيذ وظيفة المركم مرة واحدة لكل Element في Allocation. من وافتراضيًا، يتم التعامل مع القيمة النهائية لعنصر بيانات المركم نتيجة ويتم إرجاعها إلى Java. يفحص وقت تشغيل RenderScript للتأكّد من تطابق النوع Element من تخصيص الإدخال مع دالة المركم. والنموذج الأوّلي وفي حال عدم التطابق، تطرح RenderScript استثناءً.

      تحتوي نواة التقليل على إدخال Allocations واحد أو أكثر ولكن لا تحتوي على ناتج Allocations.

      يمكنك الاطّلاع على مزيد من التفاصيل هنا حول نواة تقليل الانحدار.

      تتوفّر نواات الانخفاض في الإصدار 7.0 (المستوى 24 من واجهة برمجة التطبيقات) والإصدارات الأحدث من نظام التشغيل Android.

    يمكن أن تصل دالة النواة (kernel) أو دالة مركم النواة الاختزالية إلى الإحداثيات لعملية التنفيذ الحالية باستخدام الوسيطات الخاصة x، y، وz، والذي يجب أن يكون من النوع int أو uint32_t. هذه الوسيطات اختيارية.

    دالة النواة للتعيين أو مركم النواة الخفض الدالة أيضًا على الوسيطة الخاصة الاختيارية context من النوع rs_kernel_context. تحتاج إليه مجموعة من واجهات برمجة التطبيقات (API) لوقت التشغيل المستخدمة للاستعلام خصائص معينة لعملية التنفيذ الحالية -- على سبيل المثال، rsGetDimX. (تتوفّر الوسيطة context في الإصدار Android 6.0 (المستوى 23 من واجهة برمجة التطبيقات) والإصدارات الأحدث.)

  • دالة init() اختيارية. دالة init() هي نوع خاص من دالة قابلة للاستدعاء تنفذّها RenderScript عند إنشاء مثيل للنص البرمجي لأول مرة. يسمح هذا لبعض العملية الحسابية تلقائيًا عند إنشاء النص البرمجي.
  • ليس هناك أو أكثر من عمليات عمومية ودوال نصية ثابتة. يعادل النص البرمجي الثابت العمومي بشكل عمومي باستثناء أنه لا يمكن الوصول إليه من رمز Java. الدالة الثابتة هي دالة C دالة يمكن استدعاؤها من أي دالة kernel أو دالة قابلة للاستدعاء في النص البرمجي، ولكن لم يتم الكشف عنها على واجهة برمجة تطبيقات Java. إذا كان هناك نص برمجي عام أو دالة لا يلزم الوصول إليها من رمز Java، ننصح بشدة بإدراج إعلان عنه في السمة static.

ضبط دقة النقطة العائمة

يمكنك التحكّم في المستوى المطلوب من دقة النقطة العائمة في نص برمجي. يكون هذا مفيدًا إذا معيار IEEE 754-2008 (المستخدم افتراضيًا) ليس مطلوبًا. يمكن للبراغا التالية تحديد مستوى مختلف من دقة النقطة العائمة:

  • #pragma rs_fp_full (الإعداد التلقائي في حال عدم تحديد أي شيء): للتطبيقات التي تتطلب دقة النقطة العائمة على النحو الموضح في معيار IEEE 754-2008.
  • #pragma rs_fp_relaxed: للتطبيقات التي لا تتطلّب الصارم وفقًا للمعيار IEEE 754-2008 الامتثال ويمكنه تحمل دقة أقل. يتيح هذا الوضع تدفقًا إلى صفر للفئات الجسدية نحو الصفر.
  • #pragma rs_fp_imprecise: للتطبيقات التي ليس لها دقة صارمة متطلبات المشروع. يتيح هذا الوضع تفعيل كل الميزات في rs_fp_relaxed بالإضافة إلى التالي:
    • يمكن أن تعرض العمليات التي تنتج عنها -0.0 +0.0 بدلاً من ذلك.
    • العمليات على INF وNAN غير محددة.

يمكن أن تستخدم معظم التطبيقات rs_fp_relaxed بدون أي آثار جانبية. قد يكون هذا صعبًا مفيد في بعض البُنى الأساسية بسبب التحسينات الإضافية المتاحة فقط مع الدقة (مثل تعليمات وحدة المعالجة المركزية (CPU) شريحة SIMD).

الوصول إلى واجهات برمجة تطبيقات RenderScript من Java

عند تطوير تطبيق Android يستخدم RenderScript، يمكنك الوصول إلى واجهة برمجة التطبيقات الخاصة به من خلال Java بإحدى طريقتين:

  • android.renderscript - واجهات برمجة التطبيقات في حزمة الفئة هذه متوفّرة على الأجهزة التي تعمل بنظام التشغيل Android 3.0 (المستوى 11 لواجهة برمجة التطبيقات) والإصدارات الأحدث.
  • android.support.v8.renderscript - واجهات برمجة التطبيقات في هذه الحزمة متاحة من خلال فريق الدعم المكتبة، التي تتيح لك استخدامها على الأجهزة التي تعمل بالإصدار 2.3 من نظام التشغيل Android (المستوى 9 من واجهة برمجة التطبيقات) أعلى.

في ما يلي الإيجابيات والسلبيات:

  • وإذا كنت تستخدم واجهات برمجة تطبيقات Support Library، سيصبح جزء RenderScript في تطبيقك كما يلي: متوافقة مع الأجهزة التي تعمل بالإصدار 2.3 من نظام التشغيل Android (المستوى 9 لواجهة برمجة التطبيقات) والإصدارات الأحدث، بغض النظر عن RenderScript والميزات التي تستخدمها. سيسمح هذا الإجراء لتطبيقك بالعمل على عدد من الأجهزة أكبر مما لو كنت تستخدم واجهات برمجة التطبيقات الأصلية (android.renderscript).
  • لا تتوفّر بعض ميزات RenderScript من خلال واجهات برمجة تطبيقات Support Library.
  • وإذا كنت تستخدم واجهات برمجة تطبيقات Support Library، ستحصل على حِزم APK بحجم أكبر (ربما بشكل كبير) مقارنةً بواجهات برمجة تطبيقات Support Library. في حال استخدام واجهات برمجة التطبيقات الأصلية (android.renderscript).

استخدام واجهات برمجة تطبيقات RenderScript Support Library

لاستخدام واجهات برمجة تطبيقات Support Library RenderScript، يجب ضبط إعدادات تطوير البرامج. من الوصول إليها. يجب استخدام أدوات حزمة تطوير البرامج (SDK) التالية لنظام التشغيل Android. واجهات برمجة التطبيقات التالية:

  • الإصدار 22.2 من أدوات حزمة تطوير البرامج (SDK) لنظام التشغيل Android أو إصدار أحدث
  • الإصدار 18.1.0 أو إصدار أحدث من أدوات إنشاء حزمة تطوير البرامج (SDK) لنظام التشغيل Android

ملاحظة: بدءًا من Android SDK Build-tools 24.0.0، Android 2.2 (المستوى 8 من واجهة برمجة التطبيقات) لم يعُد متاحًا.

يمكنك التحقق من الإصدار المثبّت من هذه الأدوات وتحديثه في إدارة حزمة تطوير البرامج (SDK) لنظام التشغيل Android

لاستخدام واجهات برمجة تطبيقات Support Library RenderScript، اتّبِع الخطوات التالية:

  1. تأكَّد من تثبيت الإصدار المطلوب من حزمة تطوير البرامج (SDK) لنظام التشغيل Android.
  2. عدِّل إعدادات عملية إصدار Android لتشمل إعدادات RenderScript:
    • افتح ملف build.gradle في مجلد التطبيق في وحدة التطبيق.
    • أضِف إعدادات RenderScript التالية إلى الملف:

      Groovy

              android {
                  compileSdkVersion 33
      
                  defaultConfig {
                      minSdkVersion 9
                      targetSdkVersion 19
      
                      renderscriptTargetApi 18
                      renderscriptSupportModeEnabled true
                  }
              }
              

      Kotlin

              android {
                  compileSdkVersion(33)
      
                  defaultConfig {
                      minSdkVersion(9)
                      targetSdkVersion(19)
      
                      renderscriptTargetApi = 18
                      renderscriptSupportModeEnabled = true
                  }
              }
              

      تتحكم الإعدادات المذكورة أعلاه في سلوك معين في عملية إصدار Android:

      • renderscriptTargetApi - لتحديد إصدار رمز البايت المراد التي تم إنشاؤها. ننصحك بضبط هذه القيمة على أدنى مستوى لواجهة برمجة تطبيقات يمكنك تقديمه جميع الوظائف التي تستخدمها وضبط renderscriptSupportModeEnabled إلى true. القيم الصالحة لهذا الإعداد هي أي قيمة عددية من 11 إلى أحدث مستوى لواجهة برمجة تطبيقات تم إصداره إذا كان الحد الأدنى لإصدار حزمة تطوير البرامج (SDK) المحددة في بيان التطبيق لديك على قيمة مختلفة، فإن هذه القيمة تجاهله ويتم استخدام القيمة المستهدفة في ملف الإصدار لتحديد الحد الأدنى إصدار حزمة SDK.
      • renderscriptSupportModeEnabled - للإشارة إلى أن القيمة التي تم إنشاؤها يجب أن يعود رمز البايت إلى إصدار متوافق إذا كان الجهاز يعمل on لا تدعم الإصدار الهدف.
  3. في فئات التطبيقات التي تستخدم RenderScript، أضِف عملية استيراد إلى "مكتبة الدعم". الفئات:

    Kotlin

    import android.support.v8.renderscript.*
    

    Java

    import android.support.v8.renderscript.*;
    

استخدام RenderScript من خلال رمز Java أو Kotlin

يعتمد استخدام RenderScript من رمز Java أو Kotlin على فئات واجهة برمجة التطبيقات المتوفرة في android.renderscript أو حزمة android.support.v8.renderscript معظم الأقسام تتبع التطبيقات نمط الاستخدام الأساسي نفسه:

  1. إعداد سياق RenderScript: يضمن سياق RenderScript، الذي تم إنشاؤه باستخدام create(Context)، إمكانية استخدام RenderScript ويوفّر للتحكّم في مدة بقاء كل كائنات RenderScript اللاحقة. يجب مراعاة السياق عملية الإنشاء ربما تكون طويلة الأمد، نظرًا لأنها قد تنشئ موارد على قطعة من الأجهزة؛ يجب ألا يكون في المسار الحرج للتطبيق إذا كان ممكن. وعادةً ما يحتوي التطبيق على سياق RenderScript واحد فقط في كل مرة.
  2. عليك إنشاء Allocation واحد على الأقل ليتم تمريره إلى النص البرمجي Allocation هو كائن RenderScript يوفّر التخزين لكمية ثابتة من البيانات. تستغرق النواة في النصوص البرمجية Allocation. الكائنات كمدخلات ومخرجات محددة، ويمكن تحويل كائنات Allocation يتم الوصول إليها في النواة باستخدام rsGetElementAt_type() rsSetElementAt_type() عند ربطه كنصوص عامة في النص البرمجي. تسمح كائنات Allocation بتمرير الصفائف من رمز Java إلى RenderScript والعكس صحيح. يتم عادةً إنشاء Allocation عنصر باستخدام createTyped() أو createFromBitmap().
  3. أنشِئ النصوص البرمجية اللازمة. هناك نوعان من النصوص البرمجية المتاحة إليك عند استخدام RenderScript:
    • ScriptC: هذه هي النصوص البرمجية التي يحددها المستخدم كما هو موضح في كتابة Kernel RenderScript أعلاه. كل نص برمجي له فئة Java يعكسه المحول البرمجي لـ RenderScript لتسهيل الوصول إلى النص البرمجي من رمز Java؛ هذا الصف يحمل الاسم ScriptC_filename. على سبيل المثال، إذا كانت نواة التعيين أعلاه كانت موجودة في invert.rs وكان هناك سياق RenderScript في mRenderScript، فإن رمز Java أو Kotlin لإنشاء مثيل للنص البرمجي سيكون على النحو التالي:

      Kotlin

      val invert = ScriptC_invert(renderScript)
      

      Java

      ScriptC_invert invert = new ScriptC_invert(renderScript);
      
    • ScriptIntrinsic: هي نواة RenderScript مدمَجة للعمليات الشائعة، مثل التمويه الغاوسي والالتفاف ودمج الصور. لمزيدٍ من المعلومات، راجع الفئات الفرعية ScriptIntrinsic
  4. ملء عمليات التخصيص بالبيانات: باستثناء التخصيصات التي تم إنشاؤها باستخدام createFromBitmap()، تتم تعبئة التخصيص ببيانات فارغة عند تم إنشاؤه لأول مرة. لتعبئة تخصيص، استخدِم إحدى "النسخ" في Allocation. "النسخة" تكون الطريقة متزامنة.
  5. اضبط أي عبارات عمومية ضرورية للنصوص. يمكنك تعيين دوال عمومية باستخدام الطرق الموجودة في الفئة ScriptC_filename نفسها باسم set_globalname. بالنسبة على سبيل المثال، لضبط متغيّر int باسم threshold، استخدِم السمة طريقة Java set_threshold(int) ومن أجل تحديد متغير rs_allocation اسمه lookup، استخدم Java الطريقة set_lookup(Allocation). طرق set غير متزامنة.
  6. ابدأ تشغيل النواة المناسبة والدوال القابلة للاستدعاء.

    وهناك طرق لتشغيل نواة معينة تظهر في الفئة ScriptC_filename نفسها بالطرق المسماة forEach_mappingKernelName() أو reduce_reductionKernelName(). عمليات الإطلاق هذه غير متزامنة. اعتمادًا على وسيطات النواة، على تخصيص واحد أو أكثر، ويجب أن يكون لكل منها الأبعاد نفسها. بشكل افتراضي، تُنفذ النواة على كل إحداثي في تلك الأبعاد؛ لتنفيذ نواة على مجموعة فرعية من هذه الإحداثيات، لتمرير Script.LaunchOptions مناسبة كوسيطة أخيرة إلى الطريقة forEach أو reduce.

    تشغيل دوال قابلة للاستدعاء باستخدام طرق invoke_functionName تظهر في نفس الفئة ScriptC_filename. عمليات الإطلاق هذه غير متزامنة.

  7. استرداد البيانات من Allocation عناصر وjavaFutureType. من أجل الوصول إلى البيانات من Allocation من رمز Java، يجب نسخ هذه البيانات إلى Java باستخدام إحدى "النسخ" في Allocation. للحصول على نتيجة نواة الاختزال، يجب استخدام الطريقة javaFutureType.get(). "النسخة" وget() تكون متزامنة.
  8. حدِّد سياق RenderScript. يمكنك محو سياق RenderScript. باستخدام destroy() أو من خلال السماح بسياق RenderScript سيتم جمع البيانات المهملة. يؤدي ذلك إلى أي استخدام إضافي لأي عنصر ينتمي إليه والسياق لطرح استثناء.

نموذج التنفيذ غير المتزامن

النتائج المعروضة forEach وinvoke وreduce وset طريقة غير متزامنة - قد يعود كل منهما إلى Java قبل إكمال الإجراء المطلوب. ومع ذلك، تُرسَل الإجراءات الفردية بالترتيب الذي تم إطلاقه به.

توفّر الفئة Allocation "نسخة" طرق نسخ البيانات إليها ومن التوزيعات. "نسخة" متزامنة، وتسلسلية فيما يتعلق بأي من الإجراءات غير المتزامنة أعلاه التي تلمس التخصيص نفسه.

توفر فئات javaFutureType المنعكسة طريقة get() للحصول على نتيجة الاختزال. get() هو متزامن، وتسلسلي فيما يتعلق بالتقليل (غير المتزامن).

نص RenderScript أحادي المصدر

يقدّم Android 7.0 (المستوى 24 من واجهة برمجة التطبيقات) ميزة برمجة جديدة تُعرف باسم مصدر أحادي. RenderScript، والذي يتم فيه تشغيل النواة من النص البرمجي حيث يتم تحديدها، بدلاً من من Java. يقتصر هذا النهج حاليًا على تصميم النواة على الخرائط والتي يُشار إليها ببساطة باسم "النواة" (kernel) في هذا القسم للإيجاز. تتيح هذه الميزة الجديدة أيضًا إنشاء عمليات تخصيص من النوع. rs_allocation من داخل النص البرمجي. من الممكن الآن تنفيذ خوارزمية كاملة داخل نص برمجي فقط، حتى إذا كانت هناك حاجة إلى إطلاق نواة متعددة. وهناك شقتان للفائدة، وهما تعليمة برمجية أكثر قابلية للقراءة، لأنها تحافظ على تنفيذ الخوارزمية في لغة واحدة؛ وربما يتم ترميزها بشكل أسرع، وذلك بسبب وجود عدد أقل من الانتقالات بين لغة البرمجة Java RenderScript في عمليات تشغيل النواة المتعددة.

في RenderScript أحادي المصدر، تكتب نواة كما هو موضح في كتابة نواة RenderScript: ثم تكتب دالة غير قابلة للاستدعاء تستدعي rsForEach() لإطلاقها. تتعامل واجهة برمجة التطبيقات هذه مع دالة kernel كأول تليها عمليات تخصيص المدخلات والمخرجات. واجهة برمجة تطبيقات مشابهة تستخدم rsForEachWithOptions() وسيطة إضافية من النوع. rs_script_call_t، التي تحدد مجموعة فرعية من العناصر من الإدخال تخصيصات المخرجات لدالة kernel لمعالجتها.

لبدء احتساب RenderScript، عليك استدعاء الدالة القابلة للاستدعاء من Java. اتّبِع الخطوات الواردة في استخدام RenderScript من Java Code. في خطوة إطلاق النواة المناسبة، اطلب الدالة القابلة للاستدعاء باستخدام invoke_function_name()، والتي ستبدأ العمليات الحسابية بالكامل، بما في ذلك إطلاق النواة.

غالبًا ما تكون التخصيصات مطلوبة لحفظ التغييرات واجتيازها نتائج متوسطة من إطلاق نواة إلى أخرى. يمكنك إنشاؤها باستخدام rsCreateAllocation(). أحد أشكال واجهة برمجة التطبيقات هذه السهل الاستخدام هو rsCreateAllocation_<T><W>(…)، حيث يكون T هو نوع البيانات العنصر، وW هو عرض الخط المتجه للعنصر. تأخذ واجهة برمجة التطبيقات الأحجام الأبعاد X وY وZ كوسيطات. بالنسبة إلى التخصيصات أحادية الأبعاد أو ثنائية الأبعاد، يمكن أن يكون حجم البُعد "ص" أو "ع" . على سبيل المثال، تنشئ الدالة rsCreateAllocation_uchar4(16384) عملية تخصيص يوم واحد لـ 16384 عنصرًا، كل منها من النوع uchar4.

يدير النظام عمليات التخصيص تلقائيًا. إِنْتَ إصدارها أو تحريرها بشكل صريح. ومع ذلك، يمكنك الاتصال rsClearObject(rs_allocation* alloc) للإشارة إلى أنّك لم تعُد بحاجة إلى الاسم المعرِّف alloc إلى التخصيص الأساسي، لكي يتمكن النظام من تفريغ الموارد في أقرب وقت ممكن.

يحتوي القسم كتابة Kernel في RenderScript على مثال تُقلب الصورة. يتوسع المثال أدناه بحيث يتم تطبيق أكثر من تأثير على الصورة، باستخدام RenderScript أحادي المصدر. وتتضمّن نواة أخرى، ألا وهي greyscale، والتي تحوّل ملونة إلى أبيض وأسود. دالة process() قابلة للاستدعاء بشكل متعاقب مع صورة إدخال، وينتج صورة إخراج. عمليات التخصيص لكل من المدخلات يتم تمرير المخرجات كوسيطات من النوع rs_allocation

// File: singlesource.rs

#pragma version(1)
#pragma rs java_package_name(com.android.rssample)

static const float4 weight = {0.299f, 0.587f, 0.114f, 0.0f};

uchar4 RS_KERNEL invert(uchar4 in, uint32_t x, uint32_t y) {
  uchar4 out = in;
  out.r = 255 - in.r;
  out.g = 255 - in.g;
  out.b = 255 - in.b;
  return out;
}

uchar4 RS_KERNEL greyscale(uchar4 in) {
  const float4 inF = rsUnpackColor8888(in);
  const float4 outF = (float4){ dot(inF, weight) };
  return rsPackColorTo8888(outF);
}

void process(rs_allocation inputImage, rs_allocation outputImage) {
  const uint32_t imageWidth = rsAllocationGetDimX(inputImage);
  const uint32_t imageHeight = rsAllocationGetDimY(inputImage);
  rs_allocation tmp = rsCreateAllocation_uchar4(imageWidth, imageHeight);
  rsForEach(invert, inputImage, tmp);
  rsForEach(greyscale, tmp, outputImage);
}

يمكنك استدعاء الدالة process() من Java أو Kotlin على النحو التالي:

Kotlin

val RS: RenderScript = RenderScript.create(context)
val script = ScriptC_singlesource(RS)
val inputAllocation: Allocation = Allocation.createFromBitmapResource(
        RS,
        resources,
        R.drawable.image
)
val outputAllocation: Allocation = Allocation.createTyped(
        RS,
        inputAllocation.type,
        Allocation.USAGE_SCRIPT or Allocation.USAGE_IO_OUTPUT
)
script.invoke_process(inputAllocation, outputAllocation)

Java

// File SingleSource.java

RenderScript RS = RenderScript.create(context);
ScriptC_singlesource script = new ScriptC_singlesource(RS);
Allocation inputAllocation = Allocation.createFromBitmapResource(
    RS, getResources(), R.drawable.image);
Allocation outputAllocation = Allocation.createTyped(
    RS, inputAllocation.getType(),
    Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_OUTPUT);
script.invoke_process(inputAllocation, outputAllocation);

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

نص برمجي عالمي

النص العالمي العالمي هو نص عادي غير static متغير عمومي في ملف نص برمجي (.rs). بالنسبة إلى النص عامة باسم var تم تحديدها في الملف filename.rs، سيكون هناك الطريقة get_var الواردة في الصف ScriptC_filename. ما لم هو const، ستكون هناك أيضًا الطريقة set_var.

يحتوي نص برمجي معين عام على قيمتين منفصلتين -- قيمة Java القيمة وقيمة script. ويكون سلوك هذه القيم على النحو التالي:

  • إذا كانت var تحتوي على مهيئ ثابت في النص البرمجي، تحدد القيمة الأولية var في كل من Java البرنامج النصي. وبخلاف ذلك، تكون هذه القيمة الأولية صفرًا.
  • للوصول إلى var ضمن النص البرمجي قراءة وكتابة قيمة البرنامج النصي.
  • تقرأ الطريقة get_var لغة Java
  • تُكتب الطريقة set_var (إن وجدت) قيمة Java على الفور، وتكتب قيمة البرنامج النصي بشكل غير متزامن.

ملاحظة: يعني ذلك أنه باستثناء أي مهيأ ثابت في البرنامج النصي، القيم المكتوبة إلى المعامل العمومي من ضمن برنامج نصي غير مرئية لـ Java.

نواة الانزلاق بالعمق

الخفض هو عملية دمج مجموعة من البيانات في مجموعة بيانات واحدة. وهذه طريقة أساسية مفيدة في البرمجة المتوازية، مع تطبيقات مثل التالي:

  • حساب المجموع أو الناتج على جميع البيانات
  • العمليات المنطقية للحوسبة (and، or، xor) على جميع البيانات
  • إيجاد القيمة الصغرى أو القصوى ضمن البيانات
  • تبحث عن قيمة معينة أو عن إحداثي قيمة معينة داخل البيانات

في الإصدار Android 7.0 (المستوى 24 لواجهة برمجة التطبيقات) والإصدارات الأحدث، تتوافق RenderScript مع نواة خفض للسماح خوارزميات التقليل الفعالة التي يكتبها المستخدم. يمكنك إطلاق نواة التقليل في المدخلات 1 أو 2 أو 3 أبعاد.

يوضِّح أحد الأمثلة أعلاه نواة تخفيض بسيطة للإضافة. في ما يلي نواة تقليل findMinAndMax أكثر تعقيدًا الذي يعثر على مواقع القيم الدنيا والقصوى لـ long في Allocation أحادي البُعد:

#define LONG_MAX (long)((1UL << 63) - 1)
#define LONG_MIN (long)(1UL << 63)

#pragma rs reduce(findMinAndMax) \
  initializer(fMMInit) accumulator(fMMAccumulator) \
  combiner(fMMCombiner) outconverter(fMMOutConverter)

// Either a value and the location where it was found, or INITVAL.
typedef struct {
  long val;
  int idx;     // -1 indicates INITVAL
} IndexedVal;

typedef struct {
  IndexedVal min, max;
} MinAndMax;

// In discussion below, this initial value { { LONG_MAX, -1 }, { LONG_MIN, -1 } }
// is called INITVAL.
static void fMMInit(MinAndMax *accum) {
  accum->min.val = LONG_MAX;
  accum->min.idx = -1;
  accum->max.val = LONG_MIN;
  accum->max.idx = -1;
}

//----------------------------------------------------------------------
// In describing the behavior of the accumulator and combiner functions,
// it is helpful to describe hypothetical functions
//   IndexedVal min(IndexedVal a, IndexedVal b)
//   IndexedVal max(IndexedVal a, IndexedVal b)
//   MinAndMax  minmax(MinAndMax a, MinAndMax b)
//   MinAndMax  minmax(MinAndMax accum, IndexedVal val)
//
// The effect of
//   IndexedVal min(IndexedVal a, IndexedVal b)
// is to return the IndexedVal from among the two arguments
// whose val is lesser, except that when an IndexedVal
// has a negative index, that IndexedVal is never less than
// any other IndexedVal; therefore, if exactly one of the
// two arguments has a negative index, the min is the other
// argument. Like ordinary arithmetic min and max, this function
// is commutative and associative; that is,
//
//   min(A, B) == min(B, A)               // commutative
//   min(A, min(B, C)) == min((A, B), C)  // associative
//
// The effect of
//   IndexedVal max(IndexedVal a, IndexedVal b)
// is analogous (greater . . . never greater than).
//
// Then there is
//
//   MinAndMax minmax(MinAndMax a, MinAndMax b) {
//     return MinAndMax(min(a.min, b.min), max(a.max, b.max));
//   }
//
// Like ordinary arithmetic min and max, the above function
// is commutative and associative; that is:
//
//   minmax(A, B) == minmax(B, A)                  // commutative
//   minmax(A, minmax(B, C)) == minmax((A, B), C)  // associative
//
// Finally define
//
//   MinAndMax minmax(MinAndMax accum, IndexedVal val) {
//     return minmax(accum, MinAndMax(val, val));
//   }
//----------------------------------------------------------------------

// This function can be explained as doing:
//   *accum = minmax(*accum, IndexedVal(in, x))
//
// This function simply computes minimum and maximum values as if
// INITVAL.min were greater than any other minimum value and
// INITVAL.max were less than any other maximum value.  Note that if
// *accum is INITVAL, then this function sets
//   *accum = IndexedVal(in, x)
//
// After this function is called, both accum->min.idx and accum->max.idx
// will have nonnegative values:
// - x is always nonnegative, so if this function ever sets one of the
//   idx fields, it will set it to a nonnegative value
// - if one of the idx fields is negative, then the corresponding
//   val field must be LONG_MAX or LONG_MIN, so the function will always
//   set both the val and idx fields
static void fMMAccumulator(MinAndMax *accum, long in, int x) {
  IndexedVal me;
  me.val = in;
  me.idx = x;

  if (me.val <= accum->min.val)
    accum->min = me;
  if (me.val >= accum->max.val)
    accum->max = me;
}

// This function can be explained as doing:
//   *accum = minmax(*accum, *val)
//
// This function simply computes minimum and maximum values as if
// INITVAL.min were greater than any other minimum value and
// INITVAL.max were less than any other maximum value.  Note that if
// one of the two accumulator data items is INITVAL, then this
// function sets *accum to the other one.
static void fMMCombiner(MinAndMax *accum,
                        const MinAndMax *val) {
  if ((accum->min.idx < 0) || (val->min.val < accum->min.val))
    accum->min = val->min;
  if ((accum->max.idx < 0) || (val->max.val > accum->max.val))
    accum->max = val->max;
}

static void fMMOutConverter(int2 *result,
                            const MinAndMax *val) {
  result->x = val->min.idx;
  result->y = val->max.idx;
}

ملاحظة: هناك المزيد من الأمثلة على التقليل هنا.

لتشغيل نواة مختصَرة، يُنشئ وقت تشغيل RenderScript واحدة أو أكثر متغيرات تسمى بيانات المركم item للاحتفاظ بحالة عملية التخفيض. وقت تشغيل RenderScript تختار عدد عناصر بيانات المركم بالطريقة التي يمكن استخدامها لتحسين الأداء إلى أقصى حد. النوع يتم تحديد من عناصر بيانات المركم (accumType) من خلال المركم في النواة (النواة). الدالة -- الوسيطة الأولى لتلك الدالة هي مؤشر لبيانات المركم عنصر واحد. بشكل تلقائي، يتم إعداد كل عنصر من عناصر البيانات المجمّعة على صفر (كما لو كان بواسطة memset); ومع ذلك، يمكنك كتابة دالة مهيء لتنفيذ شيء ما مختلفة.

مثال: في الإضافة kernel، تُستخدم عناصر بيانات المركم (من النوع int) لإضافة الإدخالات القيم. لا تتوفّر دالة إعداد، لذا يتم إعداد كل عنصر من عناصر المركم على صفر.

مثال: في النواة findMinAndMax، وهي عناصر بيانات المركم (من النوع MinAndMax) تُستخدم لتتبع القيم الدنيا والقصوى تم العثور عليها حتى الآن. هناك دالة إعداد لضبط هذه القيم على LONG_MAX LONG_MIN، على التوالي؛ ولتعيين مواقع هذه القيم على -1، مما يشير إلى أن لا تكون القيم موجودة بالفعل في الجزء (الفارغ) من المُدخل الذي ومعالجتها.

وتستدعي RenderScript دالة المركم مرة واحدة لكل إحداثي في المدخلات. يجب أن تقوم الدالة عادةً بتعديل عنصر بيانات المركم بطريقة ما وفقًا للإدخال.

مثال: في الإضافة فإن دالة المركم تضيف قيمة عنصر الإدخال إلى المركم. عنصر البيانات.

مثال: في بالنواة findMinAndMax، وهي دالة المركم للتحقق مما إذا كانت قيمة أحد عناصر الإدخال أقل من أو تساوي الحد الأدنى القيمة المسجّلة في عنصر بيانات المركم و/أو أكبر من أو تساوي الحدّ الأقصى القيمة المسجّلة في عنصر بيانات المركم، وتُعدِّل عنصر بيانات المركم وفقًا لذلك.

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

مثال: في الإضافة فليست هناك دالة الدمج، لذا سيتم استخدام دالة المركم. هذا هو السلوك الصحيح، لأننا إذا قسمنا مجموعة من القيم إلى جزأين، ونجمع القيم الموجودة في هاتين الجزأين على حدة، فإن جمع هذين المجموعين يساوي إضافة المجموعة بأكملها.

مثال: في النواة findMinAndMax، وهي دالة الدمج للتحقّق ممّا إذا كان الحدّ الأدنى للقيمة المسجّلة في "المصدر" بيانات المركم السلعة *val أقل من الحد الأدنى للقيمة المسجَّلة في "الوجهة" عنصر بيانات المركم *accum، وتحديثات: *accum وفقًا لذلك. وهي تقوم بعمل مماثل للقيمة القصوى. يؤدي هذا إلى تحديث *accum إلى الحالة التي كان من الممكن أن يكون عليها إذا تم تجميع جميع قيم المدخلات في *accum بدلاً من البعض إلى *accum والبعض الآخر إلى *val

بعد دمج كل عناصر بيانات المركم، يحدِّد RenderScript نتيجة التقليل للعودة إلى Java. يمكنك كتابة محوّل خارجي للقيام بذلك. لا تحتاج إلى كتابة دالة outتحويل القيمة النهائية لعناصر بيانات المركم المجمّعة التي نتجت عن التخفيض.

مثال: في النواة addint، لا توجد دالة محول للإحالات الناجحة. وتكون القيمة النهائية لعناصر البيانات المجمّعة هي مجموع جميع عناصر الإدخال، وهي القيمة التي نريد إرجاعها.

مثال: في النواة findMinAndMax، وهي دالة outconverter على ضبط قيمة نتيجة int2 للاحتفاظ بموضع الحد الأدنى القيم القصوى الناتجة عن دمج جميع عناصر بيانات المركم.

كتابة نواة الاختزال

تحدد #pragma rs reduce نواة الاختزال من خلال لتحديد اسمه وأسماء الدوال وأدوارها النواة. يجب أن تكون جميع هذه الدوال static تتطلب نواة التقليل دائمًا accumulator. الوظيفة؛ يمكنك حذف بعض أو كل الدوال الأخرى، اعتمادًا على ما تريد النواة.

#pragma rs reduce(kernelName) \
  initializer(initializerName) \
  accumulator(accumulatorName) \
  combiner(combinerName) \
  outconverter(outconverterName)

في ما يلي معنى العناصر في #pragma:

  • reduce(kernelName) (إلزامية): للإشارة إلى أنّ نواة الاختزال المحدد. ستؤدي طريقة Java المنعكسة reduce_kernelName إلى تشغيل النواة (النواة).
  • initializer(initializerName) (اختياري): تحدّد اسم لنواة الاختزال هذه. عند تشغيل النواة، يستدعي RenderScript هذه الدالة مرة واحدة لكل عنصر بيانات في المركم. تشير رسالة الأشكال البيانية على النحو التالي:

    static void initializerName(accumType *accum) { … }

    ويشير accum إلى عنصر بيانات مراكم لهذه الدالة البدء.

    في حال عدم توفير وظيفة أداة الإعداد، يهيئ RenderScript جميع مراكم عنصر البيانات إلى صفر (كما لو كان بحلول memset)، والعمل كما لو كان هناك أداة إعداد التي تبدو على النحو التالي:

    static void initializerName(accumType *accum) {
      memset(accum, 0, sizeof(*accum));
    }
  • accumulator(accumulatorName) (إلزامي): تحدّد اسم دالة المركم لهذا الغرض نواة التقليل. عند تشغيل النواة، يستدعي RenderScript هذه الدالة مرة واحدة لكل إحداثي في المدخلات، لتحديث بيانات المركم بطريقة ما وفقًا للمدخلات. الدالة على النحو التالي:

    static void accumulatorName(accumType *accum,
                                in1Type in1, …, inNType inN
                                [, specialArguments]) { … }
    

    ويشير accum إلى عنصر بيانات مراكم لهذه الدالة التعديل. من in1 إلى inN هي وسيطة أو أكثر استنادًا إلى الإدخالات التي يتم تمريرها إلى تشغيل النواة، وهي وسيطة واحدة لكل إدخال. قد تستخدم دالة المركم أيًّا من الوسيطات الخاصة بشكل اختياري.

    مثال على النواة التي تتضمّن إدخالات متعددة هي dotProduct.

  • combiner(combinerName)

    (اختياري): يحدد اسم دالة الدمج لهذا الغرض نواة التقليل. بعد استدعاء RenderScript لدالة المركم مرة واحدة لكل إحداثي في المدخلات، فإنه يستدعي هذه الدالة بأكبر عدد حسب الحاجة لدمج جميع عناصر بيانات المركم في وحدة بيانات المركم. يجب تحديد الدالة على النحو التالي:

    static void combinerName(accumType *accum, const accumType *other) { … }

    يشير accum إلى "الوجهة" عنصر بيانات هذه البيانات لتعديلها. يشير other إلى "مصدر" عنصر بيانات المركم أن تقوم هذه الدالة بـ "دمج" إلى *accum.

    ملاحظة: من الممكن أنه تم إعداد *accum أو *other أو كليهما ولكن لم يتم يتم تمريرها إلى دالة المركم؛ أي أنه لم يتم تحديث أحدهما أو كليهما من قبل وفقًا لأي بيانات إدخال. على سبيل المثال، في النواة findMinAndMax، وهي أداة الدمج تتحقق الدالة fMMCombiner صراحةً من وجود idx < 0 لأن ذلك إلى عنصر بيانات هذا المركم، وقيمته INITVAL.

    وإذا لم توفّر دالة الدمج، سيستخدم RenderScript دالة المركم في كما لو كانت هناك دالة دمج تبدو على النحو التالي:

    static void combinerName(accumType *accum, const accumType *other) {
      accumulatorName(accum, *other);
    }

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

  • outconverter(outconverterName) (اختياري): يحدد اسم الدالة outconverter لهذا الغرض نواة التقليل. بعد أن تجمع RenderScript كل المركم عناصر البيانات، فإنها تستدعي هذه الدالة لتحديد نتيجة الاختزال للعودة إلى Java. يجب تحديد الدالة على النحو التالي التالي:

    static void outconverterName(resultType *result, const accumType *accum) { … }

    result هو مؤشر إلى عنصر بيانات نتيجة (تم تخصيصه ولكن لم يتم إعداده بواسطة وقت تشغيل RenderScript) لكي تبدأ هذه الدالة مع نتيجة التقليل. resultType هو نوع عنصر البيانات هذا، ولا يلزم أن يكون هو نفسه مثل accumType. يشير accum إلى عنصر البيانات النهائي في المركم. التي تم احتسابها بواسطة دالة التجميع.

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

    static void outconverterName(accumType *result, const accumType *accum) {
      *result = *accum;
    }

    إذا كنت تريد نوع نتائج مختلف عن نوع بيانات المركم، تكون دالة outconverter إلزامية.

لاحظ أن النواة تحتوي على أنواع إدخالات ونوع عنصر بيانات مركم ونوع نتيجة، ولا ينبغي أن يكون أي منها متماثلاً. على سبيل المثال، في النواة findMinAndMax، يتم إدخال النوع long، ونوع عنصر بيانات المركم MinAndMax، والنتيجة النوع int2 مختلفة.

ما الذي لا يمكنك افتراضه؟

يجب ألا تعتمد على عدد عناصر بيانات المركم التي تم إنشاؤها بواسطة RenderScript لإطلاق النواة. ليس هناك ما يضمن إطلاق عمليتين للنواة نفسها ستنشِئ المدخلات نفسها العدد نفسه من عناصر بيانات المركم.

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

  • وليس هناك ما يضمن إعداد جميع عناصر بيانات المركم قبل إلا أنّه سيتم استدعاؤها فقط في مركم تم إعداده عنصر البيانات.
  • وليس هناك ما يضمن ترتيب نقل عناصر الإدخال إلى المركم. الأخرى.
  • ليس هناك ما يضمن استدعاء دالة المركم لجميع عناصر الإدخال. قبل استدعاء دالة الدمج.

تتمثل إحدى نتائج ذلك في أن findMinAndMax kernel غير حتمي: إذا كان المدخل يحتوي على أكثر من موضع ورود واحد للحد الأدنى أو الأقصى، فلن يكون لديك طريقة لمعرفة موضع الورود الذي العثور عليها.

ما الذي يجب أن تضمنه؟

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

غالبًا ما تنص القواعد أدناه على أن عنصرين من بيانات المركم يجب أن يتضمنا " نفس القيمة". ماذا يعني ذلك؟ هذا يعتمد على ما تريد أن تفعله النواة. بالنسبة أي اختزال رياضي مثل الدالة، فمن المنطقي عادةً عن "نفس" أي المساواة الرياضية. لـ "اختيار أي" البحث عن هذا الاسم كـ findMinAndMax ("اعثر على موقع الحد الأدنى الحد الأقصى لقيم المدخلات") حيث قد يكون هناك أكثر من موضع واحد لإدخالات متطابقة يجب اعتبار جميع المواقع لأي قيمة إدخال "نفسها". يمكنك كتابة نواة مماثلة "للعثور على موقع قيم الإدخال والحد الأدنى والأقصى لليسار" حيث يُفضل (على سبيل المثال) تفضيل قيمة دنيا في الموقع 100 على قيمة دنيا متطابقة في الموقع 200; لهذه النواة، "نفس" يعني موقع متطابق، وليس مجرد متطابقة value، وينبغي أن تكون دالتا المركم والدمج مختلفة عن تلك الخاصة بـ findMinAndMax.

يجب أن تنشئ دالة الإعداد قيمة هوية. أي، في حال إعداد I وA هما عناصر بيانات المركم. من خلال دالة الإعداد، ولم يتم أبدًا تمرير I إلى المركم (ولكن ربما كانت هناك A)، إذًا
  • يجب على combinerName(&A, &I) ترك A كما هو
  • يجب على combinerName(&I, &A) ترك I كما هو A

مثال: في الإضافة في نواة البيانات، وهو عنصر بيانات مُجمّع يتم إعداده على صفر. دالة الدمج لهذا تقوم النواة بالجمع؛ الصفر هو قيمة الهوية للإضافة.

مثال: في findMinAndMax يتم إعداد عنصر بيانات مراكم إلى INITVAL.

  • fMMCombiner(&A, &I) يترك A كما هو، لأنّ قيمة "I" هي INITVAL.
  • fMMCombiner(&I, &A) مجموعة I إلى A، لأن I هي INITVAL.

ولذلك، فإن INITVAL هي بالفعل قيمة هوية.

يجب أن تكون دالة الدمج متغيرة. أي، في حال إعداد A وB هما عناصر بيانات المركم. من خلال دالة الإعداد، وربما يكون ذلك قد تم تمريره إلى دالة المركم صفر أو أكثر من مرة، يجب عندها combinerName(&A, &B) ضبط A على القيمة نفسها الذي combinerName(&B, &A) يحدد B.

مثال: في الإضافة فإن دالة الدمج تضيف قيمتي عناصر بيانات المركم؛ الإضافة هي تبديليًا.

مثال: في النواة findMinAndMax، fMMCombiner(&A, &B) هو نفسه وA = minmax(A, B) وminmax صيغة بديلة، إنّ fMMCombiner أيضًا.

يجب أن تكون دالة الدمج رابطية. أي، إذا كانت A وB وC عناصر بيانات المركم التي أعدّتها دالة الإعداد، والتي ربما تم تجاوزها إلى دالة المركم أو بأصفار أو أكثر، فيجب أن يكون تسلسل الرمزين التاليين ضبط A على القيمة نفسها:

  • combinerName(&A, &B);
    combinerName(&A, &C);
    
  • combinerName(&B, &C);
    combinerName(&A, &B);
    

مثال: في النواة addint، تضيف دالة الدمج قيمتَي عناصر بيانات المركم:

  • A = A + B
    A = A + C
    // Same as
    //   A = (A + B) + C
    
  • B = B + C
    A = A + B
    // Same as
    //   A = A + (B + C)
    //   B = B + C
    

الجمع ترابطي، ولذلك فإن دالة الدمج هي أيضًا.

مثال: في النواة findMinAndMax،

fMMCombiner(&A, &B)
هي نفسها
A = minmax(A, B)
إذًا، التسلسلان هما

  • A = minmax(A, B)
    A = minmax(A, C)
    // Same as
    //   A = minmax(minmax(A, B), C)
    
  • B = minmax(B, C)
    A = minmax(A, B)
    // Same as
    //   A = minmax(A, minmax(B, C))
    //   B = minmax(B, C)
    

minmax ارتباطية، وكذلك fMMCombiner.

يجب أن تتبع وظيفة المركم ووظيفة الدمج معًا المبادئ قاعدة الطي أي إذا كانت A وB عبارة عن عناصر بيانات مجمّعة، وتم تحديد قيمة "A" باستخدام دالة الإعداد وقد يتم تمريرها إلى دالة المركم لم يتم إعداد B مرة أو أكثر، كما أن الوسيطات هي قائمة وسيطات الإدخال والوسيطات الخاصة لاستدعاء معين إلى المركم فإن تسلسلي الرموز التاليين يجب أن يُعيِّنا A على القيمة نفسها:

  • accumulatorName(&A, args);  // statement 1
    
  • initializerName(&B);        // statement 2
    accumulatorName(&B, args);  // statement 3
    combinerName(&A, &B);       // statement 4
    

مثال: في النواة addint، لقيمة الإدخال V:

  • العبارة 1 هي نفسها A += V
  • العبارة 2 هي نفسها B = 0
  • العبارة 3 هي نفسها B += V، والتي تماثل B = V
  • العبارة 4 هي نفسها A += B، والتي تماثل A += V

تعين العبارتين 1 و4 A على القيمة ذاتها، وبالتالي تمتثل هذه النواة لـ قاعدة الطي الأساسية.

مثال: في النواة findMinAndMax، لإدخال القيمة V في الإحداثي X:

  • العبارة 1 هي نفسها A = minmax(A, IndexedVal(V, X))
  • العبارة 2 هي نفسها B = INITVAL
  • العبارة 3 هي نفسها
    B = minmax(B, IndexedVal(V, X))
    
    والتي، لأن B هي القيمة الأولية، وهي نفسها
    B = IndexedVal(V, X)
    
  • العبارة 4 هي نفسها
    A = minmax(A, B)
    
    وهو نفس
    A = minmax(A, IndexedVal(V, X))
    

تعين العبارتين 1 و4 A على القيمة ذاتها، وبالتالي تمتثل هذه النواة لـ قاعدة الطي الأساسية.

استدعاء نواة اختزال من رمز Java

بالنسبة إلى نواة الاختزال المسماة kernelName المحددة في ملف filename.rs، تظهر ثلاث طرق تظهر في الفئة ScriptC_filename:

Kotlin

// Function 1
fun reduce_kernelName(ain1: Allocation, …,
                               ainN: Allocation): javaFutureType

// Function 2
fun reduce_kernelName(ain1: Allocation, …,
                               ainN: Allocation,
                               sc: Script.LaunchOptions): javaFutureType

// Function 3
fun reduce_kernelName(in1: Array<devecSiIn1Type>, …,
                               inN: Array<devecSiInNType>): javaFutureType

Java

// Method 1
public javaFutureType reduce_kernelName(Allocation ain1, …,
                                        Allocation ainN);

// Method 2
public javaFutureType reduce_kernelName(Allocation ain1, …,
                                        Allocation ainN,
                                        Script.LaunchOptions sc);

// Method 3
public javaFutureType reduce_kernelName(devecSiIn1Type[] in1, …,
                                        devecSiInNType[] inN);

في ما يلي بعض الأمثلة على طلب النواة المكوّن الإضافي:

Kotlin

val script = ScriptC_example(renderScript)

// 1D array
//   and obtain answer immediately
val input1 = intArrayOf()
val sum1: Int = script.reduce_addint(input1).get()  // Method 3

// 2D allocation
//   and do some additional work before obtaining answer
val typeBuilder = Type.Builder(RS, Element.I32(RS)).apply {
    setX()
    setY()
}
val input2: Allocation = Allocation.createTyped(RS, typeBuilder.create()).also {
    populateSomehow(it) // fill in input Allocation with data
}
val result2: ScriptC_example.result_int = script.reduce_addint(input2)  // Method 1
doSomeAdditionalWork() // might run at same time as reduction
val sum2: Int = result2.get()

Java

ScriptC_example script = new ScriptC_example(renderScript);

// 1D array
//   and obtain answer immediately
int input1[] = ;
int sum1 = script.reduce_addint(input1).get();  // Method 3

// 2D allocation
//   and do some additional work before obtaining answer
Type.Builder typeBuilder =
  new Type.Builder(RS, Element.I32(RS));
typeBuilder.setX();
typeBuilder.setY();
Allocation input2 = createTyped(RS, typeBuilder.create());
populateSomehow(input2);  // fill in input Allocation with data
ScriptC_example.result_int result2 = script.reduce_addint(input2);  // Method 1
doSomeAdditionalWork(); // might run at same time as reduction
int sum2 = result2.get();

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

الطريقة 2 هي نفسها الطريقة 1 باستثناء أن الطريقة 2 تأخذ مقدارًا إضافيًا الوسيطة sc التي يمكن استخدامها لتقييد تنفيذ kernel على مجموعة فرعية من الإحداثيات.

الطريقة 3 هي نفسها الطريقة 1 باستثناء بدلاً من أخذ مدخلات التخصيص، تأخذ مدخلات صفيف Java. هذه راحة توفير الوقت من الاضطرار إلى كتابة رمز لإنشاء تخصيص ونسخ البيانات إليه بشكل صريح من صفيفة Java. ومع ذلك، فإن استخدام الطريقة الثالثة بدلاً من الطريقة 1 لا يؤدي إلى زيادة أداء الرمز البرمجي. لكل صفيف إدخال، تنشئ الطريقة 3 مخططًا مؤقتًا تخصيص أحادي البُعد مع نوع Element المناسب setAutoPadding(boolean) مُفعَّل، وينسخ الصفيف إلى سيتم التخصيص كما لو تم ذلك باستخدام طريقة copyFrom() المناسبة لحساب Allocation. ثم تستدعي الطريقة 1، وتمرير هذه العناصر المؤقتة عمليات التخصيص

ملاحظة: إذا كان تطبيقك سيُجري اتصالات kernel متعددة باستخدام باستخدام المصفوفة نفسها، أو باستخدام صفائف مختلفة من السمات ونوع العنصر نفسه، يمكنك تحسين عن طريق إنشاء تخصيصات وملؤها وإعادة استخدامها بشكل صريح بنفسك، بدلاً من باستخدام الطريقة 3.

javaFutureType، نوع إرجاع طرق الاختزال المنعكسة، يعكس فئة متداخلة ثابتة ضمن ScriptC_filename الصف. وهو يمثل النتيجة المستقبلية لانخفاض لتشغيل kernel. للحصول على النتيجة الفعلية لعملية التشغيل، اتصل طريقة get() لتلك الفئة، والتي يكون ناتجها قيمة من النوع javaResultType. get() متزامن.

Kotlin

class ScriptC_filename(rs: RenderScript) : ScriptC(…) {
    object javaFutureType {
        fun get(): javaResultType { … }
    }
}

Java

public class ScriptC_filename extends ScriptC {
  public static class javaFutureType {
    public javaResultType get() { … }
  }
}

يتم تحديد javaResultType من resultType من دالة outconversion. ما لم تكن resultType نوع غير موقع (scalar أو متجه أو صفيف)، javaResultType هي النوع المقابل المباشر نوع Java. إذا كان resultType غير موقَّع، وكان هناك نوع أكبر من توقيع Java، تكون javaResultType هي النوع الأكبر حجمًا المُوقَّع لـ Java، وبخلاف ذلك، تكون العلاقة المباشرة لنوع Java المقابل. مثلاً:

  • إذا كانت قيمة resultType هي int أو int2 أو int[15]، ثم javaResultType هي int، Int2، أو int[]. يمكن تمثيل جميع قيم resultType. حسب javaResultType.
  • إذا كانت قيمة resultType هي uint أو uint2 أو uint[15]، ثم javaResultType هي long، Long2، أو long[]. يمكن تمثيل جميع قيم resultType. حسب javaResultType.
  • إذا كانت قيمة resultType هي ulong أو ulong2، أو ulong[15]، ثم javaResultType هي long أو Long2 أو long[]. هناك قيم معينة من resultType لا يمكن تمثيله بواسطة javaResultType.

javaFutureType هو نوع النتيجة المتجاوب في المستقبل إلى resultType من outconversioner .

  • إذا لم تكن resultType هو نوع مصفوفة، حينئذٍ javaFutureType يبلغ result_resultType.
  • إذا كانت resultType هو مصفوفة طول Count مع أعضاء من نوع memberType، تكون قيمة javaFutureType هي resultArrayCount_memberType.

مثلاً:

Kotlin

class ScriptC_filename(rs: RenderScript) : ScriptC(…) {

    // for kernels with int result
    object result_int {
        fun get(): Int = …
    }

    // for kernels with int[10] result
    object resultArray10_int {
        fun get(): IntArray = …
    }

    // for kernels with int2 result
    //   note that the Kotlin type name "Int2" is not the same as the script type name "int2"
    object result_int2 {
        fun get(): Int2 = …
    }

    // for kernels with int2[10] result
    //   note that the Kotlin type name "Int2" is not the same as the script type name "int2"
    object resultArray10_int2 {
        fun get(): Array<Int2> = …
    }

    // for kernels with uint result
    //   note that the Kotlin type "long" is a wider signed type than the unsigned script type "uint"
    object result_uint {
        fun get(): Long = …
    }

    // for kernels with uint[10] result
    //   note that the Kotlin type "long" is a wider signed type than the unsigned script type "uint"
    object resultArray10_uint {
        fun get(): LongArray = …
    }

    // for kernels with uint2 result
    //   note that the Kotlin type "Long2" is a wider signed type than the unsigned script type "uint2"
    object result_uint2 {
        fun get(): Long2 = …
    }

    // for kernels with uint2[10] result
    //   note that the Kotlin type "Long2" is a wider signed type than the unsigned script type "uint2"
    object resultArray10_uint2 {
        fun get(): Array<Long2> = …
    }
}

Java

public class ScriptC_filename extends ScriptC {
  // for kernels with int result
  public static class result_int {
    public int get() { … }
  }

  // for kernels with int[10] result
  public static class resultArray10_int {
    public int[] get() { … }
  }

  // for kernels with int2 result
  //   note that the Java type name "Int2" is not the same as the script type name "int2"
  public static class result_int2 {
    public Int2 get() { … }
  }

  // for kernels with int2[10] result
  //   note that the Java type name "Int2" is not the same as the script type name "int2"
  public static class resultArray10_int2 {
    public Int2[] get() { … }
  }

  // for kernels with uint result
  //   note that the Java type "long" is a wider signed type than the unsigned script type "uint"
  public static class result_uint {
    public long get() { … }
  }

  // for kernels with uint[10] result
  //   note that the Java type "long" is a wider signed type than the unsigned script type "uint"
  public static class resultArray10_uint {
    public long[] get() { … }
  }

  // for kernels with uint2 result
  //   note that the Java type "Long2" is a wider signed type than the unsigned script type "uint2"
  public static class result_uint2 {
    public Long2 get() { … }
  }

  // for kernels with uint2[10] result
  //   note that the Java type "Long2" is a wider signed type than the unsigned script type "uint2"
  public static class resultArray10_uint2 {
    public Long2[] get() { … }
  }
}

إذا كانت javaResultType نوع كائن (بما في ذلك نوع مصفوفة)، ستتمثل كل عملية استدعاء إلى javaFutureType.get() على نفس المثيل الذي سيتم إرجاعه الخاص بك.

إذا لم تتمكن javaResultType من تمثيل جميع القيم من النوع resultType، تُنتج نواة الاختزال قيمة غير قابلة للتمثيل، ثم تطرح javaFutureType.get() استثناء.

الطريقة 3 وdevecSiInXType

devecSiInXType هو نوع Java الذي يتوافق مع وinXType للوسيطة المقابلة في دالة المركم. ما لم تكن inXType نوع غير موقَّع أو نوع متجه، يكون devecSiInXType هو لغة Java المقابلة مباشرةً الكتابة. إذا كان inXType عبارة عن نوع قياسي غير موقَّع، فإن devecSiInXType هو نوع جافا يتجاوب بشكل مباشر مع النوع العددي الموقَّع من النوع نفسه الحجم. إذا كان inXType هو نوع متّجه مُوقَّع، تكون devecSiInXType هي Java تتجاوب بشكل مباشر مع نوع مكون الخط المتجه. إذا كانت inXType غير موقَّعة المتجه، فإن devecSiInXType هو نوع Java الذي يتوافق مباشرةً مع نوع عددي بعلامة بنفس حجم نوع مكون الخط المتجه. مثلاً:

  • إذا كانت قيمة inXType هي int، تكون devecSiInXType يبلغ int.
  • إذا كانت قيمة inXType هي int2، تكون devecSiInXType int. الصفيفة هي تمثيل مسطّح: حيث إنها تحتوي على ضعف العديد من العناصر العددية، لأنّ التخصيص يتضمّن متّجهًا مكوّنًا من مكوّنَين العناصر. وهذه هي الطريقة نفسها التي تعمل بها طُرق copyFrom() في Allocation.
  • إذا كانت قيمة inXType هي uint، تكون قيمة deviceSiInXType. int. يتم تفسير قيمة بعلامة في صفيف Java كقيمة غير موقعة نفس نمط البت في عملية التخصيص. وهذه هي الطريقة نفسها التي تستخدمها السمة copyFrom() طرق عمل Allocation.
  • إذا كانت قيمة inXType هي uint2، تكون قيمة deviceSiInXType. int. هذه الطريقة تجمع بين int2 وuint. تتم معالجتها: الصفيف هو تمثيل مسطح، وقيم صفيفة Java الموقعة يتم تفسيرها على أنها قيم عناصر غير موقعة في RenderScript.

يُرجى العلم أنّه بالنسبة إلى الطريقة 3، يتم التعامل مع أنواع الإدخالات بشكل مختلف. من أنواع النتائج:

  • يتم تسوية إدخال متجه النص البرمجي على جانب Java، في حين أن نتيجة متجه النص البرمجي ليست كذلك.
  • يتم تمثيل الإدخال غير الموقَّع للنص البرمجي كإدخال موقَّع بالحجم نفسه على Java بينما يتم تمثيل النتيجة غير الموقعة للنص البرمجي كنوع علامة موسّعة على Java الجانب (باستثناء في حالة ulong).

مزيد من الأمثلة على نواة التقليل

#pragma rs reduce(dotProduct) \
  accumulator(dotProductAccum) combiner(dotProductSum)

// Note: No initializer function -- therefore,
// each accumulator data item is implicitly initialized to 0.0f.

static void dotProductAccum(float *accum, float in1, float in2) {
  *accum += in1*in2;
}

// combiner function
static void dotProductSum(float *accum, const float *val) {
  *accum += *val;
}
// Find a zero Element in a 2D allocation; return (-1, -1) if none
#pragma rs reduce(fz2) \
  initializer(fz2Init) \
  accumulator(fz2Accum) combiner(fz2Combine)

static void fz2Init(int2 *accum) { accum->x = accum->y = -1; }

static void fz2Accum(int2 *accum,
                     int inVal,
                     int x /* special arg */,
                     int y /* special arg */) {
  if (inVal==0) {
    accum->x = x;
    accum->y = y;
  }
}

static void fz2Combine(int2 *accum, const int2 *accum2) {
  if (accum2->x >= 0) *accum = *accum2;
}
// Note that this kernel returns an array to Java
#pragma rs reduce(histogram) \
  accumulator(hsgAccum) combiner(hsgCombine)

#define BUCKETS 256
typedef uint32_t Histogram[BUCKETS];

// Note: No initializer function --
// therefore, each bucket is implicitly initialized to 0.

static void hsgAccum(Histogram *h, uchar in) { ++(*h)[in]; }

static void hsgCombine(Histogram *accum,
                       const Histogram *addend) {
  for (int i = 0; i < BUCKETS; ++i)
    (*accum)[i] += (*addend)[i];
}

// Determines the mode (most frequently occurring value), and returns
// the value and the frequency.
//
// If multiple values have the same highest frequency, returns the lowest
// of those values.
//
// Shares functions with the histogram reduction kernel.
#pragma rs reduce(mode) \
  accumulator(hsgAccum) combiner(hsgCombine) \
  outconverter(modeOutConvert)

static void modeOutConvert(int2 *result, const Histogram *h) {
  uint32_t mode = 0;
  for (int i = 1; i < BUCKETS; ++i)
    if ((*h)[i] > (*h)[mode]) mode = i;
  result->x = mode;
  result->y = (*h)[mode];
}

عيّنات تعليمات برمجية إضافية

BasicRenderScript، RenderScriptIntrinsic، ومرحبًا بك في Compute لتوضيح استخدام واجهات برمجة التطبيقات التي تتناولها هذه الصفحة.