Di chuyển từ RenderScript

API RenderScript sẽ không còn được dùng nữa kể từ phiên bản Android 12. Các nhà sản xuất thiết bị và thành phần đã ngừng cung cấp tính năng hỗ trợ tăng tốc phần cứng và dự kiến là tính năng hỗ trợ RenderScript sẽ bị xoá hoàn toàn trong một bản phát hành sau này.

C/C++ có thể đem lại hiệu suất phù hợp với nhiều trường hợp sử dụng. Và nếu chỉ dựa vào RenderScript để viết các hàm nội tại, thì bạn có thể dùng Bộ công cụ thay thế hàm nội tại RenderScript (RenderScript Intrinsics Replacement Toolkit) để thay thế RenderScript trong những đoạn mã có sử dụng API này. Bộ công cụ này dễ sử dụng hơn và có khả năng cải thiện hiệu suất gấp 2 lần!

Nếu cần tận dụng tối đa tính năng tăng tốc GPU, bạn nên di chuyển tập lệnh của mình sang Vulkan. Ngoài ra, để tận dụng tính năng tăng tốc, bạn có thể lựa chọn di chuyển tập lệnh sang OpenGL, sử dụng Thao tác trên hình ảnh dựa trên Canvas, hoặc tận dụng Ngôn ngữ tạo bóng đồ hoạ Android (AGSL).

Sau khi RenderScript trong nền tảng Android không được sử dụng nữa, tính năng hỗ trợ RenderScript sẽ bị xoá trong trình bổ trợ Android cho Gradle. Kể từ trình bổ trợ Android cho Gradle 7.2, API RenderScript không được dùng nữa. API này vẫn tiếp tục hoạt động nhưng sẽ đưa ra cảnh báo. Các phiên bản AGP sau này sẽ không hỗ trợ Renderscript nữa. Hướng dẫn này giải thích cách di chuyển từ RenderScript.

Di chuyển từ hàm nội tại

Tuy các hàm nội tại RenderScript sẽ tiếp tục hoạt động sau khi RenderScript không được sử dụng nữa, nhưng các hàm này sẽ chỉ có thể thực thi trên CPU thay vì GPU.

Đối với một số thao tác trong số này, hiện đã có nhiều lựa chọn hiệu quả hơn được tích hợp vào nền tảng hoặc trong thư viện Jetpack.

Tích hợp các thao tác tăng tốc hình ảnh

Nền tảng Android hỗ trợ các thao tác xử lý hình ảnh nhanh không phụ thuộc vào hàm nội tại RenderScript mà bạn có thể áp dụng đối với hình ảnh. Ví dụ:

  • Kết hợp
  • Làm mờ
  • Ma trận màu
  • Đổi kích thước

Hiệu ứng làm mờ hình ảnh sẽ được đưa vào Thành phần hiển thị trên Android 12 trở lên

RenderEffect có hỗ trợ tính năng làm mờ đã được thêm vào Android 12 (API cấp 31), cho phép bạn làm mờ RenderNode. RenderNode là một thành phần tạo nên danh sách hiển thị mà Android sẽ dùng, giúp tăng tốc đồ hoạ cho nền tảng này.

Android cung cấp một lối tắt để áp dụng hiệu ứng cho RenderNode được liên kết với View. Để làm mờ View, hãy gọi View.setRenderEffect():

val blurRenderEffect = RenderEffect.createBlurEffect(radius, radius,
        Shader.TileMode.MIRROR
    )
view.setRenderEffect(blurRenderEffect)

Hiệu ứng làm mờ hình ảnh trên Android 12 trở lên được kết xuất thành Bitmap

Nếu cần kết xuất hình ảnh được làm mờ vào Bitmap, khung này sẽ hỗ trợ tính năng tăng tốc kết xuất với HardwareRenderer được hỗ trợ bởi một HardwareBuffer. Đoạn mã sau đây sẽ tạo ra HardwareRenderer, RenderNodeRenderEffect cho hiệu ứng làm mờ:

val imageReader = ImageReader.newInstance(
    bitmap.width, bitmap.height,
    PixelFormat.RGBA_8888, numberOfOutputImages,
    HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT
)
val renderNode = RenderNode("BlurEffect")
val hardwareRenderer = HardwareRenderer()

hardwareRenderer.setSurface(imageReader.surface)
hardwareRenderer.setContentRoot(renderNode)
renderNode.setPosition(0, 0, imageReader.width, imageReader.height)
val blurRenderEffect = RenderEffect.createBlurEffect(
    radius, radius,
    Shader.TileMode.MIRROR
)
renderNode.setRenderEffect(blurRenderEffect)

Để áp dụng hiệu ứng này, bạn cần sử dụng RecordingCanvas nội bộ cho RenderNode. Đoạn mã sau đây ghi lại bản vẽ, tạo yêu cầu kết xuất, rồi chờ yêu cầu đó hoàn tất:

