Loading core/java/android/view/ThreadedRenderer.java +6 −0 Original line number Diff line number Diff line Loading @@ -893,6 +893,10 @@ public final class ThreadedRenderer { nSerializeDisplayListTree(mNativeProxy); } public static boolean copySurfaceInto(Surface surface, Bitmap bitmap) { return nCopySurfaceInto(surface, bitmap); } @Override protected void finalize() throws Throwable { try { Loading Loading @@ -1029,4 +1033,6 @@ public final class ThreadedRenderer { private static native long nAddFrameMetricsObserver(long nativeProxy, FrameMetricsObserver observer); private static native void nRemoveFrameMetricsObserver(long nativeProxy, long nativeObserver); private static native boolean nCopySurfaceInto(Surface surface, Bitmap bitmap); } core/jni/android_view_ThreadedRenderer.cpp +10 −0 Original line number Diff line number Diff line Loading @@ -663,6 +663,14 @@ static void android_view_ThreadedRenderer_setContentDrawBounds(JNIEnv* env, proxy->setContentDrawBounds(left, top, right, bottom); } static jboolean android_view_ThreadedRenderer_copySurfaceInto(JNIEnv* env, jobject clazz, jobject jsurface, jobject jbitmap) { SkBitmap bitmap; GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap); sp<Surface> surface = android_view_Surface_getSurface(env, jsurface); return RenderProxy::copySurfaceInto(surface, &bitmap); } // ---------------------------------------------------------------------------- // FrameMetricsObserver // ---------------------------------------------------------------------------- Loading Loading @@ -768,6 +776,8 @@ static const JNINativeMethod gMethods[] = { { "nRemoveFrameMetricsObserver", "(JJ)V", (void*)android_view_ThreadedRenderer_removeFrameMetricsObserver }, { "nCopySurfaceInto", "(Landroid/view/Surface;Landroid/graphics/Bitmap;)Z", (void*)android_view_ThreadedRenderer_copySurfaceInto }, }; int register_android_view_ThreadedRenderer(JNIEnv* env) { Loading graphics/java/android/graphics/PixelCopy.java 0 → 100644 +104 −0 Original line number Diff line number Diff line package android.graphics; import android.annotation.NonNull; import android.os.Handler; import android.view.Surface; import android.view.SurfaceView; import android.view.ThreadedRenderer; /** * Provides a mechanisms to issue pixel copy requests to allow for copy * operations from {@link Surface} to {@link Bitmap} * * @hide */ public final class PixelCopy { /** * Contains the result of a pixel copy request */ public static final class Response { /** * Indicates whether or not the copy request completed successfully. * If this is true, then {@link #bitmap} contains the result of the copy. * If this is false, {@link #bitmap} is unmodified from the originally * passed destination. * * For example a request might fail if the source is protected content * so copies are not allowed. Similarly if the source has nothing to * copy from, because either no frames have been produced yet or because * it has already been destroyed, then this will be false. */ public boolean success; /** * The output bitmap. This is always the same object that was passed * to request() as the 'dest' bitmap. If {@link #success} is true this * contains a copy of the pixels of the source object. If {@link #success} * is false then this is unmodified. */ @NonNull public Bitmap bitmap; } public interface OnPixelCopyFinished { /** * Callback for when a pixel copy request has completed. This will be called * regardless of whether the copy succeeded or failed. * * @param response Contains the result of the copy request which includes * whether or not the copy was successful. */ void onPixelCopyFinished(PixelCopy.Response response); } /** * Requests for the display content of a {@link SurfaceView} to be copied * into a provided {@link Bitmap}. * * The contents of the source will be scaled to fit exactly inside the bitmap. * The pixel format of the source buffer will be converted, as part of the copy, * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer * in the SurfaceView's Surface will be used as the source of the copy. * * @param source The source from which to copy * @param dest The destination of the copy. The source will be scaled to * match the width, height, and format of this bitmap. * @param listener Callback for when the pixel copy request completes * @param listenerThread The callback will be invoked on this Handler when * the copy is finished. */ public static void request(@NonNull SurfaceView source, @NonNull Bitmap dest, @NonNull OnPixelCopyFinished listener, @NonNull Handler listenerThread) { request(source.getHolder().getSurface(), dest, listener, listenerThread); } /** * Requests a copy of the pixels from a {@link Surface} to be copied into * a provided {@link Bitmap}. * * The contents of the source will be scaled to fit exactly inside the bitmap. * The pixel format of the source buffer will be converted, as part of the copy, * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer * in the Surface will be used as the source of the copy. * * @param source The source from which to copy * @param dest The destination of the copy. The source will be scaled to * match the width, height, and format of this bitmap. * @param listener Callback for when the pixel copy request completes * @param listenerThread The callback will be invoked on this Handler when * the copy is finished. */ public static void request(@NonNull Surface source, @NonNull Bitmap dest, @NonNull OnPixelCopyFinished listener, @NonNull Handler listenerThread) { // TODO: Make this actually async and fast and cool and stuff final PixelCopy.Response response = new PixelCopy.Response(); response.success = ThreadedRenderer.copySurfaceInto(source, dest); response.bitmap = dest; listenerThread.post(new Runnable() { @Override public void run() { listener.onPixelCopyFinished(response); } }); } } libs/hwui/Android.mk +1 −0 Original line number Diff line number Diff line Loading @@ -84,6 +84,7 @@ hwui_src_files := \ Properties.cpp \ PropertyValuesHolder.cpp \ PropertyValuesAnimatorSet.cpp \ Readback.cpp \ RenderBufferCache.cpp \ RenderNode.cpp \ RenderProperties.cpp \ Loading libs/hwui/Readback.cpp 0 → 100644 +159 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 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 "Readback.h" #include "Caches.h" #include "Image.h" #include "GlopBuilder.h" #include "renderstate/RenderState.h" #include "renderthread/EglManager.h" #include "utils/GLUtils.h" #include <GLES2/gl2.h> #include <ui/Fence.h> #include <ui/GraphicBuffer.h> namespace android { namespace uirenderer { bool Readback::copySurfaceInto(renderthread::RenderThread& renderThread, Surface& surface, SkBitmap* bitmap) { // TODO: Clean this up and unify it with LayerRenderer::copyLayer, // of which most of this is copied from. renderThread.eglManager().initialize(); Caches& caches = Caches::getInstance(); RenderState& renderState = renderThread.renderState(); int destWidth = bitmap->width(); int destHeight = bitmap->height(); if (destWidth > caches.maxTextureSize || destHeight > caches.maxTextureSize) { ALOGW("Can't copy surface into bitmap, %dx%d exceeds max texture size %d", destWidth, destHeight, caches.maxTextureSize); return false; } GLuint fbo = renderState.createFramebuffer(); if (!fbo) { ALOGW("Could not obtain an FBO"); return false; } SkAutoLockPixels alp(*bitmap); GLuint texture; GLenum format; GLenum type; switch (bitmap->colorType()) { case kAlpha_8_SkColorType: format = GL_ALPHA; type = GL_UNSIGNED_BYTE; break; case kRGB_565_SkColorType: format = GL_RGB; type = GL_UNSIGNED_SHORT_5_6_5; break; case kARGB_4444_SkColorType: format = GL_RGBA; type = GL_UNSIGNED_SHORT_4_4_4_4; break; case kN32_SkColorType: default: format = GL_RGBA; type = GL_UNSIGNED_BYTE; break; } renderState.bindFramebuffer(fbo); // TODO: Use layerPool or something to get this maybe? But since we // need explicit format control we can't currently. // Setup the rendertarget glGenTextures(1, &texture); caches.textureState().activateTexture(0); caches.textureState().bindTexture(texture); glPixelStorei(GL_PACK_ALIGNMENT, bitmap->bytesPerPixel()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, format, destWidth, destHeight, 0, format, type, nullptr); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); // Setup the source sp<GraphicBuffer> sourceBuffer; sp<Fence> sourceFence; // FIXME: Waiting on an API from libgui for this // surface.getLastQueuedBuffer(&sourceBuffer, &sourceFence); if (!sourceBuffer.get()) { ALOGW("Surface doesn't have any previously queued frames, nothing to readback from"); return false; } int err = sourceFence->wait(500 /* ms */); if (err != NO_ERROR) { ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt"); return false; } Image sourceImage(sourceBuffer); if (!sourceImage.getTexture()) { ALOGW("Failed to make an EGLImage from the GraphicBuffer"); return false; } Texture sourceTexture(caches); sourceTexture.wrap(sourceImage.getTexture(), sourceBuffer->getWidth(), sourceBuffer->getHeight(), 0 /* total lie */); { // Draw & readback renderState.setViewport(destWidth, destHeight); renderState.scissor().setEnabled(false); renderState.blend().syncEnabled(); renderState.stencil().disable(); Rect destRect(destWidth, destHeight); Glop glop; GlopBuilder(renderState, caches, &glop) .setRoundRectClipState(nullptr) .setMeshTexturedUvQuad(nullptr, Rect(0, 1, 1, 0)) // TODO: simplify with VBO .setFillLayer(sourceTexture, nullptr, 1.0f, SkXfermode::kSrc_Mode, Blend::ModeOrderSwap::NoSwap) .setTransform(Matrix4::identity(), TransformFlags::None) .setModelViewMapUnitToRect(destRect) .build(); Matrix4 ortho; ortho.loadOrtho(destWidth, destHeight); renderState.render(glop, ortho); glReadPixels(0, 0, bitmap->width(), bitmap->height(), format, type, bitmap->getPixels()); } // Cleanup caches.textureState().deleteTexture(texture); renderState.deleteFramebuffer(fbo); GL_CHECKPOINT(MODERATE); return true; } } // namespace uirenderer } // namespace android Loading
core/java/android/view/ThreadedRenderer.java +6 −0 Original line number Diff line number Diff line Loading @@ -893,6 +893,10 @@ public final class ThreadedRenderer { nSerializeDisplayListTree(mNativeProxy); } public static boolean copySurfaceInto(Surface surface, Bitmap bitmap) { return nCopySurfaceInto(surface, bitmap); } @Override protected void finalize() throws Throwable { try { Loading Loading @@ -1029,4 +1033,6 @@ public final class ThreadedRenderer { private static native long nAddFrameMetricsObserver(long nativeProxy, FrameMetricsObserver observer); private static native void nRemoveFrameMetricsObserver(long nativeProxy, long nativeObserver); private static native boolean nCopySurfaceInto(Surface surface, Bitmap bitmap); }
core/jni/android_view_ThreadedRenderer.cpp +10 −0 Original line number Diff line number Diff line Loading @@ -663,6 +663,14 @@ static void android_view_ThreadedRenderer_setContentDrawBounds(JNIEnv* env, proxy->setContentDrawBounds(left, top, right, bottom); } static jboolean android_view_ThreadedRenderer_copySurfaceInto(JNIEnv* env, jobject clazz, jobject jsurface, jobject jbitmap) { SkBitmap bitmap; GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap); sp<Surface> surface = android_view_Surface_getSurface(env, jsurface); return RenderProxy::copySurfaceInto(surface, &bitmap); } // ---------------------------------------------------------------------------- // FrameMetricsObserver // ---------------------------------------------------------------------------- Loading Loading @@ -768,6 +776,8 @@ static const JNINativeMethod gMethods[] = { { "nRemoveFrameMetricsObserver", "(JJ)V", (void*)android_view_ThreadedRenderer_removeFrameMetricsObserver }, { "nCopySurfaceInto", "(Landroid/view/Surface;Landroid/graphics/Bitmap;)Z", (void*)android_view_ThreadedRenderer_copySurfaceInto }, }; int register_android_view_ThreadedRenderer(JNIEnv* env) { Loading
graphics/java/android/graphics/PixelCopy.java 0 → 100644 +104 −0 Original line number Diff line number Diff line package android.graphics; import android.annotation.NonNull; import android.os.Handler; import android.view.Surface; import android.view.SurfaceView; import android.view.ThreadedRenderer; /** * Provides a mechanisms to issue pixel copy requests to allow for copy * operations from {@link Surface} to {@link Bitmap} * * @hide */ public final class PixelCopy { /** * Contains the result of a pixel copy request */ public static final class Response { /** * Indicates whether or not the copy request completed successfully. * If this is true, then {@link #bitmap} contains the result of the copy. * If this is false, {@link #bitmap} is unmodified from the originally * passed destination. * * For example a request might fail if the source is protected content * so copies are not allowed. Similarly if the source has nothing to * copy from, because either no frames have been produced yet or because * it has already been destroyed, then this will be false. */ public boolean success; /** * The output bitmap. This is always the same object that was passed * to request() as the 'dest' bitmap. If {@link #success} is true this * contains a copy of the pixels of the source object. If {@link #success} * is false then this is unmodified. */ @NonNull public Bitmap bitmap; } public interface OnPixelCopyFinished { /** * Callback for when a pixel copy request has completed. This will be called * regardless of whether the copy succeeded or failed. * * @param response Contains the result of the copy request which includes * whether or not the copy was successful. */ void onPixelCopyFinished(PixelCopy.Response response); } /** * Requests for the display content of a {@link SurfaceView} to be copied * into a provided {@link Bitmap}. * * The contents of the source will be scaled to fit exactly inside the bitmap. * The pixel format of the source buffer will be converted, as part of the copy, * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer * in the SurfaceView's Surface will be used as the source of the copy. * * @param source The source from which to copy * @param dest The destination of the copy. The source will be scaled to * match the width, height, and format of this bitmap. * @param listener Callback for when the pixel copy request completes * @param listenerThread The callback will be invoked on this Handler when * the copy is finished. */ public static void request(@NonNull SurfaceView source, @NonNull Bitmap dest, @NonNull OnPixelCopyFinished listener, @NonNull Handler listenerThread) { request(source.getHolder().getSurface(), dest, listener, listenerThread); } /** * Requests a copy of the pixels from a {@link Surface} to be copied into * a provided {@link Bitmap}. * * The contents of the source will be scaled to fit exactly inside the bitmap. * The pixel format of the source buffer will be converted, as part of the copy, * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer * in the Surface will be used as the source of the copy. * * @param source The source from which to copy * @param dest The destination of the copy. The source will be scaled to * match the width, height, and format of this bitmap. * @param listener Callback for when the pixel copy request completes * @param listenerThread The callback will be invoked on this Handler when * the copy is finished. */ public static void request(@NonNull Surface source, @NonNull Bitmap dest, @NonNull OnPixelCopyFinished listener, @NonNull Handler listenerThread) { // TODO: Make this actually async and fast and cool and stuff final PixelCopy.Response response = new PixelCopy.Response(); response.success = ThreadedRenderer.copySurfaceInto(source, dest); response.bitmap = dest; listenerThread.post(new Runnable() { @Override public void run() { listener.onPixelCopyFinished(response); } }); } }
libs/hwui/Android.mk +1 −0 Original line number Diff line number Diff line Loading @@ -84,6 +84,7 @@ hwui_src_files := \ Properties.cpp \ PropertyValuesHolder.cpp \ PropertyValuesAnimatorSet.cpp \ Readback.cpp \ RenderBufferCache.cpp \ RenderNode.cpp \ RenderProperties.cpp \ Loading
libs/hwui/Readback.cpp 0 → 100644 +159 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 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 "Readback.h" #include "Caches.h" #include "Image.h" #include "GlopBuilder.h" #include "renderstate/RenderState.h" #include "renderthread/EglManager.h" #include "utils/GLUtils.h" #include <GLES2/gl2.h> #include <ui/Fence.h> #include <ui/GraphicBuffer.h> namespace android { namespace uirenderer { bool Readback::copySurfaceInto(renderthread::RenderThread& renderThread, Surface& surface, SkBitmap* bitmap) { // TODO: Clean this up and unify it with LayerRenderer::copyLayer, // of which most of this is copied from. renderThread.eglManager().initialize(); Caches& caches = Caches::getInstance(); RenderState& renderState = renderThread.renderState(); int destWidth = bitmap->width(); int destHeight = bitmap->height(); if (destWidth > caches.maxTextureSize || destHeight > caches.maxTextureSize) { ALOGW("Can't copy surface into bitmap, %dx%d exceeds max texture size %d", destWidth, destHeight, caches.maxTextureSize); return false; } GLuint fbo = renderState.createFramebuffer(); if (!fbo) { ALOGW("Could not obtain an FBO"); return false; } SkAutoLockPixels alp(*bitmap); GLuint texture; GLenum format; GLenum type; switch (bitmap->colorType()) { case kAlpha_8_SkColorType: format = GL_ALPHA; type = GL_UNSIGNED_BYTE; break; case kRGB_565_SkColorType: format = GL_RGB; type = GL_UNSIGNED_SHORT_5_6_5; break; case kARGB_4444_SkColorType: format = GL_RGBA; type = GL_UNSIGNED_SHORT_4_4_4_4; break; case kN32_SkColorType: default: format = GL_RGBA; type = GL_UNSIGNED_BYTE; break; } renderState.bindFramebuffer(fbo); // TODO: Use layerPool or something to get this maybe? But since we // need explicit format control we can't currently. // Setup the rendertarget glGenTextures(1, &texture); caches.textureState().activateTexture(0); caches.textureState().bindTexture(texture); glPixelStorei(GL_PACK_ALIGNMENT, bitmap->bytesPerPixel()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, format, destWidth, destHeight, 0, format, type, nullptr); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); // Setup the source sp<GraphicBuffer> sourceBuffer; sp<Fence> sourceFence; // FIXME: Waiting on an API from libgui for this // surface.getLastQueuedBuffer(&sourceBuffer, &sourceFence); if (!sourceBuffer.get()) { ALOGW("Surface doesn't have any previously queued frames, nothing to readback from"); return false; } int err = sourceFence->wait(500 /* ms */); if (err != NO_ERROR) { ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt"); return false; } Image sourceImage(sourceBuffer); if (!sourceImage.getTexture()) { ALOGW("Failed to make an EGLImage from the GraphicBuffer"); return false; } Texture sourceTexture(caches); sourceTexture.wrap(sourceImage.getTexture(), sourceBuffer->getWidth(), sourceBuffer->getHeight(), 0 /* total lie */); { // Draw & readback renderState.setViewport(destWidth, destHeight); renderState.scissor().setEnabled(false); renderState.blend().syncEnabled(); renderState.stencil().disable(); Rect destRect(destWidth, destHeight); Glop glop; GlopBuilder(renderState, caches, &glop) .setRoundRectClipState(nullptr) .setMeshTexturedUvQuad(nullptr, Rect(0, 1, 1, 0)) // TODO: simplify with VBO .setFillLayer(sourceTexture, nullptr, 1.0f, SkXfermode::kSrc_Mode, Blend::ModeOrderSwap::NoSwap) .setTransform(Matrix4::identity(), TransformFlags::None) .setModelViewMapUnitToRect(destRect) .build(); Matrix4 ortho; ortho.loadOrtho(destWidth, destHeight); renderState.render(glop, ortho); glReadPixels(0, 0, bitmap->width(), bitmap->height(), format, type, bitmap->getPixels()); } // Cleanup caches.textureState().deleteTexture(texture); renderState.deleteFramebuffer(fbo); GL_CHECKPOINT(MODERATE); return true; } } // namespace uirenderer } // namespace android