RenderScript เป็นเฟรมเวิร์กสำหรับการเรียกใช้งานที่ต้องใช้การประมวลผลสูงอย่างมีประสิทธิภาพบน Android RenderScript มุ่งเน้นการใช้งานกับการประมวลผลข้อมูลแบบขนานเป็นหลัก แม้ว่าภาระงานแบบอนุกรมจะได้รับประโยชน์ด้วยเช่นกัน รันไทม์ RenderScript จะทำงานแบบขนานในโปรเซสเซอร์ที่พร้อมใช้งานในอุปกรณ์ เช่น CPU และ GPU แบบหลายคอร์ ซึ่งช่วยให้คุณมุ่งเน้นที่การแสดงอัลกอริทึมแทนการกำหนดเวลางานได้ RenderScript มีประโยชน์อย่างยิ่งสำหรับแอปพลิเคชันที่ทำการประมวลผลรูปภาพ การถ่ายภาพแบบดิจิทัล หรือคอมพิวเตอร์วิทัศน์
ในการเริ่มต้นใช้งาน RenderScript มีแนวคิดหลัก 2 ประการที่คุณควรทำความเข้าใจ ดังนี้
- ภาษานี้เป็นภาษาที่มาจาก C99 สำหรับการเขียนโค้ดการประมวลผลประสิทธิภาพสูง การเขียน RenderScript Kernel อธิบายวิธีใช้เพื่อเขียน Compute Kernel
- Control API ใช้สำหรับจัดการอายุการใช้งานของทรัพยากร RenderScript และควบคุมการดำเนินการของเคอร์เนล โดยมีให้บริการใน 3 ภาษา ได้แก่ Java, C++ ใน Android NDK และภาษาเคอร์เนลที่มาจาก C99 การใช้ RenderScript จากโค้ด Java และRenderScript แหล่งที่มาเดียวจะอธิบายตัวเลือกแรกและตัวเลือกที่ 3 ตามลำดับ
การเขียน RenderScript Kernel
โดยปกติแล้ว เคอร์เนล RenderScript จะอยู่ในไฟล์ .rs ในไดเรกทอรี <project_root>/src/rs โดยไฟล์ .rs แต่ละไฟล์จะเรียกว่าสคริปต์ สคริปต์ทุกรายการจะมีชุดเคอร์เนล ฟังก์ชัน และตัวแปรของตัวเอง สคริปต์อาจมีสิ่งต่อไปนี้
- การประกาศ Pragma (
#pragma version(1)) ที่ประกาศเวอร์ชันของภาษาเคอร์เนล RenderScript ที่ใช้ในสคริปต์นี้ ปัจจุบัน 1 เป็นค่าเดียวที่ถูกต้อง - การประกาศ Pragma (
#pragma rs java_package_name(com.example.app)) ที่ประกาศชื่อแพ็กเกจของคลาส Java ที่แสดงจากสคริปต์นี้ โปรดทราบว่าไฟล์.rsต้องเป็นส่วนหนึ่งของแพ็กเกจแอปพลิเคชัน ไม่ใช่ในโปรเจ็กต์ไลบรารี - ฟังก์ชันที่เรียกใช้ได้ตั้งแต่ 0 รายการขึ้นไป ฟังก์ชันที่เรียกใช้ได้คือฟังก์ชัน RenderScript แบบเทรดเดียวที่คุณเรียกใช้จากโค้ด Java ด้วยอาร์กิวเมนต์ที่กำหนดเองได้ ซึ่งมักจะเป็นประโยชน์สำหรับการตั้งค่าเริ่มต้นหรือการคำนวณแบบอนุกรมภายในไปป์ไลน์การประมวลผลขนาดใหญ่
script globals ตั้งแต่ 0 รายการขึ้นไป สคริปต์ส่วนกลางคล้ายกับตัวแปรส่วนกลางใน C คุณสามารถเข้าถึงตัวแปรส่วนกลางของสคริปต์จากโค้ด Java ได้ และมักใช้ตัวแปรเหล่านี้เพื่อส่งพารามิเตอร์ไปยังเคอร์เนล RenderScript ดูคำอธิบายโดยละเอียดเกี่ยวกับตัวแปรส่วนกลางของสคริปต์ได้ที่นี่
เคอร์เนลการประมวลผลตั้งแต่ 0 รายการขึ้นไป เคอร์เนลการประมวลผลคือฟังก์ชันหรือคอลเล็กชันฟังก์ชันที่คุณสามารถสั่งให้รันไทม์ RenderScript ทำงานพร้อมกันในคอลเล็กชันข้อมูลได้ เคอร์เนลการประมวลผลมี 2 ประเภท ได้แก่ เคอร์เนลการแมป (หรือที่เรียกว่าเคอร์เนล foreach) และเคอร์เนลการลด
เคอร์เนลการแมปคือฟังก์ชันคู่ขนานที่ทำงานบนคอลเล็กชัน
Allocationsที่มีมิติข้อมูลเดียวกัน โดยค่าเริ่มต้น ระบบจะดำเนินการ 1 ครั้งสำหรับทุกพิกัดในมิติข้อมูลเหล่านั้น โดยทั่วไป (แต่ไม่เสมอไป) จะใช้เพื่อแปลงคอลเล็กชันของAllocationsอินพุตเป็นElementเอาต์พุตครั้งละAllocationรายการต่อไปนี้คือตัวอย่างเคอร์เนลการแมปแบบง่าย
uchar4 RS_KERNEL invert(uchar4 in, uint32_t x, uint32_t y) { uchar4 out = in; out.r = 255 - in.r; out.g = 255 - in.g; out.b = 255 - in.b; return out; }
ในแง่ส่วนใหญ่ ฟังก์ชันนี้จะเหมือนกับฟังก์ชัน C มาตรฐาน พร็อพเพอร์ตี้
RS_KERNELที่ใช้กับต้นแบบฟังก์ชันจะระบุว่าฟังก์ชันนั้นเป็นเคอร์เนลการแมป RenderScript แทนที่จะเป็นฟังก์ชันที่เรียกใช้ได้ ระบบจะป้อนอาร์กิวเมนต์inโดยอัตโนมัติตามอินพุตAllocationที่ส่งไปยังการเปิดใช้งานเคอร์เนล อาร์กิวเมนต์xและyมีการพูดถึงด้านล่าง ระบบจะเขียนค่าที่แสดงผลจากเคอร์เนลไปยังตำแหน่งที่เหมาะสมในเอาต์พุตAllocationโดยอัตโนมัติ โดยค่าเริ่มต้น เคอร์เนลนี้จะทำงานในอินพุตทั้งหมดAllocationโดยมีการเรียกใช้ฟังก์ชันเคอร์เนล 1 ครั้งต่อElementในAllocationเคอร์เนลการแมปอาจมีอินพุต
Allocationsอย่างน้อย 1 รายการ เอาต์พุตAllocationรายการเดียว หรือทั้ง 2 อย่าง รันไทม์ RenderScript จะตรวจสอบว่าการจัดสรรอินพุตและเอาต์พุตทั้งหมดมีมิติข้อมูลเดียวกัน และประเภทElementของการจัดสรรอินพุตและเอาต์พุตตรงกับต้นแบบของเคอร์เนล หากการตรวจสอบอย่างใดอย่างหนึ่งไม่สำเร็จ RenderScript จะแสดงข้อยกเว้นหมายเหตุ: ก่อน Android 6.0 (ระดับ API 23) เคอร์เนลการแมปอาจมีอินพุต
Allocationได้ไม่เกิน 1 รายการหากคุณต้องการอินพุตหรือเอาต์พุต
Allocationsมากกว่าที่เคอร์เนลมี ออบเจ็กต์เหล่านั้นควรเชื่อมโยงกับrs_allocationสคริปต์ส่วนกลาง และเข้าถึงจากเคอร์เนลหรือฟังก์ชันที่เรียกใช้ได้ผ่านrsGetElementAt_type()หรือrsSetElementAt_type()หมายเหตุ:
RS_KERNELเป็นมาโครที่ RenderScript กำหนดโดยอัตโนมัติเพื่อความสะดวกของคุณ#define RS_KERNEL __attribute__((kernel))
เคอร์เนลการลดคือกลุ่มฟังก์ชันที่ทำงานกับคอลเล็กชันของอินพุต
Allocationsที่มีมิติข้อมูลเดียวกัน โดยค่าเริ่มต้น ฟังก์ชัน Accumulator จะทำงาน 1 ครั้งสำหรับทุกพิกัดในมิติข้อมูลเหล่านั้น โดยทั่วไป (แต่ไม่เสมอไป) จะใช้เพื่อ "ลด" คอลเล็กชันของAllocationsอินพุตให้เหลือค่าเดียวต่อไปนี้เป็นตัวอย่างของ Reduction Kernel แบบง่ายที่เพิ่ม
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ในตัวอย่างนี้) เป็นตัวชี้ไปยังรายการข้อมูลตัวสะสม และอาร์กิวเมนต์ที่ 2 (valในตัวอย่างนี้) จะได้รับการเติมโดยอัตโนมัติตามอินพุตAllocationที่ส่งไปยังการเปิดตัวเคอร์เนล รายการข้อมูล Accumulator สร้างขึ้นโดยรันไทม์ RenderScript โดยค่าเริ่มต้นจะเริ่มต้นเป็น 0 โดยค่าเริ่มต้น เคอร์เนลนี้จะทำงานในอินพุตทั้งหมดAllocationโดยมีการเรียกใช้ฟังก์ชัน Accumulator 1 ครั้งต่อElementในAllocationโดยค่าเริ่มต้น ระบบจะถือว่าค่าสุดท้ายของรายการข้อมูล Accumulator เป็นผลลัพธ์ของการลด และจะแสดงผลไปยัง Java รันไทม์ RenderScript จะตรวจสอบเพื่อให้แน่ใจว่าประเภทElementของ Allocation อินพุตตรงกับต้นแบบของฟังก์ชัน Accumulator หากไม่ตรงกัน RenderScript จะแสดงข้อยกเว้นเคอร์เนลการลดมี
Allocationsอินพุตอย่างน้อย 1 รายการ แต่ไม่มีAllocationsเอาต์พุตดูคำอธิบายโดยละเอียดเกี่ยวกับเคอร์เนลการลดขนาดได้ที่นี่
เคอร์เนลการลดรองรับใน Android 7.0 (ระดับ API 24) ขึ้นไป
ฟังก์ชันเคอร์เนลการแมปหรือฟังก์ชันสะสมเคอร์เนลการลดอาจเข้าถึงพิกัดของการดำเนินการปัจจุบันโดยใช้อาร์กิวเมนต์พิเศษ
x,yและzซึ่งต้องเป็นประเภทintหรือuint32_tอาร์กิวเมนต์เหล่านี้ไม่บังคับฟังก์ชันเคอร์เนลการแมปหรือฟังก์ชันสะสมเคอร์เนลการลดอาจใช้อาร์กิวเมนต์พิเศษ
contextที่ไม่บังคับของประเภท rs_kernel_context ด้วย ซึ่งจำเป็นสำหรับกลุ่ม 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 อย่างเคร่งครัดและยอมรับความแม่นยำที่น้อยลงได้ โหมดนี้จะเปิดใช้การปัดเศษเป็นศูนย์สำหรับค่าดีนอร์มัลและการปัดเศษเข้าหาศูนย์#pragma rs_fp_imprecise: สำหรับแอปที่ไม่มีข้อกำหนดด้านความแม่นยำที่เข้มงวด โหมดนี้จะเปิดใช้ทุกอย่างในrs_fp_relaxedพร้อมกับสิ่งต่อไปนี้- การดำเนินการที่ส่งผลให้เกิด -0.0 สามารถแสดงผลเป็น +0.0 แทนได้
- การดำเนินการกับ INF และ NAN ไม่ได้ระบุไว้
แอปพลิเคชันส่วนใหญ่สามารถใช้ rs_fp_relaxed ได้โดยไม่มีผลข้างเคียงใดๆ ซึ่งอาจเป็นประโยชน์อย่างมากในสถาปัตยกรรมบางอย่างเนื่องจากการเพิ่มประสิทธิภาพเพิ่มเติมที่ใช้ได้เฉพาะกับความแม่นยำที่ผ่อนคลาย (เช่น คำสั่ง SIMD CPU)
การเข้าถึง RenderScript API จาก Java
เมื่อพัฒนาแอปพลิเคชัน Android ที่ใช้ RenderScript คุณจะเข้าถึง API ของแอปพลิเคชันดังกล่าวจาก Java ได้ 2 วิธีดังนี้
android.renderscript- API ในแพ็กเกจคลาสนี้พร้อมใช้งานในอุปกรณ์ที่ใช้ Android 3.0 (ระดับ API 11) ขึ้นไปandroid.support.v8.renderscript- API ในแพ็กเกจนี้พร้อมใช้งานผ่านไลบรารีการสนับสนุน ซึ่งช่วยให้คุณใช้ API ดังกล่าวในอุปกรณ์ที่ใช้ Android 2.3 (ระดับ API 9) ขึ้นไปได้
ข้อดีข้อเสียมีดังนี้
- หากคุณใช้ Support Library API ส่วน RenderScript ของแอปพลิเคชันจะเข้ากันได้กับอุปกรณ์ที่ใช้ Android 2.3 (ระดับ API 9) ขึ้นไป ไม่ว่าคุณจะใช้ฟีเจอร์ RenderScript ใดก็ตาม ซึ่งจะช่วยให้แอปพลิเคชันทำงานในอุปกรณ์ได้มากกว่าการใช้ API แบบเนทีฟ (
android.renderscript) - ฟีเจอร์ RenderScript บางอย่างไม่พร้อมใช้งานผ่าน Support Library API
- หากใช้ API ของ Support Library คุณจะได้รับ APK ที่มีขนาดใหญ่กว่า (อาจใหญ่กว่ามาก) การใช้ API แบบเนทีฟ (
android.renderscript)
การใช้ RenderScript Support Library API
หากต้องการใช้ RenderScript API ของ Support Library คุณต้องกำหนดค่าสภาพแวดล้อมในการพัฒนาซอฟต์แวร์ให้เข้าถึง API ดังกล่าวได้ คุณต้องใช้เครื่องมือ Android SDK ต่อไปนี้เพื่อใช้ API เหล่านี้
- Android SDK Tools เวอร์ชัน 22.2 ขึ้นไป
- เครื่องมือสร้าง Android SDK เวอร์ชัน 18.1.0 ขึ้นไป
โปรดทราบว่าตั้งแต่ Android SDK Build-tools 24.0.0 เป็นต้นไป ระบบจะไม่รองรับ Android 2.2 (API ระดับ 8) อีกต่อไป
คุณสามารถตรวจสอบและอัปเดตเวอร์ชันที่ติดตั้งของเครื่องมือเหล่านี้ได้ในเครื่องมือจัดการ Android SDK
วิธีใช้ RenderScript API ของ Support Library
- ตรวจสอบว่าคุณได้ติดตั้ง Android SDK เวอร์ชันที่จำเป็นแล้ว
- อัปเดตการตั้งค่าสำหรับกระบวนการบิลด์ Android เพื่อรวมการตั้งค่า RenderScript ดังนี้
- เปิดไฟล์
build.gradleในโฟลเดอร์แอปของโมดูลแอปพลิเคชัน - เพิ่มการตั้งค่า RenderScript ต่อไปนี้ลงในไฟล์
ดึงดูด
android { compileSdkVersion 36 defaultConfig { minSdkVersion 9 targetSdkVersion 19 renderscriptTargetApi 18 renderscriptSupportModeEnabled true } }
Kotlin
android { compileSdkVersion(36) 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 บริบท
RenderScriptที่สร้างขึ้นด้วย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 Kernel ด้านบน สคริปต์ทุกรายการมีคลาส 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 ในตัวสำหรับการดำเนินการทั่วไป เช่น การเบลอแบบเกาส์เซียน การคอนโวลูชัน และการผสมรูปภาพ ดูข้อมูลเพิ่มเติมได้ที่คลาสย่อยของ
ScriptIntrinsic
- ScriptC: สคริปต์ที่ผู้ใช้กำหนดตามที่อธิบายไว้ในการเขียน RenderScript Kernel ด้านบน สคริปต์ทุกรายการมีคลาส Java ที่คอมไพเลอร์ RenderScript แสดงให้เห็นเพื่อให้เข้าถึงสคริปต์จากโค้ด Java ได้ง่าย โดยคลาสนี้มีชื่อว่า
- ใส่ข้อมูลลงในคอลัมน์การจัดสรร ยกเว้นการจัดสรรที่สร้างด้วย
createFromBitmap()การจัดสรรจะป้อนข้อมูลที่ว่างเปล่าเมื่อสร้างครั้งแรก หากต้องการป้อนข้อมูลการจัดสรร ให้ใช้วิธี "คัดลอก" วิธีใดวิธีหนึ่งในAllocationเมธอด "copy" เป็นแบบซิงโครนัส - ตั้งค่าตัวแปรส่วนกลางของสคริปต์ที่จำเป็น คุณอาจตั้งค่าตัวแปรส่วนกลางโดยใช้วิธีการในคลาส
ScriptC_filenameเดียวกันที่ชื่อset_globalnameตัวอย่างเช่น หากต้องการตั้งค่าตัวแปรintชื่อthresholdให้ใช้เมธอด Javaset_threshold(int)และหากต้องการตั้งค่าตัวแปรrs_allocationชื่อlookupให้ใช้เมธอด Javaset_lookup(Allocation)เมธอดsetเป็นแบบอะซิงโครนัส - เปิดใช้เคอร์เนลและฟังก์ชันที่เรียกใช้ได้ที่เหมาะสม
วิธีการเปิดตัวเคอร์เนลที่ระบุจะแสดงในคลาส
ScriptC_filenameเดียวกันซึ่งมีเมธอดชื่อforEach_mappingKernelName()หรือreduce_reductionKernelName()การเปิดตัวเหล่านี้เป็นแบบอะซิงโครนัส เมธอดจะใช้การจัดสรรอย่างน้อย 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" เพื่อคัดลอกข้อมูลไปยังและจาก Allocation เมธอด "copy" เป็นแบบซิงโครนัส และได้รับการจัดเรียงตามลำดับที่เกี่ยวข้องกับการดำเนินการแบบอะซิงโครนัสข้างต้นที่แตะการจัดสรรเดียวกัน
คลาส javaFutureType ที่แสดงผลจะมีเมธอด get() เพื่อรับผลลัพธ์ของการลด get() เป็นแบบซิงโครนัสและได้รับการจัดเรียงตามลำดับที่เกี่ยวข้องกับการลด (ซึ่งเป็นแบบอะซิงโครนัส)
RenderScript แหล่งที่มาเดียว
Android 7.0 (ระดับ API 24) เปิดตัวฟีเจอร์การเขียนโปรแกรมใหม่ที่เรียกว่า 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 Kernel มีตัวอย่างเคอร์เนลที่กลับสีรูปภาพ ตัวอย่างด้านล่างจะขยายการทำงานดังกล่าวเพื่อใช้เอฟเฟกต์มากกว่า 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 กับสคริปต์ในการเปิดตัวเคอร์เนลด้วย อัลกอริทึมแบบวนซ้ำบางรายการอาจเปิดใช้เคอร์เนลหลายร้อยครั้ง ทำให้เกิดค่าใช้จ่ายเพิ่มเติมในการเปลี่ยนผ่านดังกล่าวเป็นจำนวนมาก
Script Globals
สคริปต์ส่วนกลางคือตัวแปรส่วนกลางที่ไม่ใช่ static ธรรมดาในไฟล์สคริปต์ (.rs) สำหรับสคริปต์ส่วนกลางชื่อ var ที่กำหนดไว้ในไฟล์ filename.rs จะมีเมธอด get_var ที่แสดงในคลาส ScriptC_filename นอกจากว่าส่วนกลางจะเป็น const ก็จะมีเมธอด set_var ด้วย
สคริปต์ส่วนกลางที่ระบุมีค่าแยกกัน 2 ค่า ได้แก่ ค่า Java และค่าสคริปต์ ค่าเหล่านี้จะทำงานดังนี้
- หาก var มีตัวเริ่มต้นแบบคงที่ในสคริปต์ ระบบจะระบุค่าเริ่มต้นของ var ทั้งใน Java และสคริปต์ ไม่เช่นนั้น ค่าเริ่มต้นจะเป็น 0
- การเข้าถึง var ภายในสคริปต์จะอ่านและเขียนค่าสคริปต์
- เมธอด
get_varจะอ่านค่า Java - เมธอด
set_var(หากมี) จะเขียนค่า Java ทันที และเขียนค่าสคริปต์แบบอะซิงโครนัส
หมายเหตุ: ซึ่งหมายความว่า Java จะไม่เห็นค่าที่เขียนลงในตัวแปรส่วนกลางจากภายในสคริปต์ ยกเว้นตัวเริ่มต้นแบบคงที่ในสคริปต์
เคอร์เนลการลดแบบเจาะลึก
การลดคือกระบวนการรวมคอลเล็กชันข้อมูลให้เป็นค่าเดียว ซึ่งเป็นพื้นฐานที่มีประโยชน์ในการเขียนโปรแกรมแบบขนาน โดยมีการประยุกต์ใช้ดังต่อไปนี้
- การคำนวณผลรวมหรือผลคูณของข้อมูลทั้งหมด
- การคำนวณการดำเนินการเชิงตรรกะ (
and,or,xor) ในข้อมูลทั้งหมด - การหาค่าต่ำสุดหรือสูงสุดภายในข้อมูล
- การค้นหาค่าที่เฉพาะเจาะจงหรือพิกัดของค่าที่เฉพาะเจาะจงภายในข้อมูล
ใน Android 7.0 (ระดับ API 24) ขึ้นไป RenderScript รองรับเคอร์เนลการลดเพื่อให้ผู้ใช้เขียนอัลกอริทึมการลดได้อย่างมีประสิทธิภาพ คุณอาจเปิดใช้เคอร์เนลการลดในอินพุตที่มี 1, 2 หรือ 3 มิติข้อมูล
ตัวอย่างด้านบนแสดงเคอร์เนลการลด addint แบบง่าย
ต่อไปนี้เป็นเคอร์เนลการลด findMinAndMax ที่ซับซ้อนมากขึ้นซึ่งค้นหาตำแหน่งของค่า long ต่ำสุดและสูงสุดใน Allocation 1 มิติ
#define LONG_MAX (long)((1UL << 63) - 1) #define LONG_MIN (long)(1UL << 63) #pragma rs reduce(findMinAndMax) \ initializer(fMMInit) accumulator(fMMAccumulator) \ combiner(fMMCombiner) outconverter(fMMOutConverter) // Either a value and the location where it was found, or INITVAL. typedef struct { long val; int idx; // -1 indicates INITVAL } IndexedVal; typedef struct { IndexedVal min, max; } MinAndMax; // In discussion below, this initial value { { LONG_MAX, -1 }, { LONG_MIN, -1 } } // is called INITVAL. static void fMMInit(MinAndMax *accum) { accum->min.val = LONG_MAX; accum->min.idx = -1; accum->max.val = LONG_MIN; accum->max.idx = -1; } //---------------------------------------------------------------------- // In describing the behavior of the accumulator and combiner functions, // it is helpful to describe hypothetical functions // IndexedVal min(IndexedVal a, IndexedVal b) // IndexedVal max(IndexedVal a, IndexedVal b) // MinAndMax minmax(MinAndMax a, MinAndMax b) // MinAndMax minmax(MinAndMax accum, IndexedVal val) // // The effect of // IndexedVal min(IndexedVal a, IndexedVal b) // is to return the IndexedVal from among the two arguments // whose val is lesser, except that when an IndexedVal // has a negative index, that IndexedVal is never less than // any other IndexedVal; therefore, if exactly one of the // two arguments has a negative index, the min is the other // argument. Like ordinary arithmetic min and max, this function // is commutative and associative; that is, // // min(A, B) == min(B, A) // commutative // min(A, min(B, C)) == min((A, B), C) // associative // // The effect of // IndexedVal max(IndexedVal a, IndexedVal b) // is analogous (greater . . . never greater than). // // Then there is // // MinAndMax minmax(MinAndMax a, MinAndMax b) { // return MinAndMax(min(a.min, b.min), max(a.max, b.max)); // } // // Like ordinary arithmetic min and max, the above function // is commutative and associative; that is: // // minmax(A, B) == minmax(B, A) // commutative // minmax(A, minmax(B, C)) == minmax((A, B), C) // associative // // Finally define // // MinAndMax minmax(MinAndMax accum, IndexedVal val) { // return minmax(accum, MinAndMax(val, val)); // } //---------------------------------------------------------------------- // This function can be explained as doing: // *accum = minmax(*accum, IndexedVal(in, x)) // // This function simply computes minimum and maximum values as if // INITVAL.min were greater than any other minimum value and // INITVAL.max were less than any other maximum value. Note that if // *accum is INITVAL, then this function sets // *accum = IndexedVal(in, x) // // After this function is called, both accum->min.idx and accum->max.idx // will have nonnegative values: // - x is always nonnegative, so if this function ever sets one of the // idx fields, it will set it to a nonnegative value // - if one of the idx fields is negative, then the corresponding // val field must be LONG_MAX or LONG_MIN, so the function will always // set both the val and idx fields static void fMMAccumulator(MinAndMax *accum, long in, int x) { IndexedVal me; me.val = in; me.idx = x; if (me.val <= accum->min.val) accum->min = me; if (me.val >= accum->max.val) accum->max = me; } // This function can be explained as doing: // *accum = minmax(*accum, *val) // // This function simply computes minimum and maximum values as if // INITVAL.min were greater than any other minimum value and // INITVAL.max were less than any other maximum value. Note that if // one of the two accumulator data items is INITVAL, then this // function sets *accum to the other one. static void fMMCombiner(MinAndMax *accum, const MinAndMax *val) { if ((accum->min.idx < 0) || (val->min.val < accum->min.val)) accum->min = val->min; if ((accum->max.idx < 0) || (val->max.val > accum->max.val)) accum->max = val->max; } static void fMMOutConverter(int2 *result, const MinAndMax *val) { result->x = val->min.idx; result->y = val->max.idx; }
หมายเหตุ: ดูตัวอย่างเคอร์เนลการลดเพิ่มเติมได้ที่นี่
ในการเรียกใช้เคอร์เนลการลด รันไทม์ RenderScript จะสร้างตัวแปรอย่างน้อย 1 รายการที่เรียกว่ารายการข้อมูลตัวสะสมเพื่อเก็บสถานะของกระบวนการลด รันไทม์ RenderScript จะเลือกจำนวนรายการข้อมูลตัวสะสมในลักษณะที่เพิ่มประสิทธิภาพสูงสุด ประเภทของรายการข้อมูลตัวสะสม (accumType) จะกำหนดโดยฟังก์ชันตัวสะสมของเคอร์เนล อาร์กิวเมนต์แรกของฟังก์ชันนั้นคือตัวชี้ไปยังรายการข้อมูลตัวสะสม โดยค่าเริ่มต้น ระบบจะเริ่มต้นรายการข้อมูลตัวสะสมแต่ละรายการเป็น 0 (เหมือนกับใช้ memset) แต่คุณสามารถเขียนฟังก์ชันตัวเริ่มต้นเพื่อทำอย่างอื่นได้
ตัวอย่าง: ในเคอร์เนล addint ระบบจะใช้รายการข้อมูลตัวสะสม (ประเภท int) เพื่อบวกค่าอินพุต ไม่มีฟังก์ชันตัวเริ่มต้น ดังนั้นรายการข้อมูลตัวสะสมแต่ละรายการจะเริ่มต้นเป็น 0
ตัวอย่าง: ในเคอร์เนล findMinAndMax ระบบจะใช้รายการข้อมูล Accumulator (ประเภท MinAndMax) เพื่อติดตามค่าต่ำสุดและสูงสุดที่พบจนถึงตอนนี้ มีฟังก์ชันตัวเริ่มต้นเพื่อตั้งค่าเหล่านี้เป็น LONG_MAX และ LONG_MIN ตามลำดับ และเพื่อตั้งค่าตำแหน่งของค่าเหล่านี้เป็น -1 ซึ่งบ่งชี้ว่าค่าเหล่านี้ไม่ได้อยู่ในส่วน (ว่าง) ของอินพุตที่ประมวลผลแล้วจริงๆ
RenderScript จะเรียกใช้ฟังก์ชัน Accumulator 1 ครั้งสำหรับทุกพิกัดในอินพุต โดยปกติแล้ว ฟังก์ชันควรอัปเดตรายการข้อมูลตัวสะสมในลักษณะใดลักษณะหนึ่งตามอินพุต
ตัวอย่าง: ในเคอร์เนล addint ฟังก์ชัน Accumulator จะเพิ่มค่าขององค์ประกอบอินพุตลงในรายการข้อมูล Accumulator
ตัวอย่าง: ในเคอร์เนล findMinAndMax ฟังก์ชัน Accumulator จะตรวจสอบว่าค่าขององค์ประกอบอินพุตน้อยกว่าหรือเท่ากับค่าต่ำสุดที่บันทึกไว้ในรายการข้อมูล Accumulator และ/หรือมากกว่าหรือเท่ากับค่าสูงสุดที่บันทึกไว้ในรายการข้อมูล Accumulator หรือไม่ แล้วอัปเดตรายการข้อมูล Accumulator ตามนั้น
หลังจากเรียกใช้ฟังก์ชัน Accumulator 1 ครั้งสำหรับทุกพิกัดในอินพุตแล้ว RenderScript ต้องรวมรายการข้อมูล Accumulator เข้าด้วยกันเป็นรายการข้อมูล Accumulator รายการเดียว คุณสามารถเขียนฟังก์ชัน Combiner เพื่อดำเนินการนี้ได้ หากฟังก์ชัน Accumulator มีอินพุตเดียวและไม่มีอาร์กิวเมนต์พิเศษ คุณไม่จำเป็นต้องเขียนฟังก์ชัน Combiner เนื่องจาก RenderScript จะใช้ฟังก์ชัน Accumulator เพื่อรวมรายการข้อมูล Accumulator (คุณยังคงเขียนฟังก์ชัน Combiner ได้หากลักษณะการทำงานเริ่มต้นนี้ไม่ใช่สิ่งที่คุณต้องการ)
ตัวอย่าง: ในเคอร์เนล addint ไม่มีฟังก์ชัน Combiner ดังนั้นระบบจะใช้ฟังก์ชัน Accumulator ซึ่งเป็นลักษณะการทำงานที่ถูกต้อง เพราะหากเราแบ่งชุดค่าออกเป็น 2 ส่วน และบวกค่าใน 2 ส่วนนั้นแยกกัน การบวกผลรวมทั้ง 2 รายการจะเท่ากับการบวกทั้งชุด
ตัวอย่าง: ในเคอร์เนล findMinAndMax ฟังก์ชัน Combiner จะตรวจสอบว่าค่าต่ำสุดที่บันทึกไว้ในรายการข้อมูล Accumulator "ต้นทาง" *val น้อยกว่าค่าต่ำสุดที่บันทึกไว้ในรายการข้อมูล Accumulator "ปลายทาง" *accum หรือไม่ และอัปเดต *accum ตามนั้น และจะดำเนินการในลักษณะเดียวกันสำหรับค่าสูงสุด การดำเนินการนี้จะอัปเดต *accum เป็นสถานะที่ควรจะเป็นหากค่าอินพุตทั้งหมดสะสมอยู่ใน *accum แทนที่จะสะสมอยู่ใน *accum บางส่วนและ *val บางส่วน
หลังจากรวมรายการข้อมูลตัวสะสมทั้งหมดแล้ว RenderScript จะกำหนดผลลัพธ์ของการลดเพื่อส่งกลับไปยัง Java คุณสามารถเขียนฟังก์ชัน outconverter เพื่อดำเนินการนี้ได้ คุณไม่จำเป็นต้องเขียนฟังก์ชัน Outconverter หากต้องการให้ค่าสุดท้ายของรายการข้อมูล Accumulator ที่รวมกันเป็นผลลัพธ์ของการลด
ตัวอย่าง: ในเคอร์เนล 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)(บังคับ): ระบุชื่อของฟังก์ชัน Accumulator สำหรับเคอร์เนลการลดนี้ เมื่อคุณเปิดใช้งานเคอร์เนล RenderScript จะเรียกใช้ฟังก์ชันนี้ 1 ครั้งสำหรับทุกพิกัดในอินพุต เพื่ออัปเดตรายการข้อมูลตัวสะสมในลักษณะที่สอดคล้องกับอินพุต ต้องกำหนดฟังก์ชันดังนี้static void accumulatorName(accumType *accum, in1Type in1, …, inNType inN [, specialArguments]) { … }
accumคือตัวชี้ไปยังรายการข้อมูลตัวสะสมเพื่อให้ฟังก์ชันนี้แก้ไขin1ถึงinNคืออาร์กิวเมนต์อย่างน้อย 1 รายการที่ระบบจะป้อนข้อมูลให้โดยอัตโนมัติตามอินพุตที่ส่งไปยังการเปิดใช้งานเคอร์เนล โดยมีอาร์กิวเมนต์ 1 รายการต่ออินพุต 1 รายการ ฟังก์ชัน Accumulator อาจใช้อาร์กิวเมนต์พิเศษใดก็ได้ (ไม่บังคับ)ตัวอย่างเคอร์เนลที่มีอินพุตหลายรายการคือ
dotProductcombiner(combinerName)(ไม่บังคับ): ระบุชื่อของฟังก์ชัน Combiner สำหรับเคอร์เนลการลดนี้ หลังจาก RenderScript เรียกใช้ฟังก์ชัน Accumulator 1 ครั้งสำหรับทุกพิกัดในอินพุตแล้ว ระบบจะเรียกใช้ฟังก์ชันนี้หลายครั้งตามความจำเป็นเพื่อรวมรายการข้อมูล Accumulator ทั้งหมดไว้ในรายการข้อมูล Accumulator รายการเดียว ต้องกำหนดฟังก์ชันดังนี้
static void combinerName(accumType *accum, const accumType *other) { … }
accumเป็นตัวชี้ไปยังรายการข้อมูลตัวสะสม "ปลายทาง" สำหรับฟังก์ชันนี้ที่จะแก้ไขotherคือตัวชี้ไปยังรายการข้อมูลตัวสะสม "ต้นทาง" สำหรับฟังก์ชันนี้เพื่อ "รวม" เป็น*accumหมายเหตุ: อาจเป็นไปได้ว่ามีการเริ่มต้น
*accum,*otherหรือทั้ง 2 อย่าง แต่ไม่เคยส่งไปยังฟังก์ชัน Accumulator กล่าวคือ ไม่เคยมีการอัปเดตอย่างใดอย่างหนึ่งหรือทั้ง 2 อย่างตามข้อมูลที่ป้อน ตัวอย่างเช่น ในเคอร์เนล findMinAndMax ฟังก์ชัน CombinerfMMCombinerจะตรวจสอบidx < 0อย่างชัดแจ้งเนื่องจากเป็นการระบุรายการข้อมูล Accumulator ดังกล่าวซึ่งมีค่าเป็น INITVALหากคุณไม่ได้ระบุฟังก์ชัน Combiner ไว้ RenderScript จะใช้ฟังก์ชัน Accumulator แทน โดยทำงานเสมือนว่ามีฟังก์ชัน Combiner ที่มีลักษณะดังนี้
static void combinerName(accumType *accum, const accumType *other) { accumulatorName(accum, *other); }
ฟังก์ชัน Combiner เป็นสิ่งจำเป็นหากเคอร์เนลมีอินพุตมากกว่า 1 รายการ หากประเภทข้อมูลอินพุตไม่เหมือนกับประเภทข้อมูล Accumulator หรือหากฟังก์ชัน Accumulator รับอาร์กิวเมนต์พิเศษอย่างน้อย 1 รายการ
outconverter(outconverterName)(ไม่บังคับ): ระบุชื่อของฟังก์ชัน Outconverter สำหรับเคอร์เนลการลดนี้ หลังจาก RenderScript รวมรายการข้อมูลตัวสะสมทั้งหมดแล้ว ระบบจะเรียกใช้ฟังก์ชันนี้เพื่อกำหนดผลลัพธ์ของการลดเพื่อส่งกลับไปยัง Java โดยต้องกำหนดฟังก์ชันดังนี้static void outconverterName(resultType *result, const accumType *accum) { … }
resultเป็นตัวชี้ไปยังรายการข้อมูลผลลัพธ์ (จัดสรรไว้แต่ไม่ได้เริ่มต้นโดยรันไทม์ RenderScript) เพื่อให้ฟังก์ชันนี้เริ่มต้นด้วยผลลัพธ์ของการลด resultType คือประเภทของรายการข้อมูลนั้น ซึ่งไม่จำเป็นต้องเหมือนกับ accumTypeaccumคือตัวชี้ไปยังรายการข้อมูลตัวสะสมสุดท้ายที่คำนวณโดยฟังก์ชัน Combinerหากคุณไม่ได้ระบุฟังก์ชัน Outconverter ไว้ RenderScript จะคัดลอกรายการข้อมูล Accumulator สุดท้ายไปยังรายการข้อมูลผลลัพธ์ โดยทำงานเสมือนว่ามีฟังก์ชัน Outconverter ที่มีลักษณะดังนี้
static void outconverterName(accumType *result, const accumType *accum) { *result = *accum; }
หากต้องการผลลัพธ์ประเภทอื่นที่ไม่ใช่ประเภทข้อมูล Accumulator คุณต้องใช้ฟังก์ชัน Outconverter
โปรดทราบว่าเคอร์เนลมีประเภทอินพุต ประเภทรายการข้อมูลตัวสะสม และประเภทผลลัพธ์ ซึ่งไม่จำเป็นต้องเหมือนกัน ตัวอย่างเช่น ในเคอร์เนล findMinAndMax ประเภทอินพุต long, ประเภทรายการข้อมูลตัวสะสม MinAndMax และประเภทผลลัพธ์ int2 จะแตกต่างกันทั้งหมด
สิ่งที่คุณไม่สามารถคาดเดาได้
คุณต้องไม่พึ่งพาจำนวนรายการข้อมูลตัวสะสมที่ RenderScript สร้างขึ้นสำหรับการเปิดตัวเคอร์เนลหนึ่งๆ ไม่มีการรับประกันว่าการเปิดใช้งานเคอร์เนลเดียวกัน 2 ครั้งด้วยอินพุตเดียวกันจะสร้างรายการข้อมูล Accumulator ในจำนวนที่เท่ากัน
คุณต้องไม่พึ่งพาลำดับที่ RenderScript เรียกใช้ฟังก์ชันตัวเริ่มต้น ตัวสะสม และตัวรวม และอาจเรียกใช้ฟังก์ชันบางอย่างพร้อมกันด้วย ไม่มีการรับประกันว่าการเปิดตัวเคอร์เนลเดียวกัน 2 ครั้งด้วยอินพุตเดียวกันจะเป็นไปตามลำดับเดียวกัน สิ่งเดียวที่รับประกันได้คือมีเพียงฟังก์ชันตัวเริ่มต้นเท่านั้นที่จะเห็นรายการข้อมูลตัวสะสมที่ยังไม่ได้เริ่มต้น เช่น
- ไม่มีการรับประกันว่าระบบจะเริ่มต้นรายการข้อมูลตัวสะสมทั้งหมดก่อนที่จะเรียกใช้ฟังก์ชันตัวสะสม แม้ว่าจะมีการเรียกใช้เฉพาะในรายการข้อมูลตัวสะสมที่เริ่มต้นแล้วก็ตาม
- ไม่มีการรับประกันเกี่ยวกับลำดับการส่งองค์ประกอบอินพุตไปยังฟังก์ชัน Accumulator
- ไม่มีการรับประกันว่ามีการเรียกใช้ฟังก์ชัน Accumulator สำหรับองค์ประกอบอินพุตทั้งหมดก่อนที่จะเรียกใช้ฟังก์ชัน Combiner
ผลที่ตามมาอย่างหนึ่งคือเคอร์เนล findMinAndMax ไม่ใช่เชิงกำหนด กล่าวคือ หากอินพุตมีค่าต่ำสุดหรือสูงสุดเดียวกันมากกว่า 1 ค่า คุณจะไม่มีทางทราบได้ว่าเคอร์เนลจะพบค่าใด
คุณต้องรับประกันอะไรบ้าง
เนื่องจากระบบ RenderScript สามารถเลือกเรียกใช้เคอร์เนลได้หลายวิธี คุณจึงต้องปฏิบัติตามกฎบางอย่างเพื่อให้เคอร์เนลทำงานในแบบที่คุณต้องการ หากไม่ปฏิบัติตามกฎเหล่านี้ คุณอาจได้รับผลลัพธ์ที่ไม่ถูกต้อง ลักษณะการทำงานที่ไม่แน่นอน หรือข้อผิดพลาดขณะรันไทม์
กฎด้านล่างมักระบุว่ารายการข้อมูลตัวสะสม 2 รายการต้องมี "ค่าเดียวกัน" ผลกระทบจากการเปลี่ยนแปลงนี้ ขึ้นอยู่กับสิ่งที่คุณต้องการให้เคอร์เนลทำ สำหรับการลดทางคณิตศาสตร์ เช่น addint โดยปกติแล้ว "เหมือนกัน" จะหมายถึงความเท่ากันทางคณิตศาสตร์ สำหรับการค้นหาแบบ "เลือกค่าใดก็ได้" เช่น findMinAndMax ("ค้นหาตำแหน่งของค่าอินพุตต่ำสุดและสูงสุด") ซึ่งอาจมีค่าอินพุตที่เหมือนกันมากกว่า 1 ค่า ตำแหน่งทั้งหมดของค่าอินพุตที่ระบุจะต้องถือว่า "เหมือนกัน" คุณสามารถเขียนเคอร์เนลที่คล้ายกันเพื่อ "ค้นหาตำแหน่งของค่าอินพุตต่ำสุดและสูงสุดทางซ้ายสุด" โดยที่ (สมมติว่า) ค่าต่ำสุดที่ตำแหน่ง 100 เป็นที่ต้องการมากกว่าค่าต่ำสุดที่เหมือนกันที่ตำแหน่ง 200 สำหรับเคอร์เนลนี้ "เหมือนกัน" จะหมายถึงตำแหน่งที่เหมือนกัน ไม่ใช่แค่ค่าที่เหมือนกัน และฟังก์ชัน Accumulator และ Combiner จะต้องแตกต่างจากฟังก์ชันสำหรับ 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 เป็นรายการข้อมูล Accumulator ที่เริ่มต้นโดยฟังก์ชัน Initializer และอาจส่งไปยังฟังก์ชัน Accumulator 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 เป็นรายการข้อมูล Accumulator ที่เริ่มต้นโดยฟังก์ชัน Initializer และอาจส่งไปยังฟังก์ชัน Accumulator 0 ครั้งขึ้นไป ลำดับโค้ด 2 ลำดับต่อไปนี้ต้องตั้งค่า A เป็นค่าเดียวกัน
combinerName(&A, &B); combinerName(&A, &C);
combinerName(&B, &C); combinerName(&A, &B);
ตัวอย่าง: ในเคอร์เนล addint ฟังก์ชัน Combiner จะเพิ่มค่ารายการข้อมูล Accumulator 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 จึงเป็นแบบเปลี่ยนหมู่ด้วย
ฟังก์ชัน Accumulator และฟังก์ชัน Combiner ต้องเป็นไปตามกฎการพับพื้นฐาน กล่าวคือ หาก A และ B เป็นรายการข้อมูล Accumulator โดย A ได้รับการเริ่มต้นโดยฟังก์ชัน Initializer และอาจส่งไปยังฟังก์ชัน Accumulator 0 ครั้งขึ้นไป ส่วน B ยังไม่ได้รับการเริ่มต้น และ args เป็นรายการอาร์กิวเมนต์อินพุตและอาร์กิวเมนต์พิเศษสำหรับการเรียกใช้ฟังก์ชัน Accumulator โดยเฉพาะ ลำดับโค้ด 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 อินพุต 1 รายการสำหรับอาร์กิวเมนต์อินพุตทุกรายการในฟังก์ชันตัวสะสมของเคอร์เนล รันไทม์ RenderScript จะตรวจสอบว่าการจัดสรรอินพุตทั้งหมดมีมิติข้อมูลเดียวกัน และประเภท Element ของการจัดสรรอินพุตแต่ละรายการตรงกับอาร์กิวเมนต์อินพุตที่เกี่ยวข้องของต้นแบบฟังก์ชัน Accumulator หากการตรวจสอบเหล่านี้ไม่สำเร็จ RenderScript จะแสดงข้อยกเว้น เคอร์เนลจะดำเนินการกับพิกัดทั้งหมดในมิติข้อมูลเหล่านั้น
เมธอด 2 เหมือนกับเมธอด 1 ยกเว้นว่าเมธอด 2 จะใช้อาร์กิวเมนต์ sc เพิ่มเติมที่ใช้เพื่อจำกัดการดำเนินการเคอร์เนลให้เป็นชุดย่อยของพิกัดได้
เมธอด 3 เหมือนกับเมธอด 1 ยกเว้นว่าจะรับอินพุตอาร์เรย์ Java แทนที่จะรับอินพุตการจัดสรร ซึ่งช่วยให้คุณไม่ต้องเขียนโค้ดเพื่อสร้างการจัดสรรและคัดลอกข้อมูลจากอาร์เรย์ Java อย่างชัดเจน อย่างไรก็ตาม การใช้วิธีที่ 3 แทนวิธีที่ 1 ไม่ได้เพิ่มประสิทธิภาพของโค้ด สำหรับอาร์เรย์อินพุตแต่ละรายการ วิธีที่ 3 จะสร้างการจัดสรร 1 มิติชั่วคราวที่มีประเภท Element ที่เหมาะสมและเปิดใช้ setAutoPadding(boolean) และคัดลอกอาร์เรย์ไปยังการจัดสรรราวกับว่าใช้วิธี copyFrom() ที่เหมาะสมของ Allocation จากนั้นจะเรียกใช้เมธอด 1 โดยส่งการจัดสรรชั่วคราวเหล่านั้น
หมายเหตุ: หากแอปพลิเคชันของคุณจะทำการเรียกใช้เคอร์เนลหลายครั้งด้วยอาร์เรย์เดียวกัน หรือด้วยอาร์เรย์ที่แตกต่างกันซึ่งมีมิติข้อมูลและประเภทองค์ประกอบเดียวกัน คุณอาจปรับปรุงประสิทธิภาพได้โดยการสร้าง เติมข้อมูล และนำการจัดสรรมาใช้ซ้ำอย่างชัดเจนด้วยตนเอง แทนที่จะใช้วิธีที่ 3
javaFutureType ซึ่งเป็นประเภทการแสดงผลของเมธอดการลดที่สะท้อนเป็นคลาสที่ซ้อนกันแบบคงที่ที่สะท้อนภายในคลาส ScriptC_filename ซึ่งแสดงผลลัพธ์ในอนาคตของการเรียกใช้เคอร์เนลการลด หากต้องการดูผลลัพธ์จริงของการเรียกใช้ ให้เรียกใช้เมธอด get() ของคลาสนั้น ซึ่งจะแสดงผลค่าประเภท javaResultType get()เป็นแบบซิงโครนัส
Kotlin
class ScriptC_filename(rs: RenderScript) : ScriptC(…) { object javaFutureType { fun get(): javaResultType { … } } }
Java
public class ScriptC_filename extends ScriptC { public static class javaFutureType { public javaResultType get() { … } } }
javaResultType จะกำหนดจาก resultType ของฟังก์ชัน outconverter javaResultType เป็นประเภท Java ที่สอดคล้องกันโดยตรง เว้นแต่ resultType จะเป็นประเภทที่ไม่มีการรับรอง (สเกลาร์ เวกเตอร์ หรืออาร์เรย์) หาก resultType เป็นประเภทที่ไม่มีเครื่องหมายและมีประเภทที่มีเครื่องหมาย Java ที่ใหญ่กว่า javaResultType จะเป็นประเภทที่มีเครื่องหมาย Java ที่ใหญ่กว่านั้น ไม่เช่นนั้นจะเป็นประเภท Java ที่สอดคล้องกันโดยตรง เช่น
- หาก resultType เป็น
int,int2หรือint[15]javaResultType จะเป็นint,Int2หรือint[]ค่าทั้งหมดของ resultType สามารถแสดงด้วย javaResultType - หาก resultType เป็น
uint,uint2หรือuint[15]javaResultType จะเป็นlong,Long2หรือlong[]ค่าทั้งหมดของ resultType สามารถแสดงด้วย javaResultType - หาก resultType เป็น
ulong,ulong2หรือulong[15]แล้ว javaResultType จะเป็นlong,Long2หรือlong[]javaResultType ไม่สามารถแสดงค่าบางค่าของ resultType ได้
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 ของอาร์กิวเมนต์ที่สอดคล้องกันของฟังก์ชัน Accumulator เว้นแต่ 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 ที่ครอบคลุมในหน้านี้เพิ่มเติม