void TileClient::ValidateBackBufferFromFront(const nsIntRegion& aDirtyRegion, bool aCanRerasterizeValidRegion) { if (mBackBuffer && mFrontBuffer) { const nsIntRect tileRect = nsIntRect(0, 0, TILEDLAYERBUFFER_TILE_SIZE, TILEDLAYERBUFFER_TILE_SIZE); if (aDirtyRegion.Contains(tileRect)) { // The dirty region means that we no longer need the front buffer, so // discard it. DiscardFrontBuffer(); } else { // Region that needs copying. nsIntRegion regionToCopy = mInvalidBack; regionToCopy.Sub(regionToCopy, aDirtyRegion); if (regionToCopy.IsEmpty() || (aCanRerasterizeValidRegion && regionToCopy.Area() < MINIMUM_TILE_COPY_AREA)) { // Just redraw it all. return; } if (!mFrontBuffer->Lock(OPEN_READ)) { NS_WARNING("Failed to lock the tile's front buffer"); return; } TextureClientAutoUnlock autoFront(mFrontBuffer); if (!mBackBuffer->Lock(OPEN_WRITE)) { NS_WARNING("Failed to lock the tile's back buffer"); return; } TextureClientAutoUnlock autoBack(mBackBuffer); // Copy the bounding rect of regionToCopy. As tiles are quite small, it // is unlikely that we'd save much by copying each individual rect of the // region, but we can reevaluate this if it becomes an issue. const nsIntRect rectToCopy = regionToCopy.GetBounds(); gfx::IntRect gfxRectToCopy(rectToCopy.x, rectToCopy.y, rectToCopy.width, rectToCopy.height); gfx::IntPoint gfxRectToCopyTopLeft = gfxRectToCopy.TopLeft(); mFrontBuffer->CopyToTextureClient(mBackBuffer, &gfxRectToCopy, &gfxRectToCopyTopLeft); mInvalidBack.SetEmpty(); } } }
static Layer* FindBackgroundLayer(ReadbackLayer* aLayer, nsIntPoint* aOffset) { gfx::Matrix transform; if (!aLayer->GetTransform().Is2D(&transform) || transform.HasNonIntegerTranslation()) return nullptr; nsIntPoint transformOffset(int32_t(transform._31), int32_t(transform._32)); for (Layer* l = aLayer->GetPrevSibling(); l; l = l->GetPrevSibling()) { gfx::Matrix backgroundTransform; if (!l->GetTransform().Is2D(&backgroundTransform) || gfx::ThebesMatrix(backgroundTransform).HasNonIntegerTranslation()) return nullptr; nsIntPoint backgroundOffset(int32_t(backgroundTransform._31), int32_t(backgroundTransform._32)); IntRect rectInBackground(transformOffset - backgroundOffset, aLayer->GetSize()); const nsIntRegion visibleRegion = l->GetEffectiveVisibleRegion().ToUnknownRegion(); if (!visibleRegion.Intersects(rectInBackground)) continue; // Since l is present in the background, from here on we either choose l // or nothing. if (!visibleRegion.Contains(rectInBackground)) return nullptr; if (l->GetEffectiveOpacity() != 1.0 || l->HasMaskLayers() || !(l->GetContentFlags() & Layer::CONTENT_OPAQUE)) { return nullptr; } // cliprects are post-transform const Maybe<ParentLayerIntRect>& clipRect = l->GetEffectiveClipRect(); if (clipRect && !clipRect->Contains(ViewAs<ParentLayerPixel>(IntRect(transformOffset, aLayer->GetSize())))) return nullptr; Layer::LayerType type = l->GetType(); if (type != Layer::TYPE_COLOR && type != Layer::TYPE_PAINTED) return nullptr; *aOffset = backgroundOffset - transformOffset; return l; } return nullptr; }
bool HwcComposer2D::PrepareLayerList(Layer* aLayer, const nsIntRect& aClip, const Matrix& aParentTransform, bool aFindSidebandStreams) { // NB: we fall off this path whenever there are container layers // that require intermediate surfaces. That means all the // GetEffective*() coordinates are relative to the framebuffer. bool fillColor = false; const nsIntRegion visibleRegion = aLayer->GetLocalVisibleRegion().ToUnknownRegion(); if (visibleRegion.IsEmpty()) { return true; } uint8_t opacity = std::min(0xFF, (int)(aLayer->GetEffectiveOpacity() * 256.0)); if (opacity == 0) { LOGD("%s Layer has zero opacity; skipping", aLayer->Name()); return true; } if (!mHal->SupportTransparency() && opacity < 0xFF && !aFindSidebandStreams) { LOGD("%s Layer has planar semitransparency which is unsupported by hwcomposer", aLayer->Name()); return false; } if (aLayer->GetMaskLayer() && !aFindSidebandStreams) { LOGD("%s Layer has MaskLayer which is unsupported by hwcomposer", aLayer->Name()); return false; } nsIntRect clip; nsIntRect layerClip = aLayer->GetLocalClipRect().valueOr(ParentLayerIntRect()).ToUnknownRect(); nsIntRect* layerClipPtr = aLayer->GetLocalClipRect() ? &layerClip : nullptr; if (!HwcUtils::CalculateClipRect(aParentTransform, layerClipPtr, aClip, &clip)) { LOGD("%s Clip rect is empty. Skip layer", aLayer->Name()); return true; } // HWC supports only the following 2D transformations: // // Scaling via the sourceCrop and displayFrame in HwcLayer // Translation via the sourceCrop and displayFrame in HwcLayer // Rotation (in square angles only) via the HWC_TRANSFORM_ROT_* flags // Reflection (horizontal and vertical) via the HWC_TRANSFORM_FLIP_* flags // // A 2D transform with PreservesAxisAlignedRectangles() has all the attributes // above Matrix layerTransform; if (!aLayer->GetEffectiveTransform().Is2D(&layerTransform) || !layerTransform.PreservesAxisAlignedRectangles()) { LOGD("Layer EffectiveTransform has a 3D transform or a non-square angle rotation"); return false; } Matrix layerBufferTransform; if (!aLayer->GetEffectiveTransformForBuffer().Is2D(&layerBufferTransform) || !layerBufferTransform.PreservesAxisAlignedRectangles()) { LOGD("Layer EffectiveTransformForBuffer has a 3D transform or a non-square angle rotation"); return false; } if (ContainerLayer* container = aLayer->AsContainerLayer()) { if (container->UseIntermediateSurface() && !aFindSidebandStreams) { LOGD("Container layer needs intermediate surface"); return false; } AutoTArray<Layer*, 12> children; container->SortChildrenBy3DZOrder(children); for (uint32_t i = 0; i < children.Length(); i++) { if (!PrepareLayerList(children[i], clip, layerTransform, aFindSidebandStreams) && !aFindSidebandStreams) { return false; } } return true; } LayerRenderState state = aLayer->GetRenderState(); #if ANDROID_VERSION >= 21 if (!state.GetGrallocBuffer() && !state.GetSidebandStream().IsValid()) { #else if (!state.GetGrallocBuffer()) { #endif if (aLayer->AsColorLayer() && mColorFill) { fillColor = true; } else { LOGD("%s Layer doesn't have a gralloc buffer", aLayer->Name()); return false; } } nsIntRect visibleRect = visibleRegion.GetBounds(); nsIntRect bufferRect; if (fillColor) { bufferRect = nsIntRect(visibleRect); } else { nsIntRect layerRect; if (state.mHasOwnOffset) { bufferRect = nsIntRect(state.mOffset.x, state.mOffset.y, state.mSize.width, state.mSize.height); layerRect = bufferRect; } else { //Since the buffer doesn't have its own offset, assign the whole //surface size as its buffer bounds bufferRect = nsIntRect(0, 0, state.mSize.width, state.mSize.height); layerRect = bufferRect; if (aLayer->GetType() == Layer::TYPE_IMAGE) { ImageLayer* imageLayer = static_cast<ImageLayer*>(aLayer); if(imageLayer->GetScaleMode() != ScaleMode::SCALE_NONE) { layerRect = nsIntRect(0, 0, imageLayer->GetScaleToSize().width, imageLayer->GetScaleToSize().height); } } } // In some cases the visible rect assigned to the layer can be larger // than the layer's surface, e.g., an ImageLayer with a small Image // in it. visibleRect.IntersectRect(visibleRect, layerRect); } // Buffer rotation is not to be confused with the angled rotation done by a transform matrix // It's a fancy PaintedLayer feature used for scrolling if (state.BufferRotated()) { LOGD("%s Layer has a rotated buffer", aLayer->Name()); return false; } const bool needsYFlip = state.OriginBottomLeft() ? true : false; hwc_rect_t sourceCrop, displayFrame; if(!HwcUtils::PrepareLayerRects(visibleRect, layerTransform, layerBufferTransform, clip, bufferRect, needsYFlip, &(sourceCrop), &(displayFrame))) { return true; } // OK! We can compose this layer with hwc. int current = mList ? mList->numHwLayers : 0; // Do not compose any layer below full-screen Opaque layer // Note: It can be generalized to non-fullscreen Opaque layers. bool isOpaque = opacity == 0xFF && (state.mFlags & LayerRenderStateFlags::OPAQUE); // Currently we perform opacity calculation using the *bounds* of the layer. // We can only make this assumption if we're not dealing with a complex visible region. bool isSimpleVisibleRegion = visibleRegion.Contains(visibleRect); if (current && isOpaque && isSimpleVisibleRegion) { nsIntRect displayRect = nsIntRect(displayFrame.left, displayFrame.top, displayFrame.right - displayFrame.left, displayFrame.bottom - displayFrame.top); if (displayRect.Contains(mScreenRect)) { // In z-order, all previous layers are below // the current layer. We can ignore them now. mList->numHwLayers = current = 0; mHwcLayerMap.Clear(); } } if (!mList || current >= mMaxLayerCount) { if (!ReallocLayerList() || current >= mMaxLayerCount) { LOGE("PrepareLayerList failed! Could not increase the maximum layer count"); return false; } } HwcLayer& hwcLayer = mList->hwLayers[current]; hwcLayer.displayFrame = displayFrame; mHal->SetCrop(hwcLayer, sourceCrop); buffer_handle_t handle = nullptr; #if ANDROID_VERSION >= 21 if (state.GetSidebandStream().IsValid()) { handle = state.GetSidebandStream().GetRawNativeHandle(); } else if (state.GetGrallocBuffer()) { handle = state.GetGrallocBuffer()->getNativeBuffer()->handle; } #else if (state.GetGrallocBuffer()) { handle = state.GetGrallocBuffer()->getNativeBuffer()->handle; } #endif hwcLayer.handle = handle; hwcLayer.flags = 0; hwcLayer.hints = 0; hwcLayer.blending = isOpaque ? HWC_BLENDING_NONE : HWC_BLENDING_PREMULT; #if ANDROID_VERSION >= 17 hwcLayer.compositionType = HWC_FRAMEBUFFER; #if ANDROID_VERSION >= 21 if (state.GetSidebandStream().IsValid()) { hwcLayer.compositionType = HWC_SIDEBAND; } #endif hwcLayer.acquireFenceFd = -1; hwcLayer.releaseFenceFd = -1; #if ANDROID_VERSION >= 18 hwcLayer.planeAlpha = opacity; #endif #else hwcLayer.compositionType = HwcUtils::HWC_USE_COPYBIT; #endif if (!fillColor) { if (state.FormatRBSwapped()) { if (!mRBSwapSupport) { LOGD("No R/B swap support in H/W Composer"); return false; } hwcLayer.flags |= HwcUtils::HWC_FORMAT_RB_SWAP; } // Translation and scaling have been addressed in PrepareLayerRects(). // Given the above and that we checked for PreservesAxisAlignedRectangles() // the only possible transformations left to address are // square angle rotation and horizontal/vertical reflection. // // The rotation and reflection permutations total 16 but can be // reduced to 8 transformations after eliminating redundancies. // // All matrices represented here are in the form // // | xx xy | // | yx yy | // // And ignore scaling. // // Reflection is applied before rotation gfx::Matrix rotation = layerTransform; // Compute fuzzy zero like PreservesAxisAlignedRectangles() if (fabs(rotation._11) < 1e-6) { if (rotation._21 < 0) { if (rotation._12 > 0) { // 90 degree rotation // // | 0 -1 | // | 1 0 | // hwcLayer.transform = HWC_TRANSFORM_ROT_90; LOGD("Layer rotated 90 degrees"); } else { // Horizontal reflection then 90 degree rotation // // | 0 -1 | | -1 0 | = | 0 -1 | // | 1 0 | | 0 1 | | -1 0 | // // same as vertical reflection then 270 degree rotation // // | 0 1 | | 1 0 | = | 0 -1 | // | -1 0 | | 0 -1 | | -1 0 | // hwcLayer.transform = HWC_TRANSFORM_ROT_90 | HWC_TRANSFORM_FLIP_H; LOGD("Layer vertically reflected then rotated 270 degrees"); } } else { if (rotation._12 < 0) { // 270 degree rotation // // | 0 1 | // | -1 0 | // hwcLayer.transform = HWC_TRANSFORM_ROT_270; LOGD("Layer rotated 270 degrees"); } else { // Vertical reflection then 90 degree rotation // // | 0 1 | | -1 0 | = | 0 1 | // | -1 0 | | 0 1 | | 1 0 | // // Same as horizontal reflection then 270 degree rotation // // | 0 -1 | | 1 0 | = | 0 1 | // | 1 0 | | 0 -1 | | 1 0 | // hwcLayer.transform = HWC_TRANSFORM_ROT_90 | HWC_TRANSFORM_FLIP_V; LOGD("Layer horizontally reflected then rotated 270 degrees"); } } } else if (rotation._11 < 0) { if (rotation._22 > 0) { // Horizontal reflection // // | -1 0 | // | 0 1 | // hwcLayer.transform = HWC_TRANSFORM_FLIP_H; LOGD("Layer rotated 180 degrees"); } else { // 180 degree rotation // // | -1 0 | // | 0 -1 | // // Same as horizontal and vertical reflection // // | -1 0 | | 1 0 | = | -1 0 | // | 0 1 | | 0 -1 | | 0 -1 | // hwcLayer.transform = HWC_TRANSFORM_ROT_180; LOGD("Layer rotated 180 degrees"); } } else { if (rotation._22 < 0) { // Vertical reflection // // | 1 0 | // | 0 -1 | // hwcLayer.transform = HWC_TRANSFORM_FLIP_V; LOGD("Layer rotated 180 degrees"); } else { // No rotation or reflection // // | 1 0 | // | 0 1 | // hwcLayer.transform = 0; } } const bool needsYFlip = state.OriginBottomLeft() ? true : false; if (needsYFlip) { // Invert vertical reflection flag if it was already set hwcLayer.transform ^= HWC_TRANSFORM_FLIP_V; } hwc_region_t region; if (visibleRegion.GetNumRects() > 1) { mVisibleRegions.push_back(HwcUtils::RectVector()); HwcUtils::RectVector* visibleRects = &(mVisibleRegions.back()); bool isVisible = false; if(!HwcUtils::PrepareVisibleRegion(visibleRegion, layerTransform, layerBufferTransform, clip, bufferRect, visibleRects, isVisible)) { LOGD("A region of layer is too small to be rendered by HWC"); return false; } if (!isVisible) { // Layer is not visible, no need to render it return true; } region.numRects = visibleRects->size(); region.rects = &((*visibleRects)[0]); } else { region.numRects = 1; region.rects = &(hwcLayer.displayFrame); } hwcLayer.visibleRegionScreen = region; } else { hwcLayer.flags |= HwcUtils::HWC_COLOR_FILL; ColorLayer* colorLayer = aLayer->AsColorLayer(); if (colorLayer->GetColor().a < 1.0) { LOGD("Color layer has semitransparency which is unsupported"); return false; } hwcLayer.transform = colorLayer->GetColor().ToABGR(); } #if ANDROID_VERSION >= 21 if (aFindSidebandStreams && hwcLayer.compositionType == HWC_SIDEBAND) { mCachedSidebandLayers.AppendElement(hwcLayer); } #endif mHwcLayerMap.AppendElement(static_cast<LayerComposite*>(aLayer->ImplData())); mList->numHwLayers++; return true; } #if ANDROID_VERSION >= 17 bool HwcComposer2D::TryHwComposition(nsScreenGonk* aScreen) { DisplaySurface* dispSurface = aScreen->GetDisplaySurface(); if (!(dispSurface && dispSurface->lastHandle)) { LOGD("H/W Composition failed. DispSurface not initialized."); return false; } // Add FB layer int idx = mList->numHwLayers++; if (idx >= mMaxLayerCount) { if (!ReallocLayerList() || idx >= mMaxLayerCount) { LOGE("TryHwComposition failed! Could not add FB layer"); return false; } } Prepare(dispSurface->lastHandle, -1, aScreen); /* Possible composition paths, after hwc prepare: 1. GPU Composition 2. BLIT Composition 3. Full OVERLAY Composition 4. Partial OVERLAY Composition (GPU + OVERLAY) */ bool gpuComposite = false; bool blitComposite = false; bool overlayComposite = true; for (int j=0; j < idx; j++) { if (mList->hwLayers[j].compositionType == HWC_FRAMEBUFFER || mList->hwLayers[j].compositionType == HWC_BLIT) { // Full OVERLAY composition is not possible on this frame // It is either GPU / BLIT / partial OVERLAY composition. overlayComposite = false; break; } } if (!overlayComposite) { for (int k=0; k < idx; k++) { switch (mList->hwLayers[k].compositionType) { case HWC_FRAMEBUFFER: gpuComposite = true; break; case HWC_BLIT: blitComposite = true; break; #if ANDROID_VERSION >= 21 case HWC_SIDEBAND: #endif case HWC_OVERLAY: { // HWC will compose HWC_OVERLAY layers in partial // Overlay Composition, set layer composition flag // on mapped LayerComposite to skip GPU composition mHwcLayerMap[k]->SetLayerComposited(true); uint8_t opacity = std::min(0xFF, (int)(mHwcLayerMap[k]->GetLayer()->GetEffectiveOpacity() * 256.0)); if ((mList->hwLayers[k].hints & HWC_HINT_CLEAR_FB) && (opacity == 0xFF)) { // Clear visible rect on FB with transparent pixels. hwc_rect_t r = mList->hwLayers[k].displayFrame; mHwcLayerMap[k]->SetClearRect(nsIntRect(r.left, r.top, r.right - r.left, r.bottom - r.top)); } break; } default: break; } } if (gpuComposite) { // GPU or partial OVERLAY Composition return false; } else if (blitComposite) { // BLIT Composition, flip DispSurface target GetGonkDisplay()->UpdateDispSurface(aScreen->GetEGLDisplay(), aScreen->GetEGLSurface()); DisplaySurface* dispSurface = aScreen->GetDisplaySurface(); if (!dispSurface) { LOGE("H/W Composition failed. NULL DispSurface."); return false; } mList->hwLayers[idx].handle = dispSurface->lastHandle; mList->hwLayers[idx].acquireFenceFd = dispSurface->GetPrevDispAcquireFd(); } } // BLIT or full OVERLAY Composition return Commit(aScreen); }
bool ClientTiledLayerBuffer::ComputeProgressiveUpdateRegion(const nsIntRegion& aInvalidRegion, const nsIntRegion& aOldValidRegion, nsIntRegion& aRegionToPaint, BasicTiledLayerPaintData* aPaintData, bool aIsRepeated) { aRegionToPaint = aInvalidRegion; // If the composition bounds rect is empty, we can't make any sensible // decision about how to update coherently. In this case, just update // everything in one transaction. if (aPaintData->mCompositionBounds.IsEmpty()) { aPaintData->mPaintFinished = true; return false; } // If this is a low precision buffer, we force progressive updates. The // assumption is that the contents is less important, so visual coherency // is lower priority than speed. bool drawingLowPrecision = IsLowPrecision(); // Find out if we have any non-stale content to update. nsIntRegion staleRegion; staleRegion.And(aInvalidRegion, aOldValidRegion); // Find out the current view transform to determine which tiles to draw // first, and see if we should just abort this paint. Aborting is usually // caused by there being an incoming, more relevant paint. ParentLayerRect compositionBounds; CSSToParentLayerScale zoom; #if defined(MOZ_WIDGET_ANDROID) bool abortPaint = mManager->ProgressiveUpdateCallback(!staleRegion.Contains(aInvalidRegion), compositionBounds, zoom, !drawingLowPrecision); #else MOZ_ASSERT(mSharedFrameMetricsHelper); ContainerLayer* parent = mThebesLayer->AsLayer()->GetParent(); bool abortPaint = mSharedFrameMetricsHelper->UpdateFromCompositorFrameMetrics( parent, !staleRegion.Contains(aInvalidRegion), drawingLowPrecision, compositionBounds, zoom); #endif if (abortPaint) { // We ignore if front-end wants to abort if this is the first, // non-low-precision paint, as in that situation, we're about to override // front-end's page/viewport metrics. if (!aPaintData->mFirstPaint || drawingLowPrecision) { PROFILER_LABEL("ContentClient", "Abort painting"); aRegionToPaint.SetEmpty(); return aIsRepeated; } } // Transform the screen coordinates into transformed layout device coordinates. LayoutDeviceRect transformedCompositionBounds = TransformCompositionBounds(compositionBounds, zoom, aPaintData->mScrollOffset, aPaintData->mResolution, aPaintData->mTransformParentLayerToLayout); // Paint tiles that have stale content or that intersected with the screen // at the time of issuing the draw command in a single transaction first. // This is to avoid rendering glitches on animated page content, and when // layers change size/shape. LayoutDeviceRect coherentUpdateRect = transformedCompositionBounds.Intersect(aPaintData->mCompositionBounds); nsIntRect roundedCoherentUpdateRect = LayoutDeviceIntRect::ToUntyped(RoundedOut(coherentUpdateRect)); aRegionToPaint.And(aInvalidRegion, roundedCoherentUpdateRect); aRegionToPaint.Or(aRegionToPaint, staleRegion); bool drawingStale = !aRegionToPaint.IsEmpty(); if (!drawingStale) { aRegionToPaint = aInvalidRegion; } // Prioritise tiles that are currently visible on the screen. bool paintVisible = false; if (aRegionToPaint.Intersects(roundedCoherentUpdateRect)) { aRegionToPaint.And(aRegionToPaint, roundedCoherentUpdateRect); paintVisible = true; } // Paint area that's visible and overlaps previously valid content to avoid // visible glitches in animated elements, such as gifs. bool paintInSingleTransaction = paintVisible && (drawingStale || aPaintData->mFirstPaint); // The following code decides what order to draw tiles in, based on the // current scroll direction of the primary scrollable layer. NS_ASSERTION(!aRegionToPaint.IsEmpty(), "Unexpectedly empty paint region!"); nsIntRect paintBounds = aRegionToPaint.GetBounds(); int startX, incX, startY, incY; int tileLength = GetScaledTileLength(); if (aPaintData->mScrollOffset.x >= aPaintData->mLastScrollOffset.x) { startX = RoundDownToTileEdge(paintBounds.x); incX = tileLength; } else { startX = RoundDownToTileEdge(paintBounds.XMost() - 1); incX = -tileLength; } if (aPaintData->mScrollOffset.y >= aPaintData->mLastScrollOffset.y) { startY = RoundDownToTileEdge(paintBounds.y); incY = tileLength; } else { startY = RoundDownToTileEdge(paintBounds.YMost() - 1); incY = -tileLength; } // Find a tile to draw. nsIntRect tileBounds(startX, startY, tileLength, tileLength); int32_t scrollDiffX = aPaintData->mScrollOffset.x - aPaintData->mLastScrollOffset.x; int32_t scrollDiffY = aPaintData->mScrollOffset.y - aPaintData->mLastScrollOffset.y; // This loop will always terminate, as there is at least one tile area // along the first/last row/column intersecting with regionToPaint, or its // bounds would have been smaller. while (true) { aRegionToPaint.And(aInvalidRegion, tileBounds); if (!aRegionToPaint.IsEmpty()) { break; } if (Abs(scrollDiffY) >= Abs(scrollDiffX)) { tileBounds.x += incX; } else { tileBounds.y += incY; } } if (!aRegionToPaint.Contains(aInvalidRegion)) { // The region needed to paint is larger then our progressive chunk size // therefore update what we want to paint and ask for a new paint transaction. // If we need to draw more than one tile to maintain coherency, make // sure it happens in the same transaction by requesting this work be // repeated immediately. // If this is unnecessary, the remaining work will be done tile-by-tile in // subsequent transactions. if (!drawingLowPrecision && paintInSingleTransaction) { return true; } mManager->SetRepeatTransaction(); return false; } // We're not repeating painting and we've not requested a repeat transaction, // so the paint is finished. If there's still a separate low precision // paint to do, it will get marked as unfinished later. aPaintData->mPaintFinished = true; return false; }