ภาพรวม RenderScript

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • #pragma rs_fp_full (ค่าเริ่มต้นหากไม่ได้ระบุอะไร): สำหรับแอปที่ต้องใช้ ความแม่นยำของเลขทศนิยมตามที่ระบุไว้ในมาตรฐาน IEEE 754-2008
  • #pragma rs_fp_relaxed: สำหรับแอปที่ไม่จำเป็นต้องปฏิบัติตามมาตรฐาน IEEE 754-2008 อย่างเคร่งครัดและยอมรับความแม่นยำที่น้อยลงได้ โหมดนี้จะเปิดใช้การล้างเป็น 0 สำหรับตัวเลขที่ผิดปกติและ การปัดเศษเข้าหา 0
  • #pragma rs_fp_imprecise: สำหรับแอปที่ไม่มีข้อกำหนดด้านความแม่นยำที่เข้มงวด โหมดนี้จะเปิดใช้ทุกอย่างใน rs_fp_relaxed พร้อมกับ รายการต่อไปนี้
    • การดำเนินการที่ทำให้ได้ -0.0 อาจแสดงผลเป็น +0.0 แทน
    • การดำเนินการกับ INF และ NAN ไม่ได้กำหนดไว้

แอปพลิเคชันส่วนใหญ่ใช้ rs_fp_relaxed ได้โดยไม่มีผลข้างเคียง ซึ่งอาจเป็นประโยชน์อย่างมากในสถาปัตยกรรมบางอย่างเนื่องจากการเพิ่มประสิทธิภาพเพิ่มเติมที่ใช้ได้เฉพาะกับความแม่นยำที่ผ่อนคลาย (เช่น คำสั่ง CPU ของ SIMD)

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

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

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

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

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

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

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

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

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

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

หากต้องการใช้ RenderScript API ของไลบรารีการสนับสนุน ให้ทำดังนี้

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

      Groovy

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

      Kotlin

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

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

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

    Kotlin

    import android.support.v8.renderscript.*

    Java

    import android.support.v8.renderscript.*;

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

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

  1. เริ่มต้นบริบท RenderScript RenderScript Context ที่สร้างด้วย 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 ที่ใช้สร้างอินสแตนซ์ของสคริปต์จะเป็นดังนี้

      Kotlin

      val invert = ScriptC_invert(renderScript)

      Java

      ScriptC_invert invert = new ScriptC_invert(renderScript);
    • ScriptIntrinsic: เป็นเคอร์เนล RenderScript ในตัวสำหรับการดำเนินการทั่วไป เช่น การเบลอแบบ Gaussian, คอนโวลูชัน และการผสมรูปภาพ ดูข้อมูลเพิ่มเติมได้ที่คลาสย่อยของ ScriptIntrinsic
  4. ป้อนข้อมูลลงในการจัดสรร ยกเว้นการจัดสรรที่สร้างด้วย createFromBitmap() ระบบจะป้อนข้อมูลว่างในการจัดสรรเมื่อสร้างเป็นครั้งแรก หากต้องการป้อนข้อมูลการจัดสรร ให้ใช้วิธี "คัดลอก" วิธีใดวิธีหนึ่งใน Allocation เมธอด "คัดลอก" เป็นแบบซิงโครนัส
  5. ตั้งค่าตัวแปรส่วนกลางของสคริปต์ที่จำเป็น คุณอาจตั้งค่าส่วนกลางโดยใช้วิธีการใน ScriptC_filename คลาสเดียวกันที่ชื่อ set_globalname ตัวอย่างเช่น หากต้องการตั้งค่าตัวแปร int ที่ชื่อ threshold ให้ใช้วิธี Java set_threshold(int) และหากต้องการตั้งค่าตัวแปร rs_allocation ที่ชื่อ lookup ให้ใช้วิธี Java set_lookup(Allocation) เมธอด set เป็นแบบไม่พร้อมกัน
  6. เปิดใช้เคอร์เนลและฟังก์ชันที่เรียกใช้ได้ที่เหมาะสม

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// File: singlesource.rs

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

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

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

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

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

คุณเรียกใช้ฟังก์ชัน process() จาก Java หรือ Kotlin ได้ดังนี้

Kotlin

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

Java

// File SingleSource.java

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

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

ตัวแปรส่วนกลางของสคริปต์

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

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

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

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

Reduction Kernels in Depth

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

สิ่งที่คุณไม่ควรสมมติ

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

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

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

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

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

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

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

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

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

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

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

fMMCombiner(&A, &B)
เหมือนกับ
A = minmax(A, B)
ดังนั้นลำดับทั้ง 2 จึงเป็น
  • A = minmax(A, B)
    A = minmax(A, C)
    // Same as
    //   A = minmax(minmax(A, B), C)
  • B = minmax(B, C)
    A = minmax(A, B)
    // Same as
    //   A = minmax(A, minmax(B, C))
    //   B = minmax(B, C)

minmax เป็นการเชื่อมโยง ดังนั้น fMMCombiner จึงเป็นเช่นนั้นด้วย

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

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

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

เช่น

Kotlin

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

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

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

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

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

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

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

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

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

Java

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

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

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

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

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

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

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

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

หาก javaResultType เป็นประเภทออบเจ็กต์ (รวมถึงประเภทอาร์เรย์) การเรียกใช้ javaFutureType.get() ในอินสแตนซ์เดียวกันแต่ละครั้งจะแสดงผลออบเจ็กต์เดียวกัน

หาก javaResultType ไม่สามารถแสดงค่าทั้งหมดของประเภท resultType และเคอร์เนลการลดค่า สร้างค่าที่แสดงไม่ได้ javaFutureType.get() จะส่งข้อยกเว้น

วิธีที่ 3 และ devecSiInXType

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

  • หาก inXType เป็น int แสดงว่า devecSiInXType เป็น int
  • หาก inXType เป็น int2 แสดงว่า devecSiInXType เป็น int อาร์เรย์คือการแสดงผลแบบแบน ซึ่งมีองค์ประกอบสเกลาร์เป็น 2 เท่าขององค์ประกอบเวกเตอร์แบบ 2 องค์ประกอบในการจัดสรร ซึ่งเป็นวิธีเดียวกับที่copyFrom()ของAllocationทำงาน
  • หาก inXType เป็น uint แสดงว่า deviceSiInXType เป็น int ระบบจะตีความค่าที่มีการลงนามในอาร์เรย์ Java เป็นค่าที่ไม่มีการลงนามของ รูปแบบบิตเดียวกันในการจัดสรร ซึ่งเป็นวิธีเดียวกับที่เมธอด copyFrom() ของ Allocation ทำงาน
  • หาก inXType เป็น uint2 แสดงว่า deviceSiInXType เป็น int ซึ่งเป็นการรวมวิธีจัดการ int2 และ uint โดยอาร์เรย์คือการแสดงแบบแบน และค่าที่ลงนามของอาร์เรย์ Java จะ ตีความเป็นค่า Element ที่ไม่ได้ลงนามของ RenderScript

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

  • อินพุตเวกเตอร์ของสคริปต์จะได้รับการแปลงเป็นรูปแบบแบนในฝั่ง Java แต่ผลลัพธ์เวกเตอร์ของสคริปต์จะไม่ได้รับการแปลง
  • อินพุตที่ไม่ได้ลงนามของสคริปต์จะแสดงเป็นอินพุตที่ลงนามซึ่งมีขนาดเท่ากันในฝั่ง Java ในขณะที่ผลลัพธ์ที่ไม่ได้ลงนามของสคริปต์จะแสดงเป็นประเภทที่ลงนามแบบขยายในฝั่ง Java (ยกเว้นในกรณีของ ulong)

ตัวอย่างเคอร์เนลการลดเพิ่มเติม

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

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

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

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

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

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

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

#define BUCKETS 256
typedef uint32_t Histogram[BUCKETS];

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

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

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

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

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

ตัวอย่างโค้ดเพิ่มเติม

ตัวอย่าง BasicRenderScript RenderScriptIntrinsic และ Hello Compute แสดงให้เห็นถึงการใช้ API ที่กล่าวถึงในหน้านี้เพิ่มเติม