Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6867fc87 authored by Stan Iliev's avatar Stan Iliev
Browse files

Implement a new Shader API, which can run custom code on GPU

Add RuntimeShader hidden API, which calculates pixel output with
a fragment shader running on GPU.
Extend ColorFiltersMutateActivity HWUI test to use new API and
show how to animate uniforms on UI thread.

Test: Updated HwAccelerationTest
Change-Id: Ia26e44259b12099924facba250880cbbd9be21c7
parent 5605a0fe
Loading
Loading
Loading
Loading
+48 −0
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@
#include "SkShader.h"
#include "SkBlendMode.h"
#include "core_jni_helpers.h"
#include "src/shaders/SkRTShader.h"

#include <jni.h>

@@ -212,6 +213,44 @@ static jlong ComposeShader_create(JNIEnv* env, jobject o, jlong matrixPtr,

///////////////////////////////////////////////////////////////////////////////////////////////

static jlong RuntimeShader_create(JNIEnv* env, jobject, jlong shaderFactory, jlong matrixPtr,
        jbyteArray inputs, jlong colorSpaceHandle) {
    SkRuntimeShaderFactory* factory = reinterpret_cast<SkRuntimeShaderFactory*>(shaderFactory);
    AutoJavaByteArray arInputs(env, inputs);

    sk_sp<SkData> fData;
    fData = SkData::MakeWithCopy(arInputs.ptr(), arInputs.length());
    const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
    sk_sp<SkShader> shader = factory->make(fData, matrix);
    ThrowIAE_IfNull(env, shader);

    return reinterpret_cast<jlong>(shader.release());
}

///////////////////////////////////////////////////////////////////////////////////////////////

static jlong RuntimeShader_createShaderFactory(JNIEnv* env, jobject, jstring sksl,
        jboolean isOpaque) {
    ScopedUtfChars strSksl(env, sksl);
    SkRuntimeShaderFactory* shaderFactory = new SkRuntimeShaderFactory(SkString(strSksl.c_str()),
            isOpaque == JNI_TRUE);
    ThrowIAE_IfNull(env, shaderFactory);

    return reinterpret_cast<jlong>(shaderFactory);
}

///////////////////////////////////////////////////////////////////////////////////////////////

static void RuntimeShader_delete(SkRuntimeShaderFactory* shaderFactory) {
    delete shaderFactory;
}

static jlong RuntimeShader_getNativeFinalizer(JNIEnv*, jobject) {
    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&RuntimeShader_delete));
}

///////////////////////////////////////////////////////////////////////////////////////////////

