| // 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_rect_shadow_blur.h" |
| |
| #include <GLES2/gl2.h> |
| #include <algorithm> |
| |
| #include "base/logging.h" |
| #include "cobalt/math/transform_2d.h" |
| #include "cobalt/renderer/backend/egl/utils.h" |
| #include "starboard/memory.h" |
| |
| namespace cobalt { |
| namespace renderer { |
| namespace rasterizer { |
| namespace egl { |
| |
| namespace { |
| const float kSqrt2 = 1.414213562f; |
| const float kSqrtPi = 1.772453851f; |
| |
| // The blur kernel extends for a limited number of sigmas. |
| const float kBlurExtentInSigmas = 3.0f; |
| |
| // The error function is used to calculate the gaussian blur. Inputs for |
| // the error function should be scaled by 1 / (sqrt(2) * sigma). Alternatively, |
| // it can be viewed as kBlurDistance is the input value at which the blur |
| // intensity is effectively 0. |
| const float kBlurDistance = kBlurExtentInSigmas / kSqrt2; |
| |
| void SetBlurRRectUniforms(const ShaderFragmentColorBlurRrects& shader, |
| math::RectF rect, render_tree::RoundedCorners corners, float sigma) { |
| const float kBlurExtentInPixels = kBlurExtentInSigmas * sigma; |
| const float kOffsetScale = kBlurDistance / kBlurExtentInPixels; |
| |
| // Ensure a minimum radius for each corner to avoid division by zero. |
| // NOTE: The rounded rect is already specified in terms of sigma. |
| const float kMinSize = 0.01f * kOffsetScale; |
| rect.Outset(kMinSize, kMinSize); |
| corners = corners.Inset(-kMinSize, -kMinSize, -kMinSize, -kMinSize); |
| corners = corners.Normalize(rect); |
| |
| // A normalized rounded rect should have at least one Y value which the |
| // corners do not cross. |
| const float kCenterY = |
| 0.5f * (rect.y() + std::max(corners.top_left.vertical, |
| corners.top_right.vertical)) + |
| 0.5f * (rect.bottom() - std::max(corners.bottom_left.vertical, |
| corners.bottom_right.vertical)); |
| |
| // The blur extent is (blur_size.y, rect_min.y, rect_max.y, rect_center.y). |
| GL_CALL(glUniform4f(shader.u_blur_extent(), |
| kBlurExtentInPixels * kOffsetScale, |
| rect.y(), rect.bottom(), kCenterY)); |
| |
| // The blur rounded rect is split into top and bottom halves. |
| // The "start" values represent (left_start.xy, right_start.xy). |
| // The "scale" values represent (left_radius.x, 1 / left_radius.y, |
| // right_radius.x, 1 / right_radius.y). The sign of the scale value helps |
| // to translate between position and corner offset values, where the corner |
| // offset is positive if the position is inside the rounded corner. |
| GL_CALL(glUniform4f(shader.u_blur_start_top(), |
| rect.x() + corners.top_left.horizontal, |
| rect.y() + corners.top_left.vertical, |
| rect.right() - corners.top_right.horizontal, |
| rect.y() + corners.top_right.vertical)); |
| GL_CALL(glUniform4f(shader.u_blur_start_bottom(), |
| rect.x() + corners.bottom_left.horizontal, |
| rect.bottom() - corners.bottom_left.vertical, |
| rect.right() - corners.bottom_right.horizontal, |
| rect.bottom() - corners.bottom_right.vertical)); |
| GL_CALL(glUniform4f(shader.u_blur_scale_top(), |
| -corners.top_left.horizontal, |
| -1.0f / corners.top_left.vertical, |
| corners.top_right.horizontal, |
| -1.0f / corners.top_right.vertical)); |
| GL_CALL(glUniform4f(shader.u_blur_scale_bottom(), |
| -corners.bottom_left.horizontal, |
| 1.0f / corners.bottom_left.vertical, |
| corners.bottom_right.horizontal, |
| 1.0f / corners.bottom_right.vertical)); |
| } |
| } // namespace |
| |
| DrawRectShadowBlur::VertexAttributesSquare::VertexAttributesSquare( |
| float x, float y, float offset_scale) { |
| position[0] = x; |
| position[1] = y; |
| offset[0] = x * offset_scale; |
| offset[1] = y * offset_scale; |
| } |
| |
| DrawRectShadowBlur::VertexAttributesRound::VertexAttributesRound( |
| float x, float y, float offset_scale, const RCorner& rcorner) { |
| position[0] = x; |
| position[1] = y; |
| offset[0] = x * offset_scale; |
| offset[1] = y * offset_scale; |
| rcorner_scissor = RCorner(position, rcorner); |
| } |
| |
| DrawRectShadowBlur::DrawRectShadowBlur(GraphicsState* graphics_state, |
| const BaseState& base_state, const math::RectF& base_rect, |
| const OptionalRoundedCorners& base_corners, const math::RectF& spread_rect, |
| const OptionalRoundedCorners& spread_corners, |
| const render_tree::ColorRGBA& color, float blur_sigma, bool inset) |
| : DrawObject(base_state), |
| spread_rect_(spread_rect), |
| spread_corners_(spread_corners), |
| blur_sigma_(blur_sigma), |
| is_inset_(inset), |
| vertex_buffer_(nullptr), |
| index_buffer_(nullptr) { |
| color_ = GetDrawColor(color) * base_state_.opacity; |
| |
| // Extract scale from the transform and move it into the vertex attributes |
| // so that the anti-aliased edges remain 1 pixel wide. |
| math::Vector2dF scale = RemoveScaleFromTransform(); |
| math::RectF scaled_base_rect(base_rect); |
| scaled_base_rect.Scale(scale.x(), scale.y()); |
| OptionalRoundedCorners scaled_base_corners(base_corners); |
| if (scaled_base_corners) { |
| scaled_base_corners = scaled_base_corners->Scale(scale.x(), scale.y()); |
| } |
| spread_rect_.Scale(scale.x(), scale.y()); |
| if (spread_corners_) { |
| spread_corners_ = spread_corners_->Scale(scale.x(), scale.y()); |
| } |
| |
| // The blur algorithms used by the shaders do not produce good results with |
| // separate x and y blur sigmas. Select a single blur sigma to approximate |
| // the desired blur. |
| blur_sigma_ *= std::sqrt(scale.x() * scale.y()); |
| |
| SetGeometry(graphics_state, scaled_base_rect, scaled_base_corners); |
| } |
| |
| void DrawRectShadowBlur::ExecuteUpdateVertexBuffer( |
| GraphicsState* graphics_state, |
| ShaderProgramManager* program_manager) { |
| if (attributes_square_.size() > 0) { |
| vertex_buffer_ = graphics_state->AllocateVertexData( |
| attributes_square_.size() * sizeof(attributes_square_[0])); |
| SbMemoryCopy(vertex_buffer_, &attributes_square_[0], |
| attributes_square_.size() * sizeof(attributes_square_[0])); |
| } else if (attributes_round_.size() > 0) { |
| vertex_buffer_ = graphics_state->AllocateVertexData( |
| attributes_round_.size() * sizeof(attributes_round_[0])); |
| SbMemoryCopy(vertex_buffer_, &attributes_round_[0], |
| attributes_round_.size() * sizeof(attributes_round_[0])); |
| index_buffer_ = graphics_state->AllocateVertexIndices(indices_.size()); |
| SbMemoryCopy(index_buffer_, &indices_[0], |
| indices_.size() * sizeof(indices_[0])); |
| } |
| } |
| |
| void DrawRectShadowBlur::ExecuteRasterize( |
| GraphicsState* graphics_state, |
| ShaderProgramManager* program_manager) { |
| if (vertex_buffer_ == nullptr) { |
| return; |
| } |
| |
| // Draw the blurred shadow. |
| if (spread_corners_) { |
| ShaderProgram<ShaderVertexOffsetRcorner, |
| ShaderFragmentColorBlurRrects>* program; |
| program_manager->GetProgram(&program); |
| graphics_state->UseProgram(program->GetHandle()); |
| SetupVertexShader(graphics_state, program->GetVertexShader()); |
| SetFragmentUniforms(program->GetFragmentShader().u_color(), |
| program->GetFragmentShader().u_scale_add()); |
| SetBlurRRectUniforms(program->GetFragmentShader(), |
| spread_rect_, *spread_corners_, blur_sigma_); |
| GL_CALL(glDrawElements(GL_TRIANGLES, indices_.size(), GL_UNSIGNED_SHORT, |
| graphics_state->GetVertexIndexPointer(index_buffer_))); |
| } else { |
| ShaderProgram<ShaderVertexOffset, |
| ShaderFragmentColorBlur>* program; |
| program_manager->GetProgram(&program); |
| graphics_state->UseProgram(program->GetHandle()); |
| SetupVertexShader(graphics_state, program->GetVertexShader()); |
| SetFragmentUniforms(program->GetFragmentShader().u_color(), |
| program->GetFragmentShader().u_scale_add()); |
| GL_CALL(glUniform4f(program->GetFragmentShader().u_blur_rect(), |
| spread_rect_.x(), spread_rect_.y(), |
| spread_rect_.right(), spread_rect_.bottom())); |
| GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, attributes_square_.size())); |
| } |
| } |
| |
| base::TypeId DrawRectShadowBlur::GetTypeId() const { |
| if (spread_corners_) { |
| return ShaderProgram<ShaderVertexOffsetRcorner, |
| ShaderFragmentColorBlurRrects>::GetTypeId(); |
| } else { |
| return ShaderProgram<ShaderVertexOffset, |
| ShaderFragmentColorBlur>::GetTypeId(); |
| } |
| } |
| |
| void DrawRectShadowBlur::SetupVertexShader(GraphicsState* graphics_state, |
| const ShaderVertexOffset& shader) { |
| graphics_state->UpdateClipAdjustment(shader.u_clip_adjustment()); |
| graphics_state->UpdateTransformMatrix(shader.u_view_matrix(), |
| base_state_.transform); |
| graphics_state->Scissor(base_state_.scissor.x(), base_state_.scissor.y(), |
| base_state_.scissor.width(), base_state_.scissor.height()); |
| graphics_state->VertexAttribPointer( |
| shader.a_position(), 2, GL_FLOAT, GL_FALSE, |
| sizeof(VertexAttributesSquare), vertex_buffer_ + |
| offsetof(VertexAttributesSquare, position)); |
| graphics_state->VertexAttribPointer( |
| shader.a_offset(), 2, GL_FLOAT, GL_FALSE, |
| sizeof(VertexAttributesSquare), vertex_buffer_ + |
| offsetof(VertexAttributesSquare, offset)); |
| graphics_state->VertexAttribFinish(); |
| } |
| |
| void DrawRectShadowBlur::SetupVertexShader(GraphicsState* graphics_state, |
| const ShaderVertexOffsetRcorner& shader) { |
| graphics_state->UpdateClipAdjustment(shader.u_clip_adjustment()); |
| graphics_state->UpdateTransformMatrix(shader.u_view_matrix(), |
| base_state_.transform); |
| graphics_state->Scissor(base_state_.scissor.x(), base_state_.scissor.y(), |
| base_state_.scissor.width(), base_state_.scissor.height()); |
| graphics_state->VertexAttribPointer( |
| shader.a_position(), 2, GL_FLOAT, GL_FALSE, |
| sizeof(VertexAttributesRound), vertex_buffer_ + |
| offsetof(VertexAttributesRound, position)); |
| graphics_state->VertexAttribPointer( |
| shader.a_offset(), 2, GL_FLOAT, GL_FALSE, |
| sizeof(VertexAttributesRound), vertex_buffer_ + |
| offsetof(VertexAttributesRound, offset)); |
| graphics_state->VertexAttribPointer( |
| shader.a_rcorner(), 4, GL_FLOAT, GL_FALSE, |
| sizeof(VertexAttributesRound), vertex_buffer_ + |
| offsetof(VertexAttributesRound, rcorner_scissor)); |
| graphics_state->VertexAttribFinish(); |
| } |
| |
| void DrawRectShadowBlur::SetFragmentUniforms( |
| GLint color_uniform, GLint scale_add_uniform) { |
| GL_CALL(glUniform4f(color_uniform, |
| color_.r(), color_.g(), color_.b(), color_.a())); |
| if (is_inset_) { |
| // Invert the shadow. |
| GL_CALL(glUniform2f(scale_add_uniform, -1.0f, 1.0f)); |
| } else { |
| // Keep the normal (outset) shadow. |
| GL_CALL(glUniform2f(scale_add_uniform, 1.0f, 0.0f)); |
| } |
| } |
| |
| void DrawRectShadowBlur::SetGeometry(GraphicsState* graphics_state, |
| const math::RectF& base_rect, const OptionalRoundedCorners& base_corners) { |
| const float kBlurExtentInPixels = kBlurExtentInSigmas * blur_sigma_; |
| |
| if (base_corners || spread_corners_) { |
| // If rounded rects are specified, then both the base and spread rects |
| // must have rounded corners. |
| DCHECK(base_corners); |
| DCHECK(spread_corners_); |
| |
| if (is_inset_) { |
| // Extend the outer rect to include the antialiased edge. |
| math::RectF outer_rect(base_rect); |
| outer_rect.Outset(1.0f, 1.0f); |
| RRectAttributes rrect_outer[4]; |
| GetRRectAttributes(outer_rect, base_rect, *base_corners, rrect_outer); |
| // Inset the spread rect by the blur extent. Use that as the inner bounds. |
| RRectAttributes rrect_inner[8]; |
| math::RectF inner_rect(spread_rect_); |
| inner_rect.Inset(kBlurExtentInPixels, kBlurExtentInPixels); |
| if (!inner_rect.IsEmpty()) { |
| // Get the inner bounds excluding the inscribed rect. |
| render_tree::RoundedCorners inner_corners = spread_corners_->Inset( |
| kBlurExtentInPixels, kBlurExtentInPixels, kBlurExtentInPixels, |
| kBlurExtentInPixels); |
| inner_corners = inner_corners.Normalize(inner_rect); |
| GetRRectAttributes(outer_rect, inner_rect, inner_corners, rrect_inner); |
| } else { |
| // The blur covers everything inside the outer rect. |
| rrect_inner[0].bounds = outer_rect; |
| } |
| SetGeometry(graphics_state, rrect_outer, rrect_inner); |
| } else { |
| // Extend the outer rect to include the blur. |
| math::RectF outer_rect(spread_rect_); |
| outer_rect.Outset(kBlurExtentInPixels, kBlurExtentInPixels); |
| // Exclude the inscribed rect of the base rounded rect. |
| RRectAttributes rrect[8]; |
| GetRRectAttributes(outer_rect, base_rect, *base_corners, rrect); |
| SetGeometry(graphics_state, rrect); |
| } |
| } else { |
| // Handle box shadow with square corners. |
| if (is_inset_) { |
| math::RectF inner_rect(spread_rect_); |
| inner_rect.Inset(kBlurExtentInPixels, kBlurExtentInPixels); |
| SetGeometry(graphics_state, inner_rect, base_rect); |
| } else { |
| math::RectF outer_rect(spread_rect_); |
| outer_rect.Outset(kBlurExtentInPixels, kBlurExtentInPixels); |
| SetGeometry(graphics_state, base_rect, outer_rect); |
| } |
| } |
| } |
| |
| void DrawRectShadowBlur::SetGeometry(GraphicsState* graphics_state, |
| const math::RectF& inner_rect, const math::RectF& outer_rect) { |
| // The spread rect and offsets should be expressed in terms of sigma for the |
| // shader. |
| float offset_scale = kBlurDistance / (kBlurExtentInSigmas * blur_sigma_); |
| spread_rect_.Scale(offset_scale, offset_scale); |
| |
| // The box shadow is a triangle strip covering the area between outer rect |
| // and inner rect. |
| if (inner_rect.IsEmpty()) { |
| attributes_square_.reserve(4); |
| attributes_square_.emplace_back( |
| outer_rect.x(), outer_rect.y(), offset_scale); |
| attributes_square_.emplace_back( |
| outer_rect.right(), outer_rect.y(), offset_scale); |
| attributes_square_.emplace_back( |
| outer_rect.x(), outer_rect.bottom(), offset_scale); |
| attributes_square_.emplace_back( |
| outer_rect.right(), outer_rect.bottom(), offset_scale); |
| } else { |
| math::RectF inside_rect(inner_rect); |
| inside_rect.Intersect(outer_rect); |
| attributes_square_.reserve(10); |
| attributes_square_.emplace_back( |
| outer_rect.x(), outer_rect.y(), offset_scale); |
| attributes_square_.emplace_back( |
| inside_rect.x(), inside_rect.y(), offset_scale); |
| attributes_square_.emplace_back( |
| outer_rect.right(), outer_rect.y(), offset_scale); |
| attributes_square_.emplace_back( |
| inside_rect.right(), inside_rect.y(), offset_scale); |
| attributes_square_.emplace_back( |
| outer_rect.right(), outer_rect.bottom(), offset_scale); |
| attributes_square_.emplace_back( |
| inside_rect.right(), inside_rect.bottom(), offset_scale); |
| attributes_square_.emplace_back( |
| outer_rect.x(), outer_rect.bottom(), offset_scale); |
| attributes_square_.emplace_back( |
| inside_rect.x(), inside_rect.bottom(), offset_scale); |
| attributes_square_.emplace_back( |
| outer_rect.x(), outer_rect.y(), offset_scale); |
| attributes_square_.emplace_back( |
| inside_rect.x(), inside_rect.y(), offset_scale); |
| } |
| |
| graphics_state->ReserveVertexData( |
| attributes_square_.size() * sizeof(attributes_square_[0])); |
| } |
| |
| void DrawRectShadowBlur::SetGeometry(GraphicsState* graphics_state, |
| const RRectAttributes (&rrect)[8]) { |
| // The spread rect and offsets should be expressed in terms of sigma for the |
| // shader. |
| float offset_scale = kBlurDistance / (kBlurExtentInSigmas * blur_sigma_); |
| spread_rect_.Scale(offset_scale, offset_scale); |
| *spread_corners_ = spread_corners_->Scale(offset_scale, offset_scale); |
| |
| // The shadowed area is already split into quads. |
| for (int i = 0; i < arraysize(rrect); ++i) { |
| AddQuad(rrect[i].bounds, offset_scale, rrect[i].rcorner); |
| } |
| |
| graphics_state->ReserveVertexData( |
| attributes_round_.size() * sizeof(attributes_round_[0])); |
| graphics_state->ReserveVertexIndices(indices_.size()); |
| } |
| |
| void DrawRectShadowBlur::SetGeometry(GraphicsState* graphics_state, |
| const RRectAttributes (&rrect_outer)[4], |
| const RRectAttributes (&rrect_inner)[8]) { |
| // The spread rect and offsets should be expressed in terms of sigma for the |
| // shader. |
| float offset_scale = kBlurDistance / (kBlurExtentInSigmas * blur_sigma_); |
| spread_rect_.Scale(offset_scale, offset_scale); |
| *spread_corners_ = spread_corners_->Scale(offset_scale, offset_scale); |
| |
| // Draw the area between the inner rect and outer rect using the outer rect's |
| // rounded corners. The inner quads already exclude the inscribed rectangle. |
| for (int i = 0; i < arraysize(rrect_inner); ++i) { |
| for (int o = 0; o < arraysize(rrect_outer); ++o) { |
| math::RectF rect = math::IntersectRects( |
| rrect_inner[i].bounds, rrect_outer[o].bounds); |
| if (!rect.IsEmpty()) { |
| AddQuad(rect, offset_scale, rrect_outer[o].rcorner); |
| } |
| } |
| } |
| |
| graphics_state->ReserveVertexData( |
| attributes_round_.size() * sizeof(attributes_round_[0])); |
| graphics_state->ReserveVertexIndices(indices_.size()); |
| } |
| |
| void DrawRectShadowBlur::AddQuad(const math::RectF& rect, float scale, |
| const RCorner& rcorner) { |
| uint16_t vert = static_cast<uint16_t>(attributes_round_.size()); |
| attributes_round_.emplace_back(rect.x(), rect.y(), scale, rcorner); |
| attributes_round_.emplace_back(rect.right(), rect.y(), scale, rcorner); |
| attributes_round_.emplace_back(rect.x(), rect.bottom(), scale, rcorner); |
| attributes_round_.emplace_back(rect.right(), rect.bottom(), scale, rcorner); |
| indices_.emplace_back(vert); |
| indices_.emplace_back(vert + 1); |
| indices_.emplace_back(vert + 2); |
| indices_.emplace_back(vert + 1); |
| indices_.emplace_back(vert + 2); |
| indices_.emplace_back(vert + 3); |
| } |
| |
| } // namespace egl |
| } // namespace rasterizer |
| } // namespace renderer |
| } // namespace cobalt |