| // Copyright 2017 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "cobalt/renderer/rasterizer/egl/draw_object.h" |
| |
| #include <algorithm> |
| #include <limits> |
| |
| #include "cobalt/math/transform_2d.h" |
| #include "cobalt/renderer/backend/egl/utils.h" |
| |
| namespace cobalt { |
| namespace renderer { |
| namespace rasterizer { |
| namespace egl { |
| |
| namespace { |
| // To accommodate large radius values for rounded corners while using mediump |
| // floats in the fragment shader, scale 1 / radius.xy by kRCornerGradientScale. |
| // The fragment shader will take this into account. |
| const float kRCornerGradientScale = 16.0f; |
| |
| // Get the midpoint of the given rounded rect. A normalized rounded rect |
| // should have at least one point which the corners do not cross. |
| math::PointF GetRRectCenter(const math::RectF& rect, |
| const render_tree::RoundedCorners& corners) { |
| return math::PointF( |
| 0.5f * (rect.x() + std::max(corners.top_left.horizontal, |
| corners.bottom_left.horizontal) + |
| rect.right() - std::max(corners.top_right.horizontal, |
| corners.bottom_right.horizontal)), |
| 0.5f * (rect.y() + std::max(corners.top_left.vertical, |
| corners.top_right.vertical) + |
| rect.bottom() - std::max(corners.bottom_left.vertical, |
| corners.bottom_right.vertical))); |
| } |
| |
| math::PointF ClampToBounds(const math::RectF& bounds, float x, float y) { |
| return math::PointF( |
| std::min(std::max(bounds.x(), x), bounds.right()), |
| std::min(std::max(bounds.y(), y), bounds.bottom())); |
| } |
| } // namespace |
| |
| DrawObject::BaseState::BaseState() |
| : transform(math::Matrix3F::Identity()), |
| scissor(0, 0, |
| std::numeric_limits<int>::max(), |
| std::numeric_limits<int>::max()), |
| opacity(1.0f) {} |
| |
| DrawObject::BaseState::BaseState(const BaseState& other) |
| : transform(other.transform), |
| scissor(other.scissor), |
| rounded_scissor_rect(other.rounded_scissor_rect), |
| rounded_scissor_corners(other.rounded_scissor_corners), |
| opacity(other.opacity) {} |
| |
| DrawObject::RCorner::RCorner(const float (&position)[2], const RCorner& init) |
| : x((position[0] - init.x) * init.rx), |
| y((position[1] - init.y) * init.ry), |
| rx(init.rx * kRCornerGradientScale), |
| ry(init.ry * kRCornerGradientScale) {} |
| |
| DrawObject::DrawObject() |
| : merge_type_(base::GetTypeId<DrawObject>()) {} |
| |
| DrawObject::DrawObject(const BaseState& base_state) |
| : base_state_(base_state), |
| merge_type_(base::GetTypeId<DrawObject>()) {} |
| |
| math::Vector2dF DrawObject::GetScale() const { |
| float m00 = base_state_.transform(0, 0); |
| float m01 = base_state_.transform(0, 1); |
| float m10 = base_state_.transform(1, 0); |
| float m11 = base_state_.transform(1, 1); |
| return math::Vector2dF(std::sqrt(m00 * m00 + m10 * m10), |
| std::sqrt(m01 * m01 + m11 * m11)); |
| } |
| |
| math::Vector2dF DrawObject::RemoveScaleFromTransform() { |
| // Avoid division by zero. |
| const float kEpsilon = 0.00001f; |
| |
| math::Vector2dF scale = GetScale(); |
| base_state_.transform = base_state_.transform * |
| math::ScaleMatrix(1.0f / std::max(scale.x(), kEpsilon), |
| 1.0f / std::max(scale.y(), kEpsilon)); |
| return scale; |
| } |
| |
| // static |
| uint32_t DrawObject::GetGLRGBA(float r, float g, float b, float a) { |
| // Ensure color bytes represent RGBA, regardless of endianness. |
| union { |
| uint32_t color32; |
| uint8_t color8[4]; |
| } color_data; |
| color_data.color8[0] = static_cast<uint8_t>(r * 255.0f); |
| color_data.color8[1] = static_cast<uint8_t>(g * 255.0f); |
| color_data.color8[2] = static_cast<uint8_t>(b * 255.0f); |
| color_data.color8[3] = static_cast<uint8_t>(a * 255.0f); |
| return color_data.color32; |
| } |
| |
| // static |
| void DrawObject::GetRRectAttributes(const math::RectF& bounds, |
| math::RectF rect, render_tree::RoundedCorners corners, |
| RRectAttributes (&out_attributes)[4]) { |
| GetRCornerValues(&rect, &corners, out_attributes); |
| |
| // Calculate the bounds for each patch. Four patches will be used to cover |
| // the entire bounded area. |
| math::PointF center = GetRRectCenter(rect, corners); |
| center = ClampToBounds(bounds, center.x(), center.y()); |
| |
| out_attributes[0].bounds.SetRect(bounds.x(), bounds.y(), |
| center.x() - bounds.x(), center.y() - bounds.y()); |
| out_attributes[1].bounds.SetRect(center.x(), bounds.y(), |
| bounds.right() - center.x(), center.y() - bounds.y()); |
| out_attributes[2].bounds.SetRect(bounds.x(), center.y(), |
| center.x() - bounds.x(), bounds.bottom() - center.y()); |
| out_attributes[3].bounds.SetRect(center.x(), center.y(), |
| bounds.right() - center.x(), bounds.bottom() - center.y()); |
| } |
| |
| // static |
| void DrawObject::GetRRectAttributes(const math::RectF& bounds, |
| math::RectF rect, render_tree::RoundedCorners corners, |
| RRectAttributes (&out_attributes)[8]) { |
| GetRCornerValues(&rect, &corners, out_attributes); |
| out_attributes[4].rcorner = out_attributes[0].rcorner; |
| out_attributes[5].rcorner = out_attributes[1].rcorner; |
| out_attributes[6].rcorner = out_attributes[2].rcorner; |
| out_attributes[7].rcorner = out_attributes[3].rcorner; |
| |
| // Given an ellipse with radii A and B, the largest inscribed rectangle has |
| // dimensions sqrt(2) * A and sqrt(2) * B. To accommodate the antialiased |
| // edge, inset the inscribed rect by a pixel on each side. |
| const float kInsetScale = 0.2929f; // 1 - sqrt(2) / 2 |
| |
| // Calculate the bounds for each patch. Eight patches will be used to exclude |
| // the inscribed rect: |
| // +---+-----+-----+---+ |
| // | | 4 | 5 | | |
| // | 0 +-----+-----+ 1 | |
| // | | | | |
| // +---+ C +---+ C = center point |
| // | | | | |
| // | 2 +-----+-----+ 3 | |
| // | | 6 | 7 | | |
| // +---+-----+-----+---+ |
| math::PointF center = GetRRectCenter(rect, corners); |
| center = ClampToBounds(bounds, center.x(), center.y()); |
| math::PointF inset0 = ClampToBounds(bounds, |
| rect.x() + kInsetScale * corners.top_left.horizontal + 1.0f, |
| rect.y() + kInsetScale * corners.top_left.vertical + 1.0f); |
| math::PointF inset1 = ClampToBounds(bounds, |
| rect.right() - kInsetScale * corners.top_right.horizontal - 1.0f, |
| rect.y() + kInsetScale * corners.top_right.vertical + 1.0f); |
| math::PointF inset2 = ClampToBounds(bounds, |
| rect.x() + kInsetScale * corners.bottom_left.horizontal + 1.0f, |
| rect.bottom() - kInsetScale * corners.bottom_left.vertical - 1.0f); |
| math::PointF inset3 = ClampToBounds(bounds, |
| rect.right() - kInsetScale * corners.bottom_right.horizontal - 1.0f, |
| rect.bottom() - kInsetScale * corners.bottom_right.vertical - 1.0f); |
| |
| out_attributes[0].bounds.SetRect(bounds.x(), bounds.y(), |
| inset0.x() - bounds.x(), center.y() - bounds.y()); |
| out_attributes[1].bounds.SetRect(inset1.x(), bounds.y(), |
| bounds.right() - inset1.x(), center.y() - bounds.y()); |
| out_attributes[2].bounds.SetRect(bounds.x(), center.y(), |
| inset2.x() - bounds.x(), bounds.bottom() - center.y()); |
| out_attributes[3].bounds.SetRect(inset3.x(), center.y(), |
| bounds.right() - inset3.x(), bounds.bottom() - center.y()); |
| out_attributes[4].bounds.SetRect(inset0.x(), bounds.y(), |
| center.x() - inset0.x(), inset0.y() - bounds.y()); |
| out_attributes[5].bounds.SetRect(center.x(), bounds.y(), |
| inset1.x() - center.x(), inset1.y() - bounds.y()); |
| out_attributes[6].bounds.SetRect(inset2.x(), inset2.y(), |
| center.x() - inset2.x(), bounds.bottom() - inset2.y()); |
| out_attributes[7].bounds.SetRect(center.x(), inset3.y(), |
| inset3.x() - center.x(), bounds.bottom() - inset3.y()); |
| } |
| |
| // static |
| void DrawObject::GetRCornerValues(math::RectF* rect, |
| render_tree::RoundedCorners* corners, RRectAttributes out_rcorners[4]) { |
| // Ensure corner sizes are non-zero to allow generic handling of square and |
| // rounded corners. Corner radii must be at least 1 pixel for antialiasing |
| // to work well. |
| const float kMinCornerSize = 1.0f; |
| |
| // First inset to make room for the minimum corner size. Then outset to |
| // enforce the minimum corner size. Be careful not to inset more than the |
| // rect size, otherwise the outset rect will be off-centered. |
| rect->Inset(std::min(rect->width() * 0.5f, kMinCornerSize), |
| std::min(rect->height() * 0.5f, kMinCornerSize)); |
| *corners = corners->Inset(kMinCornerSize, kMinCornerSize, kMinCornerSize, |
| kMinCornerSize); |
| *corners = corners->Normalize(*rect); |
| rect->Outset(kMinCornerSize, kMinCornerSize); |
| *corners = corners->Inset(-kMinCornerSize, -kMinCornerSize, -kMinCornerSize, |
| -kMinCornerSize); |
| |
| // |rcorner| describes (start.xy, 1 / radius.xy) for the relevant corner. |
| // The sign of the radius component is used to facilitate the calculation: |
| // vec2 scaled_offset = (position - corner.xy) * corner.zw |
| // Such that |scaled_offset| is in the first quadrant when the pixel is |
| // in the given rounded corner. |
| COMPILE_ASSERT(sizeof(RCorner) == sizeof(float) * 4, struct_should_be_vec4); |
| out_rcorners[0].rcorner.x = rect->x() + corners->top_left.horizontal; |
| out_rcorners[0].rcorner.y = rect->y() + corners->top_left.vertical; |
| out_rcorners[0].rcorner.rx = -1.0f / corners->top_left.horizontal; |
| out_rcorners[0].rcorner.ry = -1.0f / corners->top_left.vertical; |
| |
| out_rcorners[1].rcorner.x = rect->right() - corners->top_right.horizontal; |
| out_rcorners[1].rcorner.y = rect->y() + corners->top_right.vertical; |
| out_rcorners[1].rcorner.rx = 1.0f / corners->top_right.horizontal; |
| out_rcorners[1].rcorner.ry = -1.0f / corners->top_right.vertical; |
| |
| out_rcorners[2].rcorner.x = rect->x() + corners->bottom_left.horizontal; |
| out_rcorners[2].rcorner.y = rect->bottom() - corners->bottom_left.vertical; |
| out_rcorners[2].rcorner.rx = -1.0f / corners->bottom_left.horizontal; |
| out_rcorners[2].rcorner.ry = 1.0f / corners->bottom_left.vertical; |
| |
| out_rcorners[3].rcorner.x = rect->right() - corners->bottom_right.horizontal; |
| out_rcorners[3].rcorner.y = rect->bottom() - corners->bottom_right.vertical; |
| out_rcorners[3].rcorner.rx = 1.0f / corners->bottom_right.horizontal; |
| out_rcorners[3].rcorner.ry = 1.0f / corners->bottom_right.vertical; |
| } |
| |
| } // namespace egl |
| } // namespace rasterizer |
| } // namespace renderer |
| } // namespace cobalt |