Migracja z renderScriptu

Interfejsy RenderScript API są wycofywane od Androida 12. Producenci urządzeń i komponentów już przestali obsługiwać akcelerację sprzętową, a obsługę RenderScriptu prawdopodobnie całkowicie wycofamy w przyszłej wersji.

Wydajność C/C++ może być odpowiednia w wielu przypadkach, a jeśli opierasz się tylko na RenderScript w przypadku wewnętrznych zasobów, możesz je zastąpić zestawem zamiennym RenderScript Intrinsics, który jest łatwiejszy w użyciu i potencjalnie dwukrotnie zwiększa wydajność.

Jeśli chcesz w pełni wykorzystać możliwości akceleracji GPU, zalecamy przeniesienie skryptów do interfejsu Vulkan lub korzystanie z innych opcji przyspieszających, takich jak migracja skryptów do trybu OpenGL, operacje graficzne oparte na kanwa lub Android Graphics Shading Language (AGSL).

W związku z wycofaniem z platformy Androida obsługi RenderScriptu przestaniemy ją obsługiwać we wtyczce Androida do obsługi Gradle. Od wersji 7.2 wtyczki Androida do obsługi Gradle interfejsy API RenderScript zostały wycofane. Nadal działają, ale wywołują ostrzeżenia. Przyszłe wersje AGP nie będą już obsługiwać Renderscript. Ten przewodnik wyjaśnia, jak przeprowadzić migrację z renderScriptu.

Migracja z usługi wewnętrznego

Chociaż wewnętrzne funkcje RenderScriptu nadal działają po wycofaniu tej usługi, mogą być wykonywane tylko na procesorze, a nie na procesorach graficznych.

W przypadku niektórych z tych operacji dostępne są teraz wydajniejsze opcje wbudowane w platformę lub w biblioteki Jetpack.

Wbudowane przyspieszone operacje na zdjęciach

Platforma Android obsługuje przyspieszone operacje przetwarzania obrazów, które można stosować do obrazów niezależnie od elementu RenderScript. Przykłady:

  • Różne
  • Rozmycie
  • Matryca kolorów
  • Zmień rozmiar

Rozmycie obrazu w Androidzie 12 i nowszych

Do Androida 12 (poziomu interfejsu API 31) dodano funkcję RenderEffect z obsługą rozmycia, która umożliwia rozmycie elementu RenderNode. RenderNode to konstrukcja listy wyświetlania, której Android używa do przyspieszania grafiki na platformie.

Android udostępnia skrót do zastosowania efektu do elementu RenderNode powiązanego z elementem View. Aby rozmyć obiekt View, wywołaj View.setRenderEffect():

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

Rozmycie obrazu na Androidzie 12 lub nowszym wyrenderowane w postaci mapy bitowej

Jeśli potrzebujesz rozmytego obrazu wyrenderowanego w interfejsie Bitmap, platforma obsługuje przyspieszone renderowanie z użyciem platformy HardwareRenderer wspieranej przez HardwareBuffer. Ten kod tworzy HardwareRenderer, RenderNode i RenderEffect do rozmycia:

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)

Zastosowanie efektu wymaga użycia wewnętrznego RecordingCanvas dla RenderNode. Ten kod rejestruje rysunek, tworzy żądanie renderowania, a następnie czeka na zakończenie żądania:

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

Wyrenderowany obraz znajduje się w elemencie HardwareBuffer powiązanym z elementem ImageReader. Poniższy kod pobiera Image i zwraca element Bitmap, który opakowuje HardwareBuffer.

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

Ten kod jest czyszczony po wyrenderowaniu obrazu. Pamiętaj, że ImageReader, RenderNode, RenderEffect i HardwareRenderer mogą być używane do przetwarzania wielu obrazów.

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

AGSL na potrzeby przetwarzania obrazu

Język AGSL (Android Graphics Shading Language) jest używany w Androidzie 13 i nowszych do definiowania zachowania programowalnych obiektów RuntimeShader. AGSL korzysta w znacznej części z cieniowania fragmentów GLSL, ale działa w systemie renderowania grafiki na Androidzie, aby dostosowywać malowanie w obrębie Canvas i filtrować treści w View. Można jej używać, aby dodawać niestandardowe przetwarzanie obrazu podczas operacji rysowania lub za pomocą polecenia RenderNode bezpośrednio renderować obraz w obszarze roboczym Bitmap. Poniższy przykład pokazuje, jak zastosować niestandardowy program do cieniowania, aby zastąpić efekt rozmycia obrazu.

Zacznij od utworzenia instancji RuntimeShader z użyciem kodu do cieniowania AGSL. Ten program do cieniowania służy do stosowania matrycy kolorów do rotacji kolorów:

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

