blob: 32610890512416df4abad3f27291aeb51ffb7ca6 [file] [log] [blame]
// 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