Created HardwareBufferRenderer to support rendering into
HardwareBuffer targets.

Relnote: "Created HardwareBufferRenderer API to handle
rendering a single frame into a HardwareBuffer target."

Refactored dlsym logic for dynamically resolving AHardwareBuffer
methods to be shared across multiple locations.

Bug: 255692581
Test: Created HardwareBufferRendererTests
Change-Id: I749b5d763a9ee580abc2d6cc87bd94a46b7abdd9
diff --git a/core/api/current.txt b/core/api/current.txt
index 5dd1b39..31659e4 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -15080,6 +15080,29 @@
     ctor @Deprecated public EmbossMaskFilter(float[], float, float, float);
   }
 
+  public class HardwareBufferRenderer implements java.lang.AutoCloseable {
+    ctor public HardwareBufferRenderer(@NonNull android.hardware.HardwareBuffer);
+    method public void close();
+    method public boolean isClosed();
+    method @NonNull public android.graphics.HardwareBufferRenderer.RenderRequest obtainRenderRequest();
+    method public void setContentRoot(@Nullable android.graphics.RenderNode);
+    method public void setLightSourceAlpha(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float);
+    method public void setLightSourceGeometry(float, float, @FloatRange(from=0.0f) float, @FloatRange(from=0.0f) float);
+  }
+
+  public final class HardwareBufferRenderer.RenderRequest {
+    method public void draw(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.graphics.HardwareBufferRenderer.RenderResult>);
+    method @NonNull public android.graphics.HardwareBufferRenderer.RenderRequest setBufferTransform(int);
+    method @NonNull public android.graphics.HardwareBufferRenderer.RenderRequest setColorSpace(@Nullable android.graphics.ColorSpace);
+  }
+
+  public static final class HardwareBufferRenderer.RenderResult {
+    method @NonNull public android.hardware.SyncFence getFence();
+    method public int getStatus();
+    field public static final int ERROR_UNKNOWN = 1; // 0x1
+    field public static final int SUCCESS = 0; // 0x0
+  }
+
   public class HardwareRenderer {
     ctor public HardwareRenderer();
     method public void clearContent();
diff --git a/core/java/android/hardware/SyncFence.java b/core/java/android/hardware/SyncFence.java
index 1660013..d6052cd 100644
--- a/core/java/android/hardware/SyncFence.java
+++ b/core/java/android/hardware/SyncFence.java
@@ -87,8 +87,8 @@
     // is well worth making.
     private final Runnable mCloser;
 
-    private SyncFence(@NonNull ParcelFileDescriptor wrapped) {
-        mNativePtr = nCreate(wrapped.detachFd());
+    private SyncFence(int fileDescriptor) {
+        mNativePtr = nCreate(fileDescriptor);
         mCloser = sRegistry.registerNativeAllocation(this, mNativePtr);
     }
 
@@ -136,14 +136,26 @@
     }
 
     /**
-     * Create a new SyncFence wrapped around another descriptor. By default, all method calls are
-     * delegated to the wrapped descriptor.
+     * Create a new SyncFence wrapped around another {@link ParcelFileDescriptor}. By default, all
+     * method calls are delegated to the wrapped descriptor. This takes ownership of the
+     * {@link ParcelFileDescriptor}.
      *
      * @param wrapped The descriptor to be wrapped.
      * @hide
      */
     public static @NonNull SyncFence create(@NonNull ParcelFileDescriptor wrapped) {
-        return new SyncFence(wrapped);
+        return new SyncFence(wrapped.detachFd());
+    }
+
+    /**
+     * Create a new SyncFence wrapped around another descriptor. The returned {@link SyncFence}
+     * instance takes ownership of the file descriptor.
+     *
+     * @param fileDescriptor The descriptor to be wrapped.
+     * @hide
+     */
+    public static @NonNull SyncFence adopt(int fileDescriptor) {
+        return new SyncFence(fileDescriptor);
     }
 
     /**
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp
index 93ba23b..d7cbf74 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/LayoutlibLoader.cpp
@@ -102,6 +102,7 @@
 extern int register_android_view_KeyEvent(JNIEnv* env);
 extern int register_android_view_MotionEvent(JNIEnv* env);
 extern int register_android_view_ThreadedRenderer(JNIEnv* env);
+extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env);
 extern int register_android_view_VelocityTracker(JNIEnv* env);
 extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
 
diff --git a/graphics/java/android/graphics/HardwareBufferRenderer.java b/graphics/java/android/graphics/HardwareBufferRenderer.java
new file mode 100644
index 0000000..361dc59
--- /dev/null
+++ b/graphics/java/android/graphics/HardwareBufferRenderer.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2022 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.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.ColorSpace.Named;
+import android.hardware.HardwareBuffer;
+import android.hardware.SyncFence;
+import android.view.SurfaceControl;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * <p>Creates an instance of a hardware-accelerated renderer. This is used to render a scene built
+ * from {@link RenderNode}s to an output {@link HardwareBuffer}. There can be as many
+ * HardwareBufferRenderer instances as desired.</p>
+ *
+ * <h3>Resources & lifecycle</h3>
+ *
+ * <p>All HardwareBufferRenderer and {@link HardwareRenderer} instances share a common render
+ * thread. Therefore HardwareBufferRenderer will share common resources and GPU utilization with
+ * hardware accelerated rendering initiated by the UI thread of an application.
+ * The render thread contains the GPU context & resources necessary to do GPU-accelerated
+ * rendering. As such, the first HardwareBufferRenderer created comes with the cost of also creating
+ * the associated GPU contexts, however each incremental HardwareBufferRenderer thereafter is fairly
+ * cheap. The expected usage is to have a HardwareBufferRenderer instance for every active {@link
+ * HardwareBuffer}.</p>
+ *
+ * This is useful in situations where a scene built with {@link RenderNode}s can be consumed
+ * directly by the system compositor through
+ * {@link SurfaceControl.Transaction#setBuffer(SurfaceControl, HardwareBuffer)}.
+ *
+ * HardwareBufferRenderer will never clear contents before each draw invocation so previous contents
+ * in the {@link HardwareBuffer} target will be preserved across renders.
+ */
+public class HardwareBufferRenderer implements AutoCloseable {
+
+    private static final ColorSpace DEFAULT_COLORSPACE = ColorSpace.get(Named.SRGB);
+
+    private static class HardwareBufferRendererHolder {
+        public static final NativeAllocationRegistry REGISTRY =
+                NativeAllocationRegistry.createMalloced(
+                    HardwareBufferRenderer.class.getClassLoader(), nGetFinalizer());
+    }
+
+    private final HardwareBuffer mHardwareBuffer;
+    private final RenderRequest mRenderRequest;
+    private final RenderNode mRootNode;
+    private final Runnable mCleaner;
+
+    private long mProxy;
+
+    /**
+     * Creates a new instance of {@link HardwareBufferRenderer} with the provided {@link
+     * HardwareBuffer} as the output of the rendered scene.
+     */
+    public HardwareBufferRenderer(@NonNull HardwareBuffer buffer) {
+        RenderNode rootNode = RenderNode.adopt(nCreateRootRenderNode());
+        rootNode.setClipToBounds(false);
+        mProxy = nCreateHardwareBufferRenderer(buffer, rootNode.mNativeRenderNode);
+        mCleaner = HardwareBufferRendererHolder.REGISTRY.registerNativeAllocation(this, mProxy);
+        mRenderRequest = new RenderRequest();
+        mRootNode = rootNode;
+        mHardwareBuffer = buffer;
+    }
+
+    /**
+     * Sets the content root to render. It is not necessary to call this whenever the content
+     * recording changes. Any mutations to the RenderNode content, or any of the RenderNodes
+     * contained within the content node, will be applied whenever a new {@link RenderRequest} is
+     * issued via {@link #obtainRenderRequest()} and {@link RenderRequest#draw(Executor,
+     * Consumer)}.
+     *
+     * @param content The content to set as the root RenderNode. If null the content root is removed
+     * and the renderer will draw nothing.
+     */
+    public void setContentRoot(@Nullable RenderNode content) {
+        RecordingCanvas canvas = mRootNode.beginRecording();
+        if (content != null) {
+            canvas.drawRenderNode(content);
+        }
+        mRootNode.endRecording();
+    }
+
+    /**
+     * Returns a {@link RenderRequest} that can be used to render into the provided {@link
+     * HardwareBuffer}. This is used to synchronize the RenderNode content provided by {@link
+     * #setContentRoot(RenderNode)}.
+     *
+     * @return An instance of {@link RenderRequest}. The instance may be reused for every frame, so
+     * the caller should not hold onto it for longer than a single render request.
+     */
+    @NonNull
+    public RenderRequest obtainRenderRequest() {
+        mRenderRequest.reset();
+        return mRenderRequest;
+    }
+
+    /**
+     * Returns if the {@link HardwareBufferRenderer} has already been closed. That is
+     * {@link HardwareBufferRenderer#close()} has been invoked.
+     * @return True if the {@link HardwareBufferRenderer} has been closed, false otherwise.
+     */
+    public boolean isClosed() {
+        return mProxy == 0L;
+    }
+
+    /**
+     * Releases the resources associated with this {@link HardwareBufferRenderer} instance. **Note**
+     * this does not call {@link HardwareBuffer#close()} on the provided {@link HardwareBuffer}
+     * instance
+     */
+    @Override
+    public void close() {
+        // Note we explicitly call this only here to clean-up potential animator state
+        // This is not done as part of the NativeAllocationRegistry as it would invoke animator
+        // callbacks on the wrong thread
+        nDestroyRootRenderNode(mRootNode.mNativeRenderNode);
+        if (mProxy != 0L) {
+            mCleaner.run();
+            mProxy = 0L;
+        }
+    }
+
+    /**
+     * Sets the center of the light source. The light source point controls the directionality and
+     * shape of shadows rendered by RenderNode Z & elevation.
+     *
+     * <p>The light source should be setup both as part of initial configuration, and whenever
+     * the window moves to ensure the light source stays anchored in display space instead of in
+     * window space.
+     *
+     * <p>This must be set at least once along with {@link #setLightSourceAlpha(float, float)}
+     * before shadows will work.
+     *
+     * @param lightX The X position of the light source. If unsure, a reasonable default
+     * is 'displayWidth / 2f - windowLeft'.
+     * @param lightY The Y position of the light source. If unsure, a reasonable default
+     * is '0 - windowTop'
+     * @param lightZ The Z position of the light source. Must be >= 0. If unsure, a reasonable
+     * default is 600dp.
+     * @param lightRadius The radius of the light source. Smaller radius will have sharper edges,
+     * larger radius will have softer shadows. If unsure, a reasonable default is 800 dp.
+     */
+    public void setLightSourceGeometry(
+            float lightX,
+            float lightY,
+            @FloatRange(from = 0f) float lightZ,
+            @FloatRange(from = 0f) float lightRadius
+    ) {
+        validateFinite(lightX, "lightX");
+        validateFinite(lightY, "lightY");
+        validatePositive(lightZ, "lightZ");
+        validatePositive(lightRadius, "lightRadius");
+        nSetLightGeometry(mProxy, lightX, lightY, lightZ, lightRadius);
+    }
+
+    /**
+     * Configures the ambient & spot shadow alphas. This is the alpha used when the shadow has max
+     * alpha, and ramps down from the values provided to zero.
+     *
+     * <p>These values are typically provided by the current theme, see
+     * {@link android.R.attr#spotShadowAlpha} and {@link android.R.attr#ambientShadowAlpha}.
+     *
+     * <p>This must be set at least once along with
+     * {@link #setLightSourceGeometry(float, float, float, float)} before shadows will work.
+     *
+     * @param ambientShadowAlpha The alpha for the ambient shadow. If unsure, a reasonable default
+     * is 0.039f.
+     * @param spotShadowAlpha The alpha for the spot shadow. If unsure, a reasonable default is
+     * 0.19f.
+     */
+    public void setLightSourceAlpha(@FloatRange(from = 0.0f, to = 1.0f) float ambientShadowAlpha,
+            @FloatRange(from = 0.0f, to = 1.0f) float spotShadowAlpha) {
+        validateAlpha(ambientShadowAlpha, "ambientShadowAlpha");
+        validateAlpha(spotShadowAlpha, "spotShadowAlpha");
+        nSetLightAlpha(mProxy, ambientShadowAlpha, spotShadowAlpha);
+    }
+
+    /**
+     * Class that contains data regarding the result of the render request.
+     * Consumers are to wait on the provided {@link SyncFence} before consuming the HardwareBuffer
+     * provided to {@link HardwareBufferRenderer} as well as verify that the status returned by
+     * {@link RenderResult#getStatus()} returns {@link RenderResult#SUCCESS}.
+     */
+    public static final class RenderResult {
+
+        /**
+         * Render request was completed successfully
+         */
+        public static final int SUCCESS = 0;
+
+        /**
+         * Render request failed with an unknown error
+         */
+        public static final int ERROR_UNKNOWN = 1;
+
+        /** @hide **/
+        @IntDef(value = {SUCCESS, ERROR_UNKNOWN})
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface RenderResultStatus{}
+
+        private final SyncFence mFence;
+        private final int mResultStatus;
+
+        private RenderResult(@NonNull SyncFence fence, @RenderResultStatus int resultStatus) {
+            mFence = fence;
+            mResultStatus = resultStatus;
+        }
+
+        @NonNull
+        public SyncFence getFence() {
+            return mFence;
+        }
+
+        @RenderResultStatus
+        public int getStatus() {
+            return mResultStatus;
+        }
+    }
+
+    /**
+     * Sets the parameters that can be used to control a render request for a {@link
+     * HardwareBufferRenderer}. This is not thread-safe and must not be held on to for longer than a
+     * single request.
+     */
+    public final class RenderRequest {
+
+        private ColorSpace mColorSpace = DEFAULT_COLORSPACE;
+        private int mTransform = SurfaceControl.BUFFER_TRANSFORM_IDENTITY;
+
+        private RenderRequest() { }
+
+        /**
+         * Syncs the RenderNode tree to the render thread and requests content to be drawn. This
+         * {@link RenderRequest} instance should no longer be used after calling this method. The
+         * system internally may reuse instances of {@link RenderRequest} to reduce allocation
+         * churn.
+         *
+         * @param executor Executor used to deliver callbacks
+         * @param renderCallback Callback invoked when rendering is complete. This includes a
+         * {@link RenderResult} that provides a {@link SyncFence} that should be waited upon for
+         * completion before consuming the rendered output in the provided {@link HardwareBuffer}
+         * instance.
+         *
+         * @throws IllegalStateException if attempt to draw is made when
+         * {@link HardwareBufferRenderer#isClosed()} returns true
+         */
+        public void draw(
+                @NonNull Executor executor,
+                @NonNull Consumer<RenderResult> renderCallback
+        ) {
+            Consumer<RenderResult> wrapped = consumable -> executor.execute(
+                    () -> renderCallback.accept(consumable));
+            if (!isClosed()) {
+                nRender(
+                        mProxy,
+                        mTransform,
+                        mHardwareBuffer.getWidth(),
+                        mHardwareBuffer.getHeight(),
+                        mColorSpace.getNativeInstance(),
+                        wrapped);
+            } else {
+                throw new IllegalStateException("Attempt to draw with a HardwareBufferRenderer "
+                    + "instance that has already been closed");
+            }
+        }
+
+        private void reset() {
+            mColorSpace = DEFAULT_COLORSPACE;
+            mTransform = SurfaceControl.BUFFER_TRANSFORM_IDENTITY;
+        }
+
+        /**
+         * Configures the color space which the content should be rendered in. This affects
+         * how the framework will interpret the color at each pixel. The color space provided here
+         * must be non-null, RGB based and leverage an ICC parametric curve. The min/max values
+         * of the components should not reduce the numerical range compared to the previously
+         * assigned color space. If left unspecified, the default color space of SRGB will be used.
+         *
+         * @param colorSpace The color space the content should be rendered in. If null is provided
+         * the default of SRGB will be used.
+         */
+        @NonNull
+        public RenderRequest setColorSpace(@Nullable ColorSpace colorSpace) {
+            if (colorSpace == null) {
+                mColorSpace = DEFAULT_COLORSPACE;
+            } else {
+                mColorSpace = colorSpace;
+            }
+            return this;
+        }
+
+        /**
+         * Specifies a transform to be applied before content is rendered. This is useful
+         * for pre-rotating content for the current display orientation to increase performance
+         * of displaying the associated buffer. This transformation will also adjust the light
+         * source position for the specified rotation.
+         * @see SurfaceControl.Transaction#setBufferTransform(SurfaceControl, int)
+         */
+        @NonNull
+        public RenderRequest setBufferTransform(
+                @SurfaceControl.BufferTransform int bufferTransform) {
+            boolean validTransform = bufferTransform == SurfaceControl.BUFFER_TRANSFORM_IDENTITY
+                    || bufferTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_90
+                    || bufferTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_180
+                    || bufferTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_270;
+            if (validTransform) {
+                mTransform = bufferTransform;
+            } else {
+                throw new IllegalArgumentException("Invalid transform provided, must be one of"
+                    + "the SurfaceControl.BufferTransform values");
+            }
+            return this;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    /* package */
+    static native int nRender(long renderer, int transform, int width, int height, long colorSpace,
+            Consumer<RenderResult> callback);
+
+    private static native long nCreateRootRenderNode();
+
+    private static native void nDestroyRootRenderNode(long rootRenderNode);
+
+    private static native long nCreateHardwareBufferRenderer(HardwareBuffer buffer,
+            long rootRenderNode);
+
+    private static native void nSetLightGeometry(long bufferRenderer, float lightX, float lightY,
+            float lightZ, float radius);
+
+    private static native void nSetLightAlpha(long nativeProxy, float ambientShadowAlpha,
+            float spotShadowAlpha);
+
+    private static native long nGetFinalizer();
+
+    // Called by native
+    private static void invokeRenderCallback(
+            @NonNull Consumer<RenderResult> callback,
+            int fd,
+            int status
+    ) {
+        callback.accept(new RenderResult(SyncFence.adopt(fd), status));
+    }
+
+    private static void validateAlpha(float alpha, String argumentName) {
+        if (!(alpha >= 0.0f && alpha <= 1.0f)) {
+            throw new IllegalArgumentException(argumentName + " must be a valid alpha, "
+                + alpha + " is not in the range of 0.0f to 1.0f");
+        }
+    }
+
+    private static void validateFinite(float f, String argumentName) {
+        if (!Float.isFinite(f)) {
+            throw new IllegalArgumentException(argumentName + " must be finite, given=" + f);
+        }
+    }
+
+    private static void validatePositive(float f, String argumentName) {
+        if (!(Float.isFinite(f) && f >= 0.0f)) {
+            throw new IllegalArgumentException(argumentName
+                + " must be a finite positive, given=" + f);
+        }
+    }
+}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 3e3d77b..59e4b7a 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -329,6 +329,7 @@
         "jni/android_util_PathParser.cpp",
 
         "jni/Bitmap.cpp",
+        "jni/HardwareBufferHelpers.cpp",
         "jni/BitmapFactory.cpp",
         "jni/ByteBufferStreamAdaptor.cpp",
         "jni/Camera.cpp",
@@ -397,6 +398,7 @@
                 "jni/AnimatedImageDrawable.cpp",
                 "jni/android_graphics_TextureLayer.cpp",
                 "jni/android_graphics_HardwareRenderer.cpp",
+                "jni/android_graphics_HardwareBufferRenderer.cpp",
                 "jni/BitmapRegionDecoder.cpp",
                 "jni/GIFMovie.cpp",
                 "jni/GraphicsStatsService.cpp",
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index e6cfa7b..f57d80c 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -83,6 +83,7 @@
 extern int register_android_view_DisplayListCanvas(JNIEnv* env);
 extern int register_android_view_RenderNode(JNIEnv* env);
 extern int register_android_view_ThreadedRenderer(JNIEnv* env);
+extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env);
 
 #ifdef NDEBUG
     #define REG_JNI(name)      { name }
@@ -151,6 +152,8 @@
             REG_JNI(register_android_util_PathParser),
             REG_JNI(register_android_view_RenderNode),
             REG_JNI(register_android_view_DisplayListCanvas),
+            REG_JNI(register_android_graphics_HardwareBufferRenderer),
+
             REG_JNI(register_android_view_ThreadedRenderer),
     };
 
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 94cea65..540abec 100755
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -2,48 +2,41 @@
 #define LOG_TAG "Bitmap"
 #include "Bitmap.h"
 
+#include <hwui/Bitmap.h>
+#include <hwui/Paint.h>
+
+#include "CreateJavaOutputStreamAdaptor.h"
 #include "GraphicsJNI.h"
+#include "HardwareBufferHelpers.h"
 #include "SkBitmap.h"
 #include "SkBlendMode.h"
 #include "SkCanvas.h"
 #include "SkColor.h"
 #include "SkColorSpace.h"
 #include "SkData.h"
-#include "SkImageEncoder.h"
 #include "SkImageInfo.h"
 #include "SkPaint.h"
-#include "SkPixelRef.h"
 #include "SkPixmap.h"
 #include "SkPoint.h"
 #include "SkRefCnt.h"
 #include "SkStream.h"
 #include "SkTypes.h"
-#include "SkWebpEncoder.h"
-
-
 #include "android_nio_utils.h"
-#include "CreateJavaOutputStreamAdaptor.h"
-#include <hwui/Paint.h>
-#include <hwui/Bitmap.h>
-#include <utils/Color.h>
 
 #ifdef __ANDROID__ // Layoutlib does not support graphic buffer, parcel or render thread
 #include <android-base/unique_fd.h>
 #include <android/binder_parcel.h>
 #include <android/binder_parcel_jni.h>
 #include <android/binder_parcel_platform.h>
-#include <android/binder_parcel_utils.h>
-#include <private/android/AHardwareBufferHelpers.h>
 #include <cutils/ashmem.h>
-#include <dlfcn.h>
 #include <renderthread/RenderProxy.h>
 #include <sys/mman.h>
 #endif
 
 #include <inttypes.h>
 #include <string.h>
+
 #include <memory>
-#include <string>
 
 #define DEBUG_PARCEL 0
 
@@ -1197,18 +1190,11 @@
     return createBitmap(env, bitmap.release(), getPremulBitmapCreateFlags(false));
 }
 
-#ifdef __ANDROID__ // Layoutlib does not support graphic buffer
-typedef AHardwareBuffer* (*AHB_from_HB)(JNIEnv*, jobject);
-AHB_from_HB AHardwareBuffer_fromHardwareBuffer;
-
-typedef jobject (*AHB_to_HB)(JNIEnv*, AHardwareBuffer*);
-AHB_to_HB AHardwareBuffer_toHardwareBuffer;
-#endif
-
 static jobject Bitmap_wrapHardwareBufferBitmap(JNIEnv* env, jobject, jobject hardwareBuffer,
                                                jlong colorSpacePtr) {
 #ifdef __ANDROID__ // Layoutlib does not support graphic buffer
-    AHardwareBuffer* buffer = AHardwareBuffer_fromHardwareBuffer(env, hardwareBuffer);
+    AHardwareBuffer* buffer = uirenderer::HardwareBufferHelpers::AHardwareBuffer_fromHardwareBuffer(
+            env, hardwareBuffer);
     sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer,
                                               GraphicsJNI::getNativeColorSpace(colorSpacePtr));
     if (!bitmap.get()) {
@@ -1231,7 +1217,8 @@
     }
 
     Bitmap& bitmap = bitmapHandle->bitmap();
-    return AHardwareBuffer_toHardwareBuffer(env, bitmap.hardwareBuffer());
+    return uirenderer::HardwareBufferHelpers::AHardwareBuffer_toHardwareBuffer(
+            env, bitmap.hardwareBuffer());
 #else
     return nullptr;
 #endif
@@ -1329,18 +1316,7 @@
     gBitmap_nativePtr = GetFieldIDOrDie(env, gBitmap_class, "mNativePtr", "J");
     gBitmap_constructorMethodID = GetMethodIDOrDie(env, gBitmap_class, "<init>", "(JIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V");
     gBitmap_reinitMethodID = GetMethodIDOrDie(env, gBitmap_class, "reinit", "(IIZ)V");
-
-#ifdef __ANDROID__ // Layoutlib does not support graphic buffer or parcel
-    void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
-    AHardwareBuffer_fromHardwareBuffer =
-            (AHB_from_HB)dlsym(handle_, "AHardwareBuffer_fromHardwareBuffer");
-    LOG_ALWAYS_FATAL_IF(AHardwareBuffer_fromHardwareBuffer == nullptr,
-                        "Failed to find required symbol AHardwareBuffer_fromHardwareBuffer!");
-
-    AHardwareBuffer_toHardwareBuffer = (AHB_to_HB)dlsym(handle_, "AHardwareBuffer_toHardwareBuffer");
-    LOG_ALWAYS_FATAL_IF(AHardwareBuffer_toHardwareBuffer == nullptr,
-                        " Failed to find required symbol AHardwareBuffer_toHardwareBuffer!");
-#endif
+    uirenderer::HardwareBufferHelpers::init();
     return android::RegisterMethodsOrDie(env, "android/graphics/Bitmap", gBitmapMethods,
                                          NELEM(gBitmapMethods));
 }
diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h
index 085a905..c4a61cc 100644
--- a/libs/hwui/jni/GraphicsJNI.h
+++ b/libs/hwui/jni/GraphicsJNI.h
@@ -2,19 +2,18 @@
 #define _ANDROID_GRAPHICS_GRAPHICS_JNI_H_
 
 #include <cutils/compiler.h>
+#include <hwui/Bitmap.h>
+#include <hwui/Canvas.h>
 
-#include "Bitmap.h"
 #include "BRDAllocator.h"
+#include "Bitmap.h"
 #include "SkBitmap.h"
 #include "SkCodec.h"
-#include "SkPixelRef.h"
+#include "SkColorSpace.h"
 #include "SkMallocPixelRef.h"
+#include "SkPixelRef.h"
 #include "SkPoint.h"
 #include "SkRect.h"
-#include "SkColorSpace.h"
-#include <hwui/Canvas.h>
-#include <hwui/Bitmap.h>
-
 #include "graphics_jni_helpers.h"
 
 class SkCanvas;
@@ -335,6 +334,26 @@
     int         fLen;
 };
 
+class JGlobalRefHolder {
+public:
+    JGlobalRefHolder(JavaVM* vm, jobject object) : mVm(vm), mObject(object) {}
+
+    virtual ~JGlobalRefHolder() {
+        GraphicsJNI::getJNIEnv()->DeleteGlobalRef(mObject);
+        mObject = nullptr;
+    }
+
+    jobject object() { return mObject; }
+    JavaVM* vm() { return mVm; }
+
+private:
+    JGlobalRefHolder(const JGlobalRefHolder&) = delete;
+    void operator=(const JGlobalRefHolder&) = delete;
+
+    JavaVM* mVm;
+    jobject mObject;
+};
+
 void doThrowNPE(JNIEnv* env);
 void doThrowAIOOBE(JNIEnv* env); // Array Index Out Of Bounds Exception
 void doThrowIAE(JNIEnv* env, const char* msg = NULL);   // Illegal Argument
diff --git a/libs/hwui/jni/HardwareBufferHelpers.cpp b/libs/hwui/jni/HardwareBufferHelpers.cpp
new file mode 100644
index 0000000..7e3f771
--- /dev/null
+++ b/libs/hwui/jni/HardwareBufferHelpers.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "HardwareBufferHelpers.h"
+
+#include <dlfcn.h>
+#include <log/log.h>
+
+#ifdef __ANDROID__
+typedef AHardwareBuffer* (*AHB_from_HB)(JNIEnv*, jobject);
+typedef jobject (*AHB_to_HB)(JNIEnv*, AHardwareBuffer*);
+static AHB_from_HB fromHardwareBuffer = nullptr;
+static AHB_to_HB toHardwareBuffer = nullptr;
+#endif
+
+void android::uirenderer::HardwareBufferHelpers::init() {
+#ifdef __ANDROID__  // Layoutlib does not support graphic buffer or parcel
+    void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
+    fromHardwareBuffer = (AHB_from_HB)dlsym(handle_, "AHardwareBuffer_fromHardwareBuffer");
+    LOG_ALWAYS_FATAL_IF(fromHardwareBuffer == nullptr,
+                        "Failed to find required symbol AHardwareBuffer_fromHardwareBuffer!");
+
+    toHardwareBuffer = (AHB_to_HB)dlsym(handle_, "AHardwareBuffer_toHardwareBuffer");
+    LOG_ALWAYS_FATAL_IF(toHardwareBuffer == nullptr,
+                        " Failed to find required symbol AHardwareBuffer_toHardwareBuffer!");
+#endif
+}
+
+AHardwareBuffer* android::uirenderer::HardwareBufferHelpers::AHardwareBuffer_fromHardwareBuffer(
+        JNIEnv* env, jobject hardwarebuffer) {
+#ifdef __ANDROID__
+    LOG_ALWAYS_FATAL_IF(fromHardwareBuffer == nullptr,
+                        "Failed to find symbol AHardwareBuffer_fromHardwareBuffer, did you forget "
+                        "to call HardwareBufferHelpers::init?");
+    return fromHardwareBuffer(env, hardwarebuffer);
+#else
+    ALOGE("ERROR attempting to invoke AHardwareBuffer_fromHardwareBuffer on non Android "
+          "configuration");
+    return nullptr;
+#endif
+}
+
+jobject android::uirenderer::HardwareBufferHelpers::AHardwareBuffer_toHardwareBuffer(
+        JNIEnv* env, AHardwareBuffer* ahardwarebuffer) {
+#ifdef __ANDROID__
+    LOG_ALWAYS_FATAL_IF(toHardwareBuffer == nullptr,
+                        "Failed to find symbol AHardwareBuffer_toHardwareBuffer, did you forget to "
+                        "call HardwareBufferHelpers::init?");
+    return toHardwareBuffer(env, ahardwarebuffer);
+#else
+    ALOGE("ERROR attempting to invoke AHardwareBuffer_toHardwareBuffer on non Android "
+          "configuration");
+    return nullptr;
+#endif
+}
\ No newline at end of file
diff --git a/libs/hwui/jni/HardwareBufferHelpers.h b/libs/hwui/jni/HardwareBufferHelpers.h
new file mode 100644
index 0000000..326babf
--- /dev/null
+++ b/libs/hwui/jni/HardwareBufferHelpers.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+#ifndef HARDWAREBUFFER_JNI_HELPERS_H
+#define HARDWAREBUFFER_JNI_HELPERS_H
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+namespace android {
+namespace uirenderer {
+
+class HardwareBufferHelpers {
+public:
+    static void init();
+    static AHardwareBuffer* AHardwareBuffer_fromHardwareBuffer(JNIEnv*, jobject);
+    static jobject AHardwareBuffer_toHardwareBuffer(JNIEnv*, AHardwareBuffer*);
+
+private:
+    HardwareBufferHelpers() = default;  // not to be instantiated
+};
+
+}  // namespace uirenderer
+}  // namespace android
+
+#endif  // HARDWAREBUFFER_JNI_HELPERS_H
\ No newline at end of file
diff --git a/libs/hwui/jni/JvmErrorReporter.h b/libs/hwui/jni/JvmErrorReporter.h
new file mode 100644
index 0000000..5e10b9d
--- /dev/null
+++ b/libs/hwui/jni/JvmErrorReporter.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+#ifndef JVMERRORREPORTER_H
+#define JVMERRORREPORTER_H
+
+#include <TreeInfo.h>
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+
+#include "GraphicsJNI.h"
+
+namespace android {
+namespace uirenderer {
+
+class JvmErrorReporter : public android::uirenderer::ErrorHandler {
+public:
+    JvmErrorReporter(JNIEnv* env) { env->GetJavaVM(&mVm); }
+
+    virtual void onError(const std::string& message) override {
+        JNIEnv* env = GraphicsJNI::getJNIEnv();
+        jniThrowException(env, "java/lang/IllegalStateException", message.c_str());
+    }
+
+private:
+    JavaVM* mVm;
+};
+
+}  // namespace uirenderer
+}  // namespace android
+
+#endif  // JVMERRORREPORTER_H
diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
new file mode 100644
index 0000000..4886fdd
--- /dev/null
+++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "HardwareBufferRenderer"
+#define ATRACE_TAG ATRACE_TAG_VIEW
+
+#include <GraphicsJNI.h>
+#include <RootRenderNode.h>
+#include <TreeInfo.h>
+#include <android-base/unique_fd.h>
+#include <android/native_window.h>
+#include <nativehelper/JNIPlatformHelp.h>
+#include <renderthread/CanvasContext.h>
+#include <renderthread/RenderProxy.h>
+#include <renderthread/RenderThread.h>
+
+#include "HardwareBufferHelpers.h"
+#include "JvmErrorReporter.h"
+
+namespace android {
+
+using namespace android::uirenderer;
+using namespace android::uirenderer::renderthread;
+
+struct {
+    jclass clazz;
+    jmethodID invokeRenderCallback;
+} gHardwareBufferRendererClassInfo;
+
+static RenderCallback createRenderCallback(JNIEnv* env, jobject releaseCallback) {
+    if (releaseCallback == nullptr) return nullptr;
+
+    JavaVM* vm = nullptr;
+    LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM");
+    auto globalCallbackRef =
+            std::make_shared<JGlobalRefHolder>(vm, env->NewGlobalRef(releaseCallback));
+    return [globalCallbackRef](android::base::unique_fd&& fd, int status) {
+        GraphicsJNI::getJNIEnv()->CallStaticVoidMethod(
+                gHardwareBufferRendererClassInfo.clazz,
+                gHardwareBufferRendererClassInfo.invokeRenderCallback, globalCallbackRef->object(),
+                reinterpret_cast<jint>(fd.release()), reinterpret_cast<jint>(status));
+    };
+}
+
+static long android_graphics_HardwareBufferRenderer_createRootNode(JNIEnv* env, jobject) {
+    auto* node = new RootRenderNode(std::make_unique<JvmErrorReporter>(env));
+    node->incStrong(nullptr);
+    node->setName("RootRenderNode");
+    return reinterpret_cast<jlong>(node);
+}
+
+static void android_graphics_hardwareBufferRenderer_destroyRootNode(JNIEnv*, jobject,
+                                                                    jlong renderNodePtr) {
+    auto* node = reinterpret_cast<RootRenderNode*>(renderNodePtr);
+    node->destroy();
+}
+
+static long android_graphics_HardwareBufferRenderer_create(JNIEnv* env, jobject, jobject buffer,
+                                                           jlong renderNodePtr) {
+    auto* hardwareBuffer = HardwareBufferHelpers::AHardwareBuffer_fromHardwareBuffer(env, buffer);
+    auto* rootRenderNode = reinterpret_cast<RootRenderNode*>(renderNodePtr);
+    ContextFactoryImpl factory(rootRenderNode);
+    auto* proxy = new RenderProxy(true, rootRenderNode, &factory);
+    proxy->setHardwareBuffer(hardwareBuffer);
+    return (jlong)proxy;
+}
+
+static void HardwareBufferRenderer_destroy(jobject renderProxy) {
+    auto* proxy = reinterpret_cast<RenderProxy*>(renderProxy);
+    delete proxy;
+}
+
+static SkMatrix createMatrixFromBufferTransform(SkScalar width, SkScalar height, int transform) {
+    auto matrix = SkMatrix();
+    switch (transform) {
+        case ANATIVEWINDOW_TRANSFORM_ROTATE_90:
+            matrix.setRotate(90);
+            matrix.postTranslate(width, 0);
+            break;
+        case ANATIVEWINDOW_TRANSFORM_ROTATE_180:
+            matrix.setRotate(180);
+            matrix.postTranslate(width, height);
+            break;
+        case ANATIVEWINDOW_TRANSFORM_ROTATE_270:
+            matrix.setRotate(270);
+            matrix.postTranslate(0, width);
+            break;
+        default:
+            ALOGE("Invalid transform provided. Transform should be validated from"
+                  "the java side. Leveraging identity transform as a fallback");
+            [[fallthrough]];
+        case ANATIVEWINDOW_TRANSFORM_IDENTITY:
+            break;
+    }
+    return matrix;
+}
+
+static int android_graphics_HardwareBufferRenderer_render(JNIEnv* env, jobject, jobject renderProxy,
+                                                          jint transform, jint width, jint height,
+                                                          jlong colorspacePtr, jobject consumer) {
+    auto* proxy = reinterpret_cast<RenderProxy*>(renderProxy);
+    auto skWidth = static_cast<SkScalar>(width);
+    auto skHeight = static_cast<SkScalar>(height);
+    auto matrix = createMatrixFromBufferTransform(skWidth, skHeight, transform);
+    auto colorSpace = GraphicsJNI::getNativeColorSpace(colorspacePtr);
+    proxy->setHardwareBufferRenderParams(
+            HardwareBufferRenderParams(matrix, colorSpace, createRenderCallback(env, consumer)));
+    return proxy->syncAndDrawFrame();
+}
+
+static void android_graphics_HardwareBufferRenderer_setLightGeometry(JNIEnv*, jobject,
+                                                                     jobject renderProxyPtr,
+                                                                     jfloat lightX, jfloat lightY,
+                                                                     jfloat lightZ,
+                                                                     jfloat lightRadius) {
+    auto* proxy = reinterpret_cast<RenderProxy*>(renderProxyPtr);
+    proxy->setLightGeometry((Vector3){lightX, lightY, lightZ}, lightRadius);
+}
+
+static void android_graphics_HardwareBufferRenderer_setLightAlpha(JNIEnv* env, jobject,
+                                                                  jobject renderProxyPtr,
+                                                                  jfloat ambientShadowAlpha,
+                                                                  jfloat spotShadowAlpha) {
+    auto* proxy = reinterpret_cast<RenderProxy*>(renderProxyPtr);
+    proxy->setLightAlpha((uint8_t)(255 * ambientShadowAlpha), (uint8_t)(255 * spotShadowAlpha));
+}
+
+static jlong android_graphics_HardwareBufferRenderer_getFinalizer() {
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&HardwareBufferRenderer_destroy));
+}
+
+// ----------------------------------------------------------------------------
+// JNI Glue
+// ----------------------------------------------------------------------------
+
+const char* const kClassPathName = "android/graphics/HardwareBufferRenderer";
+
+static const JNINativeMethod gMethods[] = {
+        {"nCreateHardwareBufferRenderer", "(Landroid/hardware/HardwareBuffer;J)J",
+         (void*)android_graphics_HardwareBufferRenderer_create},
+        {"nRender", "(JIIIJLjava/util/function/Consumer;)I",
+         (void*)android_graphics_HardwareBufferRenderer_render},
+        {"nCreateRootRenderNode", "()J",
+         (void*)android_graphics_HardwareBufferRenderer_createRootNode},
+        {"nSetLightGeometry", "(JFFFF)V",
+         (void*)android_graphics_HardwareBufferRenderer_setLightGeometry},
+        {"nSetLightAlpha", "(JFF)V", (void*)android_graphics_HardwareBufferRenderer_setLightAlpha},
+        {"nGetFinalizer", "()J", (void*)android_graphics_HardwareBufferRenderer_getFinalizer},
+        {"nDestroyRootRenderNode", "(J)V",
+         (void*)android_graphics_hardwareBufferRenderer_destroyRootNode}};
+
+int register_android_graphics_HardwareBufferRenderer(JNIEnv* env) {
+    jclass hardwareBufferRendererClazz =
+            FindClassOrDie(env, "android/graphics/HardwareBufferRenderer");
+    gHardwareBufferRendererClassInfo.clazz = hardwareBufferRendererClazz;
+    gHardwareBufferRendererClassInfo.invokeRenderCallback =
+            GetStaticMethodIDOrDie(env, hardwareBufferRendererClazz, "invokeRenderCallback",
+                                   "(Ljava/util/function/Consumer;II)V");
+    HardwareBufferHelpers::init();
+    return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
+}
+
+}  // namespace android
\ No newline at end of file
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index 0663121..47e2edb 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -56,6 +56,7 @@
 #include <atomic>
 #include <vector>
 
+#include "JvmErrorReporter.h"
 #include "android_graphics_HardwareRendererObserver.h"
 
 namespace android {
@@ -93,35 +94,12 @@
     jmethodID getDestinationBitmap;
 } gCopyRequest;
 
-static JNIEnv* getenv(JavaVM* vm) {
-    JNIEnv* env;
-    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
-        LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm);
-    }
-    return env;
-}
-
 typedef ANativeWindow* (*ANW_fromSurface)(JNIEnv* env, jobject surface);
 ANW_fromSurface fromSurface;
 
-class JvmErrorReporter : public ErrorHandler {
-public:
-    JvmErrorReporter(JNIEnv* env) {
-        env->GetJavaVM(&mVm);
-    }
-
-    virtual void onError(const std::string& message) override {
-        JNIEnv* env = getenv(mVm);
-        jniThrowException(env, "java/lang/IllegalStateException", message.c_str());
-    }
-private:
-    JavaVM* mVm;
-};
-
 class FrameCommitWrapper : public LightRefBase<FrameCommitWrapper> {
 public:
     explicit FrameCommitWrapper(JNIEnv* env, jobject jobject) {
-        env->GetJavaVM(&mVm);
         mObject = env->NewGlobalRef(jobject);
         LOG_ALWAYS_FATAL_IF(!mObject, "Failed to make global ref");
     }
@@ -131,19 +109,18 @@
     void onFrameCommit(bool didProduceBuffer) {
         if (mObject) {
             ATRACE_FORMAT("frameCommit success=%d", didProduceBuffer);
-            getenv(mVm)->CallVoidMethod(mObject, gFrameCommitCallback.onFrameCommit,
-                                        didProduceBuffer);
+            GraphicsJNI::getJNIEnv()->CallVoidMethod(mObject, gFrameCommitCallback.onFrameCommit,
+                                                     didProduceBuffer);
             releaseObject();
         }
     }
 
 private:
-    JavaVM* mVm;
     jobject mObject;
 
     void releaseObject() {
         if (mObject) {
-            getenv(mVm)->DeleteGlobalRef(mObject);
+            GraphicsJNI::getJNIEnv()->DeleteGlobalRef(mObject);
             mObject = nullptr;
         }
     }
@@ -443,26 +420,6 @@
     proxy->forceDrawNextFrame();
 }
 
-class JGlobalRefHolder {
-public:
-    JGlobalRefHolder(JavaVM* vm, jobject object) : mVm(vm), mObject(object) {}
-
-    virtual ~JGlobalRefHolder() {
-        getenv(mVm)->DeleteGlobalRef(mObject);
-        mObject = nullptr;
-    }
-
-    jobject object() { return mObject; }
-    JavaVM* vm() { return mVm; }
-
-private:
-    JGlobalRefHolder(const JGlobalRefHolder&) = delete;
-    void operator=(const JGlobalRefHolder&) = delete;
-
-    JavaVM* mVm;
-    jobject mObject;
-};
-
 using TextureMap = std::unordered_map<uint32_t, sk_sp<SkImage>>;
 
 struct PictureCaptureState {
@@ -578,7 +535,7 @@
         auto pictureState = std::make_shared<PictureCaptureState>();
         proxy->setPictureCapturedCallback([globalCallbackRef,
                                            pictureState](sk_sp<SkPicture>&& picture) {
-            JNIEnv* env = getenv(globalCallbackRef->vm());
+            JNIEnv* env = GraphicsJNI::getJNIEnv();
             Picture* wrapper = new PictureWrapper{std::move(picture), pictureState};
             env->CallStaticVoidMethod(gHardwareRenderer.clazz,
                     gHardwareRenderer.invokePictureCapturedCallback,
@@ -600,7 +557,7 @@
                 vm, env->NewGlobalRef(aSurfaceTransactionCallback));
         proxy->setASurfaceTransactionCallback(
                 [globalCallbackRef](int64_t transObj, int64_t scObj, int64_t frameNr) -> bool {
-                    JNIEnv* env = getenv(globalCallbackRef->vm());
+                    JNIEnv* env = GraphicsJNI::getJNIEnv();
                     jboolean ret = env->CallBooleanMethod(
                             globalCallbackRef->object(),
                             gASurfaceTransactionCallback.onMergeTransaction,
@@ -622,7 +579,7 @@
         auto globalCallbackRef =
                 std::make_shared<JGlobalRefHolder>(vm, env->NewGlobalRef(callback));
         proxy->setPrepareSurfaceControlForWebviewCallback([globalCallbackRef]() {
-            JNIEnv* env = getenv(globalCallbackRef->vm());
+            JNIEnv* env = GraphicsJNI::getJNIEnv();
             env->CallVoidMethod(globalCallbackRef->object(),
                                 gPrepareSurfaceControlForWebviewCallback.prepare);
         });
@@ -641,7 +598,7 @@
                 env->NewGlobalRef(frameCallback));
         proxy->setFrameCallback([globalCallbackRef](int32_t syncResult,
                                                     int64_t frameNr) -> std::function<void(bool)> {
-            JNIEnv* env = getenv(globalCallbackRef->vm());
+            JNIEnv* env = GraphicsJNI::getJNIEnv();
             ScopedLocalRef<jobject> frameCommitCallback(
                     env, env->CallObjectMethod(
                                  globalCallbackRef->object(), gFrameDrawingCallback.onFrameDraw,
@@ -680,7 +637,7 @@
         auto globalCallbackRef =
                 std::make_shared<JGlobalRefHolder>(vm, env->NewGlobalRef(callback));
         proxy->setFrameCompleteCallback([globalCallbackRef]() {
-            JNIEnv* env = getenv(globalCallbackRef->vm());
+            JNIEnv* env = GraphicsJNI::getJNIEnv();
             env->CallVoidMethod(globalCallbackRef->object(),
                                 gFrameCompleteCallback.onFrameComplete);
         });
@@ -693,7 +650,7 @@
             : CopyRequest(srcRect), mRefHolder(vm, jCopyRequest) {}
 
     virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) override {
-        JNIEnv* env = getenv(mRefHolder.vm());
+        JNIEnv* env = GraphicsJNI::getJNIEnv();
         jlong bitmapPtr = env->CallLongMethod(
                 mRefHolder.object(), gCopyRequest.getDestinationBitmap, srcWidth, srcHeight);
         SkBitmap bitmap;
@@ -702,7 +659,7 @@
     }
 
     virtual void onCopyFinished(CopyResult result) override {
-        JNIEnv* env = getenv(mRefHolder.vm());
+        JNIEnv* env = GraphicsJNI::getJNIEnv();
         env->CallVoidMethod(mRefHolder.object(), gCopyRequest.onCopyFinished,
                             static_cast<jint>(result));
     }
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 19cd7bd..202a62c 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -55,7 +55,9 @@
 MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() {
     // In case the surface was destroyed (e.g. a previous trimMemory call) we
     // need to recreate it here.
-    if (!isSurfaceReady() && mNativeWindow) {
+    if (mHardwareBuffer) {
+        mRenderThread.requireGlContext();
+    } else if (!isSurfaceReady() && mNativeWindow) {
         setSurface(mNativeWindow.get(), mSwapBehavior);
     }
 
@@ -67,17 +69,24 @@
 }
 
 Frame SkiaOpenGLPipeline::getFrame() {
-    LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
-                        "drawRenderNode called on a context with no surface!");
-    return mEglManager.beginFrame(mEglSurface);
+    if (mHardwareBuffer) {
+        AHardwareBuffer_Desc description;
+        AHardwareBuffer_describe(mHardwareBuffer, &description);
+        return Frame(description.width, description.height, 0);
+    } else {
+        LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
+                            "drawRenderNode called on a context with no surface!");
+        return mEglManager.beginFrame(mEglSurface);
+    }
 }
 
 IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw(
         const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
         const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
         const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
-        const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler) {
-    if (!isCapturingSkp()) {
+        const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
+        const HardwareBufferRenderParams& bufferParams) {
+    if (!isCapturingSkp() && !mHardwareBuffer) {
         mEglManager.damageFrame(frame, dirty);
     }
 
@@ -104,13 +113,25 @@
     SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
 
     SkASSERT(mRenderThread.getGrContext() != nullptr);
-    sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget(
-            mRenderThread.getGrContext(), backendRT, this->getSurfaceOrigin(), colorType,
-            mSurfaceColorSpace, &props));
+    sk_sp<SkSurface> surface;
+    SkMatrix preTransform;
+    if (mHardwareBuffer) {
+        surface = getBufferSkSurface(bufferParams);
+        preTransform = bufferParams.getTransform();
+    } else {
+        surface = SkSurface::MakeFromBackendRenderTarget(mRenderThread.getGrContext(), backendRT,
+                                                         getSurfaceOrigin(), colorType,
+                                                         mSurfaceColorSpace, &props);
+        preTransform = SkMatrix::I();
+    }
 
-    LightingInfo::updateLighting(lightGeometry, lightInfo);
+    SkPoint lightCenter = preTransform.mapXY(lightGeometry.center.x, lightGeometry.center.y);
+    LightGeometry localGeometry = lightGeometry;
+    localGeometry.center.x = lightCenter.fX;
+    localGeometry.center.y = lightCenter.fY;
+    LightingInfo::updateLighting(localGeometry, lightInfo);
     renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
-                SkMatrix::I());
+                preTransform);
 
     // Draw visual debugging features
     if (CC_UNLIKELY(Properties::showDirtyRegions ||
@@ -142,6 +163,10 @@
     // metrics the frame was swapped at this point
     currentFrameInfo->markSwapBuffers();
 
+    if (mHardwareBuffer) {
+        return false;
+    }
+
     *requireSwap = drew || mEglManager.damageRequiresSwap();
 
     if (*requireSwap && (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty)))) {
@@ -197,6 +222,26 @@
     return false;
 }
 
+[[nodiscard]] android::base::unique_fd SkiaOpenGLPipeline::flush() {
+    int fence = -1;
+    EGLSyncKHR sync = EGL_NO_SYNC_KHR;
+    mEglManager.createReleaseFence(true, &sync, &fence);
+    // If a sync object is returned here then the device does not support native
+    // fences, we block on the returned sync and return -1 as a file descriptor
+    if (sync != EGL_NO_SYNC_KHR) {
+        EGLDisplay display = mEglManager.eglDisplay();
+        EGLint result = eglClientWaitSyncKHR(display, sync, 0, 1000000000);
+        if (result == EGL_FALSE) {
+            ALOGE("EglManager::createReleaseFence: error waiting for previous fence: %#x",
+                  eglGetError());
+        } else if (result == EGL_TIMEOUT_EXPIRED_KHR) {
+            ALOGE("EglManager::createReleaseFence: timeout waiting for previous fence");
+        }
+        eglDestroySyncKHR(display, sync);
+    }
+    return android::base::unique_fd(fence);
+}
+
 bool SkiaOpenGLPipeline::isSurfaceReady() {
     return CC_UNLIKELY(mEglSurface != EGL_NO_SURFACE);
 }
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index a80c613..940d6bf 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -21,6 +21,7 @@
 
 #include "SkiaPipeline.h"
 #include "renderstate/RenderState.h"
+#include "renderthread/HardwareBufferRenderParams.h"
 
 namespace android {
 
@@ -36,19 +37,18 @@
 
     renderthread::MakeCurrentResult makeCurrent() override;
     renderthread::Frame getFrame() override;
-    renderthread::IRenderPipeline::DrawResult draw(const renderthread::Frame& frame,
-                                                   const SkRect& screenDirty, const SkRect& dirty,
-                                                   const LightGeometry& lightGeometry,
-                                                   LayerUpdateQueue* layerUpdateQueue,
-                                                   const Rect& contentDrawBounds, bool opaque,
-                                                   const LightInfo& lightInfo,
-                                                   const std::vector<sp<RenderNode> >& renderNodes,
-                                                   FrameInfoVisualizer* profiler) override;
+    renderthread::IRenderPipeline::DrawResult draw(
+            const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
+            const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
+            const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
+            const std::vector<sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler,
+            const renderthread::HardwareBufferRenderParams& bufferParams) override;
     GrSurfaceOrigin getSurfaceOrigin() override { return kBottomLeft_GrSurfaceOrigin; }
     bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
                      FrameInfo* currentFrameInfo, bool* requireSwap) override;
     DeferredLayerUpdater* createTextureLayer() override;
     bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override;
+    [[nodiscard]] android::base::unique_fd flush() override;
     void onStop() override;
     bool isSurfaceReady() override;
     bool isContextReady() override;
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index c546ada..1a336c5 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -604,6 +604,31 @@
     ALOGD("%s", log.c_str());
 }
 
+void SkiaPipeline::setHardwareBuffer(AHardwareBuffer* buffer) {
+    if (mHardwareBuffer) {
+        AHardwareBuffer_release(mHardwareBuffer);
+        mHardwareBuffer = nullptr;
+    }
+
+    if (buffer) {
+        AHardwareBuffer_acquire(buffer);
+        mHardwareBuffer = buffer;
+    }
+}
+
+sk_sp<SkSurface> SkiaPipeline::getBufferSkSurface(
+        const renderthread::HardwareBufferRenderParams& bufferParams) {
+    auto bufferColorSpace = bufferParams.getColorSpace();
+    if (mBufferSurface == nullptr || mBufferColorSpace == nullptr ||
+        !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) {
+        mBufferSurface = SkSurface::MakeFromAHardwareBuffer(
+                mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin,
+                bufferColorSpace, nullptr, true);
+        mBufferColorSpace = bufferColorSpace;
+    }
+    return mBufferSurface;
+}
+
 void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) {
     mColorMode = colorMode;
     switch (colorMode) {
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index 7887d1a..4f93346 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -20,9 +20,11 @@
 #include <SkDocument.h>
 #include <SkMultiPictureDocument.h>
 #include <SkSurface.h>
+
 #include "Lighting.h"
 #include "hwui/AnimatedImageDrawable.h"
 #include "renderthread/CanvasContext.h"
+#include "renderthread/HardwareBufferRenderParams.h"
 #include "renderthread/IRenderPipeline.h"
 
 class SkFILEWStream;
@@ -73,11 +75,20 @@
         mCaptureMode = callback ? CaptureMode::CallbackAPI : CaptureMode::None;
     }
 
+    virtual void setHardwareBuffer(AHardwareBuffer* buffer) override;
+    bool hasHardwareBuffer() override { return mHardwareBuffer != nullptr; }
+
 protected:
+    sk_sp<SkSurface> getBufferSkSurface(
+            const renderthread::HardwareBufferRenderParams& bufferParams);
     void dumpResourceCacheUsage() const;
 
     renderthread::RenderThread& mRenderThread;
 
+    AHardwareBuffer* mHardwareBuffer = nullptr;
+    sk_sp<SkSurface> mBufferSurface = nullptr;
+    sk_sp<SkColorSpace> mBufferColorSpace = nullptr;
+
     ColorMode mColorMode = ColorMode::Default;
     SkColorType mSurfaceColorType;
     sk_sp<SkColorSpace> mSurfaceColorSpace;
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index f10bca6..b94b6cf 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -57,37 +57,55 @@
 MakeCurrentResult SkiaVulkanPipeline::makeCurrent() {
     // In case the surface was destroyed (e.g. a previous trimMemory call) we
     // need to recreate it here.
-    if (!isSurfaceReady() && mNativeWindow) {
+    if (mHardwareBuffer) {
+        mRenderThread.requireVkContext();
+    } else if (!isSurfaceReady() && mNativeWindow) {
         setSurface(mNativeWindow.get(), SwapBehavior::kSwap_default);
     }
     return isContextReady() ? MakeCurrentResult::AlreadyCurrent : MakeCurrentResult::Failed;
 }
 
 Frame SkiaVulkanPipeline::getFrame() {
-    LOG_ALWAYS_FATAL_IF(mVkSurface == nullptr, "getFrame() called on a context with no surface!");
-    return vulkanManager().dequeueNextBuffer(mVkSurface);
+    if (mHardwareBuffer) {
+        AHardwareBuffer_Desc description;
+        AHardwareBuffer_describe(mHardwareBuffer, &description);
+        return Frame(description.width, description.height, 0);
+    } else {
+        LOG_ALWAYS_FATAL_IF(mVkSurface == nullptr,
+                            "getFrame() called on a context with no surface!");
+        return vulkanManager().dequeueNextBuffer(mVkSurface);
+    }
 }
 
 IRenderPipeline::DrawResult SkiaVulkanPipeline::draw(
         const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
         const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
         const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
-        const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler) {
-    sk_sp<SkSurface> backBuffer = mVkSurface->getCurrentSkSurface();
+        const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
+        const HardwareBufferRenderParams& bufferParams) {
+    sk_sp<SkSurface> backBuffer;
+    SkMatrix preTransform;
+    if (mHardwareBuffer) {
+        backBuffer = getBufferSkSurface(bufferParams);
+        preTransform = bufferParams.getTransform();
+    } else {
+        backBuffer = mVkSurface->getCurrentSkSurface();
+        preTransform = mVkSurface->getCurrentPreTransform();
+    }
+
     if (backBuffer.get() == nullptr) {
         return {false, -1};
     }
 
     // update the coordinates of the global light position based on surface rotation
-    SkPoint lightCenter = mVkSurface->getCurrentPreTransform().mapXY(lightGeometry.center.x,
-                                                                     lightGeometry.center.y);
+    SkPoint lightCenter = preTransform.mapXY(lightGeometry.center.x, lightGeometry.center.y);
     LightGeometry localGeometry = lightGeometry;
     localGeometry.center.x = lightCenter.fX;
     localGeometry.center.y = lightCenter.fY;
 
     LightingInfo::updateLighting(localGeometry, lightInfo);
     renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, backBuffer,
-                mVkSurface->getCurrentPreTransform());
+                preTransform);
 
     // Draw visual debugging features
     if (CC_UNLIKELY(Properties::showDirtyRegions ||
@@ -116,12 +134,16 @@
 
 bool SkiaVulkanPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
                                      FrameInfo* currentFrameInfo, bool* requireSwap) {
-    *requireSwap = drew;
-
     // Even if we decided to cancel the frame, from the perspective of jank
     // metrics the frame was swapped at this point
     currentFrameInfo->markSwapBuffers();
 
+    if (mHardwareBuffer) {
+        return false;
+    }
+
+    *requireSwap = drew;
+
     if (*requireSwap) {
         vulkanManager().swapBuffers(mVkSurface, screenDirty);
     }
@@ -137,6 +159,12 @@
 
 void SkiaVulkanPipeline::onStop() {}
 
+[[nodiscard]] android::base::unique_fd SkiaVulkanPipeline::flush() {
+    int fence = -1;
+    vulkanManager().createReleaseFence(&fence, mRenderThread.getGrContext());
+    return android::base::unique_fd(fence);
+}
+
 // We can safely ignore the swap behavior because VkManager will always operate
 // in a mode equivalent to EGLManager::SwapBehavior::kBufferAge
 bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior /*swapBehavior*/) {
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index f3d3613..2c7b268 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -16,14 +16,13 @@
 
 #pragma once
 
+#include "SkRefCnt.h"
 #include "SkiaPipeline.h"
+#include "renderstate/RenderState.h"
+#include "renderthread/HardwareBufferRenderParams.h"
 #include "renderthread/VulkanManager.h"
 #include "renderthread/VulkanSurface.h"
 
-#include "renderstate/RenderState.h"
-
-#include "SkRefCnt.h"
-
 class SkBitmap;
 struct SkRect;
 
@@ -38,18 +37,18 @@
 
     renderthread::MakeCurrentResult makeCurrent() override;
     renderthread::Frame getFrame() override;
-    renderthread::IRenderPipeline::DrawResult draw(const renderthread::Frame& frame,
-                                                   const SkRect& screenDirty, const SkRect& dirty,
-                                                   const LightGeometry& lightGeometry,
-                                                   LayerUpdateQueue* layerUpdateQueue,
-                                                   const Rect& contentDrawBounds, bool opaque,
-                                                   const LightInfo& lightInfo,
-                                                   const std::vector<sp<RenderNode> >& renderNodes,
-                                                   FrameInfoVisualizer* profiler) override;
+    renderthread::IRenderPipeline::DrawResult draw(
+            const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
+            const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
+            const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
+            const std::vector<sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler,
+            const renderthread::HardwareBufferRenderParams& bufferParams) override;
     GrSurfaceOrigin getSurfaceOrigin() override { return kTopLeft_GrSurfaceOrigin; }
     bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
                      FrameInfo* currentFrameInfo, bool* requireSwap) override;
     DeferredLayerUpdater* createTextureLayer() override;
+    [[nodiscard]] android::base::unique_fd flush() override;
+
     bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override;
     void onStop() override;
     bool isSurfaceReady() override;
@@ -64,7 +63,6 @@
 
 private:
     renderthread::VulkanManager& vulkanManager();
-
     renderthread::VulkanSurface* mVkSurface = nullptr;
     sp<ANativeWindow> mNativeWindow;
 };
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 78ae5cf..b769f8d 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -153,6 +153,7 @@
 
 void CanvasContext::destroy() {
     stopDrawing();
+    setHardwareBuffer(nullptr);
     setSurface(nullptr);
     setSurfaceControl(nullptr);
     freePrefetchedLayers();
@@ -176,6 +177,19 @@
     native_window_set_buffer_count(window, bufferCount);
 }
 
+void CanvasContext::setHardwareBuffer(AHardwareBuffer* buffer) {
+    if (mHardwareBuffer) {
+        AHardwareBuffer_release(mHardwareBuffer);
+        mHardwareBuffer = nullptr;
+    }
+
+    if (buffer) {
+        AHardwareBuffer_acquire(buffer);
+        mHardwareBuffer = buffer;
+    }
+    mRenderPipeline->setHardwareBuffer(mHardwareBuffer);
+}
+
 void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) {
     ATRACE_CALL();
 
@@ -257,7 +271,7 @@
             mRenderThread.removeFrameCallback(this);
             mRenderPipeline->onStop();
             mRenderThread.cacheManager().onContextStopped(this);
-        } else if (mIsDirty && hasSurface()) {
+        } else if (mIsDirty && hasOutputTarget()) {
             mRenderThread.postFrameCallback(this);
         }
     }
@@ -390,7 +404,7 @@
 
     mIsDirty = true;
 
-    if (CC_UNLIKELY(!hasSurface())) {
+    if (CC_UNLIKELY(!hasOutputTarget())) {
         mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
         info.out.canDrawThisFrame = false;
         return;
@@ -535,7 +549,7 @@
         std::scoped_lock lock(mFrameMetricsReporterMutex);
         drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry,
                                            &mLayerUpdateQueue, mContentDrawBounds, mOpaque,
-                                           mLightInfo, mRenderNodes, &(profiler()));
+                                           mLightInfo, mRenderNodes, &(profiler()), mBufferParams);
     }
 
     uint64_t frameCompleteNr = getFrameNumber();
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index e875c42..3f796d9 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -125,12 +125,13 @@
     // Won't take effect until next EGLSurface creation
     void setSwapBehavior(SwapBehavior swapBehavior);
 
+    void setHardwareBuffer(AHardwareBuffer* buffer);
     void setSurface(ANativeWindow* window, bool enableTimeout = true);
     void setSurfaceControl(ASurfaceControl* surfaceControl);
     bool pauseSurface();
     void setStopped(bool stopped);
-    bool isStopped() { return mStopped || !hasSurface(); }
-    bool hasSurface() const { return mNativeSurface.get(); }
+    bool isStopped() { return mStopped || !hasOutputTarget(); }
+    bool hasOutputTarget() const { return mNativeSurface.get() || mHardwareBuffer; }
     void allocateBuffers();
 
     void setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
@@ -206,6 +207,10 @@
         mASurfaceTransactionCallback = callback;
     }
 
+    void setHardwareBufferRenderParams(const HardwareBufferRenderParams& params) {
+        mBufferParams = params;
+    }
+
     bool mergeTransaction(ASurfaceTransaction* transaction, ASurfaceControl* control);
 
     void setPrepareSurfaceControlForWebviewCallback(const std::function<void()>& callback) {
@@ -258,6 +263,9 @@
     int32_t mLastFrameHeight = 0;
 
     RenderThread& mRenderThread;
+
+    AHardwareBuffer* mHardwareBuffer = nullptr;
+    HardwareBufferRenderParams mBufferParams;
     std::unique_ptr<ReliableSurface> mNativeSurface;
     // The SurfaceControl reference is passed from ViewRootImpl, can be set to
     // NULL to remove the reference
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 1cc82fd..b06c5dd 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -26,6 +26,7 @@
 #include "../Properties.h"
 #include "../RenderNode.h"
 #include "CanvasContext.h"
+#include "HardwareBufferRenderParams.h"
 #include "RenderThread.h"
 
 namespace android {
@@ -91,6 +92,9 @@
 
     mContext->setSyncDelayDuration(systemTime(SYSTEM_TIME_MONOTONIC) - mSyncQueued);
 
+    auto hardwareBufferParams = mHardwareBufferParams;
+    mContext->setHardwareBufferRenderParams(hardwareBufferParams);
+    IRenderPipeline* pipeline = mContext->getRenderPipeline();
     bool canUnblockUiThread;
     bool canDrawThisFrame;
     {
@@ -150,6 +154,11 @@
     if (!canUnblockUiThread) {
         unblockUiThread();
     }
+
+    if (pipeline->hasHardwareBuffer()) {
+        auto fence = pipeline->flush();
+        hardwareBufferParams.invokeRenderCallback(std::move(fence), 0);
+    }
 }
 
 bool DrawFrameTask::syncFrameState(TreeInfo& info) {
@@ -175,8 +184,9 @@
 
     // This is after the prepareTree so that any pending operations
     // (RenderNode tree state, prefetched layers, etc...) will be flushed.
-    if (CC_UNLIKELY(!mContext->hasSurface() || !canDraw)) {
-        if (!mContext->hasSurface()) {
+    bool hasTarget = mContext->hasOutputTarget();
+    if (CC_UNLIKELY(!hasTarget || !canDraw)) {
+        if (!hasTarget) {
             mSyncResult |= SyncResult::LostSurfaceRewardIfFound;
         } else {
             // If we have a surface but can't draw we must be stopped
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index fafab24..c5c5fe2 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -27,8 +27,16 @@
 #include "../Rect.h"
 #include "../TreeInfo.h"
 #include "RenderTask.h"
+#include "SkColorSpace.h"
+#include "SwapBehavior.h"
+#include "utils/TimeUtils.h"
+#ifdef __ANDROID__  // Layoutlib does not support hardware acceleration
+#include <android/hardware_buffer.h>
+#endif
+#include "HardwareBufferRenderParams.h"
 
 namespace android {
+
 namespace uirenderer {
 
 class DeferredLayerUpdater;
@@ -88,6 +96,10 @@
 
     void forceDrawNextFrame() { mForceDrawFrame = true; }
 
+    void setHardwareBufferRenderParams(const HardwareBufferRenderParams& params) {
+        mHardwareBufferParams = params;
+    }
+
 private:
     void postAndWait();
     bool syncFrameState(TreeInfo& info);
@@ -111,6 +123,7 @@
 
     int64_t mFrameInfo[UI_THREAD_FRAME_INFO_SIZE];
 
+    HardwareBufferRenderParams mHardwareBufferParams;
     std::function<std::function<void(bool)>(int32_t, int64_t)> mFrameCallback;
     std::function<void(bool)> mFrameCommitCallback;
     std::function<void()> mFrameCompleteCallback;
diff --git a/libs/hwui/renderthread/HardwareBufferRenderParams.h b/libs/hwui/renderthread/HardwareBufferRenderParams.h
new file mode 100644
index 0000000..91fe3f6
--- /dev/null
+++ b/libs/hwui/renderthread/HardwareBufferRenderParams.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#ifndef HARDWAREBUFFERRENDERER_H_
+#define HARDWAREBUFFERRENDERER_H_
+
+#include <android-base/unique_fd.h>
+#include <android/hardware_buffer.h>
+
+#include "SkColorSpace.h"
+#include "SkMatrix.h"
+#include "SkSurface.h"
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+using namespace android::uirenderer::renderthread;
+
+using RenderCallback = std::function<void(android::base::unique_fd&&, int)>;
+
+class RenderProxy;
+
+class HardwareBufferRenderParams {
+public:
+    HardwareBufferRenderParams() = default;
+    HardwareBufferRenderParams(const SkMatrix& transform, const sk_sp<SkColorSpace>& colorSpace,
+                               RenderCallback&& callback)
+            : mTransform(transform)
+            , mColorSpace(colorSpace)
+            , mRenderCallback(std::move(callback)) {}
+    const SkMatrix& getTransform() const { return mTransform; }
+    sk_sp<SkColorSpace> getColorSpace() const { return mColorSpace; }
+
+    void invokeRenderCallback(android::base::unique_fd&& fenceFd, int status) {
+        if (mRenderCallback) {
+            std::invoke(mRenderCallback, std::move(fenceFd), status);
+        }
+    }
+
+private:
+    SkMatrix mTransform = SkMatrix::I();
+    sk_sp<SkColorSpace> mColorSpace = SkColorSpace::MakeSRGB();
+    RenderCallback mRenderCallback = nullptr;
+};
+
+}  // namespace renderthread
+}  // namespace uirenderer
+}  // namespace android
+#endif  // HARDWAREBUFFERRENDERER_H_
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index 35e370f..715c17d 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -16,17 +16,19 @@
 
 #pragma once
 
+#include <SkColorSpace.h>
+#include <SkRect.h>
+#include <android-base/unique_fd.h>
+#include <utils/RefBase.h>
+
+#include "ColorMode.h"
 #include "DamageAccumulator.h"
 #include "FrameInfoVisualizer.h"
+#include "HardwareBufferRenderParams.h"
 #include "LayerUpdateQueue.h"
 #include "Lighting.h"
 #include "SwapBehavior.h"
 #include "hwui/Bitmap.h"
-#include "ColorMode.h"
-
-#include <SkColorSpace.h>
-#include <SkRect.h>
-#include <utils/RefBase.h>
 
 class GrDirectContext;
 
@@ -64,10 +66,14 @@
                             const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
                             const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
                             const std::vector<sp<RenderNode>>& renderNodes,
-                            FrameInfoVisualizer* profiler) = 0;
+                            FrameInfoVisualizer* profiler,
+                            const HardwareBufferRenderParams& bufferParams) = 0;
     virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
                              FrameInfo* currentFrameInfo, bool* requireSwap) = 0;
     virtual DeferredLayerUpdater* createTextureLayer() = 0;
+    [[nodiscard]] virtual android::base::unique_fd flush() = 0;
+    virtual void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) = 0;
+    virtual bool hasHardwareBuffer() = 0;
     virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior) = 0;
     virtual void onStop() = 0;
     virtual bool isSurfaceReady() = 0;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 07f5a78..ed01e32 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -85,6 +85,18 @@
     mRenderThread.queue().runSync([this, name]() { mContext->setName(std::string(name)); });
 }
 
+void RenderProxy::setHardwareBuffer(AHardwareBuffer* buffer) {
+    if (buffer) {
+        AHardwareBuffer_acquire(buffer);
+    }
+    mRenderThread.queue().post([this, hardwareBuffer = buffer]() mutable {
+        mContext->setHardwareBuffer(hardwareBuffer);
+        if (hardwareBuffer) {
+            AHardwareBuffer_release(hardwareBuffer);
+        }
+    });
+}
+
 void RenderProxy::setSurface(ANativeWindow* window, bool enableTimeout) {
     if (window) { ANativeWindow_acquire(window); }
     mRenderThread.queue().post([this, win = window, enableTimeout]() mutable {
@@ -324,6 +336,10 @@
     mDrawFrameTask.setContentDrawBounds(left, top, right, bottom);
 }
 
+void RenderProxy::setHardwareBufferRenderParams(const HardwareBufferRenderParams& params) {
+    mDrawFrameTask.setHardwareBufferRenderParams(params);
+}
+
 void RenderProxy::setPictureCapturedCallback(
         const std::function<void(sk_sp<SkPicture>&&)>& callback) {
     mRenderThread.queue().post(
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index a21faa8..17cf665 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -18,6 +18,7 @@
 #define RENDERPROXY_H_
 
 #include <SkRefCnt.h>
+#include <android/hardware_buffer.h>
 #include <android/native_window.h>
 #include <android/surface_control.h>
 #include <cutils/compiler.h>
@@ -76,7 +77,7 @@
     void setSwapBehavior(SwapBehavior swapBehavior);
     bool loadSystemProperties();
     void setName(const char* name);
-
+    void setHardwareBuffer(AHardwareBuffer* buffer);
     void setSurface(ANativeWindow* window, bool enableTimeout = true);
     void setSurfaceControl(ASurfaceControl* surfaceControl);
     void allocateBuffers();
@@ -84,6 +85,7 @@
     void setStopped(bool stopped);
     void setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
     void setLightGeometry(const Vector3& lightCenter, float lightRadius);
+    void setHardwareBufferRenderParams(const HardwareBufferRenderParams& params);
     void setOpaque(bool opaque);
     void setColorMode(ColorMode mode);
     int64_t* frameInfo();
diff --git a/libs/hwui/tests/unit/CanvasContextTests.cpp b/libs/hwui/tests/unit/CanvasContextTests.cpp
index 88420a5..9e376e3 100644
--- a/libs/hwui/tests/unit/CanvasContextTests.cpp
+++ b/libs/hwui/tests/unit/CanvasContextTests.cpp
@@ -38,7 +38,7 @@
     std::unique_ptr<CanvasContext> canvasContext(
             CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0));
 
-    ASSERT_FALSE(canvasContext->hasSurface());
+    ASSERT_FALSE(canvasContext->hasOutputTarget());
 
     canvasContext->destroy();
 }
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 7383d6a..616f21cb 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -1136,6 +1136,15 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="HardwareBufferRendererActivity"
+            android:label="HardwareRenderer/HardwareBufferRenderer"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="com.android.test.hwui.TEST"/>
+            </intent-filter>
+        </activity>
+
         <activity android:name="MyLittleTextureView"
              android:label="HardwareRenderer/MyLittleTextureView"
              android:screenOrientation="fullSensor"
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/HardwareBufferRendererActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/HardwareBufferRendererActivity.java
new file mode 100644
index 0000000..e4de434
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/HardwareBufferRendererActivity.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 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 com.android.test.hwui;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorSpace;
+import android.graphics.ColorSpace.Named;
+import android.graphics.HardwareBufferRenderer;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.RenderNode;
+import android.hardware.HardwareBuffer;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import java.time.Duration;
+import java.util.concurrent.Executors;
+
+public class HardwareBufferRendererActivity extends Activity {
+
+    private ImageView mImageView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mImageView = new ImageView(this);
+        mImageView.setBackgroundColor(Color.MAGENTA);
+        FrameLayout layout = new FrameLayout(this);
+        layout.setBackgroundColor(Color.CYAN);
+        layout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT));
+        layout.addView(mImageView, new FrameLayout.LayoutParams(100, 100));
+        setContentView(layout);
+
+        HardwareBuffer buffer = HardwareBuffer.create(100, 100, PixelFormat.RGBA_8888, 1,
+                HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT);
+        HardwareBufferRenderer renderer = new HardwareBufferRenderer(buffer);
+        RenderNode node = new RenderNode("content");
+        node.setPosition(0, 0, 100, 100);
+
+        Canvas canvas = node.beginRecording();
+        canvas.drawColor(Color.BLUE);
+
+        Paint paint = new Paint();
+        paint.setColor(Color.RED);
+        canvas.drawRect(0f, 0f, 50f, 50f, paint);
+        node.endRecording();
+
+        renderer.setContentRoot(node);
+
+        ColorSpace colorSpace = ColorSpace.get(Named.SRGB);
+        Handler handler = new Handler(Looper.getMainLooper());
+        renderer.obtainRenderRequest()
+                .setColorSpace(colorSpace)
+                .draw(Executors.newSingleThreadExecutor(), result -> {
+                    result.getFence().await(Duration.ofMillis(3000));
+                    handler.post(() -> {
+                        Bitmap bitmap = Bitmap.wrapHardwareBuffer(buffer, colorSpace);
+                        Bitmap copy = bitmap.copy(Config.ARGB_8888, false);
+                        mImageView.setImageBitmap(copy);
+                    });
+                });
+    }
+}