| /* | |
| * Copyright (C) 2018 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 <SkBitmap.h> | |
| #include <SkBlendMode.h> | |
| #include <SkCanvas.h> | |
| #include <SkColorSpace.h> | |
| #include <SkImage.h> | |
| #include <SkImageAndroid.h> | |
| #include <SkImageInfo.h> | |
| #include <SkMatrix.h> | |
| #include <SkPaint.h> | |
| #include <SkRect.h> | |
| #include <SkRefCnt.h> | |
| #include <SkSamplingOptions.h> | |
| #include <SkSurface.h> | |
| #include "include/gpu/GpuTypes.h" // from Skia | |
| #include <include/gpu/ganesh/SkSurfaceGanesh.h> | |
| #include <gui/TraceUtils.h> | |
| #include <private/android/AHardwareBufferHelpers.h> | |
| #include <shaders/shaders.h> | |
| #include <sync/sync.h> | |
| #include <system/window.h> | |
| #include "DeferredLayerUpdater.h" | |
| #include "Properties.h" | |
| #include "Tonemapper.h" | |
| #include "hwui/Bitmap.h" | |
| #include "pipeline/skia/LayerDrawable.h" | |
| #include "renderthread/EglManager.h" | |
| #include "renderthread/VulkanManager.h" | |
| #include "utils/Color.h" | |
| #include "utils/MathUtils.h" | |
| #include "utils/NdkUtils.h" | |
| using namespace android::uirenderer::renderthread; | |
| namespace android { | |
| namespace uirenderer { | |
| #define ARECT_ARGS(r) float((r).left), float((r).top), float((r).right), float((r).bottom) | |
| void Readback::copySurfaceInto(ANativeWindow* window, const std::shared_ptr<CopyRequest>& request) { | |
| ATRACE_CALL(); | |
| // Setup the source | |
| AHardwareBuffer* rawSourceBuffer; | |
| int rawSourceFence; | |
| ARect cropRect; | |
| uint32_t windowTransform; | |
| status_t err = ANativeWindow_getLastQueuedBuffer2(window, &rawSourceBuffer, &rawSourceFence, | |
| &cropRect, &windowTransform); | |
| base::unique_fd sourceFence(rawSourceFence); | |
| // Really this shouldn't ever happen, but better safe than sorry. | |
| if (err == UNKNOWN_TRANSACTION) { | |
| ALOGW("Readback failed to ANativeWindow_getLastQueuedBuffer2 - who are we talking to?"); | |
| return request->onCopyFinished(CopyResult::SourceInvalid); | |
| } | |
| ALOGV("Using new path, cropRect=" RECT_STRING ", transform=%x", ARECT_ARGS(cropRect), | |
| windowTransform); | |
| if (err != NO_ERROR) { | |
| ALOGW("Failed to get last queued buffer, error = %d", err); | |
| return request->onCopyFinished(CopyResult::SourceInvalid); | |
| } | |
| if (rawSourceBuffer == nullptr) { | |
| ALOGW("Surface doesn't have any previously queued frames, nothing to readback from"); | |
| return request->onCopyFinished(CopyResult::SourceEmpty); | |
| } | |
| UniqueAHardwareBuffer sourceBuffer{rawSourceBuffer}; | |
| AHardwareBuffer_Desc description; | |
| AHardwareBuffer_describe(sourceBuffer.get(), &description); | |
| if (description.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT) { | |
| ALOGW("Surface is protected, unable to copy from it"); | |
| return request->onCopyFinished(CopyResult::SourceInvalid); | |
| } | |
| { | |
| ATRACE_NAME("sync_wait"); | |
| int syncWaitTimeoutMs = 500 * Properties::timeoutMultiplier; | |
| if (sourceFence != -1 && sync_wait(sourceFence.get(), syncWaitTimeoutMs) != NO_ERROR) { | |
| ALOGE("Timeout (%dms) exceeded waiting for buffer fence, abandoning readback attempt", | |
| syncWaitTimeoutMs); | |
| return request->onCopyFinished(CopyResult::Timeout); | |
| } | |
| } | |
| int32_t dataspace = ANativeWindow_getBuffersDataSpace(window); | |
| // If the application is not updating the Surface themselves, e.g., another | |
| // process is producing buffers for the application to display, then | |
| // ANativeWindow_getBuffersDataSpace will return an unknown answer, so grab | |
| // the dataspace from buffer metadata instead, if it exists. | |
| if (dataspace == 0) { | |
| dataspace = AHardwareBuffer_getDataSpace(sourceBuffer.get()); | |
| } | |
| sk_sp<SkColorSpace> colorSpace = | |
| DataSpaceToColorSpace(static_cast<android_dataspace>(dataspace)); | |
| sk_sp<SkImage> image = SkImages::DeferredFromAHardwareBuffer(sourceBuffer.get(), | |
| kPremul_SkAlphaType, colorSpace); | |
| if (!image.get()) { | |
| return request->onCopyFinished(CopyResult::UnknownError); | |
| } | |
| sk_sp<GrDirectContext> grContext = mRenderThread.requireGrContext(); | |
| SkRect srcRect = request->srcRect.toSkRect(); | |
| SkRect imageSrcRect = SkRect::MakeIWH(description.width, description.height); | |
| SkISize imageWH = SkISize::Make(description.width, description.height); | |
| if (cropRect.left < cropRect.right && cropRect.top < cropRect.bottom) { | |
| imageSrcRect = | |
| SkRect::MakeLTRB(cropRect.left, cropRect.top, cropRect.right, cropRect.bottom); | |
| imageWH = SkISize::Make(cropRect.right - cropRect.left, cropRect.bottom - cropRect.top); | |
| // Chroma channels of YUV420 images are subsampled we may need to shrink the crop region by | |
| // a whole texel on each side. Since skia still adds its own 0.5 inset, we apply an | |
| // additional 0.5 inset. See GLConsumer::computeTransformMatrix for details. | |
| float shrinkAmount = 0.0f; | |
| switch (description.format) { | |
| // Use HAL formats since some AHB formats are only available in vndk | |
| case HAL_PIXEL_FORMAT_YCBCR_420_888: | |
| case HAL_PIXEL_FORMAT_YV12: | |
| case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED: | |
| shrinkAmount = 0.5f; | |
| break; | |
| default: | |
| break; | |
| } | |
| // Shrink the crop if it has more than 1-px and differs from the buffer size. | |
| if (imageWH.width() > 1 && imageWH.width() < (int32_t)description.width) | |
| imageSrcRect = imageSrcRect.makeInset(shrinkAmount, 0); | |
| if (imageWH.height() > 1 && imageWH.height() < (int32_t)description.height) | |
| imageSrcRect = imageSrcRect.makeInset(0, shrinkAmount); | |
| } | |
| ALOGV("imageSrcRect = " RECT_STRING, SK_RECT_ARGS(imageSrcRect)); | |
| // Represents the "logical" width/height of the texture. That is, the dimensions of the buffer | |
| // after respecting crop & rotate. flipV/flipH still result in the same width & height | |
| // so we can ignore those for this. | |
| const SkRect textureRect = | |
| (windowTransform & NATIVE_WINDOW_TRANSFORM_ROT_90) | |
| ? SkRect::MakeIWH(imageSrcRect.height(), imageSrcRect.width()) | |
| : SkRect::MakeIWH(imageSrcRect.width(), imageSrcRect.height()); | |
| if (srcRect.isEmpty()) { | |
| srcRect = textureRect; | |
| } else { | |
| ALOGV("intersecting " RECT_STRING " with " RECT_STRING, SK_RECT_ARGS(srcRect), | |
| SK_RECT_ARGS(textureRect)); | |
| if (!srcRect.intersect(textureRect)) { | |
| return request->onCopyFinished(CopyResult::UnknownError); | |
| } | |
| } | |
| SkBitmap skBitmap = request->getDestinationBitmap(srcRect.width(), srcRect.height()); | |
| SkBitmap* bitmap = &skBitmap; | |
| sk_sp<SkSurface> tmpSurface = | |
| SkSurfaces::RenderTarget(mRenderThread.getGrContext(), skgpu::Budgeted::kYes, | |
| bitmap->info(), 0, kTopLeft_GrSurfaceOrigin, nullptr); | |
| // if we can't generate a GPU surface that matches the destination bitmap (e.g. 565) then we | |
| // attempt to do the intermediate rendering step in 8888 | |
| if (!tmpSurface.get()) { | |
| SkImageInfo tmpInfo = bitmap->info().makeColorType(SkColorType::kN32_SkColorType); | |
| tmpSurface = SkSurfaces::RenderTarget(mRenderThread.getGrContext(), | |
| skgpu::Budgeted::kYes, | |
| tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr); | |
| if (!tmpSurface.get()) { | |
| ALOGW("Unable to generate GPU buffer in a format compatible with the provided bitmap"); | |
| return request->onCopyFinished(CopyResult::UnknownError); | |
| } | |
| } | |
| /* | |
| * The grand ordering of events. | |
| * First we apply the buffer's crop, done by using a srcRect of the crop with a dstRect of the | |
| * same width/height as the srcRect but with a 0x0 origin | |
| * | |
| * Second we apply the window transform via a Canvas matrix. Ordering for that is as follows: | |
| * 1) FLIP_H | |
| * 2) FLIP_V | |
| * 3) ROT_90 | |
| * as per GLConsumer::computeTransformMatrix | |
| * | |
| * Third we apply the user's supplied cropping & scale to the output by doing a RectToRect | |
| * matrix transform from srcRect to {0,0, bitmapWidth, bitmapHeight} | |
| * | |
| * Finally we're done messing with this bloody thing for hopefully the last time. | |
| * | |
| * That's a lie since... | |
| * TODO: Do all this same stuff for TextureView as it's strictly more correct & easier | |
| * to rationalize. And we can fix the 1-px crop bug. | |
| */ | |
| SkMatrix m; | |
| const SkRect imageDstRect = SkRect::Make(imageWH); | |
| const float px = imageDstRect.centerX(); | |
| const float py = imageDstRect.centerY(); | |
| if (windowTransform & NATIVE_WINDOW_TRANSFORM_FLIP_H) { | |
| m.postScale(-1.f, 1.f, px, py); | |
| } | |
| if (windowTransform & NATIVE_WINDOW_TRANSFORM_FLIP_V) { | |
| m.postScale(1.f, -1.f, px, py); | |
| } | |
| if (windowTransform & NATIVE_WINDOW_TRANSFORM_ROT_90) { | |
| m.postRotate(90, 0, 0); | |
| m.postTranslate(imageDstRect.height(), 0); | |
| } | |
| SkSamplingOptions sampling(SkFilterMode::kNearest); | |
| ALOGV("Mapping from " RECT_STRING " to " RECT_STRING, SK_RECT_ARGS(srcRect), | |
| SK_RECT_ARGS(SkRect::MakeWH(bitmap->width(), bitmap->height()))); | |
| m.postConcat(SkMatrix::MakeRectToRect(srcRect, | |
| SkRect::MakeWH(bitmap->width(), bitmap->height()), | |
| SkMatrix::kFill_ScaleToFit)); | |
| if (srcRect.width() != bitmap->width() || srcRect.height() != bitmap->height()) { | |
| sampling = SkSamplingOptions(SkFilterMode::kLinear); | |
| } | |
| SkCanvas* canvas = tmpSurface->getCanvas(); | |
| canvas->save(); | |
| canvas->concat(m); | |
| SkPaint paint; | |
| paint.setAlpha(255); | |
| paint.setBlendMode(SkBlendMode::kSrc); | |
| const bool hasBufferCrop = cropRect.left < cropRect.right && cropRect.top < cropRect.bottom; | |
| auto constraint = | |
| hasBufferCrop ? SkCanvas::kStrict_SrcRectConstraint : SkCanvas::kFast_SrcRectConstraint; | |
| static constexpr float kMaxLuminanceNits = 4000.f; | |
| tonemapPaint(image->imageInfo(), canvas->imageInfo(), kMaxLuminanceNits, paint); | |
| canvas->drawImageRect(image, imageSrcRect, imageDstRect, sampling, &paint, constraint); | |
| canvas->restore(); | |
| if (!tmpSurface->readPixels(*bitmap, 0, 0)) { | |
| // if we fail to readback from the GPU directly (e.g. 565) then we attempt to read into | |
| // 8888 and then convert that into the destination format before giving up. | |
| SkBitmap tmpBitmap; | |
| SkImageInfo tmpInfo = bitmap->info().makeColorType(SkColorType::kN32_SkColorType); | |
| if (bitmap->info().colorType() == SkColorType::kN32_SkColorType || | |
| !tmpBitmap.tryAllocPixels(tmpInfo) || !tmpSurface->readPixels(tmpBitmap, 0, 0) || | |
| !tmpBitmap.readPixels(bitmap->info(), bitmap->getPixels(), bitmap->rowBytes(), 0, 0)) { | |
| ALOGW("Unable to convert content into the provided bitmap"); | |
| return request->onCopyFinished(CopyResult::UnknownError); | |
| } | |
| } | |
| bitmap->notifyPixelsChanged(); | |
| return request->onCopyFinished(CopyResult::Success); | |
| } | |
| CopyResult Readback::copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap) { | |
| LOG_ALWAYS_FATAL_IF(!hwBitmap->isHardware()); | |
| Rect srcRect; | |
| return copyImageInto(hwBitmap->makeImage(), srcRect, bitmap); | |
| } | |
| CopyResult Readback::copyLayerInto(DeferredLayerUpdater* deferredLayer, SkBitmap* bitmap) { | |
| ATRACE_CALL(); | |
| if (!mRenderThread.getGrContext()) { | |
| return CopyResult::UnknownError; | |
| } | |
| // acquire most recent buffer for drawing | |
| deferredLayer->updateTexImage(); | |
| deferredLayer->apply(); | |
| const SkRect dstRect = SkRect::MakeIWH(bitmap->width(), bitmap->height()); | |
| CopyResult copyResult = CopyResult::UnknownError; | |
| Layer* layer = deferredLayer->backingLayer(); | |
| if (layer) { | |
| if (copyLayerInto(layer, nullptr, &dstRect, bitmap)) { | |
| copyResult = CopyResult::Success; | |
| } | |
| } | |
| return copyResult; | |
| } | |
| CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, SkBitmap* bitmap) { | |
| Rect srcRect; | |
| return copyImageInto(image, srcRect, bitmap); | |
| } | |
| CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, const Rect& srcRect, | |
| SkBitmap* bitmap) { | |
| ATRACE_CALL(); | |
| if (!image.get()) { | |
| return CopyResult::UnknownError; | |
| } | |
| if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) { | |
| mRenderThread.requireGlContext(); | |
| } else { | |
| mRenderThread.requireVkContext(); | |
| } | |
| int imgWidth = image->width(); | |
| int imgHeight = image->height(); | |
| sk_sp<GrDirectContext> grContext = sk_ref_sp(mRenderThread.getGrContext()); | |
| CopyResult copyResult = CopyResult::UnknownError; | |
| int displayedWidth = imgWidth, displayedHeight = imgHeight; | |
| 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) { | |
| return copyResult; | |
| } | |
| Layer layer(mRenderThread.renderState(), nullptr, 255, SkBlendMode::kSrc); | |
| layer.setSize(displayedWidth, displayedHeight); | |
| layer.setImage(image); | |
| // Scaling filter is not explicitly set here, because it is done inside copyLayerInfo | |
| // after checking the necessity based on the src/dest rect size and the transformation. | |
| if (copyLayerInto(&layer, &skiaSrcRect, &skiaDestRect, bitmap)) { | |
| copyResult = CopyResult::Success; | |
| } | |
| return copyResult; | |
| } | |
| bool Readback::copyLayerInto(Layer* layer, const SkRect* srcRect, const SkRect* dstRect, | |
| SkBitmap* bitmap) { | |
| /* This intermediate surface is present to work around limitations that LayerDrawable expects | |
| * to render into a GPU backed canvas. Additionally, the offscreen buffer solution works around | |
| * a scaling issue (b/62262733) that was encountered when sampling from an EGLImage into a | |
| * software buffer. | |
| */ | |
| sk_sp<SkSurface> tmpSurface = SkSurfaces::RenderTarget(mRenderThread.getGrContext(), | |
| skgpu::Budgeted::kYes, | |
| bitmap->info(), | |
| 0, | |
| kTopLeft_GrSurfaceOrigin, nullptr); | |
| // if we can't generate a GPU surface that matches the destination bitmap (e.g. 565) then we | |
| // attempt to do the intermediate rendering step in 8888 | |
| if (!tmpSurface.get()) { | |
| SkImageInfo tmpInfo = bitmap->info().makeColorType(SkColorType::kN32_SkColorType); | |
| tmpSurface = SkSurfaces::RenderTarget(mRenderThread.getGrContext(), | |
| skgpu::Budgeted::kYes, | |
| tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr); | |
| if (!tmpSurface.get()) { | |
| ALOGW("Unable to generate GPU buffer in a format compatible with the provided bitmap"); | |
| return false; | |
| } | |
| } | |
| if (!skiapipeline::LayerDrawable::DrawLayer(mRenderThread.getGrContext(), | |
| tmpSurface->getCanvas(), layer, srcRect, dstRect, | |
| false)) { | |
| ALOGW("Unable to draw content from GPU into the provided bitmap"); | |
| return false; | |
| } | |
| if (!tmpSurface->readPixels(*bitmap, 0, 0)) { | |
| // if we fail to readback from the GPU directly (e.g. 565) then we attempt to read into | |
| // 8888 and then convert that into the destination format before giving up. | |
| SkBitmap tmpBitmap; | |
| SkImageInfo tmpInfo = bitmap->info().makeColorType(SkColorType::kN32_SkColorType); | |
| if (bitmap->info().colorType() == SkColorType::kN32_SkColorType || | |
| !tmpBitmap.tryAllocPixels(tmpInfo) || | |
| !tmpSurface->readPixels(tmpBitmap, 0, 0) || | |
| !tmpBitmap.readPixels(bitmap->info(), bitmap->getPixels(), | |
| bitmap->rowBytes(), 0, 0)) { | |
| ALOGW("Unable to convert content into the provided bitmap"); | |
| return false; | |
| } | |
| } | |
| bitmap->notifyPixelsChanged(); | |
| return true; | |
| } | |
| } /* namespace uirenderer */ | |
| } /* namespace android */ |