RenderScript เป็นเฟรมเวิร์กสำหรับเรียกใช้งานที่มีการประมวลผลอย่างหนักด้วยประสิทธิภาพสูงใน Android RenderScript ออกแบบมาเพื่อใช้กับการคำนวณแบบขนานข้อมูลเป็นหลัก แม้ว่าเวิร์กโหลดแบบอนุกรม ก็อาจได้รับประโยชน์เช่นกัน รันไทม์ RenderScript จะทำงานแบบขนาน ในโปรเซสเซอร์ที่มีในอุปกรณ์ เช่น CPU และ GPU แบบมัลติคอร์ ซึ่งช่วยให้คุณมุ่งเน้นที่การแสดงออกของอัลกอริทึมแทนที่จะจัดกำหนดเวลางาน RenderScript มีประโยชน์อย่างยิ่งสำหรับแอปพลิเคชันที่ประมวลผลรูปภาพ การถ่ายภาพเชิงคำนวณ หรือคอมพิวเตอร์วิทัศน์
หากต้องการเริ่มต้นใช้งาน RenderScript คุณควรทำความเข้าใจแนวคิดหลัก 2 อย่างต่อไปนี้
- ภาษาเองเป็นภาษาที่ได้มาจาก C99 สำหรับการเขียนโค้ดการประมวลผลประสิทธิภาพสูง การเขียนเคอร์เนล RenderScript อธิบาย วิธีใช้เพื่อเขียนเคอร์เนลการคำนวณ
- Control API ใช้ในการจัดการอายุการใช้งานของทรัพยากร RenderScript และ ควบคุมการดำเนินการของเคอร์เนล โดยมีให้บริการใน 3 ภาษา ได้แก่ Java, C++ ใน Android NDK และภาษาเคอร์เนลที่ได้มาจาก C99 การใช้ RenderScript จากโค้ด Java และ RenderScript แหล่งเดียวอธิบายตัวเลือกแรกและตัวเลือกที่สาม ตามลำดับ
การเขียนเคอร์เนล RenderScript
โดยทั่วไปแล้ว เคอร์เนล RenderScript จะอยู่ในไฟล์ .rs
ในไดเรกทอรี
<project_root>/src/rs
ซึ่งไฟล์ .rs
แต่ละไฟล์เรียกว่าสคริปต์ สคริปต์ทุกรายการจะมีชุดเคอร์เนล ฟังก์ชัน และตัวแปรของตัวเอง สคริปต์อาจมีองค์ประกอบต่อไปนี้
- การประกาศ pragma (
#pragma version(1)
) ที่ประกาศเวอร์ชันของ ภาษาเคอร์เนล RenderScript ที่ใช้ในสคริปต์นี้ ปัจจุบันค่าที่ใช้ได้มีเพียง 1 - การประกาศ Pragma (
#pragma rs java_package_name(com.example.app)
) ที่ ประกาศชื่อแพ็กเกจของคลาส Java ที่แสดงจากสคริปต์นี้ โปรดทราบว่า.rs
ไฟล์ต้องเป็นส่วนหนึ่งของแพ็กเกจแอปพลิเคชัน ไม่ใช่ในโปรเจ็กต์ไลบรารี - ฟังก์ชันที่เรียกใช้ได้ตั้งแต่ 0 รายการขึ้นไป ฟังก์ชันที่เรียกใช้ได้คือฟังก์ชัน RenderScript แบบ Single-Thread ที่คุณเรียกใช้จากโค้ด Java ด้วยอาร์กิวเมนต์ที่กำหนดเองได้ ซึ่งมักมีประโยชน์สำหรับการตั้งค่าเริ่มต้นหรือการคำนวณแบบอนุกรมภายในไปป์ไลน์การประมวลผลที่ใหญ่ขึ้น
ส่วนกลางของสคริปต์ 0 รายการขึ้นไป ส่วนกลางของสคริปต์จะคล้ายกับตัวแปรส่วนกลางใน C คุณสามารถ เข้าถึงตัวแปรส่วนกลางของสคริปต์จากโค้ด Java และมักใช้ตัวแปรเหล่านี้ในการส่งพารามิเตอร์ไปยังเคอร์เนล RenderScript ดูคำอธิบายเกี่ยวกับตัวแปรส่วนกลางของสคริปต์เพิ่มเติมได้ที่นี่
เคอร์เนลการคำนวณตั้งแต่ 0 รายการขึ้นไป เคอร์เนลการคำนวณคือฟังก์ชัน หรือชุดฟังก์ชันที่คุณสามารถสั่งให้รันไทม์ RenderScript ดำเนินการแบบขนาน ในชุดข้อมูลได้ เคอร์เนลการคำนวณมี 2 ประเภท ได้แก่ เคอร์เนลการแมป (หรือที่เรียกว่าเคอร์เนล foreach) และเคอร์เนลการลด
เคอร์เนลการแมปคือฟังก์ชันแบบขนานที่ทำงานกับคอลเล็กชันของ
Allocations
ที่มีมิติข้อมูลเดียวกัน โดยค่าเริ่มต้น ฟังก์ชันนี้จะทำงาน 1 ครั้งสำหรับทุกพิกัดในมิติข้อมูลเหล่านั้น โดยปกติจะใช้ (แต่ไม่เสมอไป) เพื่อ เปลี่ยนคอลเล็กชันของอินพุตAllocations
เป็นเอาต์พุตAllocation
ทีละElement
รายการต่อไปนี้คือตัวอย่างเคอร์เนลการแมปอย่างง่าย
uchar4 RS_KERNEL invert(uchar4 in, uint32_t x, uint32_t y) { uchar4 out = in; out.r = 255 - in.r; out.g = 255 - in.g; out.b = 255 - in.b; return out; }
ในแง่มุมส่วนใหญ่ ฟังก์ชันนี้จะเหมือนกับฟังก์ชัน C มาตรฐาน พร็อพเพอร์ตี้
RS_KERNEL
ที่ใช้กับต้นแบบฟังก์ชัน จะระบุว่าฟังก์ชันเป็นเคอร์เนลการแมป RenderScript แทนที่จะเป็นฟังก์ชันที่เรียกใช้ได้ ระบบจะกรอกอาร์กิวเมนต์in
โดยอัตโนมัติตามอินพุตAllocation
ที่ส่งไปยังการเปิดตัวเคอร์เนล เราจะอธิบายอาร์กิวเมนต์x
และy
ด้านล่าง ระบบจะเขียนค่าที่แสดงผลจากเคอร์เนลไปยังตำแหน่งที่เหมาะสมในเอาต์พุตAllocation
โดยอัตโนมัติ โดยค่าเริ่มต้น เคอร์เนลนี้จะทำงานกับอินพุตทั้งหมดAllocation
โดยมีการเรียกใช้ฟังก์ชันเคอร์เนล 1 ครั้งต่อElement
ในAllocation
เคอร์เนลการแมปอาจมีอินพุต
Allocations
อย่างน้อย 1 รายการ เอาต์พุตAllocation
1 รายการ หรือทั้ง 2 อย่าง รันไทม์ RenderScript จะตรวจสอบเพื่อให้แน่ใจว่า Allocation ของอินพุตและเอาต์พุตทั้งหมดมีมิติข้อมูลเดียวกัน และElement
ประเภทของ Allocation ของอินพุตและเอาต์พุต ตรงกับต้นแบบของเคอร์เนล หากการตรวจสอบใดอย่างหนึ่งล้มเหลว RenderScript จะ ยกเว้นหมายเหตุ: ก่อน Android 6.0 (API ระดับ 23) เคอร์เนลการแมปอาจมีอินพุต
Allocation
ได้เพียงรายการเดียวหากต้องการอินพุตหรือเอาต์พุต
Allocations
มากกว่าที่เคอร์เนลมี คุณควรเชื่อมโยงออบเจ็กต์เหล่านั้นกับrs_allocation
ตัวแปรส่วนกลางของสคริปต์ และเข้าถึงจากเคอร์เนลหรือฟังก์ชันที่เรียกใช้ได้ ผ่านrsGetElementAt_type()
หรือrsSetElementAt_type()
หมายเหตุ:
RS_KERNEL
คือมาโคร ที่ RenderScript กำหนดให้โดยอัตโนมัติเพื่อความสะดวกของคุณ#define RS_KERNEL __attribute__((kernel))
เคอร์เนลการลดคือตระกูลของฟังก์ชันที่ทำงานกับคอลเล็กชันของอินพุต
Allocations
ที่มีมิติข้อมูลเดียวกัน โดยค่าเริ่มต้น ฟังก์ชันตัวสะสมจะทำงาน 1 ครั้งสำหรับพิกัดทุกรายการในมิติข้อมูลเหล่านั้น โดยปกติแล้ว (แต่ไม่เสมอไป) จะใช้เพื่อ "ลด" คอลเล็กชันของอินพุตAllocations
ให้เป็นค่าเดียวต่อไปนี้คือตัวอย่างเคอร์เนลการลดแบบง่ายที่บวก
Elements
ของอินพุต#pragma rs reduce(addint) accumulator(addintAccum) static void addintAccum(int *accum, int val) { *accum += val; }
เคอร์เนลการลดประกอบด้วยฟังก์ชันที่ผู้ใช้เขียนขึ้นอย่างน้อย 1 รายการ
#pragma rs reduce
ใช้เพื่อกำหนดเคอร์เนลโดยการระบุชื่อ (addint
ในตัวอย่างนี้) รวมถึงชื่อและบทบาทของฟังก์ชันที่ประกอบกันเป็น เคอร์เนล (ฟังก์ชันaccumulator
addintAccum
ในตัวอย่างนี้) ฟังก์ชันดังกล่าวทั้งหมดต้องเป็นstatic
เคอร์เนลการลดค่าต้องใช้accumulator
ฟังก์ชันเสมอ นอกจากนี้ยังอาจมีฟังก์ชันอื่นๆ ด้วย ทั้งนี้ขึ้นอยู่กับสิ่งที่คุณต้องการให้เคอร์เนลทำฟังก์ชันตัวสะสมเคอร์เนลการลดต้องแสดงผล
void
และต้องมีอาร์กิวเมนต์อย่างน้อย 2 รายการ อาร์กิวเมนต์แรก (accum
ในตัวอย่างนี้) คือพอยน์เตอร์ไปยัง รายการข้อมูลตัวสะสม และอาร์กิวเมนต์ที่สอง (val
ในตัวอย่างนี้) คือ ข้อมูลที่ระบบจะป้อนโดยอัตโนมัติตามอินพุตAllocation
ที่ส่งไปยัง การเปิดใช้เคอร์เนล รายการข้อมูลตัวสะสมสร้างขึ้นโดยรันไทม์ RenderScript และจะเริ่มต้นเป็น 0 โดยค่าเริ่มต้น โดยค่าเริ่มต้น เคอร์เนลนี้จะทำงานกับอินพุตทั้งหมดAllocation
โดยมีการเรียกใช้ฟังก์ชันตัวสะสม 1 ครั้งต่อElement
ในAllocation
โดย ค่าเริ่มต้น ระบบจะถือว่าค่าสุดท้ายของรายการข้อมูลตัวสะสมเป็นผลลัพธ์ของการ ลด และจะส่งคืนค่าดังกล่าวไปยัง Java รันไทม์ RenderScript จะตรวจสอบเพื่อให้แน่ใจว่าElement
ประเภทของ Allocation อินพุตตรงกับต้นแบบของฟังก์ชันตัวสะสม หากไม่ตรงกัน RenderScript จะส่งข้อยกเว้นเคอร์เนลการลดมีอินพุตอย่างน้อย 1 รายการ
Allocations
แต่ไม่มีเอาต์พุตAllocations
ดูรายละเอียดเพิ่มเติมเกี่ยวกับเคอร์เนลการลดได้ที่นี่
Android 7.0 (API ระดับ 24) ขึ้นไปรองรับเคอร์เนลการลด
ฟังก์ชันเคอร์เนลการแมปหรือฟังก์ชันตัวสะสมเคอร์เนลการลดอาจเข้าถึงพิกัด ของการดำเนินการปัจจุบันโดยใช้อาร์กิวเมนต์พิเศษ
x
,y
และz
ซึ่งต้องเป็นประเภทint
หรือuint32_t
คุณจะใช้อาร์กิวเมนต์เหล่านี้หรือไม่ก็ได้ฟังก์ชันเคอร์เนลการแมปหรือฟังก์ชันตัวสะสมเคอร์เนลการลด อาจรับอาร์กิวเมนต์พิเศษที่ไม่บังคับ
context
ประเภท rs_kernel_context ด้วย API นี้จำเป็นสำหรับ API รันไทม์ตระกูลหนึ่งซึ่งใช้เพื่อค้นหาพร็อพเพอร์ตี้บางอย่างของการดำเนินการปัจจุบัน เช่น rsGetDimX (อาร์กิวเมนต์context
พร้อมใช้งานใน Android 6.0 (API ระดับ 23) ขึ้นไป)- ฟังก์ชัน
init()
(ไม่บังคับ) ฟังก์ชันinit()
เป็นฟังก์ชันที่เรียกใช้ได้ประเภทพิเศษซึ่ง RenderScript จะเรียกใช้เมื่อมีการสร้างอินสแตนซ์ของสคริปต์เป็นครั้งแรก ซึ่งจะช่วยให้มีการคำนวณบางอย่างโดยอัตโนมัติเมื่อสร้างสคริปต์ - ฟังก์ชันและตัวแปรส่วนกลางของสคริปต์แบบคงที่ตั้งแต่ 0 รายการขึ้นไป ส่วนกลางของสคริปต์แบบคงที่จะเทียบเท่ากับส่วนกลางของสคริปต์
ยกเว้นว่าจะเข้าถึงจากโค้ด Java ไม่ได้ ฟังก์ชันแบบคงที่เป็นฟังก์ชัน C
มาตรฐานที่เรียกใช้ได้จากเคอร์เนลหรือฟังก์ชันที่เรียกใช้ได้ในสคริปต์ แต่ไม่ได้แสดง
ต่อ Java API หากไม่จำเป็นต้องเข้าถึงสคริปต์ส่วนกลางหรือฟังก์ชันจากโค้ด Java เราขอแนะนำอย่างยิ่งให้ประกาศเป็น
static
การตั้งค่าความแม่นยำของจุดลอยตัว
คุณสามารถควบคุมระดับความแม่นยำของจุดลอยตัวที่จำเป็นในสคริปต์ได้ วิธีนี้มีประโยชน์ในกรณีที่ไม่จำเป็นต้องใช้มาตรฐาน IEEE 754-2008 แบบเต็ม (ใช้โดยค่าเริ่มต้น) Pragma ต่อไปนี้สามารถตั้งค่า ระดับความแม่นยำของจุดลอยต่างกันได้
#pragma rs_fp_full
(ค่าเริ่มต้นหากไม่ได้ระบุอะไร): สำหรับแอปที่ต้องใช้ ความแม่นยำของเลขทศนิยมตามที่ระบุไว้ในมาตรฐาน IEEE 754-2008#pragma rs_fp_relaxed
: สำหรับแอปที่ไม่จำเป็นต้องปฏิบัติตามมาตรฐาน IEEE 754-2008 อย่างเคร่งครัดและยอมรับความแม่นยำที่น้อยลงได้ โหมดนี้จะเปิดใช้การล้างเป็น 0 สำหรับตัวเลขที่ผิดปกติและ การปัดเศษเข้าหา 0#pragma rs_fp_imprecise
: สำหรับแอปที่ไม่มีข้อกำหนดด้านความแม่นยำที่เข้มงวด โหมดนี้จะเปิดใช้ทุกอย่างในrs_fp_relaxed
พร้อมกับ รายการต่อไปนี้- การดำเนินการที่ทำให้ได้ -0.0 อาจแสดงผลเป็น +0.0 แทน
- การดำเนินการกับ INF และ NAN ไม่ได้กำหนดไว้
แอปพลิเคชันส่วนใหญ่ใช้ rs_fp_relaxed
ได้โดยไม่มีผลข้างเคียง ซึ่งอาจเป็นประโยชน์อย่างมากในสถาปัตยกรรมบางอย่างเนื่องจากการเพิ่มประสิทธิภาพเพิ่มเติมที่ใช้ได้เฉพาะกับความแม่นยำที่ผ่อนคลาย (เช่น คำสั่ง CPU ของ SIMD)
การเข้าถึง RenderScript API จาก Java
เมื่อพัฒนาแอปพลิเคชัน Android ที่ใช้ RenderScript คุณจะเข้าถึง API จาก Java ได้ 2 วิธี ดังนี้
android.renderscript
- API ในแพ็กเกจคลาสนี้ พร้อมใช้งานในอุปกรณ์ที่ใช้ Android 3.0 (API ระดับ 11) ขึ้นไปandroid.support.v8.renderscript
- API ในแพ็กเกจนี้พร้อมใช้งานผ่านไลบรารีการสนับสนุน ซึ่งช่วยให้คุณใช้ API เหล่านี้ในอุปกรณ์ที่ใช้ Android 2.3 (API ระดับ 9) ขึ้นไปได้
ข้อดีข้อเสียมีดังนี้
- หากคุณใช้ Support Library API ส่วน RenderScript ของแอปพลิเคชันจะ
เข้ากันได้กับอุปกรณ์ที่ใช้ Android 2.3 (API ระดับ 9) ขึ้นไป ไม่ว่าคุณจะใช้ฟีเจอร์ RenderScript ใดก็ตาม
ซึ่งจะช่วยให้แอปพลิเคชันทำงานในอุปกรณ์ได้มากกว่าการใช้ API แบบเนทีฟ (
android.renderscript
) - ฟีเจอร์ RenderScript บางอย่างไม่พร้อมใช้งานผ่าน API ของ Support Library
- หากใช้ Support Library API คุณจะได้รับ APK ที่มีขนาดใหญ่กว่า (อาจมีขนาดใหญ่กว่าอย่างมาก) เมื่อเทียบกับกรณีที่ใช้ API ดั้งเดิม (
android.renderscript
)
การใช้ API ของไลบรารีการสนับสนุน RenderScript
หากต้องการใช้ RenderScript API ของไลบรารีการสนับสนุน คุณต้องกำหนดค่าสภาพแวดล้อมการพัฒนา เพื่อให้เข้าถึง API เหล่านั้นได้ คุณต้องมีเครื่องมือ Android SDK ต่อไปนี้เพื่อใช้ API เหล่านี้
- เครื่องมือ Android SDK เวอร์ชันแก้ไข 22.2 ขึ้นไป
- เครื่องมือสร้าง Android SDK เวอร์ชันแก้ไข 18.1.0 ขึ้นไป
โปรดทราบว่าตั้งแต่เครื่องมือสร้าง Android SDK 24.0.0 เป็นต้นไป ระบบจะไม่รองรับ Android 2.2 (API ระดับ 8) อีกต่อไป
คุณสามารถตรวจสอบและอัปเดตเวอร์ชันที่ติดตั้งของเครื่องมือเหล่านี้ได้ใน Android SDK Manager
หากต้องการใช้ RenderScript API ของไลบรารีการสนับสนุน ให้ทำดังนี้
- ตรวจสอบว่าคุณได้ติดตั้ง 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
RenderScript
Context ที่สร้างด้วย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
คลาสมีเมธอด "คัดลอก" เพื่อคัดลอกข้อมูลไปยัง
และการจัดสรร เมธอด "คัดลอก" เป็นแบบซิงโครนัสและจะได้รับการซีเรียลไลซ์เมื่อเทียบกับการดำเนินการแบบไม่พร้อมกันข้างต้นที่แตะการจัดสรรเดียวกัน
คลาส javaFutureType ที่สะท้อนจะให้
เมธอด get()
เพื่อรับผลลัพธ์ของการลด get()
เป็นแบบซิงโครนัสและมีการทำให้เป็นอนุกรมเมื่อเทียบกับการลด (ซึ่งเป็นแบบอะซิงโครนัส)
RenderScript แบบแหล่งเดียว
Android 7.0 (API ระดับ 24) มีฟีเจอร์การเขียนโปรแกรมใหม่ที่เรียกว่า Single-Source
RenderScript ซึ่งจะเปิดใช้เคอร์เนลจากสคริปต์ที่กำหนดไว้ แทนที่จะเปิดใช้จาก Java ปัจจุบันวิธีนี้ใช้ได้กับการแมปเคอร์เนลเท่านั้น ซึ่งในส่วนนี้จะเรียกว่า "เคอร์เนล"
เพื่อความกระชับ ฟีเจอร์ใหม่นี้ยังรองรับการสร้างการจัดสรรประเภท
rs_allocation
จากภายในสคริปต์ด้วย ตอนนี้คุณสามารถ
ใช้ทั้งอัลกอริทึมภายในสคริปต์ได้แล้ว แม้ว่าจะต้องเปิดใช้เคอร์เนลหลายครั้งก็ตาม
ข้อดีมี 2 ประการ ได้แก่ โค้ดที่อ่านง่ายขึ้นเนื่องจากจะใช้ภาษาเดียวในการติดตั้งใช้งานอัลกอริทึม และโค้ดที่อาจเร็วกว่าเนื่องจากมีการเปลี่ยนระหว่าง Java กับ RenderScript น้อยลงในการเปิดตัวเคอร์เนลหลายรายการ
ใน RenderScript แบบแหล่งเดียว คุณจะเขียนเคอร์เนลตามที่อธิบายไว้ใน
การเขียนเคอร์เนล RenderScript จากนั้นคุณจะเขียนฟังก์ชันที่เรียกใช้ได้ซึ่งเรียก
rsForEach()
เพื่อเปิดใช้ API ดังกล่าวใช้ฟังก์ชันเคอร์เนลเป็นพารามิเตอร์แรก ตามด้วยการจัดสรรอินพุตและเอาต์พุต API ที่คล้ายกัน
rsForEachWithOptions()
จะใช้อาร์กิวเมนต์เพิ่มเติมของประเภท
rs_script_call_t
ซึ่งระบุชุดย่อยขององค์ประกอบจากการจัดสรรอินพุตและเอาต์พุตเพื่อให้ฟังก์ชันเคอร์เนลประมวลผล
หากต้องการเริ่มการคำนวณ RenderScript ให้เรียกใช้ฟังก์ชันที่เรียกใช้ได้จาก Java
ทำตามขั้นตอนในหัวข้อการใช้ RenderScript จากโค้ด Java
ในขั้นตอนเปิดตัวเคอร์เนลที่เหมาะสม ให้เรียกใช้
ฟังก์ชันที่เรียกใช้ได้โดยใช้ invoke_function_name()
ซึ่งจะเริ่ม
การคำนวณทั้งหมด รวมถึงการเปิดตัวเคอร์เนล
โดยปกติแล้วจะต้องมีการจัดสรรเพื่อบันทึกและส่งต่อ
ผลลัพธ์ระดับกลางจากการเปิดตัวเคอร์เนลหนึ่งไปยังอีกเคอร์เนลหนึ่ง คุณสร้างได้โดยใช้
rsCreateAllocation() รูปแบบหนึ่งของ API ที่ใช้งานง่ายคือ
rsCreateAllocation_<T><W>(…)
โดยที่ T คือประเภทข้อมูลสำหรับองค์ประกอบ และ W คือความกว้างของเวกเตอร์สำหรับองค์ประกอบ API จะใช้ขนาดใน
มิติข้อมูล X, Y และ Z เป็นอาร์กิวเมนต์ สำหรับการจัดสรร 1 มิติหรือ 2 มิติ คุณสามารถละเว้นขนาดสำหรับมิติข้อมูล Y หรือ Z ได้
เช่น rsCreateAllocation_uchar4(16384)
จะสร้างการจัดสรร 1 มิติของ
องค์ประกอบ 16384 รายการ ซึ่งแต่ละรายการมีประเภทเป็น uchar4
ระบบจะจัดการการจัดสรรโดยอัตโนมัติ คุณไม่จำเป็นต้องเผยแพร่หรือปล่อยให้ใช้งานได้อย่างอิสระ อย่างไรก็ตาม คุณสามารถเรียกใช้
rsClearObject(rs_allocation* alloc)
เพื่อระบุว่าคุณไม่ต้องการแฮนเดิล
alloc
สำหรับการจัดสรรพื้นฐานอีกต่อไป
เพื่อให้ระบบเพิ่มพื้นที่ว่างในทรัพยากรได้โดยเร็วที่สุด
ส่วนการเขียนเคอร์เนล RenderScript มีตัวอย่าง
เคอร์เนลที่กลับสีรูปภาพ ตัวอย่างด้านล่างจะขยายความเพื่อใช้เอฟเฟกต์มากกว่า 1 รายการกับรูปภาพ
โดยใช้ RenderScript แบบแหล่งเดียว ซึ่งรวมถึงเคอร์เนลอีกตัวหนึ่ง greyscale
ซึ่งเปลี่ยนรูปภาพสีเป็นขาวดำ จากนั้นฟังก์ชันที่เรียกใช้ได้ process()
จะใช้เคอร์เนล 2 ตัวนั้น
กับรูปภาพอินพุตอย่างต่อเนื่อง และสร้างรูปภาพเอาต์พุต ระบบจะส่งการจัดสรรทั้งอินพุตและ
เอาต์พุตเป็นอาร์กิวเมนต์ของประเภท
rs_allocation
// File: singlesource.rs #pragma version(1) #pragma rs java_package_name(com.android.rssample) static const float4 weight = {0.299f, 0.587f, 0.114f, 0.0f}; uchar4 RS_KERNEL invert(uchar4 in, uint32_t x, uint32_t y) { uchar4 out = in; out.r = 255 - in.r; out.g = 255 - in.g; out.b = 255 - in.b; return out; } uchar4 RS_KERNEL greyscale(uchar4 in) { const float4 inF = rsUnpackColor8888(in); const float4 outF = (float4){ dot(inF, weight) }; return rsPackColorTo8888(outF); } void process(rs_allocation inputImage, rs_allocation outputImage) { const uint32_t imageWidth = rsAllocationGetDimX(inputImage); const uint32_t imageHeight = rsAllocationGetDimY(inputImage); rs_allocation tmp = rsCreateAllocation_uchar4(imageWidth, imageHeight); rsForEach(invert, inputImage, tmp); rsForEach(greyscale, tmp, outputImage); }
คุณเรียกใช้ฟังก์ชัน process()
จาก Java หรือ Kotlin ได้ดังนี้
Kotlin
val RS: RenderScript = RenderScript.create(context) val script = ScriptC_singlesource(RS) val inputAllocation: Allocation = Allocation.createFromBitmapResource( RS, resources, R.drawable.image ) val outputAllocation: Allocation = Allocation.createTyped( RS, inputAllocation.type, Allocation.USAGE_SCRIPT or Allocation.USAGE_IO_OUTPUT ) script.invoke_process(inputAllocation, outputAllocation)
Java
// File SingleSource.java RenderScript RS = RenderScript.create(context); ScriptC_singlesource script = new ScriptC_singlesource(RS); Allocation inputAllocation = Allocation.createFromBitmapResource( RS, getResources(), R.drawable.image); Allocation outputAllocation = Allocation.createTyped( RS, inputAllocation.getType(), Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_OUTPUT); script.invoke_process(inputAllocation, outputAllocation);
ตัวอย่างนี้แสดงวิธีใช้การเปิดตัวเคอร์เนล 2 รายการในอัลกอริทึมในภาษา RenderScript เอง หากไม่มี RenderScript แบบแหล่งเดียว คุณจะต้องเปิดใช้ทั้ง 2 เคอร์เนลจากโค้ด Java โดยแยกการเปิดใช้เคอร์เนล ออกจากคำจำกัดความของเคอร์เนล ซึ่งจะทำให้เข้าใจอัลกอริทึมทั้งหมดได้ยากขึ้น โค้ด RenderScript แบบแหล่งข้อมูลเดียวไม่เพียงอ่านง่ายขึ้น แต่ยังช่วยลดการเปลี่ยน ระหว่าง Java กับสคริปต์เมื่อเปิดตัวเคอร์เนลด้วย อัลกอริทึมแบบวนซ้ำบางรายการอาจเปิดตัวเคอร์เนล หลายร้อยครั้ง ซึ่งทำให้ค่าใช้จ่ายในการเปลี่ยนผ่านดังกล่าวสูงมาก
ตัวแปรส่วนกลางของสคริปต์
ส่วนกลางของสคริปต์คือตัวแปรส่วนกลางที่ไม่ใช่ static
ในไฟล์สคริปต์ (.rs
) สำหรับสคริปต์
ที่ตั้งชื่อเป็น var ทั่วโลกซึ่งกำหนดไว้ใน
ไฟล์ filename.rs
จะมี
เมธอด get_var
ที่แสดงใน
คลาส ScriptC_filename
หาก global
ไม่ใช่ const
ก็จะมีเมธอด
set_var
ด้วย
สคริปต์ส่วนกลางที่กำหนดมีค่าแยกกัน 2 ค่า ได้แก่ ค่า Java และค่าสคริปต์ ค่าเหล่านี้จะทำงานดังนี้
- หาก var มีตัวเริ่มต้นแบบคงที่ในสคริปต์ สคริปต์จะ ระบุค่าเริ่มต้นของ var ทั้งใน Java และ สคริปต์ ไม่เช่นนั้น ค่าเริ่มต้นจะเป็น 0
- เข้าถึง var ภายในสคริปต์เพื่ออ่านและเขียนค่าสคริปต์
- เมธอด
get_var
จะอ่านค่า Java set_var
method (หากมี) จะเขียนค่า Java ทันที และเขียนค่าสคริปต์แบบไม่พร้อมกัน
หมายเหตุ: ซึ่งหมายความว่าค่าที่เขียนไปยังส่วนกลางจากภายในสคริปต์จะมองไม่เห็นใน Java ยกเว้นตัวเริ่มต้นแบบคงที่ในสคริปต์
Reduction Kernels in Depth
การลดคือกระบวนการรวมชุดข้อมูลเป็นค่าเดียว ซึ่งเป็นองค์ประกอบพื้นฐานที่มีประโยชน์ในการเขียนโปรแกรมแบบขนาน โดยมีแอปพลิเคชันต่างๆ เช่น ต่อไปนี้
- การคำนวณผลรวมหรือผลคูณของข้อมูลทั้งหมด
- การคำนวณการดำเนินการเชิงตรรกะ (
and
,or
,xor
) ในข้อมูลทั้งหมด - การหาค่าต่ำสุดหรือสูงสุดภายในข้อมูล
- ค้นหาค่าที่เฉพาะเจาะจงหรือพิกัดของค่าที่เฉพาะเจาะจงภายในข้อมูล
ใน Android 7.0 (API ระดับ 24) ขึ้นไป RenderScript รองรับเคอร์เนลการลดเพื่อให้ อัลกอริทึมการลดที่ผู้ใช้เขียนมีประสิทธิภาพ คุณอาจเปิดใช้เคอร์เนลการลดในอินพุตที่มี 1, 2 หรือ 3 มิติ
ตัวอย่างด้านบนแสดงเคอร์เนลการลด addint แบบง่าย
ต่อไปนี้คือเคอร์เนลการลด findMinAndMax ที่ซับซ้อนกว่า
ซึ่งจะค้นหาตำแหน่งของค่าต่ำสุดและสูงสุด long
ใน
Allocation
1 มิติ
#define LONG_MAX (long)((1UL << 63) - 1) #define LONG_MIN (long)(1UL << 63) #pragma rs reduce(findMinAndMax) \ initializer(fMMInit) accumulator(fMMAccumulator) \ combiner(fMMCombiner) outconverter(fMMOutConverter) // Either a value and the location where it was found, or INITVAL. typedef struct { long val; int idx; // -1 indicates INITVAL } IndexedVal; typedef struct { IndexedVal min, max; } MinAndMax; // In discussion below, this initial value { { LONG_MAX, -1 }, { LONG_MIN, -1 } } // is called INITVAL. static void fMMInit(MinAndMax *accum) { accum->min.val = LONG_MAX; accum->min.idx = -1; accum->max.val = LONG_MIN; accum->max.idx = -1; } //---------------------------------------------------------------------- // In describing the behavior of the accumulator and combiner functions, // it is helpful to describe hypothetical functions // IndexedVal min(IndexedVal a, IndexedVal b) // IndexedVal max(IndexedVal a, IndexedVal b) // MinAndMax minmax(MinAndMax a, MinAndMax b) // MinAndMax minmax(MinAndMax accum, IndexedVal val) // // The effect of // IndexedVal min(IndexedVal a, IndexedVal b) // is to return the IndexedVal from among the two arguments // whose val is lesser, except that when an IndexedVal // has a negative index, that IndexedVal is never less than // any other IndexedVal; therefore, if exactly one of the // two arguments has a negative index, the min is the other // argument. Like ordinary arithmetic min and max, this function // is commutative and associative; that is, // // min(A, B) == min(B, A) // commutative // min(A, min(B, C)) == min((A, B), C) // associative // // The effect of // IndexedVal max(IndexedVal a, IndexedVal b) // is analogous (greater . . . never greater than). // // Then there is // // MinAndMax minmax(MinAndMax a, MinAndMax b) { // return MinAndMax(min(a.min, b.min), max(a.max, b.max)); // } // // Like ordinary arithmetic min and max, the above function // is commutative and associative; that is: // // minmax(A, B) == minmax(B, A) // commutative // minmax(A, minmax(B, C)) == minmax((A, B), C) // associative // // Finally define // // MinAndMax minmax(MinAndMax accum, IndexedVal val) { // return minmax(accum, MinAndMax(val, val)); // } //---------------------------------------------------------------------- // This function can be explained as doing: // *accum = minmax(*accum, IndexedVal(in, x)) // // This function simply computes minimum and maximum values as if // INITVAL.min were greater than any other minimum value and // INITVAL.max were less than any other maximum value. Note that if // *accum is INITVAL, then this function sets // *accum = IndexedVal(in, x) // // After this function is called, both accum->min.idx and accum->max.idx // will have nonnegative values: // - x is always nonnegative, so if this function ever sets one of the // idx fields, it will set it to a nonnegative value // - if one of the idx fields is negative, then the corresponding // val field must be LONG_MAX or LONG_MIN, so the function will always // set both the val and idx fields static void fMMAccumulator(MinAndMax *accum, long in, int x) { IndexedVal me; me.val = in; me.idx = x; if (me.val <= accum->min.val) accum->min = me; if (me.val >= accum->max.val) accum->max = me; } // This function can be explained as doing: // *accum = minmax(*accum, *val) // // This function simply computes minimum and maximum values as if // INITVAL.min were greater than any other minimum value and // INITVAL.max were less than any other maximum value. Note that if // one of the two accumulator data items is INITVAL, then this // function sets *accum to the other one. static void fMMCombiner(MinAndMax *accum, const MinAndMax *val) { if ((accum->min.idx < 0) || (val->min.val < accum->min.val)) accum->min = val->min; if ((accum->max.idx < 0) || (val->max.val > accum->max.val)) accum->max = val->max; } static void fMMOutConverter(int2 *result, const MinAndMax *val) { result->x = val->min.idx; result->y = val->max.idx; }
หมายเหตุ: ดูตัวอย่างเคอร์เนลการลดเพิ่มเติมได้ที่นี่
หากต้องการเรียกใช้เคอร์เนลการลด รันไทม์ RenderScript จะสร้างตัวแปรอย่างน้อย 1 ตัว
ที่เรียกว่ารายการข้อมูลสะสมเพื่อเก็บสถานะของกระบวนการลด รันไทม์ RenderScript
จะเลือกจำนวนรายการข้อมูลตัวสะสมในลักษณะที่จะเพิ่มประสิทธิภาพสูงสุด ประเภทของรายการข้อมูลตัวสะสม (accumType) จะกำหนดโดยฟังก์ชันตัวสะสมของเคอร์เนล ซึ่งอาร์กิวเมนต์แรกของฟังก์ชันนั้นคือพอยน์เตอร์ไปยังรายการข้อมูลตัวสะสม โดยค่าเริ่มต้น ระบบจะเริ่มต้นรายการข้อมูลตัวสะสมทุกรายการเป็น 0 (เช่นเดียวกับmemset
) อย่างไรก็ตาม คุณอาจเขียนฟังก์ชันเริ่มต้นเพื่อทำสิ่งอื่น
ที่แตกต่างออกไป
ตัวอย่าง: ในเคอร์เนล addint
ระบบจะใช้รายการข้อมูลตัวสะสม (ประเภท int
) เพื่อบวกค่าอินพุต
ไม่มีฟังก์ชันเริ่มต้น ดังนั้นรายการข้อมูลสะสมแต่ละรายการจึงเริ่มต้นเป็น
ศูนย์
ตัวอย่าง: ในเคอร์เนล findMinAndMax ระบบจะใช้รายการข้อมูลตัวสะสม
(ประเภท MinAndMax
) เพื่อติดตามค่าต่ำสุดและสูงสุด
ที่พบจนถึงตอนนี้ มีฟังก์ชันเริ่มต้นเพื่อตั้งค่าเหล่านี้เป็น LONG_MAX
และ
LONG_MIN
ตามลำดับ และเพื่อตั้งค่าตำแหน่งของค่าเหล่านี้เป็น -1 ซึ่งบ่งชี้ว่า
ค่าดังกล่าวไม่ได้อยู่ในส่วน (ว่าง) ของอินพุตที่ได้รับการ
ประมวลผล
RenderScript จะเรียกใช้ฟังก์ชันตัวสะสม 1 ครั้งสำหรับทุกพิกัดใน อินพุต โดยปกติ ฟังก์ชันควรจะอัปเดตรายการข้อมูลตัวสะสมในลักษณะใดลักษณะหนึ่ง ตามอินพุต
ตัวอย่าง: ในเคอร์เนล addint ฟังก์ชันตัวสะสมจะเพิ่มค่าขององค์ประกอบอินพุตไปยังรายการข้อมูลตัวสะสม
ตัวอย่าง: ในเคอร์เนล findMinAndMax ฟังก์ชันตัวสะสมจะตรวจสอบว่าค่าขององค์ประกอบอินพุตน้อยกว่าหรือเท่ากับค่าต่ำสุดที่บันทึกไว้ในรายการข้อมูลตัวสะสม และ/หรือมากกว่าหรือเท่ากับค่าสูงสุดที่บันทึกไว้ในรายการข้อมูลตัวสะสมหรือไม่ จากนั้นจะอัปเดตรายการข้อมูลตัวสะสมตามนั้น
หลังจากเรียกใช้ฟังก์ชันตัวสะสม 1 ครั้งสำหรับทุกพิกัดในอินพุตแล้ว RenderScript จะต้องรวมรายการข้อมูลตัวสะสมเข้าด้วยกันเป็นรายการข้อมูลตัวสะสมรายการเดียว คุณอาจเขียนฟังก์ชัน รวมเพื่อทำสิ่งนี้ หากฟังก์ชันตัวสะสมมีอินพุตเดียวและไม่มีอาร์กิวเมนต์พิเศษ คุณก็ไม่จำเป็นต้องเขียนฟังก์ชันตัวรวม RenderScript จะใช้ฟังก์ชันตัวสะสมเพื่อรวมรายการข้อมูลตัวสะสม (คุณยังคงเขียนฟังก์ชันรวมได้หากลักษณะการทำงานเริ่มต้นนี้ไม่ใช่สิ่งที่คุณต้องการ )
ตัวอย่าง: ในเคอร์เนล addint ไม่มีฟังก์ชันรวม ดังนั้นระบบจะใช้ฟังก์ชันสะสม ซึ่งเป็นลักษณะการทำงานที่ถูกต้อง เนื่องจากหากเราแบ่งชุดค่าออกเป็น 2 ส่วน และ บวกค่าใน 2 ส่วนนั้นแยกกัน การบวกผลรวม 2 รายการนั้นจะเหมือนกับการ บวกทั้งชุดค่า
ตัวอย่าง: ในเคอร์เนล findMinAndMax ฟังก์ชัน Combiner จะตรวจสอบว่าค่าต่ำสุดที่บันทึกไว้ในรายการข้อมูลตัวสะสม "แหล่งที่มา"
*val
น้อยกว่าค่าต่ำสุดที่บันทึกไว้ในรายการข้อมูลตัวสะสม "ปลายทาง"
*accum
หรือไม่ และอัปเดต *accum
ตามนั้น โดยจะทำงานคล้ายกันสำหรับค่าสูงสุด การอัปเดตนี้จะเปลี่ยน *accum
เป็นสถานะที่ควรจะเป็นหากค่าอินพุตทั้งหมดสะสมอยู่ใน
*accum
แทนที่จะสะสมบางค่าไว้ใน *accum
และบางค่าไว้ใน
*val
หลังจากรวมรายการข้อมูลตัวสะสมทั้งหมดแล้ว RenderScript จะกำหนดผลลัพธ์ของการลดเพื่อส่งคืนไปยัง Java คุณอาจเขียนฟังก์ชัน outconverter เพื่อดำเนินการนี้ คุณไม่จำเป็นต้องเขียนฟังก์ชัน outconverter หากต้องการให้ค่าสุดท้ายของรายการข้อมูลตัวสะสมที่รวมกันเป็นผลลัพธ์ของการลด
ตัวอย่าง: ในเคอร์เนล addint ไม่มีฟังก์ชัน outconverter ค่าสุดท้ายของรายการข้อมูลที่รวมกันคือผลรวมของ องค์ประกอบทั้งหมดของอินพุต ซึ่งเป็นค่าที่เราต้องการแสดงผล
ตัวอย่าง: ในเคอร์เนล findMinAndMax ฟังก์ชัน outconverter
จะเริ่มต้นค่าผลลัพธ์ int2
เพื่อเก็บตำแหน่งของค่าต่ำสุดและ
ค่าสูงสุดที่เกิดจากการรวมรายการข้อมูลตัวสะสมทั้งหมด
การเขียนเคอร์เนลการลด
#pragma rs reduce
กําหนดเคอร์เนลการลดโดย
ระบุชื่อของเคอร์เนล รวมถึงชื่อและบทบาทของฟังก์ชันที่
ประกอบกันเป็นเคอร์เนล ฟังก์ชันดังกล่าวทั้งหมดต้องเป็นstatic
เคอร์เนลการลดต้องมีaccumulator
ฟังก์ชันเสมอ คุณจะละเว้นฟังก์ชันอื่นๆ บางส่วนหรือทั้งหมดก็ได้ ขึ้นอยู่กับว่าต้องการให้เคอร์เนลทำอะไร
#pragma rs reduce(kernelName) \ initializer(initializerName) \ accumulator(accumulatorName) \ combiner(combinerName) \ outconverter(outconverterName)
ความหมายของรายการใน #pragma
มีดังนี้
reduce(kernelName)
(ต้องระบุ): ระบุว่ากำลังกำหนดเคอร์เนลการลด เมธอด Java ที่สะท้อนreduce_kernelName
จะเปิดตัวเคอร์เนลinitializer(initializerName)
(ไม่บังคับ): ระบุชื่อของฟังก์ชันตัวเริ่มต้นสำหรับเคอร์เนลการลดนี้ เมื่อเปิดใช้เคอร์เนล RenderScript จะเรียกฟังก์ชันนี้ 1 ครั้งสำหรับรายการข้อมูลตัวสะสมแต่ละรายการ ต้องกำหนดฟังก์ชันดังนี้static void initializerName(accumType *accum) { … }
accum
เป็นตัวชี้ไปยังรายการข้อมูลตัวสะสมเพื่อให้ฟังก์ชันนี้ เริ่มต้นหากคุณไม่ได้ระบุฟังก์ชันเริ่มต้น RenderScript จะเริ่มต้นรายการข้อมูลสะสมทุกรายการเป็น 0 (ราวกับใช้
memset
) ซึ่งจะทํางานราวกับว่ามีฟังก์ชันเริ่มต้น ที่มีลักษณะดังนี้static void initializerName(accumType *accum) { memset(accum, 0, sizeof(*accum)); }
accumulator(accumulatorName)
(ต้องระบุ): ระบุชื่อของฟังก์ชันตัวสะสมสำหรับเคอร์เนลการลดนี้ เมื่อเปิดใช้เคอร์เนล RenderScript จะเรียกใช้ฟังก์ชันนี้ 1 ครั้งสำหรับทุกพิกัดในอินพุต เพื่ออัปเดตรายการข้อมูลตัวสะสมในลักษณะใดลักษณะหนึ่งตามอินพุต ฟังก์ชัน ต้องกำหนดดังนี้static void accumulatorName(accumType *accum, in1Type in1, …, inNType inN [, specialArguments]) { … }
accum
เป็นตัวชี้ไปยังรายการข้อมูลตัวสะสมเพื่อให้ฟังก์ชันนี้ แก้ไขin1
ถึงinN
คืออาร์กิวเมนต์อย่างน้อย 1 รายการที่ จะกรอกโดยอัตโนมัติตามอินพุตที่ส่งไปยังการเปิดตัวเคอร์เนล โดยมีอาร์กิวเมนต์ 1 รายการ ต่ออินพุต ฟังก์ชันตัวสะสมอาจรับอาร์กิวเมนต์พิเศษใดก็ได้ตัวอย่างเคอร์เนลที่มีอินพุตหลายรายการคือ
dotProduct
combiner(combinerName)
(ไม่บังคับ): ระบุชื่อของฟังก์ชัน Combiner สำหรับ เคอร์เนลการลดนี้ หลังจากที่ RenderScript เรียกฟังก์ชันตัวสะสม หนึ่งครั้งสำหรับทุกพิกัดในอินพุตแล้ว RenderScript จะเรียกฟังก์ชันนี้หลายครั้ง ตามที่จำเป็นเพื่อรวมรายการข้อมูลตัวสะสมทั้งหมดเป็นรายการข้อมูลตัวสะสมเดียว ต้องกำหนดฟังก์ชันดังนี้
static void combinerName(accumType *accum, const accumType *other) { … }
accum
เป็นตัวชี้ไปยังรายการข้อมูลตัวสะสม "ปลายทาง" สำหรับฟังก์ชันนี้เพื่อแก้ไขother
คือตัวชี้ไปยังรายการข้อมูลตัวสะสม "แหล่งที่มา" เพื่อให้ฟังก์ชันนี้ "รวม" เป็น*accum
หมายเหตุ: เป็นไปได้ที่
*accum
,*other
หรือทั้ง 2 รายการจะได้รับการเริ่มต้น แต่ไม่เคย ส่งไปยังฟังก์ชันตัวสะสม นั่นคือ รายการใดรายการหนึ่งหรือทั้ง 2 รายการไม่เคยได้รับการอัปเดต ตามข้อมูลอินพุตใดๆ ตัวอย่างเช่น ในเคอร์เนล findMinAndMax ฟังก์ชันรวมfMMCombiner
จะตรวจสอบidx < 0
อย่างชัดเจนเนื่องจากระบุรายการข้อมูลสะสมดังกล่าวซึ่งมีค่าเป็น INITVALหากคุณไม่ได้ระบุฟังก์ชันรวม RenderScript จะใช้ฟังก์ชันตัวสะสมแทน โดยจะทำงานราวกับว่ามีฟังก์ชันรวมที่ลักษณะดังนี้
static void combinerName(accumType *accum, const accumType *other) { accumulatorName(accum, *other); }
ต้องใช้ฟังก์ชันรวมหากเคอร์เนลมีอินพุตมากกว่า 1 รายการ หากประเภทข้อมูลอินพุตไม่เหมือนกับประเภทข้อมูลตัวสะสม หรือหากฟังก์ชันตัวสะสมใช้อาร์กิวเมนต์พิเศษอย่างน้อย 1 รายการ
outconverter(outconverterName)
(ไม่บังคับ): ระบุชื่อของฟังก์ชัน outconverter สำหรับ เคอร์เนลการลดนี้ หลังจากที่ RenderScript รวมรายการข้อมูลตัวสะสมทั้งหมดแล้ว ระบบจะเรียกใช้ฟังก์ชันนี้เพื่อกำหนดผลลัพธ์ของการ ลดค่าเพื่อส่งคืนไปยัง Java ต้องกำหนดฟังก์ชันดังนี้static void outconverterName(resultType *result, const accumType *accum) { … }
result
เป็นตัวชี้ไปยังรายการข้อมูลผลลัพธ์ (จัดสรรแล้วแต่ยังไม่ได้เริ่มต้น โดยรันไทม์ RenderScript) เพื่อให้ฟังก์ชันนี้เริ่มต้นด้วยผลลัพธ์ของ การลด resultType คือประเภทของรายการข้อมูลนั้น ซึ่งไม่จำเป็นต้องเหมือนกับ 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
คือINITVAL
fMMCombiner(&I, &A)
ตั้งค่าI
เป็นA
เนื่องจากI
คือINITVAL
ดังนั้น INITVAL
จึงเป็นค่าเอกลักษณ์
ฟังก์ชัน Combiner ต้องเป็นฟังก์ชันสลับที่ กล่าวคือ หาก A
และ B
เป็นรายการข้อมูลสะสมที่ฟังก์ชันเริ่มต้นกำหนดค่าเริ่มต้น
และอาจส่งไปยังฟังก์ชันสะสม 0 ครั้งขึ้นไป combinerName(&A, &B)
จะต้อง
ตั้งค่า A
เป็นค่าเดียวกัน
ที่ combinerName(&B, &A)
ตั้งค่า B
ตัวอย่าง: ในเคอร์เนล addint ฟังก์ชัน Combiner จะเพิ่มค่ารายการข้อมูลตัวสะสม 2 รายการ การบวก เป็นแบบสลับที่
ตัวอย่าง: ในเคอร์เนล findMinAndMax
fMMCombiner(&A, &B)
เหมือนกับ
A = minmax(A, B)
และ minmax
เป็นแบบสลับที่ ดังนั้น
fMMCombiner
จึงเป็นแบบสลับที่ด้วย
ฟังก์ชัน Combiner ต้องเป็นฟังก์ชันที่เชื่อมโยงกัน กล่าวคือ หาก A
, B
และ C
เป็น
รายการข้อมูลสะสมที่ฟังก์ชันตัวเริ่มต้นเริ่มต้น และอาจส่ง
ไปยังฟังก์ชันสะสม 0 ครั้งขึ้นไป ลำดับโค้ด 2 รายการต่อไปนี้จะต้อง
ตั้งค่า A
เป็นค่าเดียวกัน
combinerName(&A, &B); combinerName(&A, &C);
combinerName(&B, &C); combinerName(&A, &B);
ตัวอย่าง: ในเคอร์เนล addint ฟังก์ชัน combiner จะเพิ่มค่ารายการข้อมูลตัวสะสม 2 รายการ ดังนี้
A = A + B A = A + C // Same as // A = (A + B) + C
B = B + C A = A + B // Same as // A = A + (B + C) // B = B + C
การบวกเป็นแบบเชื่อมโยง ดังนั้นฟังก์ชัน Combiner จึงเป็นแบบเชื่อมโยงด้วย
ตัวอย่าง: ในเคอร์เนล findMinAndMax
fMMCombiner(&A, &B)
A = minmax(A, B)
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 อินพุตแต่ละรายการ
ตรงกับอาร์กิวเมนต์อินพุตที่เกี่ยวข้องของต้นแบบฟังก์ชัน
ตัวสะสม หากการตรวจสอบใดไม่สำเร็จ RenderScript จะส่งข้อยกเว้น เคอร์เนลจะทำงานในทุกพิกัดในมิติข้อมูลเหล่านั้น
วิธีที่ 2 เหมือนกับวิธีที่ 1 ยกเว้นว่าวิธีที่ 2 จะมีอาร์กิวเมนต์ sc
เพิ่มเติม
ซึ่งใช้เพื่อจำกัดการดำเนินการของเคอร์เนลให้เหลือเพียงชุดย่อยของพิกัดได้
วิธีที่ 3 เหมือนกับวิธีที่ 1 ยกเว้นว่า
แทนที่จะใช้ข้อมูลเข้าของการจัดสรร จะใช้ข้อมูลเข้าของอาร์เรย์ Java ซึ่งเป็นความสะดวกที่ช่วยให้คุณไม่ต้องเขียนโค้ดเพื่อสร้างการจัดสรรและคัดลอกข้อมูลไปยังการจัดสรรนั้นอย่างชัดเจนจากอาร์เรย์ Java อย่างไรก็ตาม การใช้วิธีที่ 3 แทนวิธีที่ 1 ไม่ได้ช่วยเพิ่มประสิทธิภาพของโค้ด สำหรับอาร์เรย์อินพุตแต่ละรายการ เมธอด 3 จะสร้าง Allocation แบบ 1 มิติชั่วคราวที่มีElement
ประเภทที่เหมาะสมและsetAutoPadding(boolean)
เปิดใช้ แล้วคัดลอกอาร์เรย์ไปยัง Allocation ราวกับว่าใช้เมธอด copyFrom()
ที่เหมาะสมของ Allocation
จากนั้นจะเรียกใช้เมธอด 1 โดยส่งการจัดสรรชั่วคราวเหล่านั้น
หมายเหตุ: หากแอปพลิเคชันของคุณจะทำการเรียกเคอร์เนลหลายครั้งด้วยอาร์เรย์เดียวกัน หรือด้วยอาร์เรย์ที่แตกต่างกันซึ่งมีมิติข้อมูลและประเภทองค์ประกอบเดียวกัน คุณอาจปรับปรุงประสิทธิภาพได้โดยการสร้าง ป้อนข้อมูล และนำการจัดสรรกลับมาใช้ใหม่ด้วยตนเองแทนการใช้วิธีที่ 3
javaFutureType,
ประเภทการคืนค่าของเมธอดการลดที่สะท้อนคือคลาสแบบคงที่ที่ซ้อนกันซึ่งสะท้อน
ภายในคลาส ScriptC_filename
ซึ่งแสดงถึงผลลัพธ์ในอนาคตของการลด
การเรียกใช้เคอร์เนล หากต้องการดูผลลัพธ์ที่แท้จริงของการเรียกใช้ ให้เรียกใช้เมธอด get()
ของคลาสนั้น ซึ่งจะแสดงค่าประเภท javaResultType get()
เป็นแบบเรียลไทม์
Kotlin
class ScriptC_filename(rs: RenderScript) : ScriptC(…) { object javaFutureType { fun get(): javaResultType { … } } }
Java
public class ScriptC_filename extends ScriptC { public static class javaFutureType { public javaResultType get() { … } } }
javaResultType จะกำหนดจาก resultType ของ outconverter function เว้นแต่ resultType จะเป็นประเภทที่ไม่ได้ลงนาม (สเกลาร์ เวกเตอร์ หรืออาร์เรย์) javaResultType จะเป็นประเภท Java ที่สอดคล้องกันโดยตรง หาก resultType เป็นประเภทที่ไม่มีการลงนามและมีประเภทที่ลงนามใน Java ที่ใหญ่กว่า javaResultType จะเป็นประเภทที่ลงนามใน Java ที่ใหญ่กว่านั้น หรือไม่เช่นนั้นจะเป็นประเภท Java ที่ สอดคล้องกันโดยตรง เช่น
- หาก resultType เป็น
int
,int2
หรือint[15]
javaResultType จะเป็นint
,Int2
หรือint[]
ค่าทั้งหมดของ resultType สามารถแสดงได้โดย javaResultType - หาก resultType เป็น
uint
,uint2
หรือuint[15]
javaResultType จะเป็นlong
,Long2
หรือlong[]
ค่าทั้งหมดของ resultType สามารถแสดงได้โดย javaResultType - หาก resultType เป็น
ulong
,ulong2
, หรือulong[15]
แล้ว javaResultType จะเป็นlong
,Long2
หรือlong[]
ค่าบางค่าของ resultType ไม่สามารถแสดงด้วย javaResultType
javaFutureType คือประเภทผลลัพธ์ในอนาคตที่สอดคล้องกับ resultType ของฟังก์ชัน outconverter
- หาก resultType ไม่ใช่ประเภทอาร์เรย์ javaFutureType
จะเป็น
result_resultType
- หาก resultType เป็นอาร์เรย์ที่มีความยาว Count โดยมีสมาชิกเป็นประเภท memberType
javaFutureType จะเป็น
resultArrayCount_memberType
เช่น
Kotlin
class ScriptC_filename(rs: RenderScript) : ScriptC(…) { // for kernels with int result object result_int { fun get(): Int = … } // for kernels with int[10] result object resultArray10_int { fun get(): IntArray = … } // for kernels with int2 result // note that the Kotlin type name "Int2" is not the same as the script type name "int2" object result_int2 { fun get(): Int2 = … } // for kernels with int2[10] result // note that the Kotlin type name "Int2" is not the same as the script type name "int2" object resultArray10_int2 { fun get(): Array<Int2> = … } // for kernels with uint result // note that the Kotlin type "long" is a wider signed type than the unsigned script type "uint" object result_uint { fun get(): Long = … } // for kernels with uint[10] result // note that the Kotlin type "long" is a wider signed type than the unsigned script type "uint" object resultArray10_uint { fun get(): LongArray = … } // for kernels with uint2 result // note that the Kotlin type "Long2" is a wider signed type than the unsigned script type "uint2" object result_uint2 { fun get(): Long2 = … } // for kernels with uint2[10] result // note that the Kotlin type "Long2" is a wider signed type than the unsigned script type "uint2" object resultArray10_uint2 { fun get(): Array<Long2> = … } }
Java
public class ScriptC_filename extends ScriptC { // for kernels with int result public static class result_int { public int get() { … } } // for kernels with int[10] result public static class resultArray10_int { public int[] get() { … } } // for kernels with int2 result // note that the Java type name "Int2" is not the same as the script type name "int2" public static class result_int2 { public Int2 get() { … } } // for kernels with int2[10] result // note that the Java type name "Int2" is not the same as the script type name "int2" public static class resultArray10_int2 { public Int2[] get() { … } } // for kernels with uint result // note that the Java type "long" is a wider signed type than the unsigned script type "uint" public static class result_uint { public long get() { … } } // for kernels with uint[10] result // note that the Java type "long" is a wider signed type than the unsigned script type "uint" public static class resultArray10_uint { public long[] get() { … } } // for kernels with uint2 result // note that the Java type "Long2" is a wider signed type than the unsigned script type "uint2" public static class result_uint2 { public Long2 get() { … } } // for kernels with uint2[10] result // note that the Java type "Long2" is a wider signed type than the unsigned script type "uint2" public static class resultArray10_uint2 { public Long2[] get() { … } } }
หาก javaResultType เป็นประเภทออบเจ็กต์ (รวมถึงประเภทอาร์เรย์) การเรียกใช้
javaFutureType.get()
ในอินสแตนซ์เดียวกันแต่ละครั้งจะแสดงผลออบเจ็กต์เดียวกัน
หาก javaResultType ไม่สามารถแสดงค่าทั้งหมดของประเภท resultType และเคอร์เนลการลดค่า
สร้างค่าที่แสดงไม่ได้
javaFutureType.get()
จะส่งข้อยกเว้น
วิธีที่ 3 และ devecSiInXType
devecSiInXType คือประเภท Java ที่สอดคล้องกับ inXType ของอาร์กิวเมนต์ที่เกี่ยวข้องของ ฟังก์ชันตัวสะสม เว้นแต่ inXType จะเป็น ประเภทที่ไม่มีเครื่องหมายหรือประเภทเวกเตอร์ devecSiInXType จะเป็นประเภท Java ที่สอดคล้องกันโดยตรง หาก inXType เป็นประเภทสเกลาร์ที่ไม่มีการลงนาม devecSiInXType จะเป็น ประเภท Java ที่สอดคล้องโดยตรงกับประเภทสเกลาร์ที่มีการลงนามซึ่งมีขนาดเท่ากัน หาก inXType เป็นประเภทเวกเตอร์ที่มีการลงนาม devecSiInXType จะเป็นประเภท Java ที่สอดคล้องกับประเภทคอมโพเนนต์เวกเตอร์โดยตรง หาก inXType เป็นประเภทเวกเตอร์ที่ไม่มีการลงนาม devecSiInXType จะเป็นประเภท Java ที่สอดคล้องโดยตรงกับ ประเภทสเกลาร์ที่มีการลงนามซึ่งมีขนาดเท่ากับประเภทคอมโพเนนต์เวกเตอร์ เช่น
- หาก inXType เป็น
int
แสดงว่า devecSiInXType เป็นint
- หาก inXType เป็น
int2
แสดงว่า devecSiInXType เป็นint
อาร์เรย์คือการแสดงผลแบบแบน ซึ่งมีองค์ประกอบสเกลาร์เป็น 2 เท่าขององค์ประกอบเวกเตอร์แบบ 2 องค์ประกอบในการจัดสรร ซึ่งเป็นวิธีเดียวกับที่copyFrom()
ของAllocation
ทำงาน - หาก inXType เป็น
uint
แสดงว่า deviceSiInXType เป็นint
ระบบจะตีความค่าที่มีการลงนามในอาร์เรย์ Java เป็นค่าที่ไม่มีการลงนามของ รูปแบบบิตเดียวกันในการจัดสรร ซึ่งเป็นวิธีเดียวกับที่เมธอดcopyFrom()
ของAllocation
ทำงาน - หาก inXType เป็น
uint2
แสดงว่า deviceSiInXType เป็นint
ซึ่งเป็นการรวมวิธีจัดการint2
และuint
โดยอาร์เรย์คือการแสดงแบบแบน และค่าที่ลงนามของอาร์เรย์ Java จะ ตีความเป็นค่า Element ที่ไม่ได้ลงนามของ RenderScript
โปรดทราบว่าสำหรับวิธีที่ 3 ระบบจะจัดการประเภทอินพุตแตกต่าง จากประเภทผลลัพธ์ ดังนี้
- อินพุตเวกเตอร์ของสคริปต์จะได้รับการแปลงเป็นรูปแบบแบนในฝั่ง Java แต่ผลลัพธ์เวกเตอร์ของสคริปต์จะไม่ได้รับการแปลง
- อินพุตที่ไม่ได้ลงนามของสคริปต์จะแสดงเป็นอินพุตที่ลงนามซึ่งมีขนาดเท่ากันในฝั่ง Java
ในขณะที่ผลลัพธ์ที่ไม่ได้ลงนามของสคริปต์จะแสดงเป็นประเภทที่ลงนามแบบขยายในฝั่ง Java (ยกเว้นในกรณีของ
ulong
)
ตัวอย่างเคอร์เนลการลดเพิ่มเติม
#pragma rs reduce(dotProduct) \ accumulator(dotProductAccum) combiner(dotProductSum) // Note: No initializer function -- therefore, // each accumulator data item is implicitly initialized to 0.0f. static void dotProductAccum(float *accum, float in1, float in2) { *accum += in1*in2; } // combiner function static void dotProductSum(float *accum, const float *val) { *accum += *val; }
// Find a zero Element in a 2D allocation; return (-1, -1) if none #pragma rs reduce(fz2) \ initializer(fz2Init) \ accumulator(fz2Accum) combiner(fz2Combine) static void fz2Init(int2 *accum) { accum->x = accum->y = -1; } static void fz2Accum(int2 *accum, int inVal, int x /* special arg */, int y /* special arg */) { if (inVal==0) { accum->x = x; accum->y = y; } } static void fz2Combine(int2 *accum, const int2 *accum2) { if (accum2->x >= 0) *accum = *accum2; }
// Note that this kernel returns an array to Java #pragma rs reduce(histogram) \ accumulator(hsgAccum) combiner(hsgCombine) #define BUCKETS 256 typedef uint32_t Histogram[BUCKETS]; // Note: No initializer function -- // therefore, each bucket is implicitly initialized to 0. static void hsgAccum(Histogram *h, uchar in) { ++(*h)[in]; } static void hsgCombine(Histogram *accum, const Histogram *addend) { for (int i = 0; i < BUCKETS; ++i) (*accum)[i] += (*addend)[i]; } // Determines the mode (most frequently occurring value), and returns // the value and the frequency. // // If multiple values have the same highest frequency, returns the lowest // of those values. // // Shares functions with the histogram reduction kernel. #pragma rs reduce(mode) \ accumulator(hsgAccum) combiner(hsgCombine) \ outconverter(modeOutConvert) static void modeOutConvert(int2 *result, const Histogram *h) { uint32_t mode = 0; for (int i = 1; i < BUCKETS; ++i) if ((*h)[i] > (*h)[mode]) mode = i; result->x = mode; result->y = (*h)[mode]; }
ตัวอย่างโค้ดเพิ่มเติม
ตัวอย่าง BasicRenderScript RenderScriptIntrinsic และ Hello Compute แสดงให้เห็นถึงการใช้ API ที่กล่าวถึงในหน้านี้เพิ่มเติม