RenderScript에서 이전하기

RenderScript API는 Android 12부터 지원 중단됩니다. 기기 및 구성요소 제조업체는 이미 하드웨어 가속 지원을 중단했으며 RenderScript 지원은 향후 출시에서 완전히 삭제될 예정입니다.

많은 사용 사례에 C/C++ 성능이 적합할 수 있습니다. 내장 기능에만 RenderScript를 사용했다면 이러한 용도를 RenderScript 내장 기능 대체 도구 키트로 대체할 수 있습니다. 이 도구 키트는 사용이 더 쉽고 2배의 성능 개선 효과가 있습니다.

GPU 가속을 최대한 활용해야 하는 경우 스크립트를 Vulkan으로 이전하는 것이 좋습니다. 기타 가속 옵션으로는 OpenGL로 스크립트 이전이나 캔버스 기반 이미지 작업 사용, Android Graphics Shading Language(AGSL) 활용이 있습니다.

Android 플랫폼에서 RenderScript가 지원 중단됨에 따라 Android Gradle 플러그인에서도 RenderScript 지원이 삭제됩니다. Android Gradle 플러그인 7.2부터 RenderScript API는 지원 중단됩니다. API는 계속 작동하지만 경고가 발생합니다. AGP의 향후 버전에는 더 이상 RenderScript 지원이 포함되지 않습니다. 이 가이드에서는 RenderScript에서 이전하는 방법을 설명합니다.

내장 기능에서 이전하기

RenderScript 내장 기능은 RenderScript를 지원 중단한 후에도 계속 작동하지만 GPU가 아닌 CPU에서만 실행할 수 있습니다.

일부 작업 수행을 위해 이제 플랫폼이나 Jetpack 라이브러리에 더 효율적인 옵션이 내장되어 있습니다.

기본 제공되는 가속 이미지 작업

Android 플랫폼은 RenderScript 내장 기능과 관계없이 이미지에 적용할 수 있는 가속 이미지 처리 작업을 지원합니다. 예를 들면 다음과 같습니다.

  • 혼합
  • 흐리게 처리
  • 색상 매트릭스
  • 크기 조절

Android 12 이상에서 뷰(View)로 이미지 흐리게 처리

흐리게 처리 기능을 지원하는 RenderEffect가 Android 12, API 수준 31에 추가되어 RenderNode를 흐리게 처리할 수 있습니다. RenderNode는 Android에서 플랫폼 그래픽을 가속하는 데 사용하는 표시 목록의 구성입니다.

Android에서는 View와 연결된 RenderNode에 효과를 적용하는 바로가기를 제공합니다. View를 흐리게 처리하려면 View.setRenderEffect()를 호출합니다.

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

Android 12 이상에서 비트맵으로 렌더링된 이미지 흐리게 처리

Bitmap으로 렌더링된 이미지를 흐리게 처리해야 할 경우, 프레임워크는 HardwareBuffer로 지원되는 HardwareRenderer를 사용하여 가속 렌더링을 지원합니다. 다음 코드는 이미지를 흐리게 처리하기 위한 HardwareRenderer, RenderNode, RenderEffect를 만듭니다.

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)

효과를 적용하려면 RenderNode에 내부 RecordingCanvas를 사용해야 합니다. 다음 코드는 그리기를 기록하고 렌더링 요청을 만든 후 요청이 완료될 때까지 기다립니다.

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

렌더링된 이미지는 ImageReader와 연결된 HardwareBuffer에 있습니다. 다음 코드는 Image를 획득하고, HardwareBuffer를 래핑하는 Bitmap을 반환합니다.

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")

다음 코드는 이미지를 렌더링한 후 정리합니다. ImageReader, RenderNode, RenderEffect, HardwareRenderer는 여러 이미지를 처리하는 데 사용할 수 있습니다.

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

이미지 처리를 위한 AGSL

Android Graphics Shading Language(AGSL)는 Android 13 이상에서 프로그래밍 가능한 RuntimeShader 객체의 동작을 정의하는 데 사용됩니다. AGSL은 대부분의 문법을 GLSL 프래그먼트 셰이더와 공유하지만 Android 그래픽 렌더링 시스템 내에서 작동하여 Canvas 내에서 페인팅을 맞춤설정하고 View 콘텐츠를 필터링합니다. 그리기 작업 중에 맞춤 이미지 처리를 추가하거나, RenderNode를 직접 사용하여 이미지를 Bitmap 캔버스에 렌더링하는 데 사용할 수 있습니다. 다음 예에서는 맞춤 셰이더를 적용하여 이미지를 흐리게 처리하는 효과를 대체하는 방법을 보여줍니다.

먼저 RuntimeShader를 만들고 AGSL 셰이더 코드로 인스턴스화합니다. 이 셰이더는 색조 회전을 위한 색상 매트릭스를 적용하는 데 사용됩니다.

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;
    }
