blob: fcabd2d9a3457c6ead96eace9d06f8dea412fb4a [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) {
// Ensure a minimum radius for each corner to avoid division by zero.
const float kMinSize = 0.01f;
rect.Outset(kMinSize, kMinSize);
corners = corners.Inset(-kMinSize, -kMinSize, -kMinSize, -kMinSize);
corners = corners.Normalize(rect);
// Specify the blur extent size and the (min.y, max.y) for the rect.
const float kBlurExtentInPixels = kBlurExtentInSigmas * sigma;
GL_CALL(glUniform3f(shader.u_blur_extent(),
kBlurExtentInPixels, rect.y(), rect.bottom()));
// Set the "start" and "scale" values so that (pos - start) * scale is in the
// first quadrant and normalized. Then specify "radius" so that normalized *
// radius + start specifies a point on the respective corner.
GL_CALL(glUniform4f(shader.u_spread_start_x(),
rect.x() + corners.top_left.horizontal,
rect.right() - corners.top_right.horizontal,
rect.x() + corners.bottom_left.horizontal,
rect.right() - corners.bottom_right.horizontal));
GL_CALL(glUniform4f(shader.u_spread_start_y(),
rect.y() + corners.top_left.vertical,
rect.y() + corners.top_right.vertical,
rect.bottom() - corners.bottom_left.vertical,
rect.bottom() - corners.bottom_right.vertical));
GL_CALL(glUniform4f(shader.u_spread_scale_y(),
-1.0f / corners.top_left.vertical,
-1.0f / corners.top_right.vertical,
1.0f / corners.bottom_left.vertical,
1.0f / corners.bottom_right.vertical));
GL_CALL(glUniform4f(shader.u_spread_radius_x(),
-corners.top_left.horizontal,
corners.top_right.horizontal,
-corners.bottom_left.horizontal,
corners.bottom_right.horizontal));
}
} // 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, const RCorner& init) {
position[0] = x;
position[1] = y;
rcorner_scissor = RCorner(position, init);
}
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());
scaled_base_corners = scaled_base_corners->Normalize(scaled_base_rect);
}
spread_rect_.Scale(scale.x(), scale.y());
if (spread_corners_) {
spread_corners_ = spread_corners_->Scale(scale.x(), scale.y());
spread_corners_ = spread_corners_->Normalize(spread_rect_);
}
// 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());
float sigma_scale = kBlurDistance / (kBlurExtentInSigmas * blur_sigma_);
GL_CALL(glUniform2f(program->GetFragmentShader().u_sigma_scale(),
sigma_scale, sigma_scale));
// Pre-calculate the scale values to calculate the normalized gaussian.
GL_CALL(glUniform2f(program->GetFragmentShader().u_gaussian_scale(),
-1.0f / (2.0f * blur_sigma_ * blur_sigma_),
1.0f / (kSqrt2 * kSqrtPi * blur_sigma_)));
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_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) {
// Express offset in terms of blur sigma for the shader.
float offset_scale = kBlurDistance / (kBlurExtentInSigmas * blur_sigma_);
// The spread rect should also be expressed in terms of 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 shadowed area is already split into quads.
for (int i = 0; i < arraysize(rrect); ++i) {
uint16_t vert = static_cast<uint16_t>(attributes_round_.size());
const math::RectF& bounds = rrect[i].bounds;
const RCorner& rcorner = rrect[i].rcorner;
attributes_round_.emplace_back(bounds.x(), bounds.y(), rcorner);
attributes_round_.emplace_back(bounds.right(), bounds.y(), rcorner);
attributes_round_.emplace_back(bounds.x(), bounds.bottom(), rcorner);
attributes_round_.emplace_back(bounds.right(), bounds.bottom(), 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);
}
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]) {
// 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()) {
// Use two triangles to draw the intersection.
const RCorner& rcorner = rrect_outer[o].rcorner;
uint16_t vert = static_cast<uint16_t>(attributes_round_.size());
attributes_round_.emplace_back(rect.x(), rect.y(), rcorner);
attributes_round_.emplace_back(rect.right(), rect.y(), rcorner);
attributes_round_.emplace_back(rect.x(), rect.bottom(), rcorner);
attributes_round_.emplace_back(rect.right(), rect.bottom(), 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);
}
}
}
graphics_state->ReserveVertexData(
attributes_round_.size() * sizeof(attributes_round_[0]));
graphics_state->ReserveVertexIndices(indices_.size());
}
} // namespace egl
} // namespace rasterizer
} // namespace renderer
} // namespace cobalt