Program do cieniowania można zastosować do elementu RenderNode, tak jak każdy inny element typu RenderEffect. Poniższy przykład pokazuje, jak ustawić mundury w 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)

Aby uzyskać Bitmap, użyj tej samej metody co w poprzedniej próbce rozmycia obrazu.

  • Wewnętrzna instancja RecordingCanvas w RenderNode stosuje cieniowanie.
  • Parametr Image jest pozyskiwany i zwraca Bitmap, który opakowuje swój HardwareBuffer.

Konwertuj z planarnej YUV na RGB za pomocą CameraX

Konwersja z planarnej YUV na RGB na potrzeby przetwarzania obrazu jest dostępna w ramach Analizy obrazu w aplikacji CameraX firmy Jetpack.

Zasoby dotyczące korzystania z ImageAnalysis są dostępne w ramach ćwiczenia z programowania Getting started with CameraX (w języku angielskim) oraz w repozytorium przykładów kamer w Androidzie.

Pakiet narzędzi do zastępowania wewnętrznych skryptów Renderscript

Jeśli Twoja aplikacja używa elementów wewnętrznych, możesz użyć samodzielnej biblioteki zastępczej. Z naszych testów wynika, że jest to szybsze niż użycie istniejącej implementacji procesora RenderScript.

Zestaw obejmuje następujące funkcje:

  • Różne
  • Rozmycie
  • Matryca kolorów
  • Obrotowy
  • Histogram i histogramDot
  • Tabela przeglądowa (LUT) i LUT 3D
  • Zmień rozmiar
  • YUV do RGB

Szczegółowe informacje i ograniczenia znajdziesz w dokumentach README.md i Toolkit.kt. .

Aby pobrać bibliotekę, dodać ją i jej używać, wykonaj te czynności:

  1. Pobierz projekt z GitHuba.

  2. Zlokalizuj i utwórz renderscript-toolkit module.

  3. Dodaj bibliotekę do projektu Android Studio, modyfikując plik build.gradle aplikacji.

  4. Wywołaj odpowiednią metodę narzędzia.

Przykład: migracja z funkcji ScriptIntrinsicBlur

Aby zastąpić funkcję ScriptIntrinsicBlur:

  • Aby rozmyć bitmapę, wywołaj polecenie Toolkit.blur.

    var blurredBitmap = Toolkit.blur(myBitmap, radius)
    
  • Jeśli chcesz rozmyć obraz reprezentowany przez tablicę bajtów, podaj szerokość, wysokość i liczbę bajtów na piksel.

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

Migracja ze skryptów

Jeśli Twojego przypadku użycia nie da się rozwiązać w ten sposób:

W Twoim przypadku użycia może też skorzystać z akceleracji GPU, ponieważ Android obsługuje obliczenia GPU w interfejsach API międzyplatformowych Vulkan i OpenGL ES (GLES). Może to być niepotrzebne, ponieważ na większości urządzeń Twoje skrypty są już uruchomione na procesorze, a nie na GPU. W niektórych przypadkach język C/C++ może być szybszy niż RenderScript, GLES czy Vulkan. (przynajmniej tak szybko, jak to możliwe w Twoim przypadku użycia).

Aby lepiej zrozumieć, jak przeprowadzić migrację, przejrzyj przykładową aplikację. Ten przykład pokazuje, jak zarówno rozmyć bitmapę, jak i przeprowadzić konwersję matrycy kolorów w RenderScript. Ten kod ma odpowiednik w językach Vulkan i OpenGL.

Jeśli Twoja aplikacja musi obsługiwać różne wersje, użyj RenderScript na urządzeniach z Androidem 6 (poziom interfejsu API 23) lub starszym oraz na obsługiwanych urządzeniach z Androidem 7 (poziom interfejsu API 24) lub nowszym oraz interfejsu Vulkan lub GLES. Jeśli Twój minSdkVersion ma wersję 24 lub nowszą, być może nie musisz używać RenderScriptu. Możesz używać interfejsu Vulkan lub GLES 3.1 wszędzie tam, gdzie potrzebujesz obsługi mocy obliczeniowej GPU.

Android zapewnia powiązania SDK dla interfejsów API GLES, dlatego nie trzeba używać NDK podczas pracy w trybie OpenGL ES.

Vulkan nie udostępnia powiązań pakietów SDK, więc nie ma bezpośredniego mapowania z RenderScript do Vulkan. Piszesz kod Vulkan za pomocą pakietu NDK i tworzysz funkcje JNI, aby uzyskać dostęp do kodu w kotlinach lub Javie.

Na kolejnych stronach omawiamy aspekty migracji z renderScriptu. Przykładowa aplikacja uwzględnia prawie wszystkie te kwestie. Aby je lepiej zrozumieć, porównaj kod RenderScript z odpowiednikiem w interfejsie Vulkan.