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