/* * 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 "SkiaOpenGLReadback.h" #include <GLES2/gl2.h> #include <GLES2/gl2ext.h> #include <GrBackendSurface.h> #include <SkCanvas.h> #include <SkSurface.h> #include <gl/GrGLInterface.h> #include <gl/GrGLTypes.h> #include "DeviceInfo.h" #include "Matrix.h" #include "Properties.h" using namespace android::uirenderer::renderthread; namespace android { namespace uirenderer { namespace skiapipeline { CopyResult SkiaOpenGLReadback::copyImageInto(EGLImageKHR eglImage, const Matrix4& imgTransform, int imgWidth, int imgHeight, const Rect& srcRect, SkBitmap* bitmap) { GLuint sourceTexId; glGenTextures(1, &sourceTexId); glBindTexture(GL_TEXTURE_EXTERNAL_OES, sourceTexId); glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, eglImage); sk_sp<GrContext> grContext = sk_ref_sp(mRenderThread.getGrContext()); if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) { sk_sp<const GrGLInterface> glInterface(GrGLCreateNativeInterface()); LOG_ALWAYS_FATAL_IF(!glInterface.get()); grContext = GrContext::MakeGL(std::move(glInterface)); } else { grContext->resetContext(); } GrGLTextureInfo externalTexture; externalTexture.fTarget = GL_TEXTURE_EXTERNAL_OES; externalTexture.fID = sourceTexId; GrPixelConfig pixelConfig; switch (bitmap->colorType()) { case kRGBA_F16_SkColorType: pixelConfig = kRGBA_half_GrPixelConfig; break; case kN32_SkColorType: default: pixelConfig = kRGBA_8888_GrPixelConfig; break; } if (pixelConfig == kRGBA_half_GrPixelConfig && !grContext->caps()->isConfigRenderable(kRGBA_half_GrPixelConfig, false)) { ALOGW("Can't copy surface into bitmap, RGBA_F16 config is not supported"); return CopyResult::DestinationInvalid; } GrBackendTexture backendTexture(imgWidth, imgHeight, pixelConfig, externalTexture); CopyResult copyResult = CopyResult::UnknownError; sk_sp<SkImage> image(SkImage::MakeFromAdoptedTexture(grContext.get(), backendTexture, kTopLeft_GrSurfaceOrigin)); if (image) { int displayedWidth = imgWidth, displayedHeight = imgHeight; // If this is a 90 or 270 degree rotation we need to swap width/height to get the device // size. if (imgTransform[Matrix4::kSkewX] >= 0.5f || imgTransform[Matrix4::kSkewX] <= -0.5f) { std::swap(displayedWidth, displayedHeight); } SkRect skiaDestRect = SkRect::MakeWH(bitmap->width(), bitmap->height()); SkRect skiaSrcRect = srcRect.toSkRect(); if (skiaSrcRect.isEmpty()) { skiaSrcRect = SkRect::MakeIWH(displayedWidth, displayedHeight); } bool srcNotEmpty = skiaSrcRect.intersect(SkRect::MakeIWH(displayedWidth, displayedHeight)); if (srcNotEmpty) { SkMatrix textureMatrixInv; imgTransform.copyTo(textureMatrixInv); // TODO: after skia bug https://bugs.chromium.org/p/skia/issues/detail?id=7075 is fixed // use bottom left origin and remove flipV and invert transformations. SkMatrix flipV; flipV.setAll(1, 0, 0, 0, -1, 1, 0, 0, 1); textureMatrixInv.preConcat(flipV); textureMatrixInv.preScale(1.0f / displayedWidth, 1.0f / displayedHeight); textureMatrixInv.postScale(imgWidth, imgHeight); SkMatrix textureMatrix; if (!textureMatrixInv.invert(&textureMatrix)) { textureMatrix = textureMatrixInv; } textureMatrixInv.mapRect(&skiaSrcRect); textureMatrixInv.mapRect(&skiaDestRect); // we render in an offscreen buffer to scale and to avoid an issue b/62262733 // with reading incorrect data from EGLImage backed SkImage (likely a driver bug) sk_sp<SkSurface> scaledSurface = SkSurface::MakeRenderTarget(grContext.get(), SkBudgeted::kYes, bitmap->info()); SkPaint paint; paint.setBlendMode(SkBlendMode::kSrc); // Apply a filter, which is matching OpenGL pipeline readback behaviour. Filter usage // is codified by tests using golden images like DecodeAccuracyTest. if (skiaSrcRect.width() != bitmap->width() || skiaSrcRect.height() != bitmap->height()) { // TODO: apply filter always, but check if tests will be fine paint.setFilterQuality(kLow_SkFilterQuality); } scaledSurface->getCanvas()->concat(textureMatrix); scaledSurface->getCanvas()->drawImageRect(image, skiaSrcRect, skiaDestRect, &paint, SkCanvas::kFast_SrcRectConstraint); image = scaledSurface->makeImageSnapshot(); if (image->readPixels(bitmap->info(), bitmap->getPixels(), bitmap->rowBytes(), 0, 0)) { bitmap->notifyPixelsChanged(); copyResult = CopyResult::Success; } } } // make sure that we have deleted the texture (in the SkImage) before we // destroy the EGLImage that it was created from image.reset(); return copyResult; } } /* namespace skiapipeline */ } /* namespace uirenderer */ } /* namespace android */