Loading libs/renderengine/skia/SkiaGLRenderEngine.cpp +72 −51 Original line number Original line Diff line number Diff line Loading @@ -1127,83 +1127,104 @@ inline SkRect SkiaGLRenderEngine::getSkRect(const Rect& rect) { return SkRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom); return SkRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom); } } inline std::pair<SkRRect, SkRRect> SkiaGLRenderEngine::getBoundsAndClip(const FloatRect& boundsRect, /** const FloatRect& cropRect, * Verifies that common, simple bounds + clip combinations can be converted into const float cornerRadius) { * a single RRect draw call returning true if possible. If true the radii parameter const SkRect bounds = getSkRect(boundsRect); * will be filled with the correct radii values that combined with bounds param will const SkRect crop = getSkRect(cropRect); * produce the insected roundRect. If false, the returned state of the radii param is undefined. */ SkRRect clip; static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, if (cornerRadius > 0) { const SkRect& insetCrop, float cornerRadius, // it the crop and the bounds are equivalent or there is no crop then we don't need a clip SkVector radii[4]) { if (bounds == crop || crop.isEmpty()) { return {SkRRect::MakeRectXY(bounds, cornerRadius, cornerRadius), clip}; } // This makes an effort to speed up common, simple bounds + clip combinations by // converting them to a single RRect draw. It is possible there are other cases // that can be converted. if (crop.contains(bounds)) { bool intersectionIsRoundRect = true; // check each cropped corner to ensure that it exactly matches the crop or is full SkVector radii[4]; const auto insetCrop = crop.makeInset(cornerRadius, cornerRadius); const bool leftEqual = bounds.fLeft == crop.fLeft; const bool leftEqual = bounds.fLeft == crop.fLeft; const bool topEqual = bounds.fTop == crop.fTop; const bool topEqual = bounds.fTop == crop.fTop; const bool rightEqual = bounds.fRight == crop.fRight; const bool rightEqual = bounds.fRight == crop.fRight; const bool bottomEqual = bounds.fBottom == crop.fBottom; const bool bottomEqual = bounds.fBottom == crop.fBottom; // In the event that the corners of the bounds only partially align with the crop we // need to ensure that the resulting shape can still be represented as a round rect. // In particular the round rect implementation will scale the value of all corner radii // if the sum of the radius along any edge is greater than the length of that edge. // See https://www.w3.org/TR/css-backgrounds-3/#corner-overlap const bool requiredWidth = bounds.width() > (cornerRadius * 2); const bool requiredHeight = bounds.height() > (cornerRadius * 2); if (!requiredWidth || !requiredHeight) { return false; } // Check each cropped corner to ensure that it exactly matches the crop or its corner is // contained within the cropped shape and does not need rounded. // compute the UpperLeft corner radius // compute the UpperLeft corner radius if (leftEqual && topEqual) { if (leftEqual && topEqual) { radii[0].set(cornerRadius, cornerRadius); radii[0].set(cornerRadius, cornerRadius); } else if ((leftEqual && bounds.fTop >= insetCrop.fTop) || } else if ((leftEqual && bounds.fTop >= insetCrop.fTop) || (topEqual && bounds.fLeft >= insetCrop.fLeft) || (topEqual && bounds.fLeft >= insetCrop.fLeft)) { insetCrop.contains(bounds.fLeft, bounds.fTop)) { radii[0].set(0, 0); radii[0].set(0, 0); } else { } else { intersectionIsRoundRect = false; return false; } } // compute the UpperRight corner radius // compute the UpperRight corner radius if (rightEqual && topEqual) { if (rightEqual && topEqual) { radii[1].set(cornerRadius, cornerRadius); radii[1].set(cornerRadius, cornerRadius); } else if ((rightEqual && bounds.fTop >= insetCrop.fTop) || } else if ((rightEqual && bounds.fTop >= insetCrop.fTop) || (topEqual && bounds.fRight <= insetCrop.fRight) || (topEqual && bounds.fRight <= insetCrop.fRight)) { insetCrop.contains(bounds.fRight, bounds.fTop)) { radii[1].set(0, 0); radii[1].set(0, 0); } else { } else { intersectionIsRoundRect = false; return false; } } // compute the BottomRight corner radius // compute the BottomRight corner radius if (rightEqual && bottomEqual) { if (rightEqual && bottomEqual) { radii[2].set(cornerRadius, cornerRadius); radii[2].set(cornerRadius, cornerRadius); } else if ((rightEqual && bounds.fBottom <= insetCrop.fBottom) || } else if ((rightEqual && bounds.fBottom <= insetCrop.fBottom) || (bottomEqual && bounds.fRight <= insetCrop.fRight) || (bottomEqual && bounds.fRight <= insetCrop.fRight)) { insetCrop.contains(bounds.fRight, bounds.fBottom)) { radii[2].set(0, 0); radii[2].set(0, 0); } else { } else { intersectionIsRoundRect = false; return false; } } // compute the BottomLeft corner radius // compute the BottomLeft corner radius if (leftEqual && bottomEqual) { if (leftEqual && bottomEqual) { radii[3].set(cornerRadius, cornerRadius); radii[3].set(cornerRadius, cornerRadius); } else if ((leftEqual && bounds.fBottom <= insetCrop.fBottom) || } else if ((leftEqual && bounds.fBottom <= insetCrop.fBottom) || (bottomEqual && bounds.fLeft >= insetCrop.fLeft) || (bottomEqual && bounds.fLeft >= insetCrop.fLeft)) { insetCrop.contains(bounds.fLeft, bounds.fBottom)) { radii[3].set(0, 0); radii[3].set(0, 0); } else { } else { intersectionIsRoundRect = false; return false; } return true; } inline std::pair<SkRRect, SkRRect> SkiaGLRenderEngine::getBoundsAndClip(const FloatRect& boundsRect, const FloatRect& cropRect, const float cornerRadius) { const SkRect bounds = getSkRect(boundsRect); const SkRect crop = getSkRect(cropRect); SkRRect clip; if (cornerRadius > 0) { // it the crop and the bounds are equivalent or there is no crop then we don't need a clip if (bounds == crop || crop.isEmpty()) { return {SkRRect::MakeRectXY(bounds, cornerRadius, cornerRadius), clip}; } } if (intersectionIsRoundRect) { // This makes an effort to speed up common, simple bounds + clip combinations by // converting them to a single RRect draw. It is possible there are other cases // that can be converted. if (crop.contains(bounds)) { const auto insetCrop = crop.makeInset(cornerRadius, cornerRadius); if (insetCrop.contains(bounds)) { return {SkRRect::MakeRect(bounds), clip}; // clip is empty - no rounding required } SkVector radii[4]; if (intersectionIsRoundRect(bounds, crop, insetCrop, cornerRadius, radii)) { SkRRect intersectionBounds; SkRRect intersectionBounds; intersectionBounds.setRectRadii(bounds, radii); intersectionBounds.setRectRadii(bounds, radii); return {intersectionBounds, clip}; return {intersectionBounds, clip}; } } } } // we didn't it any of our fast paths so set the clip to the cropRect // we didn't hit any of our fast paths so set the clip to the cropRect clip.setRectXY(crop, cornerRadius, cornerRadius); clip.setRectXY(crop, cornerRadius, cornerRadius); } } Loading libs/renderengine/tests/RenderEngineTest.cpp +34 −0 Original line number Original line Diff line number Diff line Loading @@ -1869,6 +1869,40 @@ TEST_P(RenderEngineTest, testRoundedCornersParentCrop) { expectBufferColor(Point(0, (DEFAULT_DISPLAY_HEIGHT / 2) - 1), 0, 255, 0, 255); expectBufferColor(Point(0, (DEFAULT_DISPLAY_HEIGHT / 2) - 1), 0, 255, 0, 255); } } TEST_P(RenderEngineTest, testRoundedCornersParentCropSmallBounds) { initializeRenderEngine(); renderengine::DisplaySettings settings; settings.physicalDisplay = fullscreenRect(); settings.clip = fullscreenRect(); settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR; std::vector<const renderengine::LayerSettings*> layers; renderengine::LayerSettings redLayer; redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR; redLayer.geometry.boundaries = FloatRect(0, 0, DEFAULT_DISPLAY_WIDTH, 32); redLayer.geometry.roundedCornersRadius = 64; redLayer.geometry.roundedCornersCrop = FloatRect(0, 0, DEFAULT_DISPLAY_WIDTH, 128); // Red background. redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f); redLayer.alpha = 1.0f; layers.push_back(&redLayer); invokeDraw(settings, layers); // Due to roundedCornersRadius, the top corners are untouched. expectBufferColor(Point(0, 0), 0, 0, 0, 0); expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, 0), 0, 0, 0, 0); // ensure that the entire height of the red layer was clipped by the rounded corners crop. expectBufferColor(Point(0, 31), 0, 0, 0, 0); expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, 31), 0, 0, 0, 0); // the bottom middle should be red expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH / 2, 31), 255, 0, 0, 255); } TEST_P(RenderEngineTest, testClear) { TEST_P(RenderEngineTest, testClear) { initializeRenderEngine(); initializeRenderEngine(); Loading Loading
libs/renderengine/skia/SkiaGLRenderEngine.cpp +72 −51 Original line number Original line Diff line number Diff line Loading @@ -1127,83 +1127,104 @@ inline SkRect SkiaGLRenderEngine::getSkRect(const Rect& rect) { return SkRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom); return SkRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom); } } inline std::pair<SkRRect, SkRRect> SkiaGLRenderEngine::getBoundsAndClip(const FloatRect& boundsRect, /** const FloatRect& cropRect, * Verifies that common, simple bounds + clip combinations can be converted into const float cornerRadius) { * a single RRect draw call returning true if possible. If true the radii parameter const SkRect bounds = getSkRect(boundsRect); * will be filled with the correct radii values that combined with bounds param will const SkRect crop = getSkRect(cropRect); * produce the insected roundRect. If false, the returned state of the radii param is undefined. */ SkRRect clip; static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, if (cornerRadius > 0) { const SkRect& insetCrop, float cornerRadius, // it the crop and the bounds are equivalent or there is no crop then we don't need a clip SkVector radii[4]) { if (bounds == crop || crop.isEmpty()) { return {SkRRect::MakeRectXY(bounds, cornerRadius, cornerRadius), clip}; } // This makes an effort to speed up common, simple bounds + clip combinations by // converting them to a single RRect draw. It is possible there are other cases // that can be converted. if (crop.contains(bounds)) { bool intersectionIsRoundRect = true; // check each cropped corner to ensure that it exactly matches the crop or is full SkVector radii[4]; const auto insetCrop = crop.makeInset(cornerRadius, cornerRadius); const bool leftEqual = bounds.fLeft == crop.fLeft; const bool leftEqual = bounds.fLeft == crop.fLeft; const bool topEqual = bounds.fTop == crop.fTop; const bool topEqual = bounds.fTop == crop.fTop; const bool rightEqual = bounds.fRight == crop.fRight; const bool rightEqual = bounds.fRight == crop.fRight; const bool bottomEqual = bounds.fBottom == crop.fBottom; const bool bottomEqual = bounds.fBottom == crop.fBottom; // In the event that the corners of the bounds only partially align with the crop we // need to ensure that the resulting shape can still be represented as a round rect. // In particular the round rect implementation will scale the value of all corner radii // if the sum of the radius along any edge is greater than the length of that edge. // See https://www.w3.org/TR/css-backgrounds-3/#corner-overlap const bool requiredWidth = bounds.width() > (cornerRadius * 2); const bool requiredHeight = bounds.height() > (cornerRadius * 2); if (!requiredWidth || !requiredHeight) { return false; } // Check each cropped corner to ensure that it exactly matches the crop or its corner is // contained within the cropped shape and does not need rounded. // compute the UpperLeft corner radius // compute the UpperLeft corner radius if (leftEqual && topEqual) { if (leftEqual && topEqual) { radii[0].set(cornerRadius, cornerRadius); radii[0].set(cornerRadius, cornerRadius); } else if ((leftEqual && bounds.fTop >= insetCrop.fTop) || } else if ((leftEqual && bounds.fTop >= insetCrop.fTop) || (topEqual && bounds.fLeft >= insetCrop.fLeft) || (topEqual && bounds.fLeft >= insetCrop.fLeft)) { insetCrop.contains(bounds.fLeft, bounds.fTop)) { radii[0].set(0, 0); radii[0].set(0, 0); } else { } else { intersectionIsRoundRect = false; return false; } } // compute the UpperRight corner radius // compute the UpperRight corner radius if (rightEqual && topEqual) { if (rightEqual && topEqual) { radii[1].set(cornerRadius, cornerRadius); radii[1].set(cornerRadius, cornerRadius); } else if ((rightEqual && bounds.fTop >= insetCrop.fTop) || } else if ((rightEqual && bounds.fTop >= insetCrop.fTop) || (topEqual && bounds.fRight <= insetCrop.fRight) || (topEqual && bounds.fRight <= insetCrop.fRight)) { insetCrop.contains(bounds.fRight, bounds.fTop)) { radii[1].set(0, 0); radii[1].set(0, 0); } else { } else { intersectionIsRoundRect = false; return false; } } // compute the BottomRight corner radius // compute the BottomRight corner radius if (rightEqual && bottomEqual) { if (rightEqual && bottomEqual) { radii[2].set(cornerRadius, cornerRadius); radii[2].set(cornerRadius, cornerRadius); } else if ((rightEqual && bounds.fBottom <= insetCrop.fBottom) || } else if ((rightEqual && bounds.fBottom <= insetCrop.fBottom) || (bottomEqual && bounds.fRight <= insetCrop.fRight) || (bottomEqual && bounds.fRight <= insetCrop.fRight)) { insetCrop.contains(bounds.fRight, bounds.fBottom)) { radii[2].set(0, 0); radii[2].set(0, 0); } else { } else { intersectionIsRoundRect = false; return false; } } // compute the BottomLeft corner radius // compute the BottomLeft corner radius if (leftEqual && bottomEqual) { if (leftEqual && bottomEqual) { radii[3].set(cornerRadius, cornerRadius); radii[3].set(cornerRadius, cornerRadius); } else if ((leftEqual && bounds.fBottom <= insetCrop.fBottom) || } else if ((leftEqual && bounds.fBottom <= insetCrop.fBottom) || (bottomEqual && bounds.fLeft >= insetCrop.fLeft) || (bottomEqual && bounds.fLeft >= insetCrop.fLeft)) { insetCrop.contains(bounds.fLeft, bounds.fBottom)) { radii[3].set(0, 0); radii[3].set(0, 0); } else { } else { intersectionIsRoundRect = false; return false; } return true; } inline std::pair<SkRRect, SkRRect> SkiaGLRenderEngine::getBoundsAndClip(const FloatRect& boundsRect, const FloatRect& cropRect, const float cornerRadius) { const SkRect bounds = getSkRect(boundsRect); const SkRect crop = getSkRect(cropRect); SkRRect clip; if (cornerRadius > 0) { // it the crop and the bounds are equivalent or there is no crop then we don't need a clip if (bounds == crop || crop.isEmpty()) { return {SkRRect::MakeRectXY(bounds, cornerRadius, cornerRadius), clip}; } } if (intersectionIsRoundRect) { // This makes an effort to speed up common, simple bounds + clip combinations by // converting them to a single RRect draw. It is possible there are other cases // that can be converted. if (crop.contains(bounds)) { const auto insetCrop = crop.makeInset(cornerRadius, cornerRadius); if (insetCrop.contains(bounds)) { return {SkRRect::MakeRect(bounds), clip}; // clip is empty - no rounding required } SkVector radii[4]; if (intersectionIsRoundRect(bounds, crop, insetCrop, cornerRadius, radii)) { SkRRect intersectionBounds; SkRRect intersectionBounds; intersectionBounds.setRectRadii(bounds, radii); intersectionBounds.setRectRadii(bounds, radii); return {intersectionBounds, clip}; return {intersectionBounds, clip}; } } } } // we didn't it any of our fast paths so set the clip to the cropRect // we didn't hit any of our fast paths so set the clip to the cropRect clip.setRectXY(crop, cornerRadius, cornerRadius); clip.setRectXY(crop, cornerRadius, cornerRadius); } } Loading
libs/renderengine/tests/RenderEngineTest.cpp +34 −0 Original line number Original line Diff line number Diff line Loading @@ -1869,6 +1869,40 @@ TEST_P(RenderEngineTest, testRoundedCornersParentCrop) { expectBufferColor(Point(0, (DEFAULT_DISPLAY_HEIGHT / 2) - 1), 0, 255, 0, 255); expectBufferColor(Point(0, (DEFAULT_DISPLAY_HEIGHT / 2) - 1), 0, 255, 0, 255); } } TEST_P(RenderEngineTest, testRoundedCornersParentCropSmallBounds) { initializeRenderEngine(); renderengine::DisplaySettings settings; settings.physicalDisplay = fullscreenRect(); settings.clip = fullscreenRect(); settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR; std::vector<const renderengine::LayerSettings*> layers; renderengine::LayerSettings redLayer; redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR; redLayer.geometry.boundaries = FloatRect(0, 0, DEFAULT_DISPLAY_WIDTH, 32); redLayer.geometry.roundedCornersRadius = 64; redLayer.geometry.roundedCornersCrop = FloatRect(0, 0, DEFAULT_DISPLAY_WIDTH, 128); // Red background. redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f); redLayer.alpha = 1.0f; layers.push_back(&redLayer); invokeDraw(settings, layers); // Due to roundedCornersRadius, the top corners are untouched. expectBufferColor(Point(0, 0), 0, 0, 0, 0); expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, 0), 0, 0, 0, 0); // ensure that the entire height of the red layer was clipped by the rounded corners crop. expectBufferColor(Point(0, 31), 0, 0, 0, 0); expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, 31), 0, 0, 0, 0); // the bottom middle should be red expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH / 2, 31), 255, 0, 0, 255); } TEST_P(RenderEngineTest, testClear) { TEST_P(RenderEngineTest, testClear) { initializeRenderEngine(); initializeRenderEngine(); Loading