ภาพรวม RenderScript

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

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

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

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

      รองรับ Kernel การลดขนาดใน 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 อย่างเคร่งครัดและยอมรับความแม่นยําที่น้อยลงได้ โหมดนี้เปิดใช้การปัดเศษเป็น 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 ของ Support Library ส่วน 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

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

  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() การเปิดตัวเหล่านี้เป็นแบบอะซิงโครนัส วิธีการจะรับ 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 เมธอด "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 แบบแหล่งที่มาเดียว ซึ่งรวมถึงอีก 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 ดังนี้

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

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

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

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

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

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

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

หลังจากรวมรายการข้อมูลตัวสะสมทั้งหมดแล้ว 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) (ต้องระบุ): ระบุว่ากําลังกําหนดเคอร์เนลการลด เมธอด 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)

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

  • outconverter(outconverterName) (ไม่บังคับ): ระบุชื่อฟังก์ชันตัวแปลงเอาต์พุตสำหรับเคอร์เนลการลดขนาดนี้ หลังจาก 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;
    }

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

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

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

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

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

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

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

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

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

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

Kotlin

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

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

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

Java

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

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

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

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

Kotlin

val script = ScriptC_example(renderScript)

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

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

Java

ScriptC_example script = new ScriptC_example(renderScript);

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

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

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

เช่น

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