副驾屏高斯模糊/Kawase方案—无wallpaper,透明区域如何实现高斯模糊/Kawase效果(卷2: 副驾屏Kawase 模糊实现方案)
一、问题描述
**项目背景:**整个30寸的屏幕,以及主驾屏(左半屏)是display0,wallpaper是全屏壁纸,显示在display0上;副驾屏(右半屏)是display5,display5是一块虚拟屏,没有壁纸。
当副驾屏未设置壁纸且运行透明背景 App 时,Android 原生高斯模糊算法会导致透明区域呈现全黑现象,具体表现为(入下图所示):
- 透明背景区域失去通透感,显示为纯黑色块
- 多任务界面视觉断层,影响车载系统交互体验。
采用了副驾屏Kawase 模糊实现方案之后,效果如下图所示:
出现透明区域黑块的原因是:
参考:副驾屏高斯模糊/Kawase方案—无wallpaper,透明区域如何实现高斯模糊/Kawase效果(卷1: Kawase 模糊的相关概念,以及在android中的流程)
Kawase模糊方案计算的是一块Display的所有layer的合成情况,而不是获取肉眼看到的区域下的所有layer,当副驾屏没有wallpaper的时候(虽然肉眼能够看到底下有wallpaper,其实是虚拟屏副驾屏display5透的下面主驾屏display0的壁纸),那么就会出现,透明区域之下的layer不存在,就是一块黑的buffer,通过Kawase模糊算法计算之后的结果,就是黑块。
如果要解决上面的Kawase模糊的黑屏问题,必须将display0的layer加入到Kawase模糊的计算过程中。我们的解决方案,就是在计算副驾屏display5 Kawase模糊之前,先截取主驾屏(全屏),然后将截取的图片在一块buffer上绘制出来,然后将其送给display5的kawase模糊计算中去。
二、详细实现方案
根据卷1我们知道,Kawase模糊的流程如下:
根据前面解释的Kawase模糊的流程,我们设计新的Kawase模糊的流程图如下,
根据上面的流程图,我们知道,当SurfaceFlinger合成的时候,会调用Output::composeSurfaces方法,接着会将Output对应的display中的使用gpu合成的layers,传给RenderEngine::drawLayers方法,紧接着就会通过SkiaGLRenderEngine::drawLayersInternal方法进行Kawase模糊计算。那么我们就必须在display5 合成之前,将display0的合成的图保存起来,然后在display5合成的时候,传过来参与合成。
(1)Output::composeSurfaces的修改
if (renderengine::RenderEngine::virtualDisplayId > 10000) {
bool isMainDisplay = false;
bool isVirtualDisplay = false;
std::optional<DisplayId> displayIdOpt = getDisplayId();
if (displayIdOpt.has_value()) {
DisplayId displayId = displayIdOpt.value();
clientCompositionDisplay.displayId = displayId.value;
//ALOGW("Kawase, present2222 DisplayId is: %" PRIu64, displayId.value);
isMainDisplay = (displayId.value == static_cast<uint64_t>(129));
isVirtualDisplay = (displayId.value == renderengine::RenderEngine::virtualDisplayId);
if (isMainDisplay && !Output::hasBlurLayer) {
//ALOGW("Kawase, -------present222 DisplayId is: %" PRIu64, displayId.value);
renderengine::RenderEngine::mainDisplayBuffer = tex;
renderengine::RenderEngine::mainDisplayBufferUpdate = true;
} /*else if (isVirtualDisplay) {
ALOGW("Kawase, -------present222 DisplayId is: %" PRIu64, displayId.value);
}*/
}
if (isVirtualDisplay) {
Output::hasBlurLayer = false;
for (const auto& layer : clientRenderEngineLayers) {
if (layer.backgroundBlurRadius > 0.0f || !layer.blurRegions.empty()) {
Output::hasBlurLayer = true;
//ALOGW("Kawase, -------present222 has blur layer");
break;
}
}
/*if (!Output::hasBlurLayer) {
ALOGW("Kawase, -------present222 no blur layer");
}
if (renderengine::RenderEngine::mainDisplayBuffer) {
ALOGW("Kawase, -------present222 mainDisplayBuffer=%p", renderengine::RenderEngine::mainDisplayBuffer.get());
}*/
}
}
(2)SkiaGLRenderEngine::drawLayersInternal的修改
// --- handle main display ExternalTexture,extract right part snapshot ---
if (display.displayId == RenderEngine::virtualDisplayId && RenderEngine::mainDisplayBuffer != nullptr) {
if (RenderEngine::mainDisplayBuffer->getWidth() >= 5120) {
if (reusedScreenshotImage()) {
//ALOGW("Kawase reusedScreenshotImage-222 ok");
blurInput = SkiaGLRenderEngine::mainDisplayBufferImage;
} else {
ALOGW("Kawase reusedScreenshotImage-222 no");
sk_sp<SkImage> mainImage = nullptr;
auto grContext = getActiveGrContext();
if (grContext) {
// create AutoBackendTexture::LocalRef,manager AHardwareBuffer texture resource
std::shared_ptr<AutoBackendTexture::LocalRef> texRef = std::make_shared<AutoBackendTexture::LocalRef>(
grContext,
RenderEngine::mainDisplayBuffer->getBuffer()->toAHardwareBuffer(),
true, // isRenderable
mTextureCleanupMgr);
if (texRef) {
// get surface,and then get snapshot
sk_sp<SkSurface> surface = texRef->getOrCreateSurface(ui::Dataspace::V0_SRGB_LINEAR, grContext);
if (surface) {
mainImage = surface->makeImageSnapshot();
}
}
}
if (mainImage) {
sk_sp<SkImage> blurInputRight = extractRightHalf(mainImage, grContext);
if (blurInputRight != nullptr) {
blurInput = blurInputRight;//right part
} else {
blurInput = mainImage;//full part
}
}
}
}
if (blurInput == nullptr) {
blurInput = activeSurface->makeImageSnapshot();//fix null pointer exception
}
} else {
blurInput = activeSurface->makeImageSnapshot();
}
(3)CustomizedRenderEngine::drawLayersInternal的修改
bool CustomizedRenderEngine::reusedScreenshotImage() {
/*ALOGW("Kawase reusedScreenshotImage RenderEngine::mainDisplayBuffer = %p, SkiaGLRenderEngine::mainDisplayBufferImage = %p,"
"RenderEngine::mainDisplayBufferUpdate = %d", RenderEngine::mainDisplayBuffer.get(),
SkiaGLRenderEngine::mainDisplayBufferImage.get(), RenderEngine::mainDisplayBufferUpdate);
*/
if (!RenderEngine::mainDisplayBufferUpdate && SkiaGLRenderEngine::mainDisplayBufferImage) {
return true;
}
return false;
}
sk_sp<SkImage> CustomizedRenderEngine::extractRightHalf(sk_sp<SkImage> blurInput, GrRecordingContext* grCtx) {
if (!blurInput) return nullptr;
int width = blurInput->width();
int height = blurInput->height();
if (width <= 1 || height <= 1) return nullptr;
int halfWidth = width / 2;
SkImageInfo info = SkImageInfo::Make(halfWidth, height, kRGBA_8888_SkColorType,
kPremul_SkAlphaType, blurInput->refColorSpace());
sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(grCtx, SkBudgeted::kYes, info);
if (!surface) {
ALOGE("Failed to create SkSurface");
return nullptr;
}
SkCanvas* canvas = surface->getCanvas();
SkRect srcRect = SkRect::MakeXYWH(halfWidth, 0, halfWidth, height); // extract right part
SkRect dstRect = SkRect::MakeWH(halfWidth, height);
ALOGW("Kawase blurInput size: %d x %d", width, height);
ALOGW("Kawase srcRect: [%f, %f, %f, %f]", srcRect.left(), srcRect.top(), srcRect.right(), srcRect.bottom());
SkSamplingOptions sampling;
SkPaint paint;
canvas->drawImageRect(blurInput.get(), srcRect, dstRect, sampling, &paint, SkCanvas::kFast_SrcRectConstraint);//kStrict_SrcRectConstraint
sk_sp<SkImage> result = surface->makeImageSnapshot();
SkiaGLRenderEngine::mainDisplayBufferImage = result;
RenderEngine::mainDisplayBufferUpdate = false;
return result;
}