""")

셰이더는 다른 RenderEffect와 마찬가지로 RenderNode에 적용할 수 있습니다. 다음 예는 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)

Bitmap을 가져오려면 이전에 흐리게 처리한 이미지 샘플과 동일한 기법을 사용합니다.

  • RenderNode의 내부 RecordingCanvas가 셰이더를 적용합니다.
  • Image를 획득하여 HardwareBuffer를 래핑하는 Bitmap을 반환합니다.

CameraX를 사용하여 평면 YUV에서 RGB로 변환

이미지 처리에 사용하기 위해 평면 YUV에서 RGB로 변환하는 작업은 Jetpack의 CameraX 내 ImageAnalysis 사용 사례의 일환으로 지원됩니다.

ImageAnalysis를 사용하는 방법에 관한 리소스는 CameraX 시작하기 Codelab과 Android 카메라 샘플 저장소에 포함되어 있습니다.

RenderScript 내장 기능 대체 도구 키트

애플리케이션에서 내장 기능을 사용하는 경우 독립형 대체 라이브러리를 사용할 수 있습니다. 테스트 결과 기존 RenderScript CPU 구현을 사용하는 것보다 더 빠릅니다.

도구 키트에는 다음 기능이 포함됩니다.

  • 혼합
  • 흐리게 처리
  • 색상 매트릭스
  • 컨볼브
  • 히스토그램 및 histogramDot
  • 참고표(LUT) 및 LUT 3D
  • 크기 조절
  • YUV를 RGB로

자세한 내용 및 제한 사항은 도구 키트의 README.mdToolkit.kt 파일을 참고하세요.

다음 단계에 따라 라이브러리를 다운로드하고 추가하고 사용합니다.

  1. GitHub에서 프로젝트를 다운로드합니다.

  2. renderscript-toolkit module을 찾아서 빌드합니다.

  3. 앱의 build.gradle 파일을 수정하여 Android 스튜디오 프로젝트에 라이브러리를 추가합니다.

  4. 도구 키트의 적절한 메서드를 호출합니다.

예: ScriptIntrinsicBlur 함수에서 이전하기

ScriptIntrinsicBlur 함수를 바꾸려면 다음을 따릅니다.

  • 비트맵을 흐리게 처리하려면 Toolkit.blur를 호출합니다.

    var blurredBitmap = Toolkit.blur(myBitmap, radius)
    
  • 바이트 배열로 표현된 이미지를 흐리게 처리하려면 너비, 높이, 픽셀당 바이트 수를 지정합니다.

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

스크립트에서 이전하기

사용 사례를 다음을 사용하여 해결할 수 없는 경우:

또한 사용 사례에 GPU 가속의 이점을 활용할 수 있습니다. Android는 크로스 플랫폼 Vulkan 및 OpenGL ES(GLES) API에서 GPU 컴퓨팅을 지원합니다. 대부분의 기기에서 스크립트가 이미 GPU가 아닌 CPU에서 실행 중이므로 이 작업이 필요하지 않을 수도 있습니다. 일부 사용 사례에서는 C/C++가 RenderScript나 GLES, Vulkan 컴퓨팅보다 빠를 수 있습니다. 적어도 사용 사례에 맞게 C/C++의 속도가 충분히 빠를 수 있습니다.

이전하는 방법을 더 자세히 알아보려면 샘플 앱을 검토하세요. 이 샘플은 RenderScript에서 비트맵을 흐리게 처리하고 색상-매트릭스 변환을 처리하는 방법을 보여주며 Vulkan 및 OpenGL에서 상응하는 코드를 제공합니다.

다양한 버전을 지원해야 하는 애플리케이션의 경우 Android 6(API 수준 23) 이하를 실행하는 기기에는 RenderScript를 사용하고 Android 7(API 수준 24) 이상의 지원 기기에서는 Vulkan 또는 GLES를 사용하세요. minSdkVersion이 24 이상이면 RenderScript를 사용할 필요가 없을 수도 있습니다. Vulkan 또는 GLES 3.1을 GPU 컴퓨팅 지원이 필요한 모든 기기에서 사용할 수 있습니다.

Android는 GLES API의 SDK 바인딩을 제공하므로 OpenGL ES에서 작업할 때 NDK를 사용할 필요가 없습니다.

Vulkan은 SDK 바인딩을 제공하지 않으므로 RenderScript에서 Vulkan으로 직접 매핑되지는 않습니다. NDK를 사용하여 Vulkan 코드를 작성하고 Kotlin이나 Java에서 이 코드에 액세스하는 JNI 함수를 만듭니다.

다음 페이지에서는 RenderScript에서 이전하는 작업을 설명합니다. 샘플 앱에는 이러한 고려사항이 거의 모두 구현되어 있습니다. 이해를 돕기 위해 RenderScript와 Vulkan의 상응 코드를 비교합니다.