RenderScript

RenderScript 是一種架構,可用於在 Android 上以高效能執行需要進行大量運算的工作。這項服務專為資料平行運算設計,但也可為序列工作負載帶來好處。RenderScript 執行階段會透過裝置上可用的各個處理器 (例如多核心 CPU 和 GPU) 平行處理工作,讓開發人員可以專注於表示演算法,而非安排執行工作。RenderScript 特別適合用於執行圖片處理作業、計算攝影或電腦視覺的應用程式。

搭載 Android 8.0 以上版本的裝置會使用下列 RenderScript 架構和供應商 HAL:

圖 1. 連結至內部程式庫的供應商程式碼。

與 Android 7.x 以下版本的 RenderScript 相比,以下是兩者之間的差異:

  • 程序中兩個 RenderScript 內部程式庫的例項。一組設定用於 CPU 備用路徑,且來自 /system/lib,另一個設定則用於 GPU 路徑,且來自 /system/lib/vndk-sp
  • /system/lib 中的 RS 內部程式庫是平台的一部分,會隨著 system.img 升級而更新。不過,/system/lib/vndk-sp 中的程式庫是專為供應商而建構,不會在 system.img 升級時更新 (雖然可以針對安全性修正進行更新,但 ABI 維持不變)。
  • 供應商程式碼 (RS HAL、RS 驅動程式和 bcc plugin) 會連結至位於 /system/lib/vndk-sp 的 RenderScript 內部程式庫。這些目錄無法與 /system/lib 中的程式庫建立連結,因為該目錄中的程式庫是專為平台所設計,因此可能與供應商程式碼不相容 (例如,可能會移除符號)。否則就無法使用僅限架構的 OTA。

設計

以下各節將詳細說明 Android 8.0 以上版本中的 RenderScript 設計。

供應商可用的 RenderScript 程式庫

本節列出可供供應商程式碼使用的算繪指令程式庫 (稱為供應商 NDK 同進程式 HAL 或 VNDK-SP),並可連結至這些程式庫。它也詳細說明與 RenderScript 無關,但也提供給供應商程式的其他程式庫。

雖然不同 Android 版本的程式庫清單可能不同,但特定 Android 版本的程式庫清單不會變更;如需最新的程式庫清單,請參閱 /system/etc/ld.config.txt

RenderScript 程式庫 非 RenderScript 程式庫
  • android.hardware.graphics.renderscript@1.0.so
  • libRS_internal.so
  • libRSCpuRef.so
  • libblas.so
  • libbcinfo.so
  • libcompiler_rt.so
  • libRSDriver.so
  • libc.so
  • libm.so
  • libdl.so
  • libstdc++.so
  • liblog.so
  • libnativewindow.so
  • libsync.so
  • libvndksupport.so
  • libbase.so
  • libc++.so
  • libcutils.so
  • libutils.so
  • libhardware.so
  • libhidlbase.so
  • libhidltransport.so
  • libhwbinder.so
  • liblzma.so
  • libz.so
  • libEGL.so
  • libGLESv1_CM.so
  • libGLESv2.so

連結器命名空間設定

在執行階段使用連結器命名空間時,會強制執行連結限制,以防止供應商程式碼使用不在 VNDK-SP 中的程式庫。(詳情請參閱 VNDK Design 簡報)。