val renderCanvas = it.renderNode.beginRecording()
renderCanvas.drawBitmap(it.bitmap, 0f, 0f, null)
renderNode.endRecording()
hardwareRenderer.createRenderRequest()
    .setWaitForPresent(true)
    .syncAndDraw()

Hình ảnh kết xuất sẽ nằm trong HardwareBuffer được liên kết với ImageReader. Đoạn mã sau đây sẽ thu nhận Image và trả về một Bitmap đóng gói HardwareBuffer của nó.

val image = imageReader.acquireNextImage() ?: throw RuntimeException("No Image")
val hardwareBuffer = image.hardwareBuffer ?: throw RuntimeException("No HardwareBuffer")
val bitmap = Bitmap.wrapHardwareBuffer(hardwareBuffer, null)
    ?: throw RuntimeException("Create Bitmap Failed")

Đoạn mã sau đây sẽ dọn dẹp bộ nhớ sau khi kết xuất hình ảnh. Xin lưu ý rằng bạn có thể dùng ImageReader, RenderNode, RenderEffectHardwareRenderer để xử lý nhiều hình ảnh.

hardwareBuffer.close()
image.close()
imageReader.close()
renderNode.discardDisplayList()
hardwareRenderer.destroy()

AGSL để xử lý hình ảnh

Android 13 trở lên sử dụng Ngôn ngữ tạo bóng đồ hoạ Android (AGSL) để xác định hành vi của các đối tượng RuntimeShader lập trình được. AGSL sử dụng chung nhiều cú pháp với chương trình đổ bóng mảnh GLSL, nhưng hoạt động trong hệ thống kết xuất đồ hoạ Android để tuỳ chỉnh tính năng vẽ trong Canvas và lọc nội dung View. Nhờ đó, bạn có thể thêm tính năng xử lý hình ảnh tuỳ chỉnh trong các thao tác vẽ, hoặc sử dụng trực tiếp RenderNode để kết xuất hình ảnh vào canvas Bitmap. Ví dụ sau minh hoạ cách áp dụng chương trình đổ bóng tuỳ chỉnh thay cho hiệu ứng làm mờ hình ảnh.

Bắt đầu bằng cách tạo một thực thể RuntimeShader bằng mã chương trình đổ bóng AGSL. Bạn sẽ dùng chương trình đổ bóng này để luân chuyển màu bằng cách áp dụng ma trận màu:

val hueShader = RuntimeShader("""
    uniform float2 iResolution;       // Viewport resolution (pixels)
    uniform float2 iImageResolution;  // iImage1 resolution (pixels)
    uniform float iRadian;            // radian to rotate things around
    uniform shader iImage1;           // An input image
    half4 main(float2 fragCoord) {
    float cosR = cos(iRadian);
    float sinR = sin(iRadian);
        mat4 hueRotation =
        mat4 (
                0.299 + 0.701 * cosR + 0.168 * sinR, //0
                0.587 - 0.587 * cosR + 0.330 * sinR, //1
                0.114 - 0.114 * cosR - 0.497 * sinR, //2
                0.0,                                 //3
                0.299 - 0.299 * cosR - 0.328 * sinR, //4
                0.587 + 0.413 * cosR + 0.035 * sinR, //5
                0.114 - 0.114 * cosR + 0.292 * sinR, //6
                0.0,                                 //7
                0.299 - 0.300 * cosR + 1.25 * sinR,  //8
                0.587 - 0.588 * cosR - 1.05 * sinR,  //9
                0.114 + 0.886 * cosR - 0.203 * sinR, //10
                0.0,                                 //11
                0.0, 0.0, 0.0, 1.0 );                //12,13,14,15
        float2 scale = iImageResolution.xy / iResolution.xy;
        return iImage1.eval(fragCoord * scale)*hueRotation;
    }
""")

Bạn có thể áp dụng chương trình đổ bóng này đối với RenderNode, giống như mọi RenderEffect khác. Ví dụ sau minh hoạ cách thiết lập màu đồng nhất trong hueShader:

hueShader.setFloatUniform("iImageResolution", bitmap.width.toFloat(),
    bitmap.height.toFloat())
hueShader.setFloatUniform("iResolution", bitmap.width.toFloat(),
    bitmap.height.toFloat())
hueShader.setFloatUniform("iRadian", radian)
hueShader.setInputShader( "iImage1", BitmapShader(bitmap, Shader.TileMode.MIRROR,
    Shader.TileMode.MIRROR))
val colorFilterEffect = RenderEffect.createShaderEffect(it.hueShader)
renderNode.setRenderEffect(colorFilterEffect)

