RenderScript เป็นเฟรมเวิร์กสำหรับเรียกใช้งานที่ต้องใช้การประมวลผลอย่างหนักที่มีประสิทธิภาพสูงบน Android RenderScript มีไว้สำหรับใช้กับการคำนวณแบบขนานกันของข้อมูลเป็นหลัก แม้ว่าเวิร์กโหลดแบบอนุกรมก็จะได้รับประโยชน์ด้วยเช่นกัน รันไทม์ RenderScript จะทำงานแบบขนานในโปรเซสเซอร์ที่มีอยู่ในอุปกรณ์ เช่น CPU และ GPU แบบหลายแกน วิธีนี้ช่วยให้ ให้คุณมุ่งเน้นที่การแสดงอัลกอริทึม มากกว่าการกำหนดเวลาทำงาน RenderScript มีประโยชน์อย่างยิ่งสำหรับแอปพลิเคชันที่ใช้การประมวลผลรูปภาพ การถ่ายภาพเชิงคำนวณ หรือคอมพิวเตอร์วิทัศน์
ในการเริ่มต้นใช้ RenderScript มีแนวคิดหลัก 2 ประการที่คุณควรทำความเข้าใจ:
- ภาษานี้เองเป็นภาษาที่มาจาก C99 สําหรับเขียนโค้ดการประมวลผลที่มีประสิทธิภาพสูง การเขียนเคอร์เนล RenderScript อธิบายถึง ในการเขียนเคอร์เนลการประมวลผล
- Control API ใช้สำหรับจัดการอายุการใช้งานของทรัพยากร RenderScript และควบคุมการเรียกใช้เคอร์เนล โดยมีให้ใช้งานใน 3 ภาษา ได้แก่ Java และ C++ ใน Android NDK และภาษาเคอร์เนลที่มาจาก C99 เอง การใช้ RenderScript จากโค้ด Java และ RenderScript แบบแหล่งที่มาเดียวอธิบายตัวเลือกแรกและตัวเลือกที่ 3 ตามลำดับ
การเขียนเคอร์เนล RenderScript
โดยปกติเคอร์เนล RenderScript จะอยู่ในไฟล์ .rs
ใน
ไดเรกทอรี <project_root>/src/rs
แต่ละไฟล์ของ .rs
จะเรียกว่า
script ทุกสคริปต์จะมีชุดเคอร์เนล ฟังก์ชัน และตัวแปรของตัวเอง สคริปต์อาจมีสิ่งต่อไปนี้
- การประกาศ 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 เคอร์เนล) และการลดเคอร์เนล
เคอร์เนลการแมปเป็นฟังก์ชันคู่ขนานที่ดำเนินการกับคอลเล็กชัน
Allocations
ของมิติข้อมูลเดียวกัน โดยค่าเริ่มต้น หนึ่งครั้งต่อทุกพิกัดในขนาดเหล่านั้น โดยปกติ (แต่ไม่ใช่เพียงอย่างเดียว) จะใช้เพื่อเปลี่ยนรูปแบบชุดข้อมูลอินพุต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
ดูคำอธิบายโดยละเอียดเพิ่มเติมเกี่ยวกับเคอร์เนลการลดได้ที่นี่
ระบบรองรับเคอร์เนลการลดใน 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 ที่เข้มงวด เป็นไปตามข้อกำหนดและใช้ความแม่นยำน้อยกว่า โหมดนี้จะเปิดใช้ Flush-to-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 ใน หนึ่งใน 2 วิธีต่อไปนี้
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
- หากใช้ API ของ Support Library คุณจะได้รับ APK ขนาดใหญ่กว่า (อาจจะอย่างมาก)
หากคุณใช้ API แบบดั้งเดิม (
android.renderscript
)
การใช้ API ไลบรารีการสนับสนุน RenderScript
หากต้องการใช้ Support Library RenderScript API คุณต้องกำหนดค่าสภาพแวดล้อมการพัฒนาเพื่อให้เข้าถึง API ได้ ต้องใช้เครื่องมือ Android SDK ต่อไปนี้ในการใช้งาน API เหล่านี้
- เครื่องมือ Android SDK เวอร์ชัน 22.2 ขึ้นไป
- เครื่องมือสร้าง Android SDK เวอร์ชันแก้ไข 18.1.0 ขึ้นไป
โปรดทราบว่าตั้งแต่ Android SDK Build-tools 24.0.0 และ Android 2.2 (API ระดับ 8) ไม่ได้รับการสนับสนุนอีกต่อไป
คุณสามารถตรวจสอบและอัปเดตเวอร์ชันที่ติดตั้งของเครื่องมือเหล่านี้ใน Android SDK Manager
หากต้องการใช้ Support Library RenderScript API ให้ทำดังนี้
- ตรวจสอบว่าคุณได้ติดตั้ง Android SDK เวอร์ชันที่จําเป็นแล้ว
- อัปเดตการตั้งค่าสำหรับกระบวนการสร้าง Android ให้รวมการตั้งค่า RenderScript ดังนี้
- เปิดไฟล์
build.gradle
ในโฟลเดอร์แอปของโมดูลแอปพลิเคชัน - เพิ่มการตั้งค่า RenderScript ต่อไปนี้ลงในไฟล์
android { compileSdkVersion 33 defaultConfig { minSdkVersion 9 targetSdkVersion 19 renderscriptTargetApi 18 renderscriptSupportModeEnabled true } }
android { compileSdkVersion(33) defaultConfig { minSdkVersion(9) targetSdkVersion(19) renderscriptTargetApi = 18 renderscriptSupportModeEnabled = true } }
การตั้งค่าที่ระบุไว้ข้างต้นจะควบคุมลักษณะการทำงานที่เฉพาะเจาะจงในกระบวนการสร้าง Android ดังนี้
renderscriptTargetApi
- ระบุเวอร์ชันไบต์โค้ดที่จะสร้าง เราขอแนะนําให้คุณตั้งค่านี้ให้เป็นระดับ API ต่ำสุดที่สามารถทำได้ ฟังก์ชันทั้งหมดที่คุณใช้และตั้งค่าrenderscriptSupportModeEnabled
ไปยังtrue
ค่าที่ถูกต้องสำหรับการตั้งค่านี้คือค่าจำนวนเต็มตั้งแต่ 11 ถึงระดับ API ที่เผยแพร่ล่าสุด หากตั้งค่าเวอร์ชัน SDK ขั้นต่ำที่ระบุไว้ในไฟล์ Manifest ของแอปพลิเคชันเป็นค่าอื่น ระบบจะไม่สนใจค่านั้น แต่จะตั้งค่าเวอร์ชัน SDK ขั้นต่ำเป็นค่าเป้าหมายในไฟล์บิลด์renderscriptSupportModeEnabled
- ระบุว่าควรใช้ไบต์โค้ดที่สร้างขึ้นกับเวอร์ชันที่เข้ากันได้หากอุปกรณ์ที่ใช้ไม่รองรับเวอร์ชันเป้าหมาย
- เปิดไฟล์
- ในคลาสแอปพลิเคชันที่ใช้ RenderScript ให้เพิ่มการนําเข้าสำหรับคลาสในคลังสนับสนุน ดังนี้
การใช้ RenderScript จากโค้ด Java หรือ Kotlin
การใช้ RenderScript จากโค้ด Java หรือ Kotlin จะใช้คลาส API ที่อยู่ในแพ็กเกจ android.renderscript
หรือ android.support.v8.renderscript
แอปพลิเคชันส่วนใหญ่มีรูปแบบการใช้งานพื้นฐานเหมือนกัน ดังนี้
- เริ่มต้นบริบท RenderScript บริบท
RenderScript
ที่สร้างขึ้นด้วยcreate(Context)
ช่วยให้มั่นใจได้ว่าจะใช้ RenderScript ได้ และมีออบเจ็กต์เพื่อควบคุมอายุการใช้งานของออบเจ็กต์ RenderScript ทั้งหมดที่ตามมา คุณควรพิจารณาบริบท เป็นการดำเนินการที่อาจใช้เวลานาน เนื่องจากอาจสร้างทรัพยากรใน ชิ้นส่วนฮาร์ดแวร์ ข้อความไม่ควรอยู่ในเส้นทางวิกฤติของแอปพลิเคชัน เท่าที่จะเป็นไปได้ โดยปกติแล้ว แอปพลิเคชันจะมีบริบท RenderScript เพียงรายการเดียวในแต่ละครั้ง - สร้าง
Allocation
อย่างน้อย 1 รายการเพื่อส่งไปยัง สคริปต์Allocation
คือออบเจ็กต์ RenderScript ที่มี พื้นที่เก็บข้อมูลในปริมาณคงที่ได้ เคอร์เนลในสคริปต์จะใช้ออบเจ็กต์Allocation
เป็นอินพุตและเอาต์พุต และสามารถเข้าถึงออบเจ็กต์Allocation
ในเคอร์เนลได้โดยใช้rsGetElementAt_type()
และrsSetElementAt_type()
เมื่อเชื่อมโยงเป็นตัวแปรส่วนกลางของสคริปต์ ออบเจ็กต์Allocation
ช่วยให้สามารถส่งอาร์เรย์จากโค้ด Java ไปยังโค้ด RenderScript และในทางกลับกันได้ โดยปกติแล้ว ออบเจ็กต์Allocation
รายการจะสร้างขึ้นโดยใช้createTyped()
หรือcreateFromBitmap()
- สร้างสคริปต์ที่จำเป็น สคริปต์มี 2 ประเภท
ให้คุณทราบเมื่อใช้ RenderScript:
- ScriptC: สคริปต์เหล่านี้คือสคริปต์ที่กำหนดโดยผู้ใช้ตามที่อธิบายไว้ในการเขียนเคอร์เนล RenderScript ข้างต้น ทุกสคริปต์มีคลาส Java
แสดงโดยคอมไพเลอร์ RenderScript เพื่อให้เข้าถึงสคริปต์จากโค้ด Java ได้ง่าย
ชั้นเรียนนี้มีชื่อ
ScriptC_filename
ตัวอย่างเช่น ถ้าเคอร์เนลการแมป ข้างต้นอยู่ในinvert.rs
และมีบริบท RenderScript อยู่แล้วmRenderScript
โค้ด Java หรือ Kotlin ที่จะสร้างอินสแตนซ์สคริปต์จะมีลักษณะดังนี้ - ScriptIntrinsic: นี่คือเคอร์เนล RenderScript ในตัวสำหรับการทำงานทั่วไป
เช่น การเบลอแบบเกาส์เชียน (Gaussian Blur) คอนโวลูชัน (Convolution) และการผสมรูปภาพ ดูข้อมูลเพิ่มเติมได้ที่คลาสย่อยของ
ScriptIntrinsic
- ScriptC: สคริปต์เหล่านี้คือสคริปต์ที่กำหนดโดยผู้ใช้ตามที่อธิบายไว้ในการเขียนเคอร์เนล RenderScript ข้างต้น ทุกสคริปต์มีคลาส Java
แสดงโดยคอมไพเลอร์ RenderScript เพื่อให้เข้าถึงสคริปต์จากโค้ด Java ได้ง่าย
ชั้นเรียนนี้มีชื่อ
- ป้อนข้อมูลการจัดสรร ยกเว้นการแบ่งสรรที่สร้างด้วย
createFromBitmap()
ระบบจะป้อนข้อมูลว่างเมื่อสร้างการแบ่งสรรเป็นครั้งแรก หากต้องการป้อนข้อมูลการจัดสรร ให้ใช้วิธีการ "copy" อย่างใดอย่างหนึ่งในAllocation
"สำเนา" เมธอดเป็นซิงโครนัส - ตั้งค่าตัวแปรส่วนกลางของสคริปต์ที่จำเป็น คุณสามารถตั้งค่าตัวแปรส่วนกลางได้โดยใช้เมธอดใน
ScriptC_filename
คลาสเดียวกันที่มีชื่อว่าset_globalname
สำหรับ เช่น หากต้องการตั้งค่าตัวแปรint
ที่ชื่อthreshold
ให้ใช้เมธอด เมธอด Javaset_threshold(int)
; และหากต้องการตั้งค่า ตัวแปรrs_allocation
ชื่อlookup
ให้ใช้ Java วิธีการset_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
ในการรับผลลัพธ์ของเคอร์เนลการลด คุณต้องใช้เมธอดjavaFutureType.get()
เมธอด "copy" และget()
เป็นแบบซิงค์ - แยกบริบท RenderScript ออก คุณสามารถทำลายบริบท RenderScript ได้
ด้วย
destroy()
หรือการอนุญาตบริบท RenderScript เป็นขยะที่รวบรวมมา การดำเนินการนี้จะทำให้มีการใช้ออบเจ็กต์ของออบเจ็กต์นั้นต่อไป ที่จะแสดงข้อยกเว้น
รูปแบบการดําเนินการแบบอะซิงโครนัส
forEach
, invoke
, reduce
ที่แสดง
และ set
เป็นเมธอดแบบไม่พร้อมกัน ซึ่งแต่ละเมธอดอาจกลับไปยัง Java ก่อนที่จะดำเนินการตาม
การดำเนินการที่ขอ อย่างไรก็ตาม การดำเนินการแต่ละรายการจะเรียงลำดับตามลำดับที่มีการเรียกใช้
คลาส Allocation
มีเมธอด "copy" ในการคัดลอกข้อมูลจากและไปยัง Allocation "สำเนา" ของเมธอดเป็นแบบซิงโครนัส และได้รับการเรียงลำดับตาม
ของการทำงานแบบอะซิงโครนัสด้านบนที่สัมผัสกับการจัดสรรเดียวกัน
คลาส javaFutureType ที่สะท้อนระบุ
เมธอด get()
เพื่อหาผลลัพธ์ของการลด get()
คือ
แบบซิงโครนัส และเรียงลำดับตามการลดลง (ซึ่งเป็นอะซิงโครนัส)
RenderScript แบบแหล่งที่มาเดียว
Android 7.0 (API ระดับ 24) เปิดตัวฟีเจอร์การเขียนโปรแกรมใหม่ที่เรียกว่าแหล่งที่มาเดียว
RenderScript ซึ่งมีการเปิดใช้งานเคอร์เนลจากสคริปต์ในจุดที่กำหนด แทนที่จะเป็น
จาก Java ปัจจุบันแนวทางนี้จํากัดไว้สําหรับการแมปเคอร์เนล ซึ่งเรียกง่ายๆ ว่า "เคอร์เนล" ในส่วนนี้เพื่อความกระชับ ฟีเจอร์ใหม่นี้ยังรองรับการสร้างการกําหนดประเภท
rs_allocation
จากภายในสคริปต์ด้วย ตอนนี้คุณใช้อัลกอริทึมทั้งหมดภายในสคริปต์ได้เพียงอย่างเดียวแล้ว แม้ว่าจะต้องมีการเริ่มเคอร์เนลหลายครั้งก็ตาม
คุณจะได้ประโยชน์แบบ 2 อย่าง คือ โค้ดที่อ่านง่ายขึ้นเพราะช่วยให้ติดตั้งใช้งานอัลกอริทึมได้อย่างต่อเนื่อง
ภาษาเดียว และเป็นโค้ดที่อาจจะเร็วกว่า เพราะมีการเปลี่ยนระหว่าง Java กับ
RenderScript เมื่อมีการเปิดตัวเคอร์เนลหลายรายการ
ใน RenderScript ของแหล่งที่มาเดียว คุณเขียนเคอร์เนลตามที่อธิบายไว้ใน
การเขียน Kernel ของ RenderScript จากนั้นคุณเขียนฟังก์ชันที่เรียกใช้ไม่ได้
rsForEach()
เพื่อเปิดใช้ API ดังกล่าวใช้ฟังก์ชันเคอร์เนลเป็นพารามิเตอร์แรก ตามด้วยการจัดสรรอินพุตและเอาต์พุต API ที่คล้ายกัน
rsForEachWithOptions()
รับอาร์กิวเมนต์ประเภทเพิ่มเติม
rs_script_call_t
ซึ่งระบุชุดย่อยขององค์ประกอบจากอินพุตและ
การจัดสรรเอาต์พุตสำหรับฟังก์ชันเคอร์เนลที่จะประมวลผล
ในการเริ่มการคำนวณ RenderScript คุณจะเรียกใช้ฟังก์ชันที่เรียกใช้ได้จาก Java
ทำตามขั้นตอนในหัวข้อการใช้ RenderScript จากโค้ด Java
ในขั้นตอนเปิดใช้งานเคอร์เนลที่เหมาะสม ให้เรียกใช้
ฟังก์ชันที่เรียกใช้ได้โดยใช้ invoke_function_name()
ซึ่งจะเริ่มต้น
การประมวลผลทั้งหมด รวมถึงการเปิดใช้งาน Kernel
การจัดสรรมักจำเป็นสำหรับการบันทึกและส่งผลลัพธ์ขั้นกลางจากการเปิดใช้งานเคอร์เนลหนึ่งไปยังอีกเคอร์เนลหนึ่ง คุณสร้างแท็กได้โดยใช้
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 ดังนี้
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)
// 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; }
หมายเหตุ: รวมถึงตัวอย่างการลดเสียง เคอร์เนลที่นี่
รันไทม์ RenderScript จะสร้างตัวแปรอย่างน้อย 1 รายการที่เรียกว่ารายการข้อมูลตัวสะสมเพื่อเก็บสถานะกระบวนการลด รันไทม์ของ RenderScript
เลือกจำนวนรายการข้อมูลสะสมในลักษณะที่จะเพิ่มประสิทธิภาพสูงสุด ประเภทของรายการข้อมูลตัวสะสม (accumType) จะเป็นค่าที่กําหนดโดยฟังก์ชันตัวสะสมของเคอร์เนล โดยอาร์กิวเมนต์แรกในฟังก์ชันดังกล่าวจะเป็นพอยน์เตอร์ไปยังรายการข้อมูลตัวสะสม โดยค่าเริ่มต้น รายการข้อมูลตัวสะสมทั้งหมดจะเริ่มต้นด้วยค่า 0 (เหมือนกับ memset
) อย่างไรก็ตาม คุณสามารถเขียนฟังก์ชันตัวเริ่มต้นเพื่อดำเนินการอย่างอื่นได้
ตัวอย่าง: ใน addint
เคอร์เนล รายการข้อมูลสะสม (ประเภท int
) จะใช้ในการเพิ่มอินพุต
ไม่มีฟังก์ชันเริ่มต้น ดังนั้นรายการข้อมูลสะสมแต่ละรายการจะเริ่มการทำงานเป็น
ศูนย์
ตัวอย่าง: ในเคอร์เนล findMinAndMax ระบบจะใช้รายการข้อมูลตัวสะสม (ประเภท MinAndMax
) เพื่อติดตามค่าต่ำสุดและสูงสุดที่พบจนถึงตอนนี้ มีฟังก์ชันเริ่มต้นเพื่อตั้งค่าเหล่านี้เป็น LONG_MAX
และ
LONG_MIN
ตามลำดับ และตั้งค่าตำแหน่งของค่าเหล่านี้เป็น -1 ซึ่งบ่งชี้ว่า
ค่าต่างๆ ไม่ได้แสดงอยู่ในส่วน (ว่าง) ของข้อมูลที่ป้อน
ประมวลผลแล้ว
RenderScript เรียกใช้ฟังก์ชัน Accumulator เพียงครั้งเดียวสำหรับทุกพิกัดใน อินพุต โดยปกติแล้ว ฟังก์ชันควรอัปเดตรายการข้อมูล Accumulator บางอย่าง ตามข้อมูลที่ป้อน
ตัวอย่าง: ในเคอร์เนล addint ฟังก์ชันตัวสะสมจะเพิ่มค่าขององค์ประกอบอินพุตลงในรายการข้อมูลของตัวสะสม
ตัวอย่าง: ในเคอร์เนล findMinAndMax ฟังก์ชันตัวสะสมจะตรวจสอบว่าค่าขององค์ประกอบอินพุตน้อยกว่าหรือเท่ากับค่าต่ำสุดที่บันทึกไว้ในรายการข้อมูลตัวสะสม และ/หรือมากกว่าหรือเท่ากับค่าสูงสุดที่บันทึกไว้ในรายการข้อมูลตัวสะสมหรือไม่ และจะอัปเดตรายการข้อมูลตัวสะสมตามความเหมาะสม
หลังจากที่เรียกฟังก์ชัน Accumulator 1 ครั้งสำหรับทุกพิกัดในอินพุตแล้ว RenderScript ต้องรวม accumulator เข้าด้วยกัน รายการข้อมูลรวมกันเป็นรายการเดียว คุณอาจเขียนฟังก์ชันการรวมเพื่อดำเนินการนี้ หากฟังก์ชัน Accumulator มีอินพุตเดียวและ ไม่มีอาร์กิวเมนต์พิเศษ คุณไม่จำเป็นต้องเขียนโปรแกรมผสม ฟังก์ชัน; RenderScript จะใช้ฟังก์ชัน Accumulator เพื่อรวมข้อมูลของสะสม รายการ (คุณยังคงเขียนฟังก์ชันการรวมได้หากลักษณะการทำงานเริ่มต้นนี้ไม่ใช่สิ่งที่คุณต้องการ)
ตัวอย่าง: ในเคอร์เนล addint ไม่มีฟังก์ชันตัวรวม ดังนั้นระบบจะใช้ฟังก์ชันตัวสะสม ลักษณะการทำงานนี้ถูกต้องแล้ว เนื่องจากหากเราแยกคอลเล็กชันค่าออกเป็น 2 ส่วน แล้วบวกค่าในส่วนนั้นๆ แยกกัน การบวกผลรวม 2 รายการนั้นเหมือนกับการบวกคอลเล็กชันทั้งหมด
ตัวอย่าง: ในเคอร์เนล findMinAndMax ฟังก์ชันตัวรวมจะตรวจสอบว่าค่าต่ำสุดที่บันทึกไว้ในรายการข้อมูลสะสม "แหล่งที่มา" *val
น้อยกว่าค่าต่ำสุดที่บันทึกไว้ในรายการข้อมูลสะสม "ปลายทาง" *accum
หรือไม่ และอัปเดต *accum
ตามนั้น โดยจะทำงานที่คล้ายกันเพื่อให้ได้ค่าสูงสุด การอัปเดตนี้ในวันที่ *accum
กลับไปเป็นสถานะที่ควรจะได้ หากค่าอินพุตทั้งหมดมีการรวบรวมไว้ใน
*accum
แทนบางส่วนลงใน *accum
และบางส่วนเป็น
*val
หลังจากรวมรายการข้อมูล Accumulator ทั้งหมดแล้ว 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)
(บังคับ): ระบุว่าเคอร์เนลการลดคือ มีการกำหนดไว้ Method ของ Java ที่สะท้อนreduce_kernelName
จะเปิดตัว เคอร์เนลinitializer(initializerName)
(ไม่บังคับ): ระบุชื่อของฟังก์ชันเริ่มต้นสำหรับเคอร์เนลการลดขนาดนี้ เมื่อคุณเปิดใช้งานเคอร์เนล RenderScript จะเรียกใช้ฟังก์ชันนี้ 1 ครั้งสําหรับรายการข้อมูลตัวสะสมแต่ละรายการ ต้องกำหนดไว้ดังนี้static void initializerName(accumType *accum) { … }
accum
คือตัวชี้ไปยังรายการข้อมูลสะสมสำหรับฟังก์ชันนี้ เริ่มต้นหากคุณไม่ได้มีฟังก์ชันเริ่มต้น RenderScript จะเป็นผู้เริ่มต้น Accumulator ทั้งหมด รายการข้อมูลเป็น 0 (เหมือนกับภายใน
memset
) โดยทำงานเสมือนว่ามีการเริ่มต้น ที่มีหน้าตาดังนี้static void initializerName(accumType *accum) { memset(accum, 0, sizeof(*accum)); }
accumulator(accumulatorName)
(ต้องระบุ): ระบุชื่อฟังก์ชันตัวสะสมสำหรับเคอร์เนลการลดนี้ เมื่อคุณเปิดใช้งานเคอร์เนล จะเรียกใช้ RenderScript ฟังก์ชันนี้ครั้งเดียวต่อทุกพิกัดในอินพุต เพื่ออัปเดตแท็ก รายการข้อมูลสะสมในทางใดทางหนึ่งตามอินพุต ฟังก์ชันต้องได้รับการกําหนดดังนี้static void accumulatorName(accumType *accum, in1Type in1, …, inNType inN [, specialArguments]) { … }
accum
คือตัวชี้ไปยังรายการข้อมูลตัวสะสมเพื่อให้ฟังก์ชันนี้แก้ไขin1
ถึงinN
คืออาร์กิวเมนต์อย่างน้อย 1 รายการที่ จะมีการเติมข้อมูลโดยอัตโนมัติตามอินพุตที่ส่งไปยังการเปิดใช้เคอร์เนล อาร์กิวเมนต์หนึ่งรายการ ต่ออินพุต ฟังก์ชันตัวสะสมอาจใช้อาร์กิวเมนต์พิเศษใดก็ได้เคอร์เนลตัวอย่างที่มีหลายอินพุตคือ
dotProduct
combiner(combinerName)
(ไม่บังคับ): ระบุชื่อฟังก์ชันตัวรวมสำหรับเคอร์เนลการลดขนาดนี้ หลังจาก RenderScript เรียกใช้ฟังก์ชัน Accumulator หนึ่งครั้งต่อทุกพิกัดในอินพุต ฟังก์ชันนี้จะเรียกใช้ฟังก์ชันนี้ จำนวนครั้งเท่าที่จำเป็นในการรวมรายการข้อมูลสะสมทั้งหมดเป็นรายการเดียว รายการข้อมูล Accumulator โดยต้องกำหนดฟังก์ชันดังนี้
static void combinerName(accumType *accum, const accumType *other) { … }
accum
คือตัวชี้ไปยังรายการข้อมูลสะสม "ปลายทาง" สำหรับฟังก์ชันนี้ที่จะแก้ไขother
เป็นตัวชี้ไปยัง "แหล่งที่มา" รายการข้อมูล Accumulator เพื่อให้ฟังก์ชันนี้ "รวม" ลงใน*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 รวม Accumulator ทั้งหมดแล้ว โมเดลจะเรียกฟังก์ชันนี้เพื่อระบุผลลัพธ์ของค่า เพื่อกลับไปยัง Java โดยต้องกำหนดฟังก์ชันดังนี้static void outconverterName(resultType *result, const accumType *accum) { … }
result
คือพอยน์เตอร์ไปยังรายการข้อมูลผลลัพธ์ (ที่จัดสรรแต่ไม่ได้เริ่มต้นโดยรันไทม์ RenderScript) เพื่อให้ฟังก์ชันนี้เริ่มต้นด้วยผลลัพธ์ของการลด resultType คือประเภทของรายการข้อมูลดังกล่าว ซึ่งไม่จำเป็นต้องเหมือนกับ accumTypeaccum
คือตัวชี้ไปยังรายการข้อมูล Accumulator สุดท้าย ที่คํานวณโดยฟังก์ชัน Combinerหากคุณไม่ได้มีฟังก์ชัน Outconverter ไว้ RenderScript จะคัดลอก Accumulator สุดท้าย ไปยังรายการข้อมูลผลลัพธ์ โดยทำงานราวกับมีฟังก์ชัน Outconverter ที่ ซึ่งมีลักษณะดังนี้
static void outconverterName(accumType *result, const accumType *accum) { *result = *accum; }
หากคุณต้องการผลลัพธ์ประเภทอื่นที่ไม่ใช่ประเภทข้อมูล Accumulator คุณต้องใช้ฟังก์ชัน Outconverter
โปรดทราบว่าเคอร์เนลจะมีประเภทอินพุต ประเภทข้อมูล Accumulator และประเภทผลลัพธ์
ซึ่งไม่จำเป็นต้องเหมือนกัน ตัวอย่างเช่น ในเคอร์เนล findMinAndMax ประเภทอินพุต long
, ประเภทรายการข้อมูลตัวสะสม MinAndMax
และประเภทผลลัพธ์ int2
ทั้งหมดจะแตกต่างกัน
สิ่งที่คุณไม่สามารถอนุมานได้
คุณต้องไม่ต้องอาศัยจำนวนรายการข้อมูลสะสมที่ RenderScript สร้างขึ้นสำหรับ ที่ระบุการเปิดเคอร์เนล ไม่มีการรับประกันว่าการเปิดตัวเคอร์เนลเดียวกัน 2 ครั้งด้วย อินพุตเดียวกันจะสร้างรายการข้อมูลสะสมจำนวนเท่ากัน
คุณไม่ควรยึดตามลำดับที่ RenderScript เรียกฟังก์ชันตัวเริ่มต้น ตัวสะสม และตัวรวม ฟังก์ชันเหล่านี้อาจเรียกใช้พร้อมกัน ไม่มีการรับประกันว่าการเปิดใช้งานเคอร์เนลเดียวกัน 2 ครั้งโดยใช้อินพุตเดียวกันจะเป็นไปตามลําดับเดียวกัน การรับประกันเพียงอย่างเดียวคือมีเพียงฟังก์ชันตัวเริ่มต้นเท่านั้นที่จะเห็นรายการข้อมูลตัวสะสมที่ยังไม่ได้เริ่มต้น เช่น
- เราไม่รับประกันว่าระบบจะเริ่มต้นรายการข้อมูลตัวสะสมทั้งหมดก่อนที่จะเรียกใช้ฟังก์ชันตัวสะสม แม้ว่าระบบจะเรียกใช้เฉพาะกับรายการข้อมูลตัวสะสมที่เริ่มต้นแล้วเท่านั้น
- ไม่มีการรับประกันลำดับการส่งองค์ประกอบอินพุตไปยัง Accumulator
- ไม่มีการรับประกันว่ามีการเรียกฟังก์ชัน accumulator สำหรับองค์ประกอบอินพุตทั้งหมด ก่อนที่จะเรียกใช้ฟังก์ชันคอมโพสิต
ผลที่ตามมาอย่างหนึ่งคือ findMinAndMax ของเคอร์เนลไม่ใช่ค่าที่แน่นอน หากอินพุตมีค่าต่ำสุดหรือสูงสุดเดียวกันซ้ำกันมากกว่า 1 ครั้ง คุณจะไม่มีทางรู้ว่าเคอร์เนลจะพบค่าใด
คุณต้องรับประกันสิ่งใด
เนื่องจากระบบ RenderScript เลือกที่จะเรียกใช้เคอร์เนลในหลาย หลากหลายวิธี คุณต้องทำตามกฎบางอย่างเพื่อให้แน่ใจว่าเคอร์เนลทำงานตาม ในแบบที่คุณต้องการ หากไม่ปฏิบัติตามกฎเหล่านี้ คุณอาจได้ผลลัพธ์ที่ไม่ถูกต้อง ลักษณะการทำงานที่ไม่แน่นอน หรือข้อผิดพลาดรันไทม์
กฎด้านล่างมักระบุว่ารายการข้อมูลตัวสะสม 2 รายการต้องมี "ค่าเดียวกัน" หมายความว่าอย่างไร ขึ้นอยู่กับสิ่งที่คุณต้องการให้เคอร์เนลทำ สำหรับ การลดทางคณิตศาสตร์ เช่น addint มักจะเหมาะสมกว่า สำหรับ "เดียวกัน" หมายถึงความเท่าเทียมทางคณิตศาสตร์ สำหรับ "เลือกอะไรก็ได้" ค้นหาแบบนั้น เป็น findMinAndMax ("ค้นหาตำแหน่งต่ำสุดและ ค่าอินพุตสูงสุด") ซึ่งอาจจะมีอินพุตที่เหมือนกันมากกว่า 1 รายการ ตำแหน่งทั้งหมดของค่าอินพุตที่ระบุต้องมีสถานะเป็น "เดียวกัน" คุณสามารถเขียนว่า เคอร์เนลที่คล้ายกันเพื่อ "หาตำแหน่งของค่าอินพุตต่ำสุดและสูงสุดซ้ายสุด" โดยที่ (เช่น) จะเลือกใช้ค่าขั้นต่ำที่ตำแหน่ง 100 มากกว่าค่าขั้นต่ำที่เหมือนกันที่สถานที่ตั้ง 200; สำหรับเคอร์เนลนี้ "เดียวกัน" จะหมายถึงตำแหน่งเดียวกัน ไม่ใช่แค่ value ที่เหมือนกัน และฟังก์ชัน Accumulator และคำสั่งรวมจะต้อง แตกต่างจากช่อง 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 เคอร์เนล ฟังก์ชันชุดค่าผสมจะเพิ่มค่ารายการข้อมูล Accumulator 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
:
// 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
// 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
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()
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 รายการสำหรับ
ทุกอาร์กิวเมนต์อินพุตใน accumulator ของเคอร์เนล
Functions รันไทม์ของ RenderScript ตรวจสอบเพื่อให้แน่ใจว่าการจัดสรรอินพุตทั้งหมด
มีมิติข้อมูลเดียวกัน และ Element
ประเภทของแต่ละ
การจัดสรรอินพุตตรงกับอาร์กิวเมนต์อินพุตที่เกี่ยวข้องของ accumulator
ต้นแบบของฟังก์ชัน หากการตรวจสอบเหล่านี้ล้มเหลว RenderScript จะส่งข้อยกเว้น โดยการประมวลผลของเคอร์เนลจะดำเนินการกับพิกัดทุกรายการในมิติข้อมูลเหล่านั้น
วิธีที่ 2 จะเหมือนกับวิธีที่ 1 เว้นแต่ว่าวิธีที่ 2 จะใช้เวลาเพิ่มเติม
อาร์กิวเมนต์ sc
ที่สามารถใช้เพื่อจำกัดการดำเนินการของเคอร์เนลกับส่วนย่อยของ
พิกัด
วิธีที่ 3 เหมือนกับวิธีที่ 1 เว้นแต่ว่า
แทนที่จะใช้อินพุตการจัดสรร
แต่จะต้องใช้อินพุตอาร์เรย์ Java ซึ่งเป็นการอำนวยความสะดวก
ช่วยคุณไม่ต้องเขียนโค้ดเพื่อสร้างการจัดสรรและคัดลอกข้อมูลอย่างชัดแจ้ง
จากอาร์เรย์ Java อย่างไรก็ตาม การใช้วิธีที่ 3 แทนวิธีที่ 1 ไม่ได้เพิ่มอัตรา
ประสิทธิภาพของโค้ด สำหรับอาร์เรย์อินพุตแต่ละรายการ เมธอด 3 จะสร้างการจัดสรรชั่วคราว 1 มิติที่มีประเภท Element
ที่เหมาะสมและเปิดใช้ setAutoPadding(boolean)
แล้วคัดลอกอาร์เรย์ไปยังการจัดสรรราวกับว่าใช้เมธอด copyFrom()
ที่เหมาะสมของ Allocation
จากนั้นจึงเรียกใช้เมธอดที่ 1 โดยส่งการจัดสรรชั่วคราวเหล่านั้น
หมายเหตุ: หากแอปพลิเคชันของคุณใช้การเรียกเคอร์เนลหลายครั้งด้วย อาร์เรย์เดียวกัน หรือที่มีมิติข้อมูลและประเภทขององค์ประกอบเดียวกันในอาร์เรย์ที่แตกต่างกัน คุณสามารถปรับปรุง โดยการสร้าง ป้อนข้อมูล และนำการจัดสรรซ้ำไปใช้ ด้วยตัวเอง แทนที่จะ โดยใช้วิธีที่ 3
javaFutureType
ประเภทการแสดงผลจากวิธีลดที่สะท้อนกลับแสดงถึง
คลาสที่ซ้อนกันแบบคงที่ภายใน ScriptC_filename
ซึ่งเป็นผลลัพธ์ในอนาคตของ
เคอร์เนล ในการรับผลลัพธ์ที่แท้จริงของการเรียกใช้ ให้เรียกใช้
เมธอด get()
ของคลาสนั้น ซึ่งจะแสดงค่า
ประเภท javaResultType get()
เป็นแบบเรียลไทม์
class ScriptC_filename(rs: RenderScript) : ScriptC(…) { object javaFutureType { fun get(): javaResultType { … } } }
public class ScriptC_filename extends ScriptC { public static class javaFutureType { public javaResultType get() { … } } }
javaResultType ที่กำหนดจาก resultType ของ ฟังก์ชันเอาต์แปลง เว้นแต่ 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
เช่น
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> = … } }
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
อาร์เรย์เป็นการนำเสนอแบบ flatten ซึ่งมีมากกว่า 2 เท่า องค์ประกอบสเกลาร์จำนวนมากเนื่องจากการจัดสรรมีเวกเตอร์ 2 องค์ประกอบ องค์ประกอบ ซึ่งทำงานในลักษณะเดียวกับเมธอดcopyFrom()
ของAllocation
- หาก inXType เป็น
uint
แสดงว่า deviceSiInXType มีค่าเป็นint
ระบบจะตีความค่าที่มีเครื่องหมายในอาร์เรย์ Java เป็นค่าที่ไม่มีเครื่องหมายของรูปแบบบิตเดียวกันในการแบ่งสรร ซึ่งจะเป็นวิธีเดียวกับที่copyFrom()
วิธีการทำงานAllocation
- หาก inXType เป็น
uint2
ให้ใช้ deviceSiInXType มีค่าint
วิธีนี้เป็นการผสมผสานระหว่างint2
กับuint
มีการจัดการ: อาร์เรย์เป็นการแสดงค่าเดี่ยว และค่าที่มีการรับรองของอาร์เรย์ Java คือ ถูกตีความเป็นค่า Element ที่ไม่มีการลงชื่อใน 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 ที่กล่าวถึงในหน้านี้