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