Để lấy Bitmap, bạn có thể sử dụng chính kỹ thuật trong mẫu làm mờ hình ảnh trước đó.

  • RecordingCanvas nội bộ của RenderNode sẽ áp dụng chương trình đổ bóng.
  • Image sẽ được thu nạp, trả về một Bitmap đóng gói HardwareBuffer.

Chuyển đổi từ YUV hai chiều sang RGB bằng CameraX

Tính năng chuyển đổi từ YUV hai chiều (planar YUV) sang RGB để sử dụng trong quá trình xử lý hình ảnh được hỗ trợ trong trường hợp sử dụng ImageAnalysis trong CameraX của Jetpack.

Có một số tài nguyên về cách sử dụng ImageAnalysis trong lớp học lập trình Bắt đầu sử dụng CameraX và trong kho lưu trữ mẫu dành cho camera Android.

Bộ công cụ thay thế hàm nội tại Renderscript

Nếu ứng dụng sử dụng các hàm nội tại, thì bạn có thể sử dụng thư viện thay thế độc lập. Kết quả kiểm thử của chúng tôi cho thấy rằng việc này nhanh hơn so với việc triển khai CPU RenderScript hiện nay.

Bộ công cụ này bao gồm các hàm sau:

  • Kết hợp
  • Làm mờ
  • Ma trận màu
  • Tích chập
  • Biểu đồ và histogramDot
  • Bảng tra cứu (LUT) và LUT 3D
  • Đổi kích thước
  • YUV sang RGB

Để biết đầy đủ thông tin chi tiết và các hạn chế, hãy xem tệp README.mdToolkit.kt của bộ công cụ. Tệp

Thực hiện các bước sau để tải xuống, thêm và sử dụng thư viện:

  1. Tải dự án xuống từ GitHub.

  2. Tìm và tạo renderscript-toolkit module.

  3. Thêm thư viện vào dự án Android Studio bằng cách chỉnh sửa tệp build.gradle của ứng dụng.

  4. Gọi phương thức thích hợp của bộ công cụ.

Ví dụ về việc di chuyển từ hàm ScriptIntrinsicBlur

Để thay thế hàm ScriptIntrinsicBlur, hãy làm như sau:

  • Để làm mờ bitmap, hãy gọi Toolkit.blur.

    var blurredBitmap = Toolkit.blur(myBitmap, radius)
    
  • Nếu bạn muốn làm mờ hình ảnh biểu thị bằng một mảng byte, hãy chỉ định chiều rộng, chiều cao và số byte theo đơn vị pixel.

    val outArray = Toolkit.blur(inputArray, bytesPerPixel, width, height, radius)
    

Di chuyển từ tập lệnh

Nếu trường hợp sử dụng của bạn không giải quyết được bằng:

Và bạn có thể hưởng lợi trong trường hợp sử dụng của mình nhờ tính năng tăng tốc GPU, Android hỗ trợ điện toán GPU trên các API Vulkan và OpenGL ES (GLES) đa nền tảng. Bạn có thể thấy điều này là không cần thiết vì trên hầu hết thiết bị, tập lệnh của bạn vẫn đang chạy trên CPU thay vì GPU: C/C++ có thể nhanh hơn RenderScript hoặc điện toán Vulkan trong một số trường hợp sử dụng. (hoặc ít ra thì đủ nhanh đối với trường hợp sử dụng của bạn)

Để hiểu rõ hơn về cách di chuyển, hãy tham khảo ứng dụng mẫu. Ứng dụng mẫu này hướng dẫn cách làm mờ bitmap và chuyển đổi ma trận màu trong RenderScript, đồng thời có mã tương đương trong Vulkan và OpenGL.

Nếu ứng dụng cần hỗ trợ nhiều bản phát hành, hãy sử dụng RenderScript cho các thiết bị chạy Android 6 (API cấp 23) trở xuống, và Vulkan hoặc GLES trên các thiết bị được hỗ trợ đang chạy Android 7 (API cấp 24) trở lên. Nếu minSdkVersion của bạn là cấp 24 trở lên, có thể bạn sẽ không cần sử dụng RenderScript. Bạn có thể sử dụng Vulkan hoặc GLES 3.1 trên mọi thiết bị cần hỗ trợ điện toán GPU.

Android cung cấp các mối liên kết SDK cho API GLES, nên bạn sẽ không cần sử dụng NDK khi làm việc trong OpenGL ES.

Vulkan không cung cấp mối liên kết SDK, nên giữa RenderScript và Vulkan không có ánh xạ trực tiếp. Bạn sẽ viết mã Vulkan bằng NDK và tạo các hàm JNI để truy cập mã này từ Kotlin hoặc Java.

Các trang sau đây trình bày các khía cạnh của việc di chuyển từ RenderScript. Ứng dụng mẫu triển khai hầu hết các khía cạnh này. Để hiểu rõ hơn, hãy so sánh RenderScript và mã Vulkan tương đương.