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

Commit 10dd0585 authored by John Reck's avatar John Reck
Browse files

Framework-side of SurfaceView#getBitmap

Bug: 27708453

Change-Id: Ie6fd7eca522d3e6549d8af587c975fd7e6053649
parent ffdaae00
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -903,6 +903,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 {
@@ -1040,4 +1044,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);
}
+10 −0
Original line number Diff line number Diff line
@@ -669,6 +669,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
// ----------------------------------------------------------------------------
@@ -775,6 +783,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) {
+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);
            }
        });
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -84,6 +84,7 @@ hwui_src_files := \
    Properties.cpp \
    PropertyValuesHolder.cpp \
    PropertyValuesAnimatorSet.cpp \
    Readback.cpp \
    RenderBufferCache.cpp \
    RenderNode.cpp \
    RenderProperties.cpp \

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