ภาพรวม RenderScript

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

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

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

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

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

  • การประกาศ 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 ประเภท ได้แก่ การแมปเคอร์เนล (หรือที่เรียกว่า foreach เคอร์เนล) และการลดเคอร์เนล

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

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

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

      ในทุกๆ ด้าน ค่านี้เหมือนกับ C มาตรฐาน พร็อพเพอร์ตี้ RS_KERNEL ที่นําไปใช้กับโปรโตไทป์ฟังก์ชันจะระบุว่าฟังก์ชันนั้นเป็นเคอร์เนลการแมปของ RenderScript แทนที่จะเป็นฟังก์ชันที่เรียกใช้ได้ ระบบจะเติมอาร์กิวเมนต์ in โดยอัตโนมัติตาม อินพุต Allocation ที่ส่งไปยังการเปิดเคอร์เนล อาร์กิวเมนต์ x และ y จะอธิบายไว้ด้านล่าง ค่าที่แสดงผลจากเคอร์เนลคือ เขียนไปยังตำแหน่งที่เหมาะสมในเอาต์พุต Allocation โดยอัตโนมัติ โดยค่าเริ่มต้น เคอร์เนลนี้จะทำงานกับอินพุตทั้งหมด Allocation โดยมีการเรียกใช้ฟังก์ชันเคอร์เนล 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 เสมอ และอาจมีฟังก์ชันอื่นๆ ด้วย ทั้งนี้ขึ้นอยู่กับสิ่งที่คุณต้องการให้กริดทํา

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

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

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

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

    ฟังก์ชัน Kernel สำหรับการแมปหรือฟังก์ชันตัวสะสม Kernel ของการลดอาจเข้าถึงพิกัดของการดำเนินการปัจจุบันได้โดยใช้อาร์กิวเมนต์พิเศษ 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 สำหรับเดนอร์มและ โดยปัดเศษเป็นศูนย์
  • #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) ขึ้นไปได้

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

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

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

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

  • เครื่องมือ Android SDK เวอร์ชัน 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 ต่อไปนี้ลงในไฟล์
      ดึงดูดKotlin
              android {
                  compileSdkVersion 33
      
                  defaultConfig {
                      minSdkVersion 9
                      targetSdkVersion 19
      
                      renderscriptTargetApi 18
                      renderscriptSupportModeEnabled true
                  }
              }
              
              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 ให้เพิ่มการนําเข้าสำหรับคลาสในคลังสนับสนุน ดังนี้
    KotlinJava
    import android.support.v8.renderscript.*
    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 ข้างต้น ทุกสคริปต์มีคลาส Java แสดงโดยคอมไพเลอร์ RenderScript เพื่อให้เข้าถึงสคริปต์จากโค้ด Java ได้ง่าย ชั้นเรียนนี้มีชื่อ ScriptC_filename ตัวอย่างเช่น ถ้าเคอร์เนลการแมป ข้างต้นอยู่ใน invert.rs และมีบริบท RenderScript อยู่แล้ว mRenderScript โค้ด Java หรือ Kotlin ที่จะสร้างอินสแตนซ์สคริปต์จะมีลักษณะดังนี้
      KotlinJava
      val invert = ScriptC_invert(renderScript)
      ScriptC_invert invert = new ScriptC_invert(renderScript);
    • ScriptIntrinsic: นี่คือเคอร์เนล RenderScript ในตัวสำหรับการทำงานทั่วไป เช่น การเบลอแบบเกาส์เชียน (Gaussian Blur) คอนโวลูชัน (Convolution) และการผสมรูปภาพ ดูข้อมูลเพิ่มเติมได้ที่คลาสย่อยของ ScriptIntrinsic
  4. ป้อนข้อมูลการจัดสรร ยกเว้นการแบ่งสรรที่สร้างด้วย createFromBitmap() ระบบจะป้อนข้อมูลว่างเมื่อสร้างการแบ่งสรรเป็นครั้งแรก หากต้องการป้อนข้อมูลการจัดสรร ให้ใช้วิธีการ "copy" อย่างใดอย่างหนึ่งใน Allocation "สำเนา" เมธอดเป็นซิงโครนัส
  5. ตั้งค่าตัวแปรส่วนกลางของสคริปต์ที่จำเป็น คุณสามารถตั้งค่าตัวแปรส่วนกลางได้โดยใช้เมธอดในScriptC_filenameคลาสเดียวกันที่มีชื่อว่า set_globalname สำหรับ เช่น หากต้องการตั้งค่าตัวแปร int ที่ชื่อ threshold ให้ใช้เมธอด เมธอด Java set_threshold(int); และหากต้องการตั้งค่า ตัวแปร rs_allocation ชื่อ lookup ให้ใช้ Java วิธีการset_lookup(Allocation) เมธอด set เป็นแบบอะซิงโครนัส
  6. เปิดใช้งานเคอร์เนลและฟังก์ชันที่เรียกใช้ได้

    วิธีเปิดใช้งานเคอร์เนลที่กำหนดมีดังนี้ แสดงในคลาส ScriptC_filename เดียวกันด้วยเมธอดชื่อ forEach_mappingKernelName() หรือ reduce_reductionKernelName() การเปิดตัวเหล่านี้เป็นแบบไม่พร้อมกัน วิธีการจะรับ Allocation อย่างน้อย 1 รายการ โดย Allocation ทั้งหมดต้องมีมิติข้อมูลเดียวกัน ทั้งนี้ขึ้นอยู่กับอาร์กิวเมนต์ที่ส่งไปยังเคอร์เนล โดยค่าเริ่มต้น แอตทริบิวต์ เคอร์เนลจะทำงานในทุกพิกัดในขนาดเหล่านั้น เพื่อเรียกใช้เคอร์เนลบนเซ็ตย่อยของพิกัดเหล่านั้น ส่ง Script.LaunchOptions ที่เหมาะสมเป็นอาร์กิวเมนต์สุดท้ายไปยังเมธอด forEach หรือ reduce

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

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