static const JNINativeMethod gColorMethods[] = {
    { "nativeRGBToHSV",    "(III[F)V", (void*)Color_RGBToHSV   },
    { "nativeHSVToColor",  "(I[F)I",   (void*)Color_HSVToColor }
@@ -241,6 +280,13 @@ static const JNINativeMethod gComposeShaderMethods[] = {
    { "nativeCreate",      "(JJJI)J",   (void*)ComposeShader_create     },
};

static const JNINativeMethod gRuntimeShaderMethods[] = {
    { "nativeGetFinalizer",   "()J",    (void*)RuntimeShader_getNativeFinalizer },
    { "nativeCreate",     "(JJ[BJ)J",  (void*)RuntimeShader_create     },
    { "nativeCreateShaderFactory",     "(Ljava/lang/String;Z)J",
      (void*)RuntimeShader_createShaderFactory     },
};

int register_android_graphics_Shader(JNIEnv* env)
{
    android::RegisterMethodsOrDie(env, "android/graphics/Color", gColorMethods,
@@ -257,6 +303,8 @@ int register_android_graphics_Shader(JNIEnv* env)
                                  NELEM(gSweepGradientMethods));
    android::RegisterMethodsOrDie(env, "android/graphics/ComposeShader", gComposeShaderMethods,
                                  NELEM(gComposeShaderMethods));
    android::RegisterMethodsOrDie(env, "android/graphics/RuntimeShader", gRuntimeShaderMethods,
                                  NELEM(gRuntimeShaderMethods));

    return 0;
}
+88 −0
Original line number Diff line number Diff line
/*
 * Copyright 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.graphics;

import android.annotation.NonNull;
import android.annotation.Nullable;

import libcore.util.NativeAllocationRegistry;

/**
 * Shader that calculates pixel output with a program (fragment shader) running on a GPU.
 * @hide
 */
public class RuntimeShader extends Shader {

    private static class NoImagePreloadHolder {
        public static final NativeAllocationRegistry sRegistry =
                NativeAllocationRegistry.createMalloced(
                RuntimeShader.class.getClassLoader(), nativeGetFinalizer());
    }

    private byte[] mUniforms;

    /**
     * Current native shader factory instance.
     */
    private long mNativeInstanceRuntimeShaderFactory;

    /**
     * Creates a new RuntimeShader.
     *
     * @param sksl The text of SKSL program to run on the GPU.
     * @param uniforms Array of parameters passed by the SKSL shader. Array size depends
     *                 on number of uniforms declared by sksl.
     * @param isOpaque True if all pixels have alpha 1.0f.
     */
    public RuntimeShader(@NonNull String sksl, @Nullable byte[] uniforms, boolean isOpaque) {
        this(sksl, uniforms, isOpaque, ColorSpace.get(ColorSpace.Named.SRGB));
    }

    private RuntimeShader(@NonNull String sksl, @Nullable byte[] uniforms, boolean isOpaque,
            ColorSpace colorSpace) {
        super(colorSpace);
        mUniforms = uniforms;
        mNativeInstanceRuntimeShaderFactory = nativeCreateShaderFactory(sksl, isOpaque);
        NoImagePreloadHolder.sRegistry.registerNativeAllocation(this,
                mNativeInstanceRuntimeShaderFactory);
    }

    /**
     * Sets new value for shader parameters.
     *
     * @param uniforms Array of parameters passed by the SKSL shader. Array size depends
     *                 on number of uniforms declared by mSksl.
     */
    public void updateUniforms(@Nullable byte[] uniforms) {
        mUniforms = uniforms;
        discardNativeInstance();
    }

    @Override
    long createNativeInstance(long nativeMatrix) {
        return nativeCreate(mNativeInstanceRuntimeShaderFactory, nativeMatrix, mUniforms,
                colorSpace().getNativeInstance());
    }

    private static native long nativeCreate(long shaderFactory, long matrix, byte[] inputs,
            long colorSpaceHandle);

    private static native long nativeCreateShaderFactory(String sksl, boolean isOpaque);

    private static native long nativeGetFinalizer();
}
+48 −0
Original line number Diff line number Diff line
@@ -29,9 +29,13 @@ import android.graphics.LightingColorFilter;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.RuntimeShader;
import android.os.Bundle;
import android.view.View;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

@SuppressWarnings({"UnusedDeclaration"})
public class ColorFiltersMutateActivity extends Activity {
    @Override
@@ -47,12 +51,21 @@ public class ColorFiltersMutateActivity extends Activity {
        private final Paint mColorMatrixPaint;
        private final Paint mLightingPaint;
        private final Paint mBlendPaint;
        private final Paint mShaderPaint;

        private float mSaturation = 0.0f;
        private int mLightAdd = 0;
        private int mLightMul = 0;
        private int mPorterDuffColor = 0;

        static final String sSkSL =
                "uniform float param1;\n"
                + "void main(float x, float y, inout half4 color) {\n"
                + "color = half4(color.r, half(param1), color.b, 1.0);\n"
                + "}\n";

        private byte[] mUniforms = new byte[4];

        BitmapsView(Context c) {
            super(c);

@@ -70,6 +83,10 @@ public class ColorFiltersMutateActivity extends Activity {
            mBlendPaint = new Paint();
            mBlendPaint.setColorFilter(new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_OVER));

            mShaderPaint = new Paint();
            mShaderPaint.setShader(new RuntimeShader(sSkSL, mUniforms, true));
            setShaderParam1(0.0f);

            ObjectAnimator sat = ObjectAnimator.ofFloat(this, "saturation", 1.0f);
            sat.setDuration(1000);
            sat.setRepeatCount(ObjectAnimator.INFINITE);
@@ -96,6 +113,12 @@ public class ColorFiltersMutateActivity extends Activity {
            color.setRepeatCount(ObjectAnimator.INFINITE);
            color.setRepeatMode(ObjectAnimator.REVERSE);
            color.start();

            ObjectAnimator shaderUniform = ObjectAnimator.ofFloat(this, "shaderParam1", 1.0f);
            shaderUniform.setDuration(1000);
            shaderUniform.setRepeatCount(ObjectAnimator.INFINITE);
            shaderUniform.setRepeatMode(ObjectAnimator.REVERSE);
            shaderUniform.start();
        }

        public int getPorterDuffColor() {
@@ -148,6 +171,23 @@ public class ColorFiltersMutateActivity extends Activity {
            return mSaturation;
        }

        public void setShaderParam1(float value) {
            RuntimeShader shader = (RuntimeShader) mShaderPaint.getShader();
            ByteBuffer buffer = ByteBuffer.wrap(mUniforms);
            buffer.order(ByteOrder.LITTLE_ENDIAN);
            buffer.putFloat(value);
            shader.updateUniforms(mUniforms);
            invalidate();
        }

        // If either valueFrom or valueTo is null, then a getter function will also be derived
        // and called by the animator class.
        public float getShaderParam1() {
            ByteBuffer buffer = ByteBuffer.wrap(mUniforms);
            buffer.order(ByteOrder.LITTLE_ENDIAN);
            return buffer.getFloat();
        }

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
@@ -163,6 +203,10 @@ public class ColorFiltersMutateActivity extends Activity {

            canvas.translate(0.0f, 50.0f + mBitmap1.getHeight());
            canvas.drawBitmap(mBitmap1, 0.0f, 0.0f, mBlendPaint);

            canvas.translate(0.0f, 50.0f + mBitmap1.getHeight());
            canvas.drawRect(0.0f, 0.0f, mBitmap1.getWidth(), mBitmap1.getHeight(),
                    mShaderPaint);
            canvas.restore();

            canvas.save();
@@ -174,6 +218,10 @@ public class ColorFiltersMutateActivity extends Activity {

            canvas.translate(0.0f, 50.0f + mBitmap2.getHeight());
            canvas.drawBitmap(mBitmap2, 0.0f, 0.0f, mBlendPaint);

            canvas.translate(0.0f, 50.0f + mBitmap2.getHeight());
            canvas.drawRoundRect(0.0f, 0.0f, mBitmap2.getWidth(), mBitmap2.getHeight(), 20, 20,
                    mShaderPaint);
            canvas.restore();
        }
    }