Mobile Development 13 min read

Inject OpenGL Rendering into Android RenderThread Using drawFunctor

This article explains the background, principles, and implementation steps for using Android's drawFunctor to inject custom OpenGL rendering into the RenderThread, including C++ and Java code, handling state preservation, view transformations, context loss, and performance benefits demonstrated by memory and CPU usage comparisons.

Alipay Experience Technology
Alipay Experience Technology
Alipay Experience Technology
Inject OpenGL Rendering into Android RenderThread Using drawFunctor

1. Background

In the Ant Group NativeCanvas Android project, GL rendering is implemented using a TextureView, which requires a separate GraphicBuffer and incurs high memory usage because the RenderThread must wrap the buffer into an EGLImage before rendering.

To reduce memory consumption, the drawFunctor mechanism—originally used to synchronize WebView composition—was investigated and successfully employed to inject GL rendering into the RenderThread.

2. drawFunctor Principle

drawFunctor is an Android-provided mechanism that allows code to be inserted into the RenderThread rendering pipeline. The framework implements it in three steps:

During the UI thread's onDraw, the functor is inserted into the DisplayList via RecordingCanvas.invoke.

When the RenderThread processes the DisplayList, it detects functor ops and saves the current GL state.

The functor logic is executed in the RenderThread, after which the GL state is restored and rendering continues.

Only View.onDraw can inject a functor, so unattached views cannot be used. The functor can execute arbitrary code, such as statistics or performance checks, and the system saves and restores basic GL state around its execution.

If a View uses a HardwareLayer, the RenderThread renders the view into an off‑screen FBO; the functor then runs with that FBO as the target, writing into the FBO rather than the window buffer.

3. Injecting GL Rendering with drawFunctor

Because drawFunctor runs inside the RenderThread, any OpenGL API can be called. Two strategies exist: using the RenderThread's default EGL context or creating a shared EGL context. The article focuses on the first strategy.

Android Functor Definition

The Functor class from the Android source is introduced and its header is included.

namespace android {
    class Functor {
    public:
        Functor() {}
        virtual ~Functor() {}
        virtual int operator()(int /*what*/, void * /*data*/) { return 0; }
    };
}

The DrawGlInfo structure, which is passed to the functor, is also shown.

namespace android {
    namespace uirenderer {
        struct DrawGlInfo {
            int clipLeft; int clipTop; int clipRight; int clipBottom;
            int width; int height;
            bool isLayer;
            float transform[16];
            const void* color_space_ptr;
            float dirtyLeft; float dirtyTop; float dirtyRight; float dirtyBottom;
            enum Mode { kModeDraw, kModeProcess, kModeProcessNoContext, kModeSync };
            enum Status { kStatusDone = 0x0, kStatusDrew = 0x4 };
        };
    }
}

Functor Design

A C++ subclass MyFunctor overrides operator() to handle only kModeDraw and forwards execution to onExec.

class MyFunctor : public Functor {
public:
    MyFunctor();
    virtual ~MyFunctor() {}
    virtual void onExec(int what, android::uirenderer::DrawGlInfo* info) = 0;
    virtual std::string getFunctorName() = 0;
    int operator()(int what, void* data) override {
        if (what == android::uirenderer::DrawGlInfo::Mode::kModeDraw) {
            auto info = static_cast<android::uirenderer::DrawGlInfo*>(data);
            onExec(what, info);
        }
        return android::uirenderer::DrawGlInfo::Status::kStatusDone;
    }
};

On the Java side a MyFunctor class holds a native handle and creates it via JNI.

class MyFunctor {
    private long nativeHandle;
    public MyFunctor() {
        nativeHandle = createNativeHandle();
    }
    public long getNativeHandle() { return nativeHandle; }
    private native long createNativeHandle();
}

The corresponding JNI function allocates the native C++ object and returns its pointer.

extern "C" JNIEXPORT jlong JNICALL
Java_com_test_MyFunctor_createNativeHandle(JNIEnv* env, jobject thiz) {
    auto p = new MyFunctor();
    return (jlong)p;
}

Scheduling the Functor in View.onDraw()

A custom View subclass obtains the hidden drawGLFunction method via reflection and invokes the functor during onDraw.

public class FunctorView extends View {
    private static Method sDrawGLFunction;
    private MyFunctor myFunctor = new MyFunctor();

    @Override
    public void onDraw(Canvas cvs) {
        super.onDraw(cvs);
        getDrawFunctorMethodIfNot();
        invokeFunctor(cvs, myFunctor);
    }

    private void invokeFunctor(Canvas canvas, MyFunctor functor) {
        if (functor.getNativeHandle() != 0 && sDrawGLFunction != null) {
            try {
                sDrawGLFunction.invoke(canvas, functor.getNativeHandle());
            } catch (Throwable t) {
                // log
            }
        }
    }

    public static synchronized Method getDrawFunctorMethodIfNot() {
        if (sDrawGLFunction != null) return sDrawGLFunction;
        // Determine class and method name based on SDK version
        // (reflection code omitted for brevity)
        return sDrawGLFunction;
    }
}

Note that reflection of hidden APIs may fail on Android 10 and later, requiring additional workarounds.

4. Practical Issues Encountered

GL State Save & Restore

The RenderThread saves only a subset of GL state before invoking a functor. Additional state (e.g., stencil, blend) must be saved and restored manually to avoid rendering artifacts.

View Transform Handling

When the functor‑host view is inside a ScrollView, ViewPager, or animated, the RenderThread applies a 4×4 transform matrix. The matrix is available in DrawGlInfo::transform and should be passed to shaders as the model matrix.

Context Lost

During trimMemory the RenderThread may destroy its EGL context, invalidating shaders, programs, and textures used by the functor. Register a ComponentCallbacks2 listener to detect low‑memory events and recreate GL resources before the next draw.

5. Results

A simple 1080×1920 OpenGL case was tested. Compared with the traditional TextureView approach, drawFunctor reduced memory from 100 MB to 84 MB and CPU usage from 6 % to 4 %.

TextureView: 100 MB memory (Graphics 38 MB), 6 % CPU.

drawFunctor: 84 MB memory (Graphics 26 MB), 4 % CPU.

The measurements demonstrate that drawFunctor offers clear advantages in memory consumption and CPU overhead, making it suitable for interactive page rendering, video rendering, and similar scenarios.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Mobile DevelopmentAndroidOpenGLRenderThreaddrawFunctorGL injection
Alipay Experience Technology
Written by

Alipay Experience Technology

Exploring ultimate user experience and best engineering practices

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.