ภาพรวม RenderScript

RenderScript เป็นเฟรมเวิร์กสำหรับการเรียกใช้งานที่ต้องใช้การประมวลผลสูงอย่างมีประสิทธิภาพบน Android RenderScript มุ่งเน้นการใช้งานกับการประมวลผลข้อมูลแบบขนานเป็นหลัก แม้ว่าภาระงานแบบอนุกรมจะได้รับประโยชน์ด้วยเช่นกัน รันไทม์ RenderScript จะทำงานแบบขนานในโปรเซสเซอร์ที่พร้อมใช้งานในอุปกรณ์ เช่น CPU และ GPU แบบหลายคอร์ ซึ่งช่วยให้คุณมุ่งเน้นที่การแสดงอัลกอริทึมแทนการกำหนดเวลางานได้ RenderScript มีประโยชน์อย่างยิ่งสำหรับแอปพลิเคชันที่ทำการประมวลผลรูปภาพ การถ่ายภาพแบบดิจิทัล หรือคอมพิวเตอร์วิทัศน์

ในการเริ่มต้นใช้งาน RenderScript มีแนวคิดหลัก 2 ประการที่คุณควรทำความเข้าใจ ดังนี้

  • ภาษานี้เป็นภาษาที่มาจาก C99 สำหรับการเขียนโค้ดการประมวลผลประสิทธิภาพสูง การเขียน RenderScript Kernel อธิบายวิธีใช้เพื่อเขียน Compute Kernel
  • Control API ใช้สำหรับจัดการอายุการใช้งานของทรัพยากร RenderScript และควบคุมการดำเนินการของเคอร์เนล โดยมีให้บริการใน 3 ภาษา ได้แก่ Java, C++ ใน Android NDK และภาษาเคอร์เนลที่มาจาก C99 การใช้ RenderScript จากโค้ด Java และRenderScript แหล่งที่มาเดียวจะอธิบายตัวเลือกแรกและตัวเลือกที่ 3 ตามลำดับ

การเขียน RenderScript Kernel

โดยปกติแล้ว เคอร์เนล RenderScript จะอยู่ในไฟล์ .rs ในไดเรกทอรี <project_root>/src/rs โดยไฟล์ .rs แต่ละไฟล์จะเรียกว่าสคริปต์ สคริปต์ทุกรายการจะมีชุดเคอร์เนล ฟังก์ชัน และตัวแปรของตัวเอง สคริปต์อาจมีสิ่งต่อไปนี้

  • การประกาศ Pragma (#pragma version(1)) ที่ประกาศเวอร์ชันของภาษาเคอร์เนล RenderScript ที่ใช้ในสคริปต์นี้ ปัจจุบัน 1 เป็นค่าเดียวที่ถูกต้อง
  • การประกาศ Pragma (#pragma rs java_package_name(com.example.app)) ที่ประกาศชื่อแพ็กเกจของคลาส Java ที่แสดงจากสคริปต์นี้ โปรดทราบว่าไฟล์ .rs ต้องเป็นส่วนหนึ่งของแพ็กเกจแอปพลิเคชัน ไม่ใช่ในโปรเจ็กต์ไลบรารี
  • ฟังก์ชันที่เรียกใช้ได้ตั้งแต่ 0 รายการขึ้นไป ฟังก์ชันที่เรียกใช้ได้คือฟังก์ชัน RenderScript แบบเทรดเดียวที่คุณเรียกใช้จากโค้ด Java ด้วยอาร์กิวเมนต์ที่กำหนดเองได้ ซึ่งมักจะเป็นประโยชน์สำหรับการตั้งค่าเริ่มต้นหรือการคำนวณแบบอนุกรมภายในไปป์ไลน์การประมวลผลขนาดใหญ่
  • script globals ตั้งแต่ 0 รายการขึ้นไป สคริปต์ส่วนกลางคล้ายกับตัวแปรส่วนกลางใน C คุณสามารถเข้าถึงตัวแปรส่วนกลางของสคริปต์จากโค้ด Java ได้ และมักใช้ตัวแปรเหล่านี้เพื่อส่งพารามิเตอร์ไปยังเคอร์เนล RenderScript ดูคำอธิบายโดยละเอียดเกี่ยวกับตัวแปรส่วนกลางของสคริปต์ได้ที่นี่

  • เคอร์เนลการประมวลผลตั้งแต่ 0 รายการขึ้นไป เคอร์เนลการประมวลผลคือฟังก์ชันหรือคอลเล็กชันฟังก์ชันที่คุณสามารถสั่งให้รันไทม์ RenderScript ทำงานพร้อมกันในคอลเล็กชันข้อมูลได้ เคอร์เนลการประมวลผลมี 2 ประเภท ได้แก่ เคอร์เนลการแมป (หรือที่เรียกว่าเคอร์เนล foreach) และเคอร์เนลการลด

    เคอร์เนลการแมปคือฟังก์ชันคู่ขนานที่ทำงานบนคอลเล็กชัน Allocations ที่มีมิติข้อมูลเดียวกัน โดยค่าเริ่มต้น ระบบจะดำเนินการ 1 ครั้งสำหรับทุกพิกัดในมิติข้อมูลเหล่านั้น โดยทั่วไป (แต่ไม่เสมอไป) จะใช้เพื่อแปลงคอลเล็กชันของ Allocations อินพุตเป็น Element เอาต์พุตครั้งละ Allocation รายการ

    • ต่อไปนี้คือตัวอย่างเคอร์เนลการแมปแบบง่าย

      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 โดยมีการเรียกใช้ฟังก์ชันเคอร์เนล 1 ครั้งต่อ Element ใน Allocation

      เคอร์เนลการแมปอาจมีอินพุต Allocations อย่างน้อย 1 รายการ เอาต์พุต Allocation รายการเดียว หรือทั้ง 2 อย่าง รันไทม์ RenderScript จะตรวจสอบว่าการจัดสรรอินพุตและเอาต์พุตทั้งหมดมีมิติข้อมูลเดียวกัน และประเภท Element ของการจัดสรรอินพุตและเอาต์พุตตรงกับต้นแบบของเคอร์เนล หากการตรวจสอบอย่างใดอย่างหนึ่งไม่สำเร็จ RenderScript จะแสดงข้อยกเว้น

      หมายเหตุ: ก่อน Android 6.0 (ระดับ API 23) เคอร์เนลการแมปอาจมีอินพุต Allocation ได้ไม่เกิน 1 รายการ

      หากคุณต้องการอินพุตหรือเอาต์พุต Allocations มากกว่าที่เคอร์เนลมี ออบเจ็กต์เหล่านั้นควรเชื่อมโยงกับ rs_allocation สคริปต์ส่วนกลาง และเข้าถึงจากเคอร์เนลหรือฟังก์ชันที่เรียกใช้ได้ผ่าน rsGetElementAt_type() หรือ rsSetElementAt_type()

      หมายเหตุ: RS_KERNEL เป็นมาโครที่ RenderScript กำหนดโดยอัตโนมัติเพื่อความสะดวกของคุณ

      #define RS_KERNEL __attribute__((kernel))

    เคอร์เนลการลดคือกลุ่มฟังก์ชันที่ทำงานกับคอลเล็กชันของอินพุต Allocations ที่มีมิติข้อมูลเดียวกัน โดยค่าเริ่มต้น ฟังก์ชัน Accumulator จะทำงาน 1 ครั้งสำหรับทุกพิกัดในมิติข้อมูลเหล่านั้น โดยทั่วไป (แต่ไม่เสมอไป) จะใช้เพื่อ "ลด" คอลเล็กชันของ Allocations อินพุตให้เหลือค่าเดียว

    • ต่อไปนี้เป็นตัวอย่างของ Reduction Kernel แบบง่ายที่เพิ่ม Elements ของอินพุต

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

      เคอร์เนลการลดประกอบด้วยฟังก์ชันที่ผู้ใช้เขียนขึ้นอย่างน้อย 1 ฟังก์ชัน #pragma rs reduce ใช้เพื่อกำหนดเคอร์เนลโดยระบุชื่อ (addint ในตัวอย่างนี้) รวมถึงชื่อและบทบาทของฟังก์ชันที่ประกอบขึ้นเป็นเคอร์เนล (ฟังก์ชัน accumulator addintAccum ในตัวอย่างนี้) ฟังก์ชันดังกล่าวทั้งหมดต้องเป็น static เคอร์เนลการลดต้องใช้ฟังก์ชัน accumulator เสมอ และอาจมีฟังก์ชันอื่นๆ ด้วย ทั้งนี้ขึ้นอยู่กับสิ่งที่คุณต้องการให้เคอร์เนลทำ

      ฟังก์ชันตัวสะสมเคอร์เนลการลดต้องแสดงผล void และต้องมีอาร์กิวเมนต์อย่างน้อย 2 รายการ อาร์กิวเมนต์แรก (accum ในตัวอย่างนี้) เป็นตัวชี้ไปยังรายการข้อมูลตัวสะสม และอาร์กิวเมนต์ที่ 2 (val ในตัวอย่างนี้) จะได้รับการเติมโดยอัตโนมัติตามอินพุต Allocation ที่ส่งไปยังการเปิดตัวเคอร์เนล รายการข้อมูล Accumulator สร้างขึ้นโดยรันไทม์ RenderScript โดยค่าเริ่มต้นจะเริ่มต้นเป็น 0 โดยค่าเริ่มต้น เคอร์เนลนี้จะทำงานในอินพุตทั้งหมด Allocation โดยมีการเรียกใช้ฟังก์ชัน Accumulator 1 ครั้งต่อ Element ใน Allocation โดยค่าเริ่มต้น ระบบจะถือว่าค่าสุดท้ายของรายการข้อมูล Accumulator เป็นผลลัพธ์ของการลด และจะแสดงผลไปยัง Java รันไทม์ RenderScript จะตรวจสอบเพื่อให้แน่ใจว่าประเภท Element ของ Allocation อินพุตตรงกับต้นแบบของฟังก์ชัน Accumulator หากไม่ตรงกัน RenderScript จะแสดงข้อยกเว้น

      เคอร์เนลการลดมี Allocations อินพุตอย่างน้อย 1 รายการ แต่ไม่มี Allocations เอาต์พุต

      ดูคำอธิบายโดยละเอียดเกี่ยวกับเคอร์เนลการลดขนาดได้ที่นี่

      เคอร์เนลการลดรองรับใน Android 7.0 (ระดับ API 24) ขึ้นไป

    ฟังก์ชันเคอร์เนลการแมปหรือฟังก์ชันสะสมเคอร์เนลการลดอาจเข้าถึงพิกัดของการดำเนินการปัจจุบันโดยใช้อาร์กิวเมนต์พิเศษ x, y และ z ซึ่งต้องเป็นประเภท int หรือ uint32_t อาร์กิวเมนต์เหล่านี้ไม่บังคับ

    ฟังก์ชันเคอร์เนลการแมปหรือฟังก์ชันสะสมเคอร์เนลการลดอาจใช้อาร์กิวเมนต์พิเศษ context ที่ไม่บังคับของประเภท rs_kernel_context ด้วย ซึ่งจำเป็นสำหรับกลุ่ม API รันไทม์ที่ใช้ในการค้นหาพร็อพเพอร์ตี้บางอย่างของการดำเนินการปัจจุบัน เช่น rsGetDimX (อาร์กิวเมนต์ context พร้อมใช้งานใน Android 6.0 (ระดับ API 23) ขึ้นไป)

  • ฟังก์ชัน init() ที่ไม่บังคับ ฟังก์ชัน init() เป็นฟังก์ชันที่เรียกใช้ได้ประเภทพิเศษที่ RenderScript จะเรียกใช้เมื่อสร้างอินสแตนซ์สคริปต์เป็นครั้งแรก ซึ่งจะช่วยให้การคํานวณบางอย่างเกิดขึ้นโดยอัตโนมัติเมื่อสร้างสคริปต์
  • ฟังก์ชันและตัวแปรส่วนกลางของสคริปต์แบบคงที่อย่างน้อย 0 รายการ สคริปต์ส่วนกลางแบบคงที่จะเทียบเท่ากับสคริปต์ส่วนกลาง ยกเว้นว่าจะเข้าถึงจากโค้ด Java ไม่ได้ ฟังก์ชันแบบคงที่เป็นฟังก์ชัน C มาตรฐานที่เรียกใช้ได้จากเคอร์เนลหรือฟังก์ชันที่เรียกใช้ได้ในสคริปต์ แต่จะไม่แสดงต่อ Java API หากไม่จำเป็นต้องเข้าถึงสคริปต์ส่วนกลางหรือฟังก์ชันจากโค้ด Java เราขอแนะนำอย่างยิ่งให้ประกาศ static

การตั้งค่าความแม่นยำของจุดลอยตัว

คุณสามารถควบคุมระดับความแม่นยำของจุดลอยตัวที่จำเป็นในสคริปต์ได้ ซึ่งจะมีประโยชน์ในกรณีที่ไม่จำเป็นต้องใช้มาตรฐาน IEEE 754-2008 แบบเต็ม (ใช้โดยค่าเริ่มต้น) Pragma ต่อไปนี้สามารถตั้งค่าระดับความแม่นยำของจุดลอยตัวที่แตกต่างกันได้

  • #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 ได้โดยไม่มีผลข้างเคียงใดๆ ซึ่งอาจเป็นประโยชน์อย่างมากในสถาปัตยกรรมบางอย่างเนื่องจากการเพิ่มประสิทธิภาพเพิ่มเติมที่ใช้ได้เฉพาะกับความแม่นยำที่ผ่อนคลาย (เช่น คำสั่ง SIMD CPU)

การเข้าถึง RenderScript API จาก Java

เมื่อพัฒนาแอปพลิเคชัน Android ที่ใช้ RenderScript คุณจะเข้าถึง API ของแอปพลิเคชันดังกล่าวจาก Java ได้ 2 วิธีดังนี้

  • android.renderscript - API ในแพ็กเกจคลาสนี้พร้อมใช้งานในอุปกรณ์ที่ใช้ Android 3.0 (ระดับ API 11) ขึ้นไป
  • android.support.v8.renderscript - API ในแพ็กเกจนี้พร้อมใช้งานผ่านไลบรารีการสนับสนุน ซึ่งช่วยให้คุณใช้ API ดังกล่าวในอุปกรณ์ที่ใช้ Android 2.3 (ระดับ API 9) ขึ้นไปได้

ข้อดีข้อเสียมีดังนี้

  • หากคุณใช้ Support Library API ส่วน RenderScript ของแอปพลิเคชันจะเข้ากันได้กับอุปกรณ์ที่ใช้ Android 2.3 (ระดับ API 9) ขึ้นไป ไม่ว่าคุณจะใช้ฟีเจอร์ RenderScript ใดก็ตาม ซึ่งจะช่วยให้แอปพลิเคชันทำงานในอุปกรณ์ได้มากกว่าการใช้ API แบบเนทีฟ (android.renderscript)
  • ฟีเจอร์ RenderScript บางอย่างไม่พร้อมใช้งานผ่าน Support Library API
  • หากใช้ API ของ Support Library คุณจะได้รับ APK ที่มีขนาดใหญ่กว่า (อาจใหญ่กว่ามาก) การใช้ API แบบเนทีฟ (android.renderscript)

การใช้ RenderScript Support Library API

หากต้องการใช้ RenderScript API ของ Support Library คุณต้องกำหนดค่าสภาพแวดล้อมในการพัฒนาซอฟต์แวร์ให้เข้าถึง API ดังกล่าวได้ คุณต้องใช้เครื่องมือ Android SDK ต่อไปนี้เพื่อใช้ API เหล่านี้

  • Android SDK Tools เวอร์ชัน 22.2 ขึ้นไป
  • เครื่องมือสร้าง Android SDK เวอร์ชัน 18.1.0 ขึ้นไป

โปรดทราบว่าตั้งแต่ Android SDK Build-tools 24.0.0 เป็นต้นไป ระบบจะไม่รองรับ Android 2.2 (API ระดับ 8) อีกต่อไป

คุณสามารถตรวจสอบและอัปเดตเวอร์ชันที่ติดตั้งของเครื่องมือเหล่านี้ได้ในเครื่องมือจัดการ Android SDK

วิธีใช้ RenderScript API ของ Support Library

  1. ตรวจสอบว่าคุณได้ติดตั้ง Android SDK เวอร์ชันที่จำเป็นแล้ว
  2. อัปเดตการตั้งค่าสำหรับกระบวนการบิลด์ Android เพื่อรวมการตั้งค่า RenderScript ดังนี้
    • เปิดไฟล์ build.gradle ในโฟลเดอร์แอปของโมดูลแอปพลิเคชัน
    • เพิ่มการตั้งค่า RenderScript ต่อไปนี้ลงในไฟล์

      ดึงดูด

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

      Kotlin

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

      การตั้งค่าที่แสดงด้านบนจะควบคุมลักษณะการทำงานที่เฉพาะเจาะจงในกระบวนการบิลด์ของ Android ดังนี้

      • renderscriptTargetApi - ระบุเวอร์ชันไบต์โค้ดที่จะสร้าง เราขอแนะนำให้คุณตั้งค่านี้เป็นระดับ API ต่ำสุดที่สามารถให้บริการฟังก์ชันทั้งหมดที่คุณใช้อยู่ และตั้งค่า renderscriptSupportModeEnabled เป็น true ค่าที่ถูกต้องสำหรับการตั้งค่านี้คือค่าจำนวนเต็มตั้งแต่ 11 จนถึงระดับ API ที่เผยแพร่ล่าสุด หากเวอร์ชัน SDK ขั้นต่ำที่ระบุไว้ในไฟล์ Manifest ของแอปพลิเคชันตั้งค่าเป็นค่าอื่น ระบบจะไม่สนใจค่านั้นและจะใช้ค่าเป้าหมายในไฟล์บิลด์เพื่อตั้งค่าเวอร์ชัน SDK ขั้นต่ำ
      • renderscriptSupportModeEnabled - ระบุว่าไบต์โค้ดที่สร้างขึ้นควรเปลี่ยนกลับไปใช้เวอร์ชันที่เข้ากันได้หากอุปกรณ์ที่ใช้งานไม่รองรับเวอร์ชันเป้าหมาย
  3. ในคลาสแอปพลิเคชันที่ใช้ RenderScript ให้เพิ่มการนําเข้าสําหรับคลาส Support Library ดังนี้

    Kotlin

    import android.support.v8.renderscript.*

    Java

    import android.support.v8.renderscript.*;

การใช้ RenderScript จากโค้ด Java หรือ Kotlin

การใช้ RenderScript จากโค้ด Java หรือ Kotlin จะอาศัยคลาส API ที่อยู่ในแพ็กเกจ android.renderscript หรือ android.support.v8.renderscript แอปพลิเคชันส่วนใหญ่มีรูปแบบการใช้งานพื้นฐานเหมือนกัน ดังนี้

  1. เริ่มต้นบริบท RenderScript บริบท RenderScript ที่สร้างขึ้นด้วย create(Context) ช่วยให้มั่นใจได้ว่าสามารถใช้ RenderScript ได้ และมีออบเจ็กต์สำหรับควบคุมอายุการใช้งานของออบเจ็กต์ RenderScript ที่ตามมาทั้งหมด คุณควรพิจารณาว่าการสร้างบริบทเป็นการดำเนินการที่อาจใช้เวลานาน เนื่องจากอาจสร้างทรัพยากรในฮาร์ดแวร์ที่แตกต่างกัน และไม่ควรอยู่ในเส้นทางที่สำคัญของแอปพลิเคชันหากเป็นไปได้ โดยปกติแล้ว แอปพลิเคชันจะมีบริบท RenderScript เพียงรายการเดียวในแต่ละครั้ง
  2. สร้าง Allocation อย่างน้อย 1 รายการเพื่อส่งไปยังสคริปต์ Allocation คือออบเจ็กต์ RenderScript ที่มีพื้นที่เก็บข้อมูลสำหรับข้อมูลจำนวนคงที่ เคอร์เนลในสคริปต์จะใช้ออบเจ็กต์ Allocation เป็นอินพุตและเอาต์พุต และสามารถเข้าถึงออบเจ็กต์ Allocation ในเคอร์เนลได้โดยใช้ rsGetElementAt_type() และ rsSetElementAt_type() เมื่อเชื่อมโยงเป็นตัวแปรส่วนกลางของสคริปต์ ออบเจ็กต์ Allocation ช่วยให้ส่งอาร์เรย์จากโค้ด Java ไปยังโค้ด RenderScript และในทางกลับกันได้ โดยปกติแล้ว ออบเจ็กต์ Allocation จะสร้างขึ้นโดยใช้ createTyped() หรือ createFromBitmap()
  3. สร้างสคริปต์ที่จำเป็น สคริปต์มี 2 ประเภทให้คุณใช้เมื่อใช้ RenderScript ดังนี้
    • ScriptC: สคริปต์ที่ผู้ใช้กำหนดตามที่อธิบายไว้ในการเขียน RenderScript Kernel ด้านบน สคริปต์ทุกรายการมีคลาส 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 เมธอด "copy" เป็นแบบซิงโครนัส
  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() การเปิดตัวเหล่านี้เป็นแบบอะซิงโครนัส เมธอดจะใช้การจัดสรรอย่างน้อย 1 รายการ โดยขึ้นอยู่กับอาร์กิวเมนต์ของเคอร์เนล ซึ่งทั้งหมดต้องมีมิติข้อมูลเดียวกัน โดยค่าเริ่มต้น เคอร์เนลจะดำเนินการกับพิกัดทั้งหมดในมิติข้อมูลเหล่านั้น หากต้องการดำเนินการเคอร์เนลกับชุดย่อยของพิกัดเหล่านั้น ให้ส่ง Script.LaunchOptions ที่เหมาะสมเป็นอาร์กิวเมนต์สุดท้ายไปยังเมธอด forEach หรือ reduce

    เปิดใช้ฟังก์ชันที่เรียกใช้ได้โดยใช้เมธอด invoke_functionName ที่แสดงในคลาส ScriptC_filename เดียวกัน การเปิดตัวเหล่านี้เป็นแบบอะซิงโครนัส

  7. ดึงข้อมูลจากออบเจ็กต์ Allocation และออบเจ็กต์ javaFutureType หากต้องการเข้าถึงข้อมูลจาก Allocation จากโค้ด Java คุณต้องคัดลอกข้อมูลนั้นกลับไปยัง Java โดยใช้วิธี "คัดลอก" วิธีใดวิธีหนึ่งใน Allocation หากต้องการรับผลลัพธ์ของเคอร์เนลการลด คุณต้องใช้เมธอด javaFutureType.get() เมธอด "copy" และ get() เป็นแบบซิงโครนัส
  8. ปิดบริบท RenderScript คุณสามารถทำลายบริบท RenderScript ด้วย destroy() หรือโดยการอนุญาตให้เก็บขยะออบเจ็กต์บริบท RenderScript ซึ่งจะทำให้การใช้ออบเจ็กต์ใดๆ ที่เป็นของบริบทนั้นทำให้เกิดข้อยกเว้น

โมเดลการดำเนินการแบบอะซิงโครนัส

เมธอด forEach, invoke, reduce และ set ที่แสดงผลเป็นแบบอะซิงโครนัส โดยแต่ละเมธอดอาจกลับไปที่ Java ก่อนที่จะดำเนินการตามคำขอเสร็จสมบูรณ์ อย่างไรก็ตาม การดำเนินการแต่ละรายการจะได้รับการจัดลำดับตามลำดับที่เปิดตัว

คลาส Allocation มีเมธอด "copy" เพื่อคัดลอกข้อมูลไปยังและจาก Allocation เมธอด "copy" เป็นแบบซิงโครนัส และได้รับการจัดเรียงตามลำดับที่เกี่ยวข้องกับการดำเนินการแบบอะซิงโครนัสข้างต้นที่แตะการจัดสรรเดียวกัน

คลาส javaFutureType ที่แสดงผลจะมีเมธอด get() เพื่อรับผลลัพธ์ของการลด get() เป็นแบบซิงโครนัสและได้รับการจัดเรียงตามลำดับที่เกี่ยวข้องกับการลด (ซึ่งเป็นแบบอะซิงโครนัส)

RenderScript แหล่งที่มาเดียว

Android 7.0 (ระดับ API 24) เปิดตัวฟีเจอร์การเขียนโปรแกรมใหม่ที่เรียกว่า RenderScript แหล่งที่มาเดียว ซึ่งจะเปิดตัวเคอร์เนลจากสคริปต์ที่กำหนดไว้ แทนที่จะเป็นจาก Java ปัจจุบันแนวทางนี้จำกัดไว้เฉพาะเคอร์เนลการแมป ซึ่งเรียกสั้นๆ ว่า "เคอร์เนล" ในส่วนนี้เพื่อความกระชับ ฟีเจอร์ใหม่นี้ยังรองรับการสร้างการจัดสรรประเภท rs_allocation จากภายในสคริปต์ด้วย ตอนนี้คุณสามารถใช้อัลกอริทึมทั้งหมดภายในสคริปต์เพียงอย่างเดียวได้แล้ว แม้ว่าจะต้องเปิดใช้งานเคอร์เนลหลายรายการก็ตาม ซึ่งมีประโยชน์ 2 ประการ ได้แก่ โค้ดที่อ่านง่ายขึ้น เนื่องจากจะเก็บการใช้งานอัลกอริทึมไว้ในภาษาเดียว และโค้ดที่อาจทำงานเร็วขึ้น เนื่องจากมีการเปลี่ยนระหว่าง Java กับ RenderScript น้อยลงในการเปิดตัวเคอร์เนลหลายครั้ง

ใน RenderScript แหล่งที่มาเดียว คุณจะเขียนเคอร์เนลตามที่อธิบายไว้ใน การเขียนเคอร์เนล RenderScript จากนั้นคุณเขียนฟังก์ชันที่เรียกใช้ได้ซึ่งจะเรียก rsForEach() เพื่อเปิดใช้งาน API นั้นจะใช้ฟังก์ชันเคอร์เนลเป็นพารามิเตอร์แรก ตามด้วยการจัดสรรอินพุตและเอาต์พุต API ที่คล้ายกัน rsForEachWithOptions() ใช้อาร์กิวเมนต์เพิ่มเติมของประเภท rs_script_call_t ซึ่งระบุชุดย่อยขององค์ประกอบจากการจัดสรรอินพุตและเอาต์พุตเพื่อให้ฟังก์ชันเคอร์เนลประมวลผล

หากต้องการเริ่มการคํานวณ RenderScript ให้เรียกใช้ฟังก์ชันที่เรียกใช้ได้จาก Java ทำตามขั้นตอนในหัวข้อการใช้ RenderScript จากโค้ด Java ในขั้นตอนเปิดใช้เคอร์เนลที่เหมาะสม ให้เรียกใช้ฟังก์ชันที่เรียกใช้ได้โดยใช้ invoke_function_name() ซึ่งจะเริ่มการคำนวณทั้งหมด รวมถึงการเปิดใช้เคอร์เนล

การจัดสรรทรัพยากรมักจำเป็นสำหรับการบันทึกและส่งผลลัพธ์ระดับกลางจากการเปิดตัวเคอร์เนลหนึ่งไปยังอีกเคอร์เนลหนึ่ง คุณสร้างได้โดยใช้ rsCreateAllocation() รูปแบบหนึ่งของ API ดังกล่าวที่ใช้งานง่ายคือ rsCreateAllocation_<T><W>(…) โดยที่ T คือประเภทข้อมูลขององค์ประกอบ และ W คือความกว้างของเวกเตอร์สำหรับองค์ประกอบ API จะใช้ขนาดในมิติข้อมูล X, Y และ Z เป็นอาร์กิวเมนต์ สำหรับการจัดสรรแบบ 1 มิติหรือ 2 มิติ คุณสามารถละเว้นขนาดของมิติข้อมูล Y หรือ Z ได้ ตัวอย่างเช่น rsCreateAllocation_uchar4(16384) จะสร้างการจัดสรร 1 มิติขององค์ประกอบ 16384 รายการ ซึ่งแต่ละรายการเป็นประเภท uchar4

ระบบจะจัดการการจัดสรรโดยอัตโนมัติ คุณไม่จำเป็นต้องปล่อยหรือทำให้ว่างอย่างชัดเจน อย่างไรก็ตาม คุณสามารถเรียกใช้ rsClearObject(rs_allocation* alloc) เพื่อระบุว่าคุณไม่ต้องการแฮนเดิล alloc สำหรับการจัดสรรที่เกี่ยวข้องอีกต่อไป เพื่อให้ระบบเพิ่มพื้นที่ว่างในทรัพยากรได้โดยเร็วที่สุด

ส่วนการเขียน RenderScript Kernel มีตัวอย่างเคอร์เนลที่กลับสีรูปภาพ ตัวอย่างด้านล่างจะขยายการทำงานดังกล่าวเพื่อใช้เอฟเฟกต์มากกว่า 1 รายการกับรูปภาพโดยใช้ RenderScript แหล่งที่มาเดียว ซึ่งรวมถึงเคอร์เนลอีกตัวหนึ่งคือ greyscale ซึ่งจะเปลี่ยนรูปภาพสีเป็นขาวดำ จากนั้นฟังก์ชันที่เรียกใช้ได้ process() จะใช้เคอร์เนลทั้ง 2 รายการนั้นกับรูปภาพอินพุตอย่างต่อเนื่อง และสร้างรูปภาพเอาต์พุต ระบบจะส่งการจัดสรรสำหรับทั้งอินพุตและเอาต์พุตเป็นอาร์กิวเมนต์ประเภท 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);

ตัวอย่างนี้แสดงวิธีใช้อัลกอริทึมที่เกี่ยวข้องกับการเปิดตัวเคอร์เนล 2 รายการอย่างสมบูรณ์ในภาษา RenderScript เอง หากไม่มี RenderScript แบบแหล่งที่มาเดียว คุณจะต้องเปิดใช้งานทั้ง 2 เคอร์เนลจากโค้ด Java โดยแยกการเปิดใช้งานเคอร์เนลออกจากการกำหนดเคอร์เนล ซึ่งทำให้เข้าใจอัลกอริทึมทั้งหมดได้ยากขึ้น โค้ด RenderScript แบบแหล่งเดียวไม่เพียงแต่อ่านง่ายขึ้นเท่านั้น แต่ยังช่วยลดการเปลี่ยนระหว่าง Java กับสคริปต์ในการเปิดตัวเคอร์เนลด้วย อัลกอริทึมแบบวนซ้ำบางรายการอาจเปิดใช้เคอร์เนลหลายร้อยครั้ง ทำให้เกิดค่าใช้จ่ายเพิ่มเติมในการเปลี่ยนผ่านดังกล่าวเป็นจำนวนมาก

Script Globals

สคริปต์ส่วนกลางคือตัวแปรส่วนกลางที่ไม่ใช่ static ธรรมดาในไฟล์สคริปต์ (.rs) สำหรับสคริปต์ส่วนกลางชื่อ var ที่กำหนดไว้ในไฟล์ filename.rs จะมีเมธอด get_var ที่แสดงในคลาส ScriptC_filename นอกจากว่าส่วนกลางจะเป็น const ก็จะมีเมธอด set_var ด้วย

สคริปต์ส่วนกลางที่ระบุมีค่าแยกกัน 2 ค่า ได้แก่ ค่า Java และค่าสคริปต์ ค่าเหล่านี้จะทำงานดังนี้

  • หาก var มีตัวเริ่มต้นแบบคงที่ในสคริปต์ ระบบจะระบุค่าเริ่มต้นของ var ทั้งใน Java และสคริปต์ ไม่เช่นนั้น ค่าเริ่มต้นจะเป็น 0
  • การเข้าถึง var ภายในสคริปต์จะอ่านและเขียนค่าสคริปต์
  • เมธอด get_var จะอ่านค่า Java
  • เมธอด set_var (หากมี) จะเขียนค่า Java ทันที และเขียนค่าสคริปต์แบบอะซิงโครนัส

หมายเหตุ: ซึ่งหมายความว่า Java จะไม่เห็นค่าที่เขียนลงในตัวแปรส่วนกลางจากภายในสคริปต์ ยกเว้นตัวเริ่มต้นแบบคงที่ในสคริปต์

เคอร์เนลการลดแบบเจาะลึก

การลดคือกระบวนการรวมคอลเล็กชันข้อมูลให้เป็นค่าเดียว ซึ่งเป็นพื้นฐานที่มีประโยชน์ในการเขียนโปรแกรมแบบขนาน โดยมีการประยุกต์ใช้ดังต่อไปนี้

  • การคำนวณผลรวมหรือผลคูณของข้อมูลทั้งหมด
  • การคำนวณการดำเนินการเชิงตรรกะ (and, or, xor) ในข้อมูลทั้งหมด
  • การหาค่าต่ำสุดหรือสูงสุดภายในข้อมูล
  • การค้นหาค่าที่เฉพาะเจาะจงหรือพิกัดของค่าที่เฉพาะเจาะจงภายในข้อมูล

ใน Android 7.0 (ระดับ API 24) ขึ้นไป RenderScript รองรับเคอร์เนลการลดเพื่อให้ผู้ใช้เขียนอัลกอริทึมการลดได้อย่างมีประสิทธิภาพ คุณอาจเปิดใช้เคอร์เนลการลดในอินพุตที่มี 1, 2 หรือ 3 มิติข้อมูล

ตัวอย่างด้านบนแสดงเคอร์เนลการลด addint แบบง่าย ต่อไปนี้เป็นเคอร์เนลการลด findMinAndMax ที่ซับซ้อนมากขึ้นซึ่งค้นหาตำแหน่งของค่า long ต่ำสุดและสูงสุดใน Allocation 1 มิติ

#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 จะสร้างตัวแปรอย่างน้อย 1 รายการที่เรียกว่ารายการข้อมูลตัวสะสมเพื่อเก็บสถานะของกระบวนการลด รันไทม์ RenderScript จะเลือกจำนวนรายการข้อมูลตัวสะสมในลักษณะที่เพิ่มประสิทธิภาพสูงสุด ประเภทของรายการข้อมูลตัวสะสม (accumType) จะกำหนดโดยฟังก์ชันตัวสะสมของเคอร์เนล อาร์กิวเมนต์แรกของฟังก์ชันนั้นคือตัวชี้ไปยังรายการข้อมูลตัวสะสม โดยค่าเริ่มต้น ระบบจะเริ่มต้นรายการข้อมูลตัวสะสมแต่ละรายการเป็น 0 (เหมือนกับใช้ memset) แต่คุณสามารถเขียนฟังก์ชันตัวเริ่มต้นเพื่อทำอย่างอื่นได้

ตัวอย่าง: ในเคอร์เนล addint ระบบจะใช้รายการข้อมูลตัวสะสม (ประเภท int) เพื่อบวกค่าอินพุต ไม่มีฟังก์ชันตัวเริ่มต้น ดังนั้นรายการข้อมูลตัวสะสมแต่ละรายการจะเริ่มต้นเป็น 0

ตัวอย่าง: ในเคอร์เนล findMinAndMax ระบบจะใช้รายการข้อมูล Accumulator (ประเภท MinAndMax) เพื่อติดตามค่าต่ำสุดและสูงสุดที่พบจนถึงตอนนี้ มีฟังก์ชันตัวเริ่มต้นเพื่อตั้งค่าเหล่านี้เป็น LONG_MAX และ LONG_MIN ตามลำดับ และเพื่อตั้งค่าตำแหน่งของค่าเหล่านี้เป็น -1 ซึ่งบ่งชี้ว่าค่าเหล่านี้ไม่ได้อยู่ในส่วน (ว่าง) ของอินพุตที่ประมวลผลแล้วจริงๆ

RenderScript จะเรียกใช้ฟังก์ชัน Accumulator 1 ครั้งสำหรับทุกพิกัดในอินพุต โดยปกติแล้ว ฟังก์ชันควรอัปเดตรายการข้อมูลตัวสะสมในลักษณะใดลักษณะหนึ่งตามอินพุต

ตัวอย่าง: ในเคอร์เนล addint ฟังก์ชัน Accumulator จะเพิ่มค่าขององค์ประกอบอินพุตลงในรายการข้อมูล Accumulator

ตัวอย่าง: ในเคอร์เนล findMinAndMax ฟังก์ชัน Accumulator จะตรวจสอบว่าค่าขององค์ประกอบอินพุตน้อยกว่าหรือเท่ากับค่าต่ำสุดที่บันทึกไว้ในรายการข้อมูล Accumulator และ/หรือมากกว่าหรือเท่ากับค่าสูงสุดที่บันทึกไว้ในรายการข้อมูล Accumulator หรือไม่ แล้วอัปเดตรายการข้อมูล Accumulator ตามนั้น

หลังจากเรียกใช้ฟังก์ชัน Accumulator 1 ครั้งสำหรับทุกพิกัดในอินพุตแล้ว RenderScript ต้องรวมรายการข้อมูล Accumulator เข้าด้วยกันเป็นรายการข้อมูล Accumulator รายการเดียว คุณสามารถเขียนฟังก์ชัน Combiner เพื่อดำเนินการนี้ได้ หากฟังก์ชัน Accumulator มีอินพุตเดียวและไม่มีอาร์กิวเมนต์พิเศษ คุณไม่จำเป็นต้องเขียนฟังก์ชัน Combiner เนื่องจาก RenderScript จะใช้ฟังก์ชัน Accumulator เพื่อรวมรายการข้อมูล Accumulator (คุณยังคงเขียนฟังก์ชัน Combiner ได้หากลักษณะการทำงานเริ่มต้นนี้ไม่ใช่สิ่งที่คุณต้องการ)

ตัวอย่าง: ในเคอร์เนล addint ไม่มีฟังก์ชัน Combiner ดังนั้นระบบจะใช้ฟังก์ชัน Accumulator ซึ่งเป็นลักษณะการทำงานที่ถูกต้อง เพราะหากเราแบ่งชุดค่าออกเป็น 2 ส่วน และบวกค่าใน 2 ส่วนนั้นแยกกัน การบวกผลรวมทั้ง 2 รายการจะเท่ากับการบวกทั้งชุด

ตัวอย่าง: ในเคอร์เนล findMinAndMax ฟังก์ชัน Combiner จะตรวจสอบว่าค่าต่ำสุดที่บันทึกไว้ในรายการข้อมูล Accumulator "ต้นทาง" *val น้อยกว่าค่าต่ำสุดที่บันทึกไว้ในรายการข้อมูล Accumulator "ปลายทาง" *accum หรือไม่ และอัปเดต *accum ตามนั้น และจะดำเนินการในลักษณะเดียวกันสำหรับค่าสูงสุด การดำเนินการนี้จะอัปเดต *accum เป็นสถานะที่ควรจะเป็นหากค่าอินพุตทั้งหมดสะสมอยู่ใน *accum แทนที่จะสะสมอยู่ใน *accum บางส่วนและ *val บางส่วน

หลังจากรวมรายการข้อมูลตัวสะสมทั้งหมดแล้ว RenderScript จะกำหนดผลลัพธ์ของการลดเพื่อส่งกลับไปยัง Java คุณสามารถเขียนฟังก์ชัน outconverter เพื่อดำเนินการนี้ได้ คุณไม่จำเป็นต้องเขียนฟังก์ชัน Outconverter หากต้องการให้ค่าสุดท้ายของรายการข้อมูล Accumulator ที่รวมกันเป็นผลลัพธ์ของการลด

ตัวอย่าง: ในเคอร์เนล addint ไม่มีฟังก์ชัน Outconverter ค่าสุดท้ายของรายการข้อมูลที่รวมกันคือผลรวมขององค์ประกอบทั้งหมดของอินพุต ซึ่งเป็นค่าที่เราต้องการแสดงผล

ตัวอย่าง: ในเคอร์เนล 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 จะเรียกใช้ฟังก์ชันนี้ 1 ครั้งสำหรับรายการข้อมูลตัวสะสมแต่ละรายการ ต้องกำหนดฟังก์ชันดังนี้

    static void initializerName(accumType *accum) { … }

    accum คือตัวชี้ไปยังรายการข้อมูลตัวสะสมเพื่อให้ฟังก์ชันนี้เริ่มต้น

    หากคุณไม่ได้ระบุฟังก์ชันตัวเริ่มต้น RenderScript จะเริ่มต้นรายการข้อมูลตัวสะสมทุกรายการเป็น 0 (เหมือนกับ memset) โดยทำงานเหมือนกับว่ามีฟังก์ชันตัวเริ่มต้นที่มีลักษณะดังนี้

    static void initializerName(accumType *accum) {
      memset(accum, 0, sizeof(*accum));
    }
  • accumulator(accumulatorName) (บังคับ): ระบุชื่อของฟังก์ชัน Accumulator สำหรับเคอร์เนลการลดนี้ เมื่อคุณเปิดใช้งานเคอร์เนล RenderScript จะเรียกใช้ฟังก์ชันนี้ 1 ครั้งสำหรับทุกพิกัดในอินพุต เพื่ออัปเดตรายการข้อมูลตัวสะสมในลักษณะที่สอดคล้องกับอินพุต ต้องกำหนดฟังก์ชันดังนี้

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

    accum คือตัวชี้ไปยังรายการข้อมูลตัวสะสมเพื่อให้ฟังก์ชันนี้แก้ไข in1 ถึง inN คืออาร์กิวเมนต์อย่างน้อย 1 รายการที่ระบบจะป้อนข้อมูลให้โดยอัตโนมัติตามอินพุตที่ส่งไปยังการเปิดใช้งานเคอร์เนล โดยมีอาร์กิวเมนต์ 1 รายการต่ออินพุต 1 รายการ ฟังก์ชัน Accumulator อาจใช้อาร์กิวเมนต์พิเศษใดก็ได้ (ไม่บังคับ)

    ตัวอย่างเคอร์เนลที่มีอินพุตหลายรายการคือ dotProduct

  • combiner(combinerName)

    (ไม่บังคับ): ระบุชื่อของฟังก์ชัน Combiner สำหรับเคอร์เนลการลดนี้ หลังจาก RenderScript เรียกใช้ฟังก์ชัน Accumulator 1 ครั้งสำหรับทุกพิกัดในอินพุตแล้ว ระบบจะเรียกใช้ฟังก์ชันนี้หลายครั้งตามความจำเป็นเพื่อรวมรายการข้อมูล Accumulator ทั้งหมดไว้ในรายการข้อมูล Accumulator รายการเดียว ต้องกำหนดฟังก์ชันดังนี้

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

    accum เป็นตัวชี้ไปยังรายการข้อมูลตัวสะสม "ปลายทาง" สำหรับฟังก์ชันนี้ที่จะแก้ไข other คือตัวชี้ไปยังรายการข้อมูลตัวสะสม "ต้นทาง" สำหรับฟังก์ชันนี้เพื่อ "รวม" เป็น *accum

    หมายเหตุ: อาจเป็นไปได้ว่ามีการเริ่มต้น *accum, *other หรือทั้ง 2 อย่าง แต่ไม่เคยส่งไปยังฟังก์ชัน Accumulator กล่าวคือ ไม่เคยมีการอัปเดตอย่างใดอย่างหนึ่งหรือทั้ง 2 อย่างตามข้อมูลที่ป้อน ตัวอย่างเช่น ในเคอร์เนล findMinAndMax ฟังก์ชัน Combiner fMMCombiner จะตรวจสอบ idx < 0 อย่างชัดแจ้งเนื่องจากเป็นการระบุรายการข้อมูล Accumulator ดังกล่าวซึ่งมีค่าเป็น INITVAL

    หากคุณไม่ได้ระบุฟังก์ชัน Combiner ไว้ RenderScript จะใช้ฟังก์ชัน Accumulator แทน โดยทำงานเสมือนว่ามีฟังก์ชัน Combiner ที่มีลักษณะดังนี้

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

    ฟังก์ชัน Combiner เป็นสิ่งจำเป็นหากเคอร์เนลมีอินพุตมากกว่า 1 รายการ หากประเภทข้อมูลอินพุตไม่เหมือนกับประเภทข้อมูล Accumulator หรือหากฟังก์ชัน Accumulator รับอาร์กิวเมนต์พิเศษอย่างน้อย 1 รายการ

  • outconverter(outconverterName) (ไม่บังคับ): ระบุชื่อของฟังก์ชัน Outconverter สำหรับเคอร์เนลการลดนี้ หลังจาก RenderScript รวมรายการข้อมูลตัวสะสมทั้งหมดแล้ว ระบบจะเรียกใช้ฟังก์ชันนี้เพื่อกำหนดผลลัพธ์ของการลดเพื่อส่งกลับไปยัง Java โดยต้องกำหนดฟังก์ชันดังนี้

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

    result เป็นตัวชี้ไปยังรายการข้อมูลผลลัพธ์ (จัดสรรไว้แต่ไม่ได้เริ่มต้นโดยรันไทม์ RenderScript) เพื่อให้ฟังก์ชันนี้เริ่มต้นด้วยผลลัพธ์ของการลด resultType คือประเภทของรายการข้อมูลนั้น ซึ่งไม่จำเป็นต้องเหมือนกับ accumType accum คือตัวชี้ไปยังรายการข้อมูลตัวสะสมสุดท้ายที่คำนวณโดยฟังก์ชัน Combiner

    หากคุณไม่ได้ระบุฟังก์ชัน Outconverter ไว้ RenderScript จะคัดลอกรายการข้อมูล Accumulator สุดท้ายไปยังรายการข้อมูลผลลัพธ์ โดยทำงานเสมือนว่ามีฟังก์ชัน Outconverter ที่มีลักษณะดังนี้

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

    หากต้องการผลลัพธ์ประเภทอื่นที่ไม่ใช่ประเภทข้อมูล Accumulator คุณต้องใช้ฟังก์ชัน Outconverter

โปรดทราบว่าเคอร์เนลมีประเภทอินพุต ประเภทรายการข้อมูลตัวสะสม และประเภทผลลัพธ์ ซึ่งไม่จำเป็นต้องเหมือนกัน ตัวอย่างเช่น ในเคอร์เนล findMinAndMax ประเภทอินพุต long, ประเภทรายการข้อมูลตัวสะสม MinAndMax และประเภทผลลัพธ์ int2 จะแตกต่างกันทั้งหมด

สิ่งที่คุณไม่สามารถคาดเดาได้

คุณต้องไม่พึ่งพาจำนวนรายการข้อมูลตัวสะสมที่ RenderScript สร้างขึ้นสำหรับการเปิดตัวเคอร์เนลหนึ่งๆ ไม่มีการรับประกันว่าการเปิดใช้งานเคอร์เนลเดียวกัน 2 ครั้งด้วยอินพุตเดียวกันจะสร้างรายการข้อมูล Accumulator ในจำนวนที่เท่ากัน

คุณต้องไม่พึ่งพาลำดับที่ RenderScript เรียกใช้ฟังก์ชันตัวเริ่มต้น ตัวสะสม และตัวรวม และอาจเรียกใช้ฟังก์ชันบางอย่างพร้อมกันด้วย ไม่มีการรับประกันว่าการเปิดตัวเคอร์เนลเดียวกัน 2 ครั้งด้วยอินพุตเดียวกันจะเป็นไปตามลำดับเดียวกัน สิ่งเดียวที่รับประกันได้คือมีเพียงฟังก์ชันตัวเริ่มต้นเท่านั้นที่จะเห็นรายการข้อมูลตัวสะสมที่ยังไม่ได้เริ่มต้น เช่น

  • ไม่มีการรับประกันว่าระบบจะเริ่มต้นรายการข้อมูลตัวสะสมทั้งหมดก่อนที่จะเรียกใช้ฟังก์ชันตัวสะสม แม้ว่าจะมีการเรียกใช้เฉพาะในรายการข้อมูลตัวสะสมที่เริ่มต้นแล้วก็ตาม
  • ไม่มีการรับประกันเกี่ยวกับลำดับการส่งองค์ประกอบอินพุตไปยังฟังก์ชัน Accumulator
  • ไม่มีการรับประกันว่ามีการเรียกใช้ฟังก์ชัน Accumulator สำหรับองค์ประกอบอินพุตทั้งหมดก่อนที่จะเรียกใช้ฟังก์ชัน Combiner

ผลที่ตามมาอย่างหนึ่งคือเคอร์เนล findMinAndMax ไม่ใช่เชิงกำหนด กล่าวคือ หากอินพุตมีค่าต่ำสุดหรือสูงสุดเดียวกันมากกว่า 1 ค่า คุณจะไม่มีทางทราบได้ว่าเคอร์เนลจะพบค่าใด

คุณต้องรับประกันอะไรบ้าง

เนื่องจากระบบ RenderScript สามารถเลือกเรียกใช้เคอร์เนลได้หลายวิธี คุณจึงต้องปฏิบัติตามกฎบางอย่างเพื่อให้เคอร์เนลทำงานในแบบที่คุณต้องการ หากไม่ปฏิบัติตามกฎเหล่านี้ คุณอาจได้รับผลลัพธ์ที่ไม่ถูกต้อง ลักษณะการทำงานที่ไม่แน่นอน หรือข้อผิดพลาดขณะรันไทม์

กฎด้านล่างมักระบุว่ารายการข้อมูลตัวสะสม 2 รายการต้องมี "ค่าเดียวกัน" ผลกระทบจากการเปลี่ยนแปลงนี้ ขึ้นอยู่กับสิ่งที่คุณต้องการให้เคอร์เนลทำ สำหรับการลดทางคณิตศาสตร์ เช่น addint โดยปกติแล้ว "เหมือนกัน" จะหมายถึงความเท่ากันทางคณิตศาสตร์ สำหรับการค้นหาแบบ "เลือกค่าใดก็ได้" เช่น findMinAndMax ("ค้นหาตำแหน่งของค่าอินพุตต่ำสุดและสูงสุด") ซึ่งอาจมีค่าอินพุตที่เหมือนกันมากกว่า 1 ค่า ตำแหน่งทั้งหมดของค่าอินพุตที่ระบุจะต้องถือว่า "เหมือนกัน" คุณสามารถเขียนเคอร์เนลที่คล้ายกันเพื่อ "ค้นหาตำแหน่งของค่าอินพุตต่ำสุดและสูงสุดทางซ้ายสุด" โดยที่ (สมมติว่า) ค่าต่ำสุดที่ตำแหน่ง 100 เป็นที่ต้องการมากกว่าค่าต่ำสุดที่เหมือนกันที่ตำแหน่ง 200 สำหรับเคอร์เนลนี้ "เหมือนกัน" จะหมายถึงตำแหน่งที่เหมือนกัน ไม่ใช่แค่ค่าที่เหมือนกัน และฟังก์ชัน Accumulator และ Combiner จะต้องแตกต่างจากฟังก์ชันสำหรับ findMinAndMax

ฟังก์ชันตัวเริ่มต้นต้องสร้างค่าเอกลักษณ์ กล่าวคือ หาก I และ A เป็นรายการข้อมูลตัวสะสมที่เริ่มต้นโดยฟังก์ชันตัวเริ่มต้น และไม่เคยส่ง I ไปยังฟังก์ชันตัวสะสม (แต่ A อาจส่งไปแล้ว)

ตัวอย่าง: ในเคอร์เนล addint ระบบจะเริ่มต้นรายการข้อมูลตัวสะสมเป็น 0 ฟังก์ชันตัวรวมสำหรับเคอร์เนลนี้จะทำการบวก โดยที่ 0 คือค่าเอกลักษณ์สำหรับการบวก

ตัวอย่าง: ในเคอร์เนล findMinAndMax ระบบจะเริ่มต้นรายการข้อมูลตัวสะสมเป็น INITVAL

  • fMMCombiner(&A, &I) ทำให้ A เหมือนเดิม เนื่องจาก I คือ INITVAL
  • fMMCombiner(&I, &A) ตั้งค่า I เป็น A เนื่องจาก I เป็น INITVAL

ดังนั้น INITVAL จึงเป็นค่าเอกลักษณ์จริงๆ

ฟังก์ชัน Combiner ต้องเป็นแบบสลับที่ได้ กล่าวคือ หาก A และ B เป็นรายการข้อมูล Accumulator ที่เริ่มต้นโดยฟังก์ชัน Initializer และอาจส่งไปยังฟังก์ชัน Accumulator 0 ครั้งขึ้นไป combinerName(&A, &B) ต้องตั้งค่า A เป็นค่าเดียวกันกับที่ combinerName(&B, &A) ตั้งค่า B

ตัวอย่าง: ในเคอร์เนล addint ฟังก์ชัน Combiner จะเพิ่มค่ารายการข้อมูลตัวสะสม 2 ค่า โดยการบวกมีคุณสมบัติสลับที่

ตัวอย่าง: ในเคอร์เนล findMinAndMax fMMCombiner(&A, &B) จะเหมือนกับ A = minmax(A, B) และ minmax เป็นแบบสลับที่ได้ ดังนั้น fMMCombiner จึงเป็นแบบสลับที่ได้เช่นกัน

ฟังก์ชัน Combiner ต้องเป็นแบบเชื่อมโยง กล่าวคือ หาก A, B และ C เป็นรายการข้อมูล Accumulator ที่เริ่มต้นโดยฟังก์ชัน Initializer และอาจส่งไปยังฟังก์ชัน Accumulator 0 ครั้งขึ้นไป ลำดับโค้ด 2 ลำดับต่อไปนี้ต้องตั้งค่า A เป็นค่าเดียวกัน

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

ตัวอย่าง: ในเคอร์เนล addint ฟังก์ชัน Combiner จะเพิ่มค่ารายการข้อมูล Accumulator 2 ค่า ดังนี้

  • 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

การบวกเป็นแบบเปลี่ยนหมู่ได้ ดังนั้นฟังก์ชัน Combiner จึงเป็นแบบเปลี่ยนหมู่ได้เช่นกัน

ตัวอย่าง: ในเคอร์เนล findMinAndMax

fMMCombiner(&A, &B)
เหมือนกับ
A = minmax(A, B)
ดังนั้นลำดับทั้ง 2 จึงเป็นดังนี้
  • 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 จึงเป็นแบบเปลี่ยนหมู่ด้วย

ฟังก์ชัน Accumulator และฟังก์ชัน Combiner ต้องเป็นไปตามกฎการพับพื้นฐาน กล่าวคือ หาก A และ B เป็นรายการข้อมูล Accumulator โดย A ได้รับการเริ่มต้นโดยฟังก์ชัน Initializer และอาจส่งไปยังฟังก์ชัน Accumulator 0 ครั้งขึ้นไป ส่วน B ยังไม่ได้รับการเริ่มต้น และ args เป็นรายการอาร์กิวเมนต์อินพุตและอาร์กิวเมนต์พิเศษสำหรับการเรียกใช้ฟังก์ชัน Accumulator โดยเฉพาะ ลำดับโค้ด 2 รายการต่อไปนี้ต้องตั้งค่า 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 มี 3 วิธีที่แสดงในคลาส 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);

ตัวอย่างการเรียกใช้เคอร์เนล addint มีดังนี้

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 อินพุต 1 รายการสำหรับอาร์กิวเมนต์อินพุตทุกรายการในฟังก์ชันตัวสะสมของเคอร์เนล รันไทม์ RenderScript จะตรวจสอบว่าการจัดสรรอินพุตทั้งหมดมีมิติข้อมูลเดียวกัน และประเภท Element ของการจัดสรรอินพุตแต่ละรายการตรงกับอาร์กิวเมนต์อินพุตที่เกี่ยวข้องของต้นแบบฟังก์ชัน Accumulator หากการตรวจสอบเหล่านี้ไม่สำเร็จ RenderScript จะแสดงข้อยกเว้น เคอร์เนลจะดำเนินการกับพิกัดทั้งหมดในมิติข้อมูลเหล่านั้น

เมธอด 2 เหมือนกับเมธอด 1 ยกเว้นว่าเมธอด 2 จะใช้อาร์กิวเมนต์ sc เพิ่มเติมที่ใช้เพื่อจำกัดการดำเนินการเคอร์เนลให้เป็นชุดย่อยของพิกัดได้

เมธอด 3 เหมือนกับเมธอด 1 ยกเว้นว่าจะรับอินพุตอาร์เรย์ Java แทนที่จะรับอินพุตการจัดสรร ซึ่งช่วยให้คุณไม่ต้องเขียนโค้ดเพื่อสร้างการจัดสรรและคัดลอกข้อมูลจากอาร์เรย์ Java อย่างชัดเจน อย่างไรก็ตาม การใช้วิธีที่ 3 แทนวิธีที่ 1 ไม่ได้เพิ่มประสิทธิภาพของโค้ด สำหรับอาร์เรย์อินพุตแต่ละรายการ วิธีที่ 3 จะสร้างการจัดสรร 1 มิติชั่วคราวที่มีประเภท Element ที่เหมาะสมและเปิดใช้ setAutoPadding(boolean) และคัดลอกอาร์เรย์ไปยังการจัดสรรราวกับว่าใช้วิธี copyFrom() ที่เหมาะสมของ Allocation จากนั้นจะเรียกใช้เมธอด 1 โดยส่งการจัดสรรชั่วคราวเหล่านั้น

หมายเหตุ: หากแอปพลิเคชันของคุณจะทำการเรียกใช้เคอร์เนลหลายครั้งด้วยอาร์เรย์เดียวกัน หรือด้วยอาร์เรย์ที่แตกต่างกันซึ่งมีมิติข้อมูลและประเภทองค์ประกอบเดียวกัน คุณอาจปรับปรุงประสิทธิภาพได้โดยการสร้าง เติมข้อมูล และนำการจัดสรรมาใช้ซ้ำอย่างชัดเจนด้วยตนเอง แทนที่จะใช้วิธีที่ 3

javaFutureType ซึ่งเป็นประเภทการแสดงผลของเมธอดการลดที่สะท้อนเป็นคลาสที่ซ้อนกันแบบคงที่ที่สะท้อนภายในคลาส ScriptC_filename ซึ่งแสดงผลลัพธ์ในอนาคตของการเรียกใช้เคอร์เนลการลด หากต้องการดูผลลัพธ์จริงของการเรียกใช้ ให้เรียกใช้เมธอด 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 ของฟังก์ชัน outconverter javaResultType เป็นประเภท Java ที่สอดคล้องกันโดยตรง เว้นแต่ resultType จะเป็นประเภทที่ไม่มีการรับรอง (สเกลาร์ เวกเตอร์ หรืออาร์เรย์) หาก 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[] javaResultType ไม่สามารถแสดงค่าบางค่าของ resultType ได้

javaFutureType คือประเภทผลลัพธ์ในอนาคตที่สอดคล้องกับ resultType ของฟังก์ชัน outconverter

  • หาก 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 ของอาร์กิวเมนต์ที่สอดคล้องกันของฟังก์ชัน Accumulator เว้นแต่ inXType จะเป็นประเภทที่ไม่มีเครื่องหมายหรือประเภทเวกเตอร์ devecSiInXType จะเป็นประเภท Java ที่สอดคล้องกันโดยตรง หาก inXType เป็นประเภทสเกลาร์ที่ไม่มีเครื่องหมาย devecSiInXType จะเป็นประเภท Java ที่สอดคล้องกับประเภทสเกลาร์ที่มีเครื่องหมายขนาดเดียวกันโดยตรง หาก inXType เป็นประเภทเวกเตอร์ที่มีเครื่องหมาย devecSiInXType จะเป็นประเภท Java ที่สอดคล้องกับประเภทคอมโพเนนต์เวกเตอร์โดยตรง หาก inXType เป็นประเภทเวกเตอร์ที่ไม่มีเครื่องหมาย devecSiInXType จะเป็นประเภท Java ที่สอดคล้องกับประเภทสเกลาร์ที่มีเครื่องหมายซึ่งมีขนาดเดียวกับประเภทคอมโพเนนต์เวกเตอร์โดยตรง เช่น

  • หาก inXType เป็น int แล้ว devecSiInXType จะเป็น int
  • หาก inXType เป็น int2 แล้ว devecSiInXType จะเป็น int อาร์เรย์เป็นการแสดงผลแบบแบนราบ โดยมีองค์ประกอบสเกลาร์เป็น 2 เท่าขององค์ประกอบเวกเตอร์ 2 คอมโพเนนต์ในการจัดสรร ซึ่งเป็นวิธีเดียวกับที่เมธอด copyFrom() ของ Allocation ทำงาน
  • หาก inXType เป็น uint แล้ว deviceSiInXType จะเป็น int ค่าที่มีเครื่องหมายในอาร์เรย์ Java จะได้รับการตีความว่าเป็นค่าที่ไม่มีเครื่องหมายของรูปแบบบิตเดียวกันในการจัดสรร ซึ่งเป็นวิธีเดียวกับที่เมธอด copyFrom() ของ Allocation ทำงาน
  • หาก inXType เป็น uint2 แล้ว deviceSiInXType จะเป็น int ซึ่งเป็นการรวมวิธีจัดการ int2 และ uint เข้าด้วยกัน โดยอาร์เรย์จะเป็นการแสดงผลแบบแบน และค่าที่มีเครื่องหมายของอาร์เรย์ Java จะได้รับการตีความว่าเป็นค่า Element ที่ไม่มีเครื่องหมายของ 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 และ Hello Compute จะสาธิตการใช้ API ที่ครอบคลุมในหน้านี้เพิ่มเติม