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 รายการ เอาต์พุตAllocation1 รายการ หรือทั้ง 2 อย่าง รันไทม์ RenderScript จะตรวจสอบเพื่อให้แน่ใจว่า Allocation ของอินพุตและเอาต์พุตทั้งหมดมีมิติข้อมูลเดียวกัน และElementประเภทของ Allocation ของอินพุตและเอาต์พุตตรงกับต้นแบบของเคอร์เนล หากการตรวจสอบใดตรวจสอบหนึ่งล้มเหลว 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ในตัวอย่างนี้) รวมถึงชื่อและบทบาทของฟังก์ชันที่ประกอบกันเป็น เคอร์เนล (ฟังก์ชันaccumulatoraddintAccumในตัวอย่างนี้) ฟังก์ชันดังกล่าวทั้งหมดต้องเป็น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 ของไลบรารีการสนับสนุน ให้ทำดังนี้
- ตรวจสอบว่าคุณได้ติดตั้ง Android SDK เวอร์ชันที่จำเป็นแล้ว
- อัปเดตการตั้งค่าสำหรับกระบวนการบิลด์ 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- ระบุว่าไบต์โค้ดที่สร้างขึ้น ควรย้อนกลับไปใช้เวอร์ชันที่เข้ากันได้หากอุปกรณ์ที่เรียกใช้ ไม่รองรับเวอร์ชันเป้าหมาย
- เปิดไฟล์
- ในคลาสแอปพลิเคชันที่ใช้ 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 แอปพลิเคชันส่วนใหญ่
มีรูปแบบการใช้งานพื้นฐานเดียวกัน ดังนี้
- เริ่มต้นบริบท RenderScript
RenderScriptContext ที่สร้างด้วยcreate(Context)ช่วยให้มั่นใจได้ว่า RenderScript สามารถใช้งานได้ และมีออบเจ็กต์เพื่อควบคุมอายุการใช้งานของออบเจ็กต์ RenderScript ทั้งหมดในภายหลัง คุณควรพิจารณาว่าการสร้างบริบทอาจเป็นการดำเนินการที่ใช้เวลานาน เนื่องจากอาจสร้างทรัพยากรในฮาร์ดแวร์ส่วนต่างๆ และไม่ควรอยู่ในเส้นทางวิกฤตของแอปพลิเคชันหากเป็นไปได้ โดยปกติแล้ว แอปพลิเคชันจะมีบริบท RenderScript เพียงบริบทเดียวในแต่ละครั้ง - สร้าง
Allocationอย่างน้อย 1 รายการเพื่อส่งไปยังสคริปต์Allocationคือออบเจ็กต์ RenderScript ที่มี พื้นที่เก็บข้อมูลสำหรับข้อมูลจํานวนหนึ่ง เคอร์เนลในสคริปต์จะใช้Allocationออบเจ็กต์เป็นอินพุตและเอาต์พุต และเข้าถึงออบเจ็กต์Allocationได้ในเคอร์เนลโดยใช้rsGetElementAt_type()และrsSetElementAt_type()เมื่อผูกเป็นตัวแปรส่วนกลางของสคริปต์ ออบเจ็กต์Allocationช่วยให้ส่งอาร์เรย์จากโค้ด Java ไปยังโค้ด RenderScript และในทางกลับกันได้ โดยปกติแล้วจะสร้างออบเจ็กต์Allocationโดยใช้createTyped()หรือcreateFromBitmap() - สร้างสคริปต์ที่จำเป็น สคริปต์มี 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
- ScriptC: สคริปต์ที่ผู้ใช้กำหนดเองตามที่อธิบายไว้ในการเขียนเคอร์เนล RenderScript ด้านบน ทุกสคริปต์จะมีคลาส Java
ที่คอมไพเลอร์ RenderScript แสดงเพื่อให้เข้าถึงสคริปต์จากโค้ด Java ได้ง่าย
คลาสนี้มีชื่อว่า
- ป้อนข้อมูลการจัดสรร ยกเว้นการจัดสรรที่สร้างด้วย
createFromBitmap()ระบบจะป้อนข้อมูลว่างในการจัดสรรเมื่อสร้างเป็นครั้งแรก หากต้องการป้อนข้อมูลการจัดสรร ให้ใช้วิธี "คัดลอก" วิธีใดวิธีหนึ่งในAllocationเมธอด "คัดลอก" เป็นแบบซิงโครนัส - ตั้งค่าตัวแปรส่วนกลางของสคริปต์ที่จำเป็น คุณอาจตั้งค่าส่วนกลางโดยใช้วิธีการใน
ScriptC_filenameคลาสเดียวกันที่ชื่อset_globalnameตัวอย่างเช่น หากต้องการตั้งค่าตัวแปรintที่ชื่อthresholdให้ใช้วิธี Javaset_threshold(int)และหากต้องการตั้งค่าตัวแปรrs_allocationที่ชื่อlookupให้ใช้วิธี Javaset_lookup(Allocation)เมธอดsetเป็นแบบไม่พร้อมกัน - เปิดใช้เคอร์เนลและฟังก์ชันที่เรียกใช้ได้ที่เหมาะสม
วิธีการเปิดใช้เคอร์เนลที่กำหนดจะแสดงในคลาส
ScriptC_filenameเดียวกันโดยมีวิธีการชื่อforEach_mappingKernelName()หรือreduce_reductionKernelName()การเปิดตัวเหล่านี้เป็นแบบอะซิงโครนัส เมธอดนี้จะใช้ Allocation อย่างน้อย 1 รายการ ซึ่งทั้งหมดต้องมีมิติข้อมูลเดียวกัน ทั้งนี้ขึ้นอยู่กับอาร์กิวเมนต์ที่ส่งไปยังเคอร์เนล โดยค่าเริ่มต้น เคอร์เนลจะดำเนินการกับทุกพิกัดในมิติข้อมูลเหล่านั้น หากต้องการดำเนินการกับเคอร์เนลในชุดย่อยของพิกัดเหล่านั้น ให้ส่งScript.LaunchOptionsที่เหมาะสมเป็นอาร์กิวเมนต์สุดท้ายไปยังเมธอดforEachหรือreduceเปิดใช้ฟังก์ชันที่เรียกใช้ได้โดยใช้เมธอด
invoke_functionNameที่แสดงในคลาสScriptC_filenameเดียวกัน การเปิดตัวเหล่านี้เป็นแบบอะซิงโครนัส - เรียกข้อมูลจากออบเจ็กต์
Allocationและออบเจ็กต์ javaFutureType หากต้องการ เข้าถึงข้อมูลจากAllocationจากโค้ด Java คุณต้องคัดลอกข้อมูลนั้น กลับไปยัง Java โดยใช้วิธี "คัดลอก" วิธีใดวิธีหนึ่งในAllocationหากต้องการรับผลลัพธ์ของเคอร์เนลการลด คุณต้องใช้วิธีjavaFutureType.get()เมธอด "copy" และget()เป็นแบบซิงโครนัส - ล้างบริบท RenderScript คุณสามารถทำลายบริบท RenderScript
ด้วย
destroy()หรือโดยอนุญาตให้ระบบรวบรวมออบเจ็กต์บริบท RenderScript ซึ่งจะทำให้การใช้ออบเจ็กต์ใดๆ ที่เป็นของบริบทนั้นต่อไป จะทำให้เกิดข้อยกเว้น
รูปแบบการดำเนินการแบบอะซิงโครนัส
เมธอด forEach, invoke, reduce และ set ที่สะท้อนเป็นแบบไม่พร้อมกัน ซึ่งแต่ละเมธอดอาจกลับไปที่ Java ก่อนที่จะดำเนินการที่ขอให้เสร็จสมบูรณ์ อย่างไรก็ตาม การดำเนินการแต่ละอย่างจะเรียงตามลำดับที่เปิดใช้
Allocation คลาสมีเมธอด "copy" เพื่อคัดลอกข้อมูลไปยัง
และการจัดสรร เมธอด "คัดลอก" เป็นแบบซิงโครนัสและจะได้รับการซีเรียลไลซ์เมื่อเทียบกับการดำเนินการแบบไม่พร้อมกันข้างต้นที่แตะการจัดสรรเดียวกัน
คลาส javaFutureType ที่สะท้อนจะให้
เมธอด get() เพื่อรับผลลัพธ์ของการลด get() เป็นแบบซิงโครนัสและมีการทำให้เป็นอนุกรมเมื่อเทียบกับการลด (ซึ่งเป็นแบบอะซิงโครนัส)
RenderScript แบบแหล่งเดียว
Android 7.0 (API ระดับ 24) มีฟีเจอร์การเขียนโปรแกรมใหม่ที่เรียกว่า Single-Source
RenderScript ซึ่งเคอร์เนลจะเปิดจากสคริปต์ที่กำหนดไว้ ไม่ใช่จาก Java ปัจจุบันวิธีนี้จำกัดไว้สำหรับการแมปเคอร์เนล ซึ่งในส่วนนี้จะเรียกว่า "เคอร์เนล"
เพื่อความกระชับ ฟีเจอร์ใหม่นี้ยังรองรับการสร้างการจัดสรรประเภท
rs_allocation จากภายในสคริปต์ด้วย ตอนนี้คุณสามารถ
ใช้ทั้งอัลกอริทึมภายในสคริปต์เพียงอย่างเดียวได้แล้ว แม้ว่าจะต้องเปิดใช้เคอร์เนลหลายครั้งก็ตาม
ข้อดีมี 2 ประการ ได้แก่ โค้ดที่อ่านง่ายขึ้นเนื่องจากจะใช้ภาษาเดียวในการติดตั้งใช้งานอัลกอริทึม และโค้ดที่อาจเร็วขึ้นเนื่องจากมีการเปลี่ยนระหว่าง Java กับ RenderScript น้อยลงในการเปิดตัวเคอร์เนลหลายรายการ
ใน RenderScript แบบแหล่งเดียว คุณจะเขียนเคอร์เนลตามที่อธิบายไว้ใน
การเขียนเคอร์เนล RenderScript จากนั้นคุณจะเขียนฟังก์ชันที่เรียกใช้ได้ซึ่งเรียก
rsForEach() เพื่อเปิดใช้ API ดังกล่าวใช้ฟังก์ชันเคอร์เนลเป็นพารามิเตอร์แรก ตามด้วยการจัดสรรอินพุตและเอาต์พุต API ที่คล้ายกัน
rsForEachWithOptions() จะใช้อาร์กิวเมนต์เพิ่มเติมของประเภท
rs_script_call_t ซึ่งระบุชุดย่อยขององค์ประกอบจากการจัดสรรอินพุตและเอาต์พุตเพื่อให้ฟังก์ชันเคอร์เนลประมวลผล
หากต้องการเริ่มการคำนวณ RenderScript ให้เรียกใช้ฟังก์ชันที่เรียกใช้ได้จาก Java
ทำตามขั้นตอนในหัวข้อการใช้ RenderScript จากโค้ด Java
ในขั้นตอนเปิดตัวเคอร์เนลที่เหมาะสม ให้เรียกใช้
ฟังก์ชันที่เรียกใช้ได้โดยใช้ invoke_function_name() ซึ่งจะเริ่ม
การคำนวณทั้งหมด รวมถึงการเปิดตัวเคอร์เนล
โดยปกติแล้วจะต้องมีการจัดสรรเพื่อบันทึกและส่งต่อ
ผลลัพธ์ระดับกลางจากการเปิดตัวเคอร์เนลหนึ่งไปยังอีกเคอร์เนลหนึ่ง คุณสร้างได้โดยใช้
rsCreateAllocation() รูปแบบหนึ่งของ API ที่ใช้งานง่ายคือ
rsCreateAllocation_<T><W>(…) โดยที่ T คือประเภทข้อมูลสำหรับองค์ประกอบ และ W คือความกว้างของเวกเตอร์สำหรับองค์ประกอบ API จะใช้ขนาดใน
มิติข้อมูล X, Y และ Z เป็นอาร์กิวเมนต์ สำหรับการจัดสรร 1 มิติหรือ 2 มิติ คุณสามารถละเว้นขนาดสำหรับมิติข้อมูล Y หรือ Z ได้
เช่น rsCreateAllocation_uchar4(16384) จะสร้างการจัดสรร 1 มิติของ
องค์ประกอบ 16384 รายการ ซึ่งแต่ละรายการมีประเภทเป็น uchar4
ระบบจะจัดการการจัดสรรโดยอัตโนมัติ คุณ
ไม่จำเป็นต้องเผยแพร่หรือปล่อยให้ใช้งานได้อย่างอิสระ อย่างไรก็ตาม คุณสามารถเรียกใช้
rsClearObject(rs_allocation* alloc) เพื่อระบุว่าคุณไม่ต้องการแฮนเดิล
alloc สำหรับการจัดสรรพื้นฐานอีกต่อไป
เพื่อให้ระบบเพิ่มพื้นที่ว่างในทรัพยากรได้โดยเร็วที่สุด
ส่วนการเขียนเคอร์เนล RenderScript มีตัวอย่าง
เคอร์เนลที่กลับสีรูปภาพ ตัวอย่างด้านล่างจะขยายความเพื่อใช้เอฟเฟกต์มากกว่า 1 รายการกับรูปภาพ
โดยใช้ RenderScript แบบแหล่งเดียว ซึ่งมีเคอร์เนลอีกตัวหนึ่ง greyscale ที่เปลี่ยนรูปภาพสีเป็นขาวดำ จากนั้นฟังก์ชันที่เรียกใช้ได้ process() จะใช้เคอร์เนล 2 ตัวดังกล่าว
กับรูปภาพอินพุตอย่างต่อเนื่อง และสร้างรูปภาพเอาต์พุต ระบบจะส่งการจัดสรรสำหรับทั้งอินพุตและเอาต์พุตเป็นอาร์กิวเมนต์ของประเภท
rs_allocation
// File: singlesource.rs #pragma version(1) #pragma rs java_package_name(com.android.rssample) static const float4 weight = {0.299f, 0.587f, 0.114f, 0.0f}; uchar4 RS_KERNEL invert(uchar4 in, uint32_t x, uint32_t y) { uchar4 out = in; out.r = 255 - in.r; out.g = 255 - in.g; out.b = 255 - in.b; return out; } uchar4 RS_KERNEL greyscale(uchar4 in) { const float4 inF = rsUnpackColor8888(in); const float4 outF = (float4){ dot(inF, weight) }; return rsPackColorTo8888(outF); } void process(rs_allocation inputImage, rs_allocation outputImage) { const uint32_t imageWidth = rsAllocationGetDimX(inputImage); const uint32_t imageHeight = rsAllocationGetDimY(inputImage); rs_allocation tmp = rsCreateAllocation_uchar4(imageWidth, imageHeight); rsForEach(invert, inputImage, tmp); rsForEach(greyscale, tmp, outputImage); }
คุณเรียกใช้ฟังก์ชัน process() จาก Java หรือ Kotlin ได้ดังนี้
Kotlin
val RS: RenderScript = RenderScript.create(context) val script = ScriptC_singlesource(RS) val inputAllocation: Allocation = Allocation.createFromBitmapResource( RS, resources, R.drawable.image ) val outputAllocation: Allocation = Allocation.createTyped( RS, inputAllocation.type, Allocation.USAGE_SCRIPT or Allocation.USAGE_IO_OUTPUT ) script.invoke_process(inputAllocation, outputAllocation)
Java
// File SingleSource.java RenderScript RS = RenderScript.create(context); ScriptC_singlesource script = new ScriptC_singlesource(RS); Allocation inputAllocation = Allocation.createFromBitmapResource( RS, getResources(), R.drawable.image); Allocation outputAllocation = Allocation.createTyped( RS, inputAllocation.getType(), Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_OUTPUT); script.invoke_process(inputAllocation, outputAllocation);
ตัวอย่างนี้แสดงวิธีใช้การดำเนินการของอัลกอริทึมที่เกี่ยวข้องกับการเปิดตัวเคอร์เนล 2 รายการในภาษา RenderScript เอง หากไม่มี 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_varmethod (หากมี) จะเขียนค่า 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 ฟังก์ชันรวม
จะตรวจสอบว่าค่าต่ำสุดที่บันทึกไว้ในรายการข้อมูลตัวสะสม "แหล่งที่มา"
*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 รายการ ต่ออินพุต ฟังก์ชันตัวสะสมอาจรับอาร์กิวเมนต์พิเศษใดก็ได้ตัวอย่างเคอร์เนลที่มีอินพุตหลายรายการคือ
dotProductcombiner(combinerName)(ไม่บังคับ): ระบุชื่อของฟังก์ชันรวมสำหรับเคอร์เนลการลดนี้ หลังจากที่ 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 คือประเภทของรายการข้อมูลดังกล่าว ซึ่งไม่จำเป็นต้องเหมือนกับ accumTypeaccumคือตัวชี้ไปยังรายการข้อมูลตัวสะสมสุดท้าย ที่คำนวณโดยฟังก์ชัน 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 อาจส่งไปแล้ว) แสดงว่า
combinerName(&A, &I)ต้อง ปล่อยให้AเหมือนเดิมcombinerName(&I, &A)ต้อง ปล่อยให้IเหมือนเดิมกับA
ตัวอย่าง: ในเคอร์เนล addint ระบบจะเริ่มต้นรายการข้อมูลตัวสะสมเป็น 0 ฟังก์ชันตัวรวมสำหรับเคอร์เนลนี้ จะทำการบวก โดยมี 0 เป็นค่าเอกลักษณ์สำหรับการบวก
ตัวอย่าง: ในเคอร์เนล findMinAndMax
ระบบจะเริ่มต้นรายการข้อมูลตัวสะสม
เป็น INITVAL
fMMCombiner(&A, &I)จะทำให้Aเหมือนเดิม เนื่องจากIคือINITVALfMMCombiner(&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)
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 เป็นค่าเริ่มต้น จึงเหมือนกับB = minmax(B, IndexedVal(V, X))
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 อินพุตแต่ละรายการ
ตรงกับอาร์กิวเมนต์อินพุตที่เกี่ยวข้องของต้นแบบฟังก์ชัน Accumulator
หากการตรวจสอบใดไม่สำเร็จ 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 function
- หาก 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 ที่กล่าวถึงในหน้านี้เพิ่มเติม