OpenGL ES 3.1로 스크립트 이전

GPU 컴퓨팅이 이상적인 워크로드의 경우 RenderScript 스크립트를 OpenGL ES(GLES)로 이전하면 Kotlin, Java 또는 NDK를 사용하여 작성된 애플리케이션이 GPU 하드웨어를 활용할 수 있습니다. 다음은 OpenGL ES 3.1 컴퓨팅 셰이더를 사용하여 RenderScript 스크립트를 대체하는 데 도움이 되는 대략적인 개요입니다.

GLES 초기화

RenderScript 컨텍스트 객체를 만드는 대신 다음 단계에 따라 EGL을 사용하여 GLES 오프스크린 컨텍스트를 만드세요.

  1. 기본 디스플레이를 가져옵니다.

  2. 기본 디스플레이를 사용하여 EGL을 초기화하고 GLES 버전을 지정합니다.

  3. 노출 영역 유형이 EGL_PBUFFER_BIT인 EGL 구성을 선택합니다.

  4. 디스플레이 및 구성을 사용하여 EGL 컨텍스트를 만듭니다.

  5. eglCreatePBufferSurface를 사용하여 오프스크린 노출 영역을 만듭니다. 컨텍스트가 컴퓨팅에만 사용된다면 노출 영역은 매우 작은 (1x1)일 수 있습니다.

  6. 렌더링 스레드를 만들고 디스플레이, 노출 영역, EGL 컨텍스트로 렌더링 스레드에서 eglMakeCurrent를 호출하여 GL 컨텍스트를 스레드에 바인딩합니다.

샘플 앱은 GLSLImageProcessor.kt에서 GLES 컨텍스트를 초기화하는 방법을 보여줍니다. 자세한 내용은 EGLSurfaces 및 OpenGL ES를 참고하세요.

GLES 디버그 출력

OpenGL에서 유용한 오류를 가져오면 확장 프로그램을 사용하여, 디버그 출력 콜백을 설정하는 디버그 로깅을 사용 설정합니다. SDK에서 이 작업을 실행하는 메서드 glDebugMessageCallbackKHR은 구현된 적이 없으며 예외를 발생시킵니다. 샘플 앱에는 NDK 코드의 콜백용 래퍼가 포함되어 있습니다.

GLES 할당

RenderScript 할당은 변경 불가능한 저장소 텍스처 또는 셰이더 저장소 버퍼 객체로 이전할 수 있습니다. 읽기 전용 이미지의 경우 필터링을 허용하는 샘플러 객체를 사용할 수 있습니다.

GLES 리소스는 GLES 내에서 할당됩니다. 다른 Android 구성요소와 상호작용할 때 메모리 복사 오버헤드를 방지하기 위해 이미지 데이터의 2D 배열을 공유할 수 있는 KHR 이미지 확장 프로그램이 있습니다. 이 확장 프로그램은 Android 8.0부터 Android 기기에 필요합니다. graphics-core Android Jetpack 라이브러리에는 관리 코드 내에서 이러한 이미지를 만들고 이를 할당된 HardwareBuffer에 매핑하기 위한 지원이 포함되어 있습니다.

val outputBuffers = Array(numberOfOutputImages) {
  HardwareBuffer.create(
    width, height, HardwareBuffer.RGBA_8888, 1,
    HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
  )
}
val outputEGLImages = Array(numberOfOutputImages) { i ->
    androidx.opengl.EGLExt.eglCreateImageFromHardwareBuffer(
        display,
        outputBuffers[i]
    )!!
}

안타깝게도 이 방법은 컴퓨팅 셰이더가 버퍼에 직접 쓰는 데 필요한 변경 불가능한 저장소 텍스처를 만들지 않습니다. 이 샘플은 glCopyTexSubImage2D를 사용하여 컴퓨팅 셰이더에서 사용한 저장소 텍스처를 KHR Image에 복사합니다. OpenGL 드라이버가 EGL 이미지 저장소 확장 프로그램을 지원하는 경우 변경 불가능한 공유 저장소 텍스처를 만들어 복사를 방지하는 데 이 확장 프로그램을 사용할 수 있습니다.

GLSL 컴퓨팅 셰이더로 변환

RenderScript 스크립트는 GLSL 컴퓨팅 셰이더로 변환됩니다.

GLSL 컴퓨팅 셰이더 작성

OpenGL ES에서 컴퓨팅 셰이더는 OpenGL 셰이딩 언어(GLSL)로 작성됩니다.

스크립트 전역 변수 조정

스크립트 전역 변수의 특성에 따라 셰이더 내에서 수정되지 않는 전역 변수에 유니폼이나 유니폼 버퍼 객체를 사용할 수 있습니다.

  • 균일 버퍼: 푸시 상수 한도보다 크며 변경 빈도가 큰 스크립트 전역 변수에 권장됩니다.