在搭載 Android 8.0 以上版本的裝置上,所有相同程序 HAL (SP-HAL) 除了 RenderScript 以外,都會載入至連結器命名空間 sphal 中。RenderScript 會載入至專屬於 RenderScript 的命名空間 rs,這個位置可讓 RenderScript 程式庫的執行較為寬鬆。由於 RS 實作需要載入已編譯的位元碼,因此 /data/*/*.so 會新增至 rs 命名空間的路徑中 (系統不允許其他 SP-HAL 從資料分區載入程式庫)。

此外,rs 命名空間允許的程式庫比其他命名空間提供的程式庫更多。libmediandk.solibft2.so 會向 rs 命名空間公開,因為 libRS_internal.so 具有這些程式庫的內部依附元件。

圖 2. 連結器的命名空間設定。

負載驅動程式

CPU 備用路徑

視建立 RS 情境時是否存在 RS_CONTEXT_LOW_LATENCY 位元而定,系統會選取 CPU 或 GPU 路徑。選取 CPU 路徑後,系統會直接從提供 RS 程式庫平台版本的預設連接器命名空間的 libRS_internal.so (RS 架構的主要實作) 直接 dlopen

當採用 CPU 備用路徑,並使用空值 mVendorDriverName 建立 RsContext 物件時,系統完全不會使用供應商的 RS HAL 實作項目。libRSDriver.so 預設會 dlopen,且驅動程式程式庫會從 default 命名空間載入,因為呼叫端 (libRS_internal.so) 也會在 default 命名空間中載入。

圖 3. CPU 回退路徑。

GPU 路徑

對於 GPU 路徑,libRS_internal.so 的載入方式有所不同。首先,libRS.so 會使用 android.hardware.renderscript@1.0.so (以及其底層 libhidltransport.so) 將 android.hardware.renderscript@1.0-impl.so (RS HAL 的供應商實作) 載入至名為 sphal 的不同連結器命名空間。RS HAL 可以在另一個名為 rs 的連結器命名空間中 dlopenlibRS_internal.so

供應商可以設定建構時間旗標 OVERRIDE_RS_DRIVER (嵌入 RS HAL 實作項目 (hardware/interfaces/renderscript/1.0/default/Context.cpp) 來提供自己的 RS 驅動程式。之後,這個驅動程式名稱會dlopen用於 GPU 路徑的 RS 情境。

RsContext 物件的建立作業會委派給 RS HAL 實作。HAL 會使用 rsContextCreateVendor() 函式回呼至 RS 架構,並使用驅動程式的名稱做為引數。接著,RS 架構會在 RsContext 初始化時載入指定的驅動程式。在本範例中,驅動程式程式庫會載入至 rs 命名空間,因為 RsContext 物件是在 rs 命名空間中建立,/vendor/lib 則位於命名空間的搜尋路徑中。

圖 4. GPU 備用路徑。

default 命名空間轉換至 sphal 命名空間時,libhidltransport.so 會使用 android_load_sphal_library() 函式明確排序動態連結器,從 sphal 命名空間載入 -impl.so 程式庫。

sphal 命名空間轉換至 rs 命名空間時,載入作業會間接透過 /system/etc/ld.config.txt 中的下列行完成:

namespace.sphal.link.rs.shared_libs = libRS_internal.so

這行程式碼會指定動態連結器應在無法從 sphal 命名空間找到/載入 lib 時,從 rs 命名空間載入 libRS_internal.so (這一點一向如此,因為 sphal 命名空間不會搜尋 libRS_internal.so 所在的 /system/lib/vndk-sp)。有了這項設定,只要對 libRS_internal.so 發出簡單的 dlopen() 呼叫,就能進行命名空間轉換。

載入密件副本外掛程式

bcc plugin 是供應商提供的程式庫,已載入至 bcc 編譯器。由於 bcc/system/bin 目錄中的系統程序,因此 bcc plugin 程式庫可視為 SP-HAL (也就是可直接載入系統程序的供應商 HAL,無須繫結)。bcc-plugin 程式庫是 SP-HAL,可執行以下操作:

  • 無法連結至僅限架構的程式庫,例如 libLLVM.so
  • 只能連結供應商可用的 VNDK-SP 程式庫。

這項限制是透過使用 android_sphal_load_library() 函式,將 bcc plugin 載入 sphal 命名空間來強制執行。在舊版 Android 中,外掛程式名稱是使用 -load 選項指定,而程式庫則是使用 libLLVM.so 的簡易 dlopen() 載入。在 Android 8.0 以上版本中,-plugin 選項會指定這個項目,而 bcc 本身會直接載入 lib。這個選項可啟用非 Android 專屬的開放原始碼 LLVM 專案路徑。

圖 5. 載入 bcc 外掛程式,Android 7.x 以下版本。



圖 6. 載入 bcc 外掛程式,Android 8.0 以上版本。

ld.mc 的搜尋路徑

執行 ld.mc 時,系統會將某些 RS 執行階段程式庫做為連接器的輸入內容提供。應用程式中的 RS 位元碼會連結至執行階段程式庫,而當轉換後的位元碼載入至應用程式程序時,執行階段程式庫會再次從轉換後的位元碼動態連結。

執行階段程式庫包括:

  • libcompiler_rt.so
  • libm.so
  • libc.so
  • RS 驅動程式 (libRSDriver.soOVERRIDE_RS_DRIVER)

將編譯的位元碼載入應用程式程序時,請提供 ld.mc 使用的確切相同程式庫。否則,編譯的位元碼可能無法找到連結時可用的符號。

為此,RS 架構會在執行 ld.mc 時,根據 RS 架構本身是從 /system/lib 還是 /system/lib/vndk-sp 載入,使用不同的執行階段程式庫搜尋路徑。您可以透過讀取 RS 架構程式庫任意符號的地址,然後使用 dladdr() 取得對應至該地址的檔案路徑,來判斷這項資訊。

SELinux 政策

由於 Android 8.0 以上版本的 SELinux 政策有所變更,因此您必須在 vendor 分割區中標示其他檔案時,遵循特定規則 (透過 neverallows 強制執行):

  • vendor_file」必須是 vendor 分區中所有檔案的預設標籤。平台政策要求這項權限才能存取直通 HAL 實作。
  • 透過供應商 SEPolicy 在 vendor 分區中新增的所有新 exec_types 都必須具有 vendor_file_type 屬性。這項規定會透過 neverallows 強制執行。
  • 為避免與日後的平台/架構更新發生衝突,請勿在 vendor 分割區中標記 exec_types 以外的檔案。
  • AOSP 所識別的相同程序 HAL 的所有程式庫依附元件,都必須標示為 same_process_hal_file

如要進一步瞭解 SELinux 政策,請參閱「Android 中的安全增強式 Linux」。

中間碼的 ABI 相容性

如未新增任何 API,也就是沒有 HAL 版本串聯,RS 架構會繼續使用現有的 GPU (HAL 1.0) 驅動程式。

如果是不會影響位元碼的次要 HAL 變更 (HAL 1.1),架構應為這些新加入的 API 改用 CPU,並在其他地方繼續使用 GPU (HAL 1.0) 驅動程式。

如果 HAL 有重大變更 (HAL 2.0) 影響位元碼編譯/連結,RS 架構應選擇不載入供應商提供的 GPU 驅動程式,改為使用 CPU 或 Vulkan 路徑進行加速。

使用 RenderScript 中間碼分成三個階段:

征途城市 詳細說明
編譯
  • bcc 的輸入位元碼 (.bc) 必須採用 LLVM 3.2 位元碼格式,且 bcc 必須與現有 (舊版) 應用程式相容。
  • 不過,.bc 中的中繼資料可能會變更 (可能會有新的執行階段函式,例如分配 setter 和 getter、數學函式等)。部分的執行階段函式位於 libclcore.bc 中,部分則位於 LibRSDriver 或相應的供應商中。
  • 使用新的執行階段函式或破壞中繼資料變更時,必須增加 Bitcode API 級別。由於供應商驅動程式無法使用,因此 HAL 版本也必須增加。
  • 供應商可能有自己的編譯器,但 bcc 的結論/要求也適用於這些編譯器。
連結
  • 編譯後的 .o 會連結至供應商驅動程式,例如libRSDriver_foo.solibcompiler_rt.so。CPU 路徑會連結至 libRSDriver.so
  • 如果 .o 需要 libRSDriver_foo 中的新執行階段 API,供應商驅動程式必須更新才能支援。
  • 某些供應商可能有自己的連結器,但 ld.mc 的引數也適用於這些供應商。
載入
  • libRSCpuRef 會載入共用物件。如果這個介面有變更,就需要升級 HAL 版本。
  • 供應商會依賴 libRSCpuRef 載入共用物件,或是自行實作。

除了 HAL 外,執行階段 API 和匯出的符號也是介面。自 Android 7.0 (API 24) 以來,這兩個介面都沒有變更,且目前沒有在 Android 8.0 以上版本中變更介面的計畫。不過,如果介面確實有變更,HAL 版本也會隨之增加。

供應商實作

如要讓 Android 8.0 以上版本的 GPU 驅動程式正常運作,您必須進行部分 GPU 驅動程式變更。

驅動程式模組

  • 驅動程式模組不得依附不在清單中的任何系統程式庫。
  • 驅動程式必須提供自己的 android.hardware.renderscript@1.0-impl_{NAME},或宣告預設實作 android.hardware.renderscript@1.0-impl 為依附元件。
  • CPU 實作 libRSDriver.so 是移除非 VNDK-SP 依附元件的絕佳範例。

中間碼編譯器

您可以透過兩種方式為供應商驅動程式編譯 RenderScript 中間碼:

  1. /vendor/bin/ 中叫用供應商專屬的 RenderScript 編譯器 (GPU 編譯的首選方法)。與其他驅動程式模組類似,供應商編譯器的二進位檔不得依附任何不在供應商可用的 RenderScript 程式庫清單中的系統程式庫。
  2. 使用供應商提供的 bcc plugin 叫用系統密件副本:/system/bin/bcc;這個外掛程式不得依附任何未列在 RenderScript 程式庫清單中的系統程式庫。

如果供應商 bcc plugin 需要干擾 CPU 編譯作業,且其對 libLLVM.so 的依附元件無法輕易移除,供應商應將 bcc (以及所有非 LL-NDK 依附元件,包括 libLLVM.solibbcc.so) 複製到 /vendor 分割區。

此外,供應商也必須進行以下變更:

圖 7. 供應商驅動程式變更。

  1. libclcore.bc 複製到 /vendor 分區。這可確保 libclcore.bclibLLVM.solibbcc.so 保持同步。
  2. 從 RS HAL 實作設定 RsdCpuScriptImpl::BCC_EXE_PATH,變更 bcc 可執行檔的路徑。

SELinux 政策

SELinux 政策會影響驅動程式和編譯器執行檔。所有驅動程式模組都必須在裝置的 file_contexts 中加上 same_process_hal_file 標籤。例如:

/vendor/lib(64)?/libRSDriver_EXAMPLE\.so     u:object_r:same_process_hal_file:s0

應用程式程序必須能夠叫用編譯器執行檔,供應商的 bcc (/vendor/bin/bcc) 副本也一樣。例如:

device/vendor_foo/device_bar/sepolicy/file_contexts:
/vendor/bin/bcc                    u:object_r:same_process_hal_file:s0

舊版裝置

舊版裝置須符合下列條件:

  1. PRODUCT_SHIPPING_API_LEVEL 低於 26。
  2. 未定義 PRODUCT_FULL_TREBLE_OVERRIDE

對於舊裝置,升級至 Android 8.0 以上版本時不會強制執行限制,這表示驅動程式可繼續連結至 /system/lib[64] 中的程式庫。不過,由於與 OVERRIDE_RS_DRIVER 相關的架構變更,android.hardware.renderscript@1.0-impl 必須安裝至 /vendor 分區;如果未安裝,RenderScript 執行階段就會強制改用 CPU 路徑。

如要瞭解 Renderscript 淘汰的原因,請參閱 Android 開發人員網誌:Android GPU Compute 的未來發展。這項淘汰作業的資源資訊包括: