diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 4402fb162df07..da81efcc1dee0 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -894,6 +894,15 @@ public final class ThreadedRenderer { } } + /** + * Creates a {@link android.graphics.Bitmap.Config#HARDWARE} bitmap from the given + * RenderNode. Note that the RenderNode should be created as a root node (so x/y of 0,0), and + * not the RenderNode from a View. + **/ + public static Bitmap createHardwareBitmap(RenderNode node, int width, int height) { + return nCreateHardwareBitmap(node.getNativeDisplayList(), width, height); + } + @Override protected void finalize() throws Throwable { try { @@ -1037,4 +1046,6 @@ public final class ThreadedRenderer { private static native int nCopySurfaceInto(Surface surface, int srcLeft, int srcTop, int srcRight, int srcBottom, Bitmap bitmap); + + private static native Bitmap nCreateHardwareBitmap(long renderNode, int width, int height); } diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index aa1893c6110f7..4c530d7d16812 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -25,6 +25,10 @@ #include #include +#include +#include +#include + #include #include #include @@ -839,6 +843,75 @@ static jint android_view_ThreadedRenderer_copySurfaceInto(JNIEnv* env, return RenderProxy::copySurfaceInto(surface, left, top, right, bottom, &bitmap); } +class ContextFactory : public IContextFactory { +public: + virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) { + return new AnimationContext(clock); + } +}; + +static jobject android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode(JNIEnv* env, + jobject clazz, jlong renderNodePtr, jint jwidth, jint jheight) { + RenderNode* renderNode = reinterpret_cast(renderNodePtr); + if (jwidth <= 0 || jheight <= 0) { + ALOGW("Invalid width %d or height %d", jwidth, jheight); + return nullptr; + } + + uint32_t width = jwidth; + uint32_t height = jheight; + + // Create a Surface wired up to a BufferItemConsumer + sp producer; + sp rawConsumer; + BufferQueue::createBufferQueue(&producer, &rawConsumer); + rawConsumer->setMaxBufferCount(1); + sp consumer = new BufferItemConsumer(rawConsumer, + GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_SW_READ_NEVER | GRALLOC_USAGE_SW_WRITE_NEVER); + consumer->setDefaultBufferSize(width, height); + sp surface = new Surface(producer); + + // Render into the surface + { + ContextFactory factory; + RenderProxy proxy{false, renderNode, &factory}; + proxy.loadSystemProperties(); + proxy.setSwapBehavior(SwapBehavior::kSwap_discardBuffer); + proxy.initialize(surface); + // Shadows can't be used via this interface, so just set the light source + // to all 0s. + proxy.setup(0, 0, 0); + proxy.setLightCenter((Vector3){0, 0, 0}); + nsecs_t vsync = systemTime(CLOCK_MONOTONIC); + UiFrameInfoBuilder(proxy.frameInfo()) + .setVsync(vsync, vsync) + .addFlag(FrameInfoFlags::SurfaceCanvas); + proxy.syncAndDrawFrame(); + } + + // Yank out the GraphicBuffer + BufferItem bufferItem; + status_t err; + if ((err = consumer->acquireBuffer(&bufferItem, 0, true)) != OK) { + ALOGW("Failed to acquireBuffer, error %d (%s)", err, strerror(-err)); + return nullptr; + } + sp buffer = bufferItem.mGraphicBuffer; + // We don't really care if this fails or not since we're just going to destroy this anyway + consumer->releaseBuffer(bufferItem); + if (!buffer.get()) { + ALOGW("GraphicBuffer is null?"); + return nullptr; + } + if (buffer->getWidth() != width || buffer->getHeight() != height) { + ALOGW("GraphicBuffer size mismatch, got %dx%d expected %dx%d", + buffer->getWidth(), buffer->getHeight(), width, height); + // Continue I guess? + } + sk_sp bitmap = Bitmap::createFrom(buffer); + return createBitmap(env, bitmap.release(), android::bitmap::kBitmapCreateFlag_Mutable); +} + // ---------------------------------------------------------------------------- // FrameMetricsObserver // ---------------------------------------------------------------------------- @@ -934,6 +1007,8 @@ static const JNINativeMethod gMethods[] = { (void*)android_view_ThreadedRenderer_removeFrameMetricsObserver }, { "nCopySurfaceInto", "(Landroid/view/Surface;IIIILandroid/graphics/Bitmap;)I", (void*)android_view_ThreadedRenderer_copySurfaceInto }, + { "nCreateHardwareBitmap", "(JII)Landroid/graphics/Bitmap;", + (void*)android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode }, }; int register_android_view_ThreadedRenderer(JNIEnv* env) { diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index b4f3d6964befc..9caf9d0f6e26e 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -112,6 +112,15 @@ + + + + + + + diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/DrawIntoHwBitmapActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/DrawIntoHwBitmapActivity.java new file mode 100644 index 0000000000000..faabdfc01de76 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/DrawIntoHwBitmapActivity.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2017 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 static android.graphics.GraphicBuffer.USAGE_HW_TEXTURE; +import static android.graphics.GraphicBuffer.USAGE_SW_READ_NEVER; +import static android.graphics.GraphicBuffer.USAGE_SW_WRITE_NEVER; +import static android.graphics.GraphicBuffer.USAGE_SW_WRITE_RARELY; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.GraphicBuffer; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.SurfaceTexture; +import android.os.Bundle; +import android.view.DisplayListCanvas; +import android.view.RenderNode; +import android.view.Surface; +import android.view.ThreadedRenderer; +import android.widget.ImageView; + +public class DrawIntoHwBitmapActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ImageView view = new ImageView(this); + view.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + setContentView(view); + view.setImageBitmap(createBitmap()); + } + + Bitmap createBitmap() { + RenderNode node = RenderNode.create("HwuiCanvas", null); + node.setLeftTopRightBottom(0, 0, 500, 500); + node.setClipToBounds(false); + DisplayListCanvas canvas = node.start(500, 500); + Paint p = new Paint(); + p.setColor(Color.BLACK); + p.setTextSize(20 * getResources().getDisplayMetrics().density); + canvas.drawColor(0xFF2196F3); + p.setColor(0xFFBBDEFB); + canvas.drawRect(0, 0, 500, 100, p); + p.setColor(Color.BLACK); + canvas.drawText("Hello, World!", 0, 90, p); + node.end(canvas); + return ThreadedRenderer.createHardwareBitmap(node, 500, 500); + } +}