blob: c8031c5a53219b25db9460f801fa4d1efb6cb5f9 [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 {
// The blur kernel extends for a limited number of sigmas.
const float kBlurExtentInSigmas = 3.0f;
// The gaussian integral formula used to calculate blur intensity reaches 0
// intensity at a distance of 1.5. To express pixels in terms of input for
// this function, a distance of kBlurExtentInSigmas * |blur_sigma| should equal
// kBlurDistance.
const float kBlurDistance = 1.5f;
void SetSigmaTweak(GLint sigma_tweak_uniform, const math::RectF& spread_rect,
const DrawObject::OptionalRoundedCorners& spread_corners,
float blur_sigma) {
const float kBlurExtentInPixels = kBlurExtentInSigmas * blur_sigma;
// If the blur kernel is larger than the spread rect, then adjust the input
// values to the blur calculations to better match the reference output.
const float kTweakScale = 0.15f;
float sigma_tweak[2] = {
std::max(kBlurExtentInPixels - spread_rect.width(), 0.0f) *
kTweakScale / blur_sigma,
std::max(kBlurExtentInPixels - spread_rect.height(), 0.0f) *
kTweakScale / blur_sigma,
};
if (spread_corners) {
// The shader for blur with rounded rects does not fully factor in
// proximity to rounded corners. For example, given a 15-pixel wide
// spread rect with two neighboring rounded corners with radius 7,
// the center pixels would not include the nearby corners in the
// calculation of distance to the edge -- it would always be 7.
float corner_gap[2] = {
std::min(spread_rect.width() - spread_corners->top_left.horizontal
- spread_corners->top_right.horizontal,
spread_rect.width() - spread_corners->bottom_left.horizontal
- spread_corners->bottom_right.horizontal),
std::min(spread_rect.height() - spread_corners->top_left.vertical
- spread_corners->bottom_left.vertical,
spread_rect.height() - spread_corners->top_right.vertical
- spread_corners->bottom_right.vertical),
};
// This tweak is limited because each edge can have different-sized
// gaps between corners. However, the shader is already pretty large,
// so this tweak will do for now. The situation is only noticeable with
// relatively large blur sigmas.
const float kCornerTweakScale = 0.03f;
sigma_tweak[0] += std::max(kBlurExtentInPixels - corner_gap[0], 0.0f) *
kCornerTweakScale / blur_sigma;
sigma_tweak[1] += std::max(kBlurExtentInPixels - corner_gap[1], 0.0f) *
kCornerTweakScale / blur_sigma;
}
GL_CALL(glUniform2fv(sigma_tweak_uniform, 1, sigma_tweak));
}
} // namespace
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)
: DrawRectShadowSpread(graphics_state, base_state),
spread_rect_(spread_rect),
spread_corners_(spread_corners),
blur_sigma_(blur_sigma),
is_inset_(inset) {
const float kBlurExtentInPixels = kBlurExtentInSigmas * blur_sigma;
if (inset) {
outer_rect_ = base_rect;
outer_corners_ = base_corners;
inner_rect_ = spread_rect;
inner_rect_.Inset(kBlurExtentInPixels, kBlurExtentInPixels);
if (inner_rect_.IsEmpty()) {
inner_rect_.set_origin(spread_rect.CenterPoint());
}
if (spread_corners) {
inner_corners_ = spread_corners->Inset(kBlurExtentInPixels,
kBlurExtentInPixels, kBlurExtentInPixels, kBlurExtentInPixels);
}
} else {
inner_rect_ = base_rect;
inner_corners_ = base_corners;
outer_rect_ = spread_rect;
outer_rect_.Outset(kBlurExtentInPixels, kBlurExtentInPixels);
if (spread_corners) {
outer_corners_ = spread_corners->Inset(-kBlurExtentInPixels,
-kBlurExtentInPixels, -kBlurExtentInPixels, -kBlurExtentInPixels);
}
}
if (base_corners || spread_corners) {
// If rounded rects are specified, then both the base and spread rects
// must have rounded corners.
DCHECK(inner_corners_);
DCHECK(outer_corners_);
DCHECK(spread_corners_);
inner_corners_->Normalize(inner_rect_);
outer_corners_->Normalize(outer_rect_);
spread_corners_->Normalize(spread_rect_);
} else {
// Non-rounded rects specify vertex offset in terms of sigma from the
// center of the spread rect.
offset_scale_ = kBlurDistance / kBlurExtentInPixels;
offset_center_ = spread_rect_.CenterPoint();
}
color_ = GetGLRGBA(color * base_state_.opacity);
}
void DrawRectShadowBlur::ExecuteRasterize(
GraphicsState* graphics_state,
ShaderProgramManager* program_manager) {
// Draw the blurred shadow.
if (inner_corners_) {
ShaderProgram<CommonVertexShader,
ShaderFragmentColorBlurRrects>* program;
program_manager->GetProgram(&program);
graphics_state->UseProgram(program->GetHandle());
SetupShader(program->GetVertexShader(), graphics_state);
float sigma_scale = kBlurDistance / (kBlurExtentInSigmas * blur_sigma_);
GL_CALL(glUniform2f(program->GetFragmentShader().u_sigma_scale(),
sigma_scale, sigma_scale));
SetSigmaTweak(program->GetFragmentShader().u_sigma_tweak(),
spread_rect_, spread_corners_, blur_sigma_);
if (is_inset_) {
// Set the outer rect to be an inclusive scissor, and invert the shadow.
SetRRectUniforms(program->GetFragmentShader().u_scissor_rect(),
program->GetFragmentShader().u_scissor_corners(),
outer_rect_, *outer_corners_, 0.5f);
GL_CALL(glUniform2f(program->GetFragmentShader().u_scale_add(),
-1.0f, 1.0f));
} else {
// Set the inner rect to be an exclusive scissor.
SetRRectUniforms(program->GetFragmentShader().u_scissor_rect(),
program->GetFragmentShader().u_scissor_corners(),
inner_rect_, *inner_corners_, 0.5f);
GL_CALL(glUniform2f(program->GetFragmentShader().u_scale_add(),
1.0f, 0.0f));
}
SetRRectUniforms(program->GetFragmentShader().u_spread_rect(),
program->GetFragmentShader().u_spread_corners(),
spread_rect_, *spread_corners_, 0.0f);
} else {
ShaderProgram<CommonVertexShader,
ShaderFragmentColorBlur>* program;
program_manager->GetProgram(&program);
graphics_state->UseProgram(program->GetHandle());
SetupShader(program->GetVertexShader(), graphics_state);
SetSigmaTweak(program->GetFragmentShader().u_sigma_tweak(),
spread_rect_, spread_corners_, blur_sigma_);
if (is_inset_) {
// Invert the shadow.
GL_CALL(glUniform2f(program->GetFragmentShader().u_scale_add(),
-1.0f, 1.0f));
} else {
// Keep the normal (outset) shadow.
GL_CALL(glUniform2f(program->GetFragmentShader().u_scale_add(),
1.0f, 0.0f));
}
GL_CALL(glUniform2f(program->GetFragmentShader().u_blur_radius(),
spread_rect_.width() * 0.5f * offset_scale_,
spread_rect_.height() * 0.5f * offset_scale_));
}
GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, vertex_count_));
}
base::TypeId DrawRectShadowBlur::GetTypeId() const {
if (inner_corners_) {
return ShaderProgram<CommonVertexShader,
ShaderFragmentColorBlurRrects>::GetTypeId();
} else {
return ShaderProgram<CommonVertexShader,
ShaderFragmentColorBlur>::GetTypeId();
}
}
} // namespace egl
} // namespace rasterizer
} // namespace renderer
} // namespace cobalt