셰이더 내에서 변경되는 전역 변수의 경우 변경 불가능한 저장소 텍스처 또는 셰이더 저장소 버퍼 객체를 사용할 수 있습니다.

계산 실행

컴퓨팅 셰이더는 그래픽 파이프라인의 일부가 아닙니다. 컴퓨팅 셰이더는 범용이며 고도로 병렬 가능한 작업을 계산하도록 설계되었습니다. 따라서 실행 방법을 더 세부적으로 제어할 수 있지만 작업이 병렬 처리되는 방식을 좀 더 이해해야 합니다.

컴퓨팅 프로그램 만들기 및 초기화

컴퓨팅 프로그램을 만들고 초기화하는 것은 다른 GLES 셰이더로 작업하는 것과 많은 공통점이 있습니다.

  1. 프로그램 및 프로그램에 연결된 컴퓨팅 셰이더를 만듭니다.

  2. 셰이더 소스를 연결하고 셰이더를 컴파일한 다음 컴파일 결과를 확인합니다.

  3. 셰이더를 연결하고 프로그램을 연결한 후 프로그램을 사용합니다.

  4. 유니폼을 만들고 초기화하고 바인딩합니다.

계산 시작하기

컴퓨팅 셰이더는 셰이더 소스 코드 내에 정의되는 일련의 작업 그룹의 추상 1D, 2D 또는 3D 공간 내에서 작동하며 셰이더의 도형과 최소 호출 크기를 나타냅니다. 다음 셰이더는 2D 이미지에서 작동하며 작업 그룹을 2차원으로 정의합니다.

private const val WORKGROUP_SIZE_X = 8
private const val WORKGROUP_SIZE_Y = 8
private const val ROTATION_MATRIX_SHADER =
    """#version 310 es
    layout (local_size_x = $WORKGROUP_SIZE_X, local_size_y = $WORKGROUP_SIZE_Y, local_size_z = 1) in;

작업 그룹은 GL_MAX_COMPUTE_SHARED_MEMORY_SIZE로 정의된 메모리(32KB 이상)를 공유할 수 있으며 memoryBarrierShared()를 활용하여 일관된 메모리 액세스를 제공할 수 있습니다.

작업 그룹 크기 정의

문제 공간이 작업 그룹 크기 1에서 잘 작동하더라도 컴퓨팅 셰이더를 병렬화하려면 적절한 작업 그룹 크기를 설정하는 것이 중요합니다. 예를 들어 크기가 너무 작으면 GPU 드라이버가 계산을 충분히 병렬화하지 못할 수 있습니다. 이러한 크기는 GPU별로 조정하는 것이 이상적이지만, 적절한 기본값은 현재 기기에서 충분히 잘 작동합니다. 셰이더 스니펫의 작업 그룹 크기 8x8을 예로 들 수 있습니다.

GL_MAX_COMPUTE_WORK_GROUP_COUNT가 있지만 크기가 상당합니다. 사양에 따라 세 축에서 모두 최소 65535여야 합니다.

셰이더 전달

계산 실행의 마지막 단계는 glDispatchCompute와 같은 전달 함수 중 하나를 사용하여 셰이더를 전달하는 것입니다. 전달 함수는 각 축의 작업 그룹 수를 설정합니다.

GLES31.glDispatchCompute(
  roundUp(inputImage.width, WORKGROUP_SIZE_X),
  roundUp(inputImage.height, WORKGROUP_SIZE_Y),
  1 // Z workgroup size. 1 == only one z level, which indicates a 2D kernel
)

값을 반환하려면 먼저 메모리 배리어를 사용하여 컴퓨팅 작업이 완료될 때까지 기다립니다.

GLES31.glMemoryBarrier(GLES31.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT)

여러 커널을 함께 연결하려면(예: ScriptGroup을 사용하여 코드를 이전하기 위해) 여러 프로그램을 만들고 전달하고 출력 액세스를 메모리 배리어와 동기화합니다.

샘플 앱은 다음 두 가지 컴퓨팅 작업을 보여줍니다.

  • HCE 회전: 단일 컴퓨팅 셰이더가 있는 컴퓨팅 작업입니다. 코드 샘플은 GLSLImageProcessor::rotateHue를 참고하세요.
  • 흐리게 처리: 컴퓨팅 셰이더 두 개를 순차적으로 실행하는 더 복잡한 컴퓨팅 작업입니다. 코드 샘플은 GLSLImageProcessor::blur를 참고하세요.

메모리 배리어에 관한 자세한 내용은 공유 변수가시성 보장을 참고하세요.