ภาพรวม RenderScript

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

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

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

การเขียนเคอร์เนล RenderScript

โดยปกติแล้วเคอร์เนล 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 ด้วยอาร์กิวเมนต์ที่กำหนดเอง วิธีเหล่านี้มักมีประโยชน์สำหรับการตั้งค่าเริ่มต้นหรือการคำนวณแบบอนุกรมภายในไปป์ไลน์การประมวลผลขนาดใหญ่
  • ตัวแปรส่วนกลางของสคริปต์อย่างน้อย 0 รายการ สคริปต์ส่วนกลางจะคล้ายกับตัวแปรส่วนกลางใน C คุณเข้าถึงส่วนกลางของสคริปต์ได้จากโค้ด Java ซึ่งมักใช้สำหรับการส่งพารามิเตอร์ไปยังเคอร์เนล RenderScript ดูคำอธิบายเกี่ยวกับตัวแปรส่วนกลางของสคริปต์อย่างละเอียดได้ที่นี่

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

    Mapping Kernel คือฟังก์ชันแบบขนานที่ทำงานกับคอลเล็กชัน Allocations ของมิติข้อมูลเดียวกัน โดยค่าเริ่มต้น ระบบจะเรียกใช้การดำเนินการนี้ 1 ครั้งสำหรับพิกัดทุกรายการในมิติข้อมูลเหล่านั้น โดยทั่วไปจะใช้ (แต่ไม่ใช่เฉพาะ) ในการแปลงคอลเล็กชันของอินพุต Allocations เป็นเอาต์พุต Allocation ครั้งละ 1 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 โดยมีการเรียกใช้ฟังก์ชันเคอร์เนล 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 อินพุตของมิติข้อมูลเดียวกัน โดยค่าเริ่มต้น ฟังก์ชันตัวสะสมจะทำงาน 1 ครั้งสําหรับพิกัดทุกรายการในมิติข้อมูลเหล่านั้น โดยปกติแล้ว (แต่ไม่ใช่เพียงอย่างเดียว) จะใช้เพื่อ "ลด" ชุดอินพุต Allocations ให้เป็นค่าเดี่ยว

    • ต่อไปนี้คือตัวอย่างของเคอร์เนลการลดแบบง่ายที่จะรวม 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 เสมอ และอาจมีฟังก์ชันอื่นๆ ด้วย ทั้งนี้ขึ้นอยู่กับสิ่งที่คุณต้องการให้กริดทํา

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

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

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

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

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

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

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

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

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

  • #pragma rs_fp_full (ค่าเริ่มต้นหากไม่ได้ระบุ): สําหรับแอปที่ต้องใช้ความแม่นยําของทศนิยมตามที่ระบุไว้ในมาตรฐาน IEEE 754-2008
  • #pragma rs_fp_relaxed: สำหรับแอปที่ไม่จำเป็นต้องปฏิบัติตามข้อกำหนด IEEE 754-2008 อย่างเคร่งครัดและใช้ความแม่นยำน้อยกว่า โหมดนี้เปิดใช้ Flush-to-0 สำหรับเดนอร์มและปัดเศษเป็น 0
  • #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 โดยใช้วิธีใดวิธีหนึ่งต่อไปนี้

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

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

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

การใช้ API ของไลบรารีการสนับสนุน RenderScript

หากต้องการใช้ Support Library RenderScript API คุณต้องกำหนดค่าสภาพแวดล้อมการพัฒนาเพื่อให้เข้าถึง 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 Manager

วิธีใช้ Support Library RenderScript API

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

    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. สร้างสคริปต์ที่จำเป็น สคริปต์ที่ใช้ได้เมื่อใช้ RenderScript มี 2 ประเภท ได้แก่
    • ScriptC: สคริปต์ที่ผู้ใช้กำหนดตามที่อธิบายไว้ในการเขียนเคอร์เนล 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: เหล่านี้คือ Kernel ของ RenderScript ในตัวสําหรับการดำเนินการทั่วไป เช่น การเบลอแบบ Gaussian การฟุ้ง และการผสมภาพ ดูข้อมูลเพิ่มเติมได้ที่คลาสย่อยของ ScriptIntrinsic
  4. ป้อนข้อมูลการจัดสรร ระบบจะเติมการจัดสรรด้วยข้อมูลที่ว่างเปล่าเมื่อสร้างครั้งแรก ยกเว้นการจัดสรรที่สร้างด้วย createFromBitmap() หากต้องการป้อนข้อมูลการจัดสรร ให้ใช้วิธีการ "copy" อย่างใดอย่างหนึ่งใน 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() เมธอด "คัดลอก" และ get() เป็นแบบซิงโครนัส
  8. เลิกใช้งานบริบท RenderScript คุณทำลายบริบท RenderScript ได้โดยใช้ destroy() หรือโดยการอนุญาตให้ระบบเก็บขยะออบเจ็กต์บริบท RenderScript ซึ่งจะทำให้การใช้ออบเจ็กต์ใดก็ตามที่เป็นของบริบทนั้นทำให้เกิดข้อยกเว้น

โมเดลการดำเนินการแบบไม่พร้อมกัน

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

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

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

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

Android 7.0 (API ระดับ 24) เปิดตัวฟีเจอร์การเขียนโปรแกรมใหม่ที่เรียกว่า Single-Source 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 มีตัวอย่างเคอร์เนลที่เปลี่ยนสีรูปภาพเป็นสีกลับ ตัวอย่างด้านล่างขยายการทำงานดังกล่าวเพื่อใช้เอฟเฟกต์มากกว่า 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 หากไม่มี Single-Source RenderScript คุณจะต้องเปิดใช้งานทั้ง 2 เมล็ดประมวลผลจากโค้ด Java ซึ่งจะแยกการเปิดใช้งานเมล็ดประมวลผลออกจากการกําหนดค่าเมล็ดประมวลผล และทําให้เข้าใจอัลกอริทึมทั้งหมดได้ยากขึ้น โค้ด RenderScript ที่มาแหล่งเดียวไม่เพียงอ่านง่ายขึ้นเท่านั้น แต่ยังช่วยลดการเปลี่ยนระหว่าง Java กับสคริปต์ในการเปิดใช้งานเคอร์เนลด้วย อัลกอริทึมแบบซ้ำบางรายการอาจเปิดใช้งานเคอร์เนลหลายร้อยครั้ง ทำให้การโอนดังกล่าวมีค่าใช้จ่ายเพิ่มเติมมาก

สคริปต์ส่วนกลาง

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

ตัวแปรส่วนกลางของสคริปต์หนึ่งๆ จะมี 2 ค่าแยกกัน ได้แก่ ค่า Java และค่า script ค่าเหล่านี้มีลักษณะการทำงานดังต่อไปนี้

  • หาก 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;
}

หมายเหตุ: ดูตัวอย่างเพิ่มเติมของ Kernel การลดขนาดได้ที่นี่

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

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

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

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

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

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

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

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

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

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

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

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

การเขียนเคอร์เนลการลด

#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) (ต้องระบุ): ระบุชื่อฟังก์ชันตัวสะสมสำหรับเคอร์เนลการลดนี้ เมื่อคุณเปิดใช้งานเคอร์เนล RenderScript จะเรียกใช้ฟังก์ชันนี้ 1 ครั้งสำหรับทุกๆ พิกัดในอินพุต เพื่ออัปเดตรายการข้อมูลสะสมในทางใดทางหนึ่งตามอินพุต ฟังก์ชันต้องได้รับการกําหนดดังนี้

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

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

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

  • combiner(combinerName)

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

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

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

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

    หากคุณไม่ได้ระบุฟังก์ชันตัวรวม RenderScript จะใช้ฟังก์ชันตัวสะสมแทน โดยทํางานราวกับว่ามีฟังก์ชันตัวรวมดังต่อไปนี้

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

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

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

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

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

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

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

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

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

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

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

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

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

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

สิ่งที่คุณต้องรับประกัน

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

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

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

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

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

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

ดังนั้น INITVAL จึงเป็นค่าระบุตัวตน

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

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

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

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

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

ตัวอย่าง: ในเคอร์เนล addint ฟังก์ชันคอมไบนเนอร์จะเพิ่มค่ารายการข้อมูลตัวสะสม 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

การดำเนินการบวกเป็นแบบเชื่อมโยงกัน ดังนั้นฟังก์ชันการรวมก็เป็นแบบเชื่อมโยงกันด้วย

ตัวอย่าง: ในเคอร์เนล 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 และฟังก์ชันคอมไพเลอร์ร่วมกันต้องเป็นไปตามกฎการพับพื้นฐาน กล่าวคือ หาก A และ B เป็นรายการข้อมูลตัวสะสม A ได้รับการเริ่มต้นโดยฟังก์ชันตัวเริ่มต้น และอาจส่งไปยังฟังก์ชันตัวสะสมตั้งแต่ 0 ครั้งขึ้นไป B ยังไม่ได้รับการเริ่มต้น และ args คือรายการอาร์กิวเมนต์อินพุตและอาร์กิวเมนต์พิเศษสำหรับการเรียกฟังก์ชันตัวสะสมหนึ่งๆ เฉพาะ ลำดับโค้ด 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 รายการสำหรับอาร์กิวเมนต์อินพุตทุกรายการในฟังก์ชัน accumulator ของเคอร์เนล รันไทม์ของ RenderScript ตรวจสอบเพื่อให้แน่ใจว่าการจัดสรรอินพุตทั้งหมดมีมิติข้อมูลเดียวกัน และประเภท Element ของการจัดสรรอินพุตแต่ละรายการตรงกับอาร์กิวเมนต์อินพุตที่เกี่ยวข้องของต้นแบบของฟังก์ชัน accumulator หากการตรวจสอบเหล่านี้ล้มเหลว RenderScript จะส่งข้อยกเว้น โดยการประมวลผลของเคอร์เนลจะดำเนินการกับพิกัดทุกรายการในมิติข้อมูลเหล่านั้น

เมธอด 2 เหมือนกับเมธอด 1 ยกเว้นว่าเมธอด 2 จะใช้อาร์กิวเมนต์ sc เพิ่มเติม ซึ่งสามารถใช้เพื่อจำกัดการเรียกใช้เคอร์เนลให้อยู่เฉพาะบางส่วนของพิกัด

วิธีที่ 3 เหมือนกับวิธีที่ 1 ยกเว้นที่จะใช้อินพุตอาร์เรย์ Java แทนที่จะใช้อินพุตการจัดสรร ซึ่งจะช่วยลดความยุ่งยากในการเขียนโค้ดเพื่อสร้าง Allocation และคัดลอกข้อมูลไปยัง Allocation จากอาร์เรย์ 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[] มีบางค่าของ resultType ที่ javaResultType แสดงไม่ได้

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

  • หาก 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 จะเป็นประเภท 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 จะตีความเป็นค่าองค์ประกอบที่ไม่มีเครื่องหมายของ 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 ที่ครอบคลุมในหน้านี้