1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "OpenGLReadback.h"
18
19#include "Caches.h"
20#include "GlLayer.h"
21#include "GlopBuilder.h"
22#include "Image.h"
23#include "renderstate/RenderState.h"
24#include "renderthread/EglManager.h"
25#include "utils/GLUtils.h"
26
27#include <GLES2/gl2.h>
28#include <gui/Surface.h>
29#include <ui/Fence.h>
30#include <ui/GraphicBuffer.h>
31
32namespace android {
33namespace uirenderer {
34
35CopyResult OpenGLReadback::copySurfaceInto(Surface& surface, const Rect& srcRect,
36                                           SkBitmap* bitmap) {
37    ATRACE_CALL();
38    // Setup the source
39    sp<GraphicBuffer> sourceBuffer;
40    sp<Fence> sourceFence;
41    Matrix4 texTransform;
42    status_t err = surface.getLastQueuedBuffer(&sourceBuffer, &sourceFence, texTransform.data);
43    texTransform.invalidateType();
44    if (err != NO_ERROR) {
45        ALOGW("Failed to get last queued buffer, error = %d", err);
46        return CopyResult::UnknownError;
47    }
48    if (!sourceBuffer.get()) {
49        ALOGW("Surface doesn't have any previously queued frames, nothing to readback from");
50        return CopyResult::SourceEmpty;
51    }
52    if (sourceBuffer->getUsage() & GRALLOC_USAGE_PROTECTED) {
53        ALOGW("Surface is protected, unable to copy from it");
54        return CopyResult::SourceInvalid;
55    }
56    err = sourceFence->wait(500 /* ms */);
57    if (err != NO_ERROR) {
58        ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt");
59        return CopyResult::Timeout;
60    }
61
62    return copyGraphicBufferInto(sourceBuffer.get(), texTransform, srcRect, bitmap);
63}
64
65CopyResult OpenGLReadback::copyGraphicBufferInto(GraphicBuffer* graphicBuffer,
66                                                 Matrix4& texTransform, const Rect& srcRect,
67                                                 SkBitmap* bitmap) {
68    mRenderThread.eglManager().initialize();
69    // TODO: Can't use Image helper since it forces GL_TEXTURE_2D usage via
70    // GL_OES_EGL_image, which doesn't work since we need samplerExternalOES
71    // to be able to properly sample from the buffer.
72
73    // Create the EGLImage object that maps the GraphicBuffer
74    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
75    EGLClientBuffer clientBuffer = (EGLClientBuffer)graphicBuffer->getNativeBuffer();
76    EGLint attrs[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
77
78    EGLImageKHR sourceImage = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,
79                                                clientBuffer, attrs);
80
81    if (sourceImage == EGL_NO_IMAGE_KHR) {
82        ALOGW("eglCreateImageKHR failed (%#x)", eglGetError());
83        return CopyResult::UnknownError;
84    }
85
86    uint32_t width = graphicBuffer->getWidth();
87    uint32_t height = graphicBuffer->getHeight();
88    CopyResult copyResult =
89            copyImageInto(sourceImage, texTransform, width, height, srcRect, bitmap);
90
91    // All we're flushing & finishing is the deletion of the texture since
92    // copyImageInto already did a major flush & finish as an implicit
93    // part of glReadPixels, so this shouldn't pose any major stalls.
94    glFinish();
95    eglDestroyImageKHR(display, sourceImage);
96    return copyResult;
97}
98
99CopyResult OpenGLReadback::copyGraphicBufferInto(GraphicBuffer* graphicBuffer, SkBitmap* bitmap) {
100    Rect srcRect;
101    Matrix4 transform;
102    transform.loadScale(1, -1, 1);
103    transform.translate(0, -1);
104    return copyGraphicBufferInto(graphicBuffer, transform, srcRect, bitmap);
105}
106
107static float sFlipVInit[16] = {
108        1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1,
109};
110
111static const Matrix4 sFlipV(sFlipVInit);
112
113////////////////////////////////////////////////////////////////////////////////
114////////////////////////////////////////////////////////////////////////////////
115
116inline CopyResult copyTextureInto(Caches& caches, RenderState& renderState, Texture& sourceTexture,
117                                  const Matrix4& texTransform, const Rect& srcRect,
118                                  SkBitmap* bitmap) {
119    int destWidth = bitmap->width();
120    int destHeight = bitmap->height();
121    if (destWidth > caches.maxTextureSize || destHeight > caches.maxTextureSize) {
122        ALOGW("Can't copy surface into bitmap, %dx%d exceeds max texture size %d", destWidth,
123              destHeight, caches.maxTextureSize);
124        return CopyResult::DestinationInvalid;
125    }
126
127    if (bitmap->colorType() == kRGBA_F16_SkColorType &&
128        !caches.extensions().hasRenderableFloatTextures()) {
129        ALOGW("Can't copy surface into bitmap, RGBA_F16 config is not supported");
130        return CopyResult::DestinationInvalid;
131    }
132
133    GLuint fbo = renderState.createFramebuffer();
134    if (!fbo) {
135        ALOGW("Could not obtain an FBO");
136        return CopyResult::UnknownError;
137    }
138
139    GLuint texture;
140
141    GLenum format;
142    GLenum internalFormat;
143    GLenum type;
144
145    switch (bitmap->colorType()) {
146        case kAlpha_8_SkColorType:
147            format = GL_ALPHA;
148            internalFormat = GL_ALPHA;
149            type = GL_UNSIGNED_BYTE;
150            break;
151        case kRGB_565_SkColorType:
152            format = GL_RGB;
153            internalFormat = GL_RGB;
154            type = GL_UNSIGNED_SHORT_5_6_5;
155            break;
156        case kARGB_4444_SkColorType:
157            format = GL_RGBA;
158            internalFormat = GL_RGBA;
159            type = GL_UNSIGNED_SHORT_4_4_4_4;
160            break;
161        case kRGBA_F16_SkColorType:
162            format = GL_RGBA;
163            internalFormat = GL_RGBA16F;
164            type = GL_HALF_FLOAT;
165            break;
166        case kN32_SkColorType:
167        default:
168            format = GL_RGBA;
169            internalFormat = GL_RGBA;
170            type = GL_UNSIGNED_BYTE;
171            break;
172    }
173
174    renderState.bindFramebuffer(fbo);
175
176    // TODO: Use layerPool or something to get this maybe? But since we
177    // need explicit format control we can't currently.
178
179    // Setup the rendertarget
180    glGenTextures(1, &texture);
181    caches.textureState().activateTexture(0);
182    caches.textureState().bindTexture(texture);
183    glPixelStorei(GL_PACK_ALIGNMENT, bitmap->bytesPerPixel());
184    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
185    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
186    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
187    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
188    glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, destWidth, destHeight, 0, format, type, nullptr);
189    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
190
191    {
192        bool requiresFilter;
193        // Draw & readback
194        renderState.setViewport(destWidth, destHeight);
195        renderState.scissor().setEnabled(false);
196        renderState.blend().syncEnabled();
197        renderState.stencil().disable();
198
199        Matrix4 croppedTexTransform(texTransform);
200        if (!srcRect.isEmpty()) {
201            // We flipV to convert to 0,0 top-left for the srcRect
202            // coordinates then flip back to 0,0 bottom-left for
203            // GLES coordinates.
204            croppedTexTransform.multiply(sFlipV);
205            croppedTexTransform.translate(srcRect.left / sourceTexture.width(),
206                                          srcRect.top / sourceTexture.height(), 0);
207            croppedTexTransform.scale(srcRect.getWidth() / sourceTexture.width(),
208                                      srcRect.getHeight() / sourceTexture.height(), 1);
209            croppedTexTransform.multiply(sFlipV);
210            requiresFilter = srcRect.getWidth() != (float)destWidth ||
211                             srcRect.getHeight() != (float)destHeight;
212        } else {
213            requiresFilter = sourceTexture.width() != (uint32_t)destWidth ||
214                             sourceTexture.height() != (uint32_t)destHeight;
215        }
216        Glop glop;
217        GlopBuilder(renderState, caches, &glop)
218                .setRoundRectClipState(nullptr)
219                .setMeshTexturedUnitQuad(nullptr)
220                .setFillExternalTexture(sourceTexture, croppedTexTransform, requiresFilter)
221                .setTransform(Matrix4::identity(), TransformFlags::None)
222                .setModelViewMapUnitToRect(Rect(destWidth, destHeight))
223                .build();
224        Matrix4 ortho;
225        ortho.loadOrtho(destWidth, destHeight);
226        renderState.render(glop, ortho, false);
227
228        // TODO: We should convert to linear space when the target is RGBA16F
229        glReadPixels(0, 0, bitmap->width(), bitmap->height(), format, type, bitmap->getPixels());
230        bitmap->notifyPixelsChanged();
231    }
232
233    // Cleanup
234    caches.textureState().deleteTexture(texture);
235    renderState.deleteFramebuffer(fbo);
236
237    GL_CHECKPOINT(MODERATE);
238
239    return CopyResult::Success;
240}
241
242CopyResult OpenGLReadbackImpl::copyImageInto(EGLImageKHR eglImage, const Matrix4& imgTransform,
243                                             int imgWidth, int imgHeight, const Rect& srcRect,
244                                             SkBitmap* bitmap) {
245    // If this is a 90 or 270 degree rotation we need to swap width/height
246    // This is a fuzzy way of checking that.
247    if (imgTransform[Matrix4::kSkewX] >= 0.5f || imgTransform[Matrix4::kSkewX] <= -0.5f) {
248        std::swap(imgWidth, imgHeight);
249    }
250
251    Caches& caches = Caches::getInstance();
252    GLuint sourceTexId;
253    // Create a 2D texture to sample from the EGLImage
254    glGenTextures(1, &sourceTexId);
255    caches.textureState().bindTexture(GL_TEXTURE_EXTERNAL_OES, sourceTexId);
256    glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, eglImage);
257
258    GLenum status = GL_NO_ERROR;
259    while ((status = glGetError()) != GL_NO_ERROR) {
260        ALOGW("glEGLImageTargetTexture2DOES failed (%#x)", status);
261        return CopyResult::UnknownError;
262    }
263
264    Texture sourceTexture(caches);
265    sourceTexture.wrap(sourceTexId, imgWidth, imgHeight, 0, 0 /* total lie */,
266                       GL_TEXTURE_EXTERNAL_OES);
267
268    CopyResult copyResult = copyTextureInto(caches, mRenderThread.renderState(), sourceTexture,
269                                            imgTransform, srcRect, bitmap);
270    sourceTexture.deleteTexture();
271    return copyResult;
272}
273
274bool OpenGLReadbackImpl::copyLayerInto(renderthread::RenderThread& renderThread, GlLayer& layer,
275                                       SkBitmap* bitmap) {
276    if (!layer.isRenderable()) {
277        // layer has never been updated by DeferredLayerUpdater, abort copy
278        return false;
279    }
280
281    return CopyResult::Success == copyTextureInto(Caches::getInstance(), renderThread.renderState(),
282                                                  layer.getTexture(), layer.getTexTransform(),
283                                                  Rect(), bitmap);
284}
285
286}  // namespace uirenderer
287}  // namespace android
288