รูปแบบการดําเนินการแบบอะซิงโครนัส

forEach, invoke, reduce ที่แสดง และ set เป็นเมธอดแบบไม่พร้อมกัน ซึ่งแต่ละเมธอดอาจกลับไปยัง Java ก่อนที่จะดำเนินการตาม การดำเนินการที่ขอ อย่างไรก็ตาม การดำเนินการแต่ละรายการจะเรียงลำดับตามลำดับที่มีการเรียกใช้

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

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

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

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

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

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

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

KotlinJava
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)
// 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;
}

หมายเหตุ: รวมถึงตัวอย่างการลดเสียง เคอร์เนลที่นี่

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

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

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

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

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

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

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

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

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

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

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

    static void initializerName(accumType *accum) {  }

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

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

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

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

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

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

  • combiner(combinerName)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ตัวอย่าง: ใน findMinAndMax เคอร์เนล รายการข้อมูลสะสมเริ่มต้นแล้ว ถึง INITVAL

  • fMMCombiner(&A, &I) ออกจาก A เหมือนเดิม เนื่องจากIคือINITVAL
  • fMMCombiner(&I, &A) เซ็ต I เป็น A เนื่องจาก I มีค่าเป็น INITVAL

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

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

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

ตัวอย่าง: ในเคอร์เนล 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 ก็เป็นฟังก์ชันการรวมด้วย

ฟังก์ชันตัวสะสมและฟังก์ชันตัวรวมต้องเป็นไปตามกฎการรวมพื้นฐาน กล่าวคือ หาก 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:

KotlinJava
// 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
// 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

KotlinJava
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()
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 ของเคอร์เนล Functions รันไทม์ของ 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() เป็นแบบเรียลไทม์

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

javaResultType ที่กำหนดจาก resultType ของ ฟังก์ชันเอาต์แปลง เว้นแต่ resultType จะเป็น ประเภทที่ไม่มีการรับรอง (สเกลาร์ เวกเตอร์ หรืออาร์เรย์) javaResultType คือการจับคู่โดยตรง ประเภท Java หาก resultType เป็นประเภทที่ไม่มีค่าลงนามและมีประเภท Java ที่มีค่าลงนามขนาดใหญ่กว่า javaResultType จะเป็นประเภท Java ที่มีค่าลงนามขนาดใหญ่กว่านั้น มิเช่นนั้นจะเป็นประเภท Java ที่เกี่ยวข้องโดยตรง เช่น

  • หาก resultType คือ int, int2 หรือ int[15] แล้ว javaResultType คือ int, Int2 หรือ int[] ค่าทั้งหมดของ resultType แสดงได้ด้วย javaResultType
  • หาก resultType เป็น uint, uint2 หรือ uint[15] javaResultType จะเป็น long, Long2 หรือ long[] ค่าทั้งหมดของ resultType แสดงได้ด้วย javaResultType
  • หาก resultType คือ ulong, ulong2 หรือ ulong[15] แล้วกด javaResultType คือ long, Long2 หรือ long[] มีค่าบางอย่าง ของ resultType ที่แสดงด้วย javaResultType ไม่ได้

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

  • หาก resultType ไม่ใช่ประเภทอาร์เรย์ javaFutureType จะเท่ากับ result_resultType
  • หาก resultType เป็นอาร์เรย์ของความยาว Count ที่มีสมาชิกประเภท memberType javaFutureType จะเป็น resultArrayCount_memberType

เช่น

KotlinJava
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> =     }
}
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 อาร์เรย์เป็นการนำเสนอแบบ flatten ซึ่งมีมากกว่า 2 เท่า องค์ประกอบสเกลาร์จำนวนมากเนื่องจากการจัดสรรมีเวกเตอร์ 2 องค์ประกอบ องค์ประกอบ ซึ่งทำงานในลักษณะเดียวกับเมธอด copyFrom() ของ Allocation
  • หาก inXType เป็น uint แสดงว่า deviceSiInXType มีค่าเป็น int ระบบจะตีความค่าที่มีเครื่องหมายในอาร์เรย์ Java เป็นค่าที่ไม่มีเครื่องหมายของรูปแบบบิตเดียวกันในการแบ่งสรร ซึ่งจะเป็นวิธีเดียวกับที่ copyFrom() วิธีการทำงานAllocation
  • หาก inXType เป็น uint2 ให้ใช้ deviceSiInXType มีค่า int วิธีนี้เป็นการผสมผสานระหว่าง int2 กับ uint มีการจัดการ: อาร์เรย์เป็นการแสดงค่าเดี่ยว และค่าที่มีการรับรองของอาร์เรย์ Java คือ ถูกตีความเป็นค่า Element ที่ไม่มีการลงชื่อใน RenderScript

โปรดทราบว่าสำหรับวิธีที่ 3 ระบบจะจัดการประเภทอินพุตแตกต่างจากประเภทผลลัพธ์ ดังนี้

  • อินพุตเวกเตอร์ของสคริปต์จะได้รับการแปลงเป็น 2 มิติในฝั่ง 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 ที่กล่าวถึงในหน้านี้