Bind correct FBO when drawing a WebView into a layer.
The WebView was unable to draw into either a standard clipped
layer or the "fading edges" unclipped layer. This CL and its
companion test cases ensure that both work with simple and
complex clips.
Bug: 79619253
Bug: 80443556
Bug: 80477645
Test: atest CtsUiRenderingTestCases:.LayerTests
Change-Id: I0e16b724f74415a61cc2a841ccf4a491f293ac94
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index e2e3420..476d3f4 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -41,6 +41,7 @@
"external/skia/src/image",
"external/skia/src/utils",
"external/skia/src/gpu",
+ "external/skia/src/shaders",
],
product_variables: {
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index 08f8da8..b0fec7a 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -22,6 +22,10 @@
#include "RenderNode.h"
#include "SkAndroidFrameworkUtils.h"
#include "SkClipStack.h"
+#include "SkRect.h"
+#include "GrBackendSurface.h"
+#include "GrRenderTarget.h"
+#include "GrRenderTargetContext.h"
namespace android {
namespace uirenderer {
@@ -45,6 +49,31 @@
glScissor(clip.fLeft, y, clip.width(), height);
}
+static bool GetFboDetails(SkCanvas* canvas, GLuint* outFboID, SkISize* outFboSize) {
+ GrRenderTargetContext *renderTargetContext =
+ canvas->internal_private_accessTopLayerRenderTargetContext();
+ if (!renderTargetContext) {
+ ALOGW("Unable to extract renderTarget info from canvas; aborting GLFunctor draw");
+ return false;
+ }
+
+ GrRenderTarget *renderTarget = renderTargetContext->accessRenderTarget();
+ if (!renderTarget) {
+ ALOGW("Unable to extract renderTarget info from canvas; aborting GLFunctor draw");
+ return false;
+ }
+
+ GrGLFramebufferInfo fboInfo;
+ if (!renderTarget->getBackendRenderTarget().getGLFramebufferInfo(&fboInfo)) {
+ ALOGW("Unable to extract renderTarget info from canvas; aborting GLFunctor draw");
+ return false;
+ }
+
+ *outFboID = fboInfo.fFBOID;
+ *outFboSize = SkISize::Make(renderTargetContext->width(), renderTargetContext->height());
+ return true;
+}
+
void GLFunctorDrawable::onDraw(SkCanvas* canvas) {
if (canvas->getGrContext() == nullptr) {
SkDEBUGF(("Attempting to draw GLFunctor into an unsupported surface"));
@@ -56,41 +85,97 @@
return;
}
- SkImageInfo canvasInfo = canvas->imageInfo();
+ GLuint fboID = 0;
+ SkISize fboSize;
+ if (!GetFboDetails(canvas, &fboID, &fboSize)) {
+ return;
+ }
+
+ SkIRect surfaceBounds = canvas->internal_private_getTopLayerBounds();
+ SkIRect clipBounds = canvas->getDeviceClipBounds();
SkMatrix44 mat4(canvas->getTotalMatrix());
-
- SkIRect ibounds = canvas->getDeviceClipBounds();
-
- DrawGlInfo info;
- info.clipLeft = ibounds.fLeft;
- info.clipTop = ibounds.fTop;
- info.clipRight = ibounds.fRight;
- info.clipBottom = ibounds.fBottom;
- // info.isLayer = hasLayer();
- info.isLayer = false;
- info.width = canvasInfo.width();
- info.height = canvasInfo.height();
- mat4.asColMajorf(&info.transform[0]);
-
- bool clearStencilAfterFunctor = false;
-
- // apply a simple clip with a scissor or a complex clip with a stencil
SkRegion clipRegion;
canvas->temporary_internal_getRgnClip(&clipRegion);
+
+ sk_sp<SkSurface> tmpSurface;
+ // we are in a state where there is an unclipped saveLayer
+ if (fboID != 0 && !surfaceBounds.contains(clipBounds)) {
+
+ // create an offscreen layer and clear it
+ SkImageInfo surfaceInfo = canvas->imageInfo().makeWH(clipBounds.width(), clipBounds.height());
+ tmpSurface = SkSurface::MakeRenderTarget(canvas->getGrContext(), SkBudgeted::kYes,
+ surfaceInfo);
+ tmpSurface->getCanvas()->clear(SK_ColorTRANSPARENT);
+
+ GrGLFramebufferInfo fboInfo;
+ if (!tmpSurface->getBackendRenderTarget(SkSurface::kFlushWrite_BackendHandleAccess)
+ .getGLFramebufferInfo(&fboInfo)) {
+ ALOGW("Unable to extract renderTarget info from offscreen canvas; aborting GLFunctor");
+ return;
+ }
+
+ fboSize = SkISize::Make(surfaceInfo.width(), surfaceInfo.height());
+ fboID = fboInfo.fFBOID;
+
+ // update the matrix and clip that we pass to the WebView to match the coordinates of
+ // the offscreen layer
+ mat4.preTranslate(-clipBounds.fLeft, -clipBounds.fTop, 0);
+ clipBounds.offsetTo(0, 0);
+ clipRegion.translate(-surfaceBounds.fLeft, -surfaceBounds.fTop);
+
+ } else if (fboID != 0) {
+ // we are drawing into a (clipped) offscreen layer so we must update the clip and matrix
+ // from device coordinates to the layer's coordinates
+ clipBounds.offset(-surfaceBounds.fLeft, -surfaceBounds.fTop);
+ mat4.preTranslate(-surfaceBounds.fLeft, -surfaceBounds.fTop, 0);
+ }
+
+ DrawGlInfo info;
+ info.clipLeft = clipBounds.fLeft;
+ info.clipTop = clipBounds.fTop;
+ info.clipRight = clipBounds.fRight;
+ info.clipBottom = clipBounds.fBottom;
+ info.isLayer = fboID != 0;
+ info.width = fboSize.width();
+ info.height = fboSize.height();
+ mat4.asColMajorf(&info.transform[0]);
+
+ // ensure that the framebuffer that the webview will render into is bound before we clear
+ // the stencil and/or draw the functor.
canvas->flush();
- glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, info.width, info.height);
+ glBindFramebuffer(GL_FRAMEBUFFER, fboID);
+
+ // apply a simple clip with a scissor or a complex clip with a stencil
+ bool clearStencilAfterFunctor = false;
if (CC_UNLIKELY(clipRegion.isComplex())) {
+ // clear the stencil
//TODO: move stencil clear and canvas flush to SkAndroidFrameworkUtils::clipWithStencil
glDisable(GL_SCISSOR_TEST);
glStencilMask(0x1);
glClearStencil(0);
glClear(GL_STENCIL_BUFFER_BIT);
+
+ // notify Skia that we just updated the FBO and stencil
+ const uint32_t grState = kStencil_GrGLBackendState | kRenderTarget_GrGLBackendState;
+ canvas->getGrContext()->resetContext(grState);
+
+ SkCanvas* tmpCanvas = canvas;
+ if (tmpSurface) {
+ tmpCanvas = tmpSurface->getCanvas();
+ // set the clip on the new canvas
+ tmpCanvas->clipRegion(clipRegion);
+ }
+
// GL ops get inserted here if previous flush is missing, which could dirty the stencil
- bool stencilWritten = SkAndroidFrameworkUtils::clipWithStencil(canvas);
- canvas->flush(); //need this flush for the single op that draws into the stencil
- glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ bool stencilWritten = SkAndroidFrameworkUtils::clipWithStencil(tmpCanvas);
+ tmpCanvas->flush(); //need this flush for the single op that draws into the stencil
+
+ // ensure that the framebuffer that the webview will render into is bound before after we
+ // draw into the stencil
glViewport(0, 0, info.width, info.height);
+ glBindFramebuffer(GL_FRAMEBUFFER, fboID);
+
if (stencilWritten) {
glStencilMask(0x1);
glStencilFunc(GL_EQUAL, 0x1, 0x1);
@@ -121,6 +206,20 @@
}
canvas->getGrContext()->resetContext();
+
+ // if there were unclipped save layers involved we draw our offscreen surface to the canvas
+ if (tmpSurface) {
+ SkAutoCanvasRestore acr(canvas, true);
+ SkMatrix invertedMatrix;
+ if (!canvas->getTotalMatrix().invert(&invertedMatrix)) {
+ ALOGW("Unable to extract invert canvas matrix; aborting GLFunctor draw");
+ return;
+ }
+ canvas->concat(invertedMatrix);
+
+ const SkIRect deviceBounds = canvas->getDeviceClipBounds();
+ tmpSurface->draw(canvas, deviceBounds.fLeft, deviceBounds.fTop, nullptr);
+ }
}
}; // namespace skiapipeline