| // 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_radial_gradient.h" |
| |
| #include <GLES2/gl2.h> |
| #include <algorithm> |
| |
| #include "base/logging.h" |
| #include "cobalt/renderer/backend/egl/utils.h" |
| #include "egl/generated_shader_impl.h" |
| #include "starboard/memory.h" |
| |
| namespace cobalt { |
| namespace renderer { |
| namespace rasterizer { |
| namespace egl { |
| |
| namespace { |
| const int kVertexCount = 4; |
| } // namespace |
| |
| DrawRectRadialGradient::DrawRectRadialGradient(GraphicsState* graphics_state, |
| const BaseState& base_state, const math::RectF& rect, |
| const render_tree::RadialGradientBrush& brush, |
| const GetScratchTextureFunction& get_scratch_texture) |
| : DrawObject(base_state), |
| lookup_texture_(nullptr), |
| vertex_buffer_(nullptr) { |
| // Calculate the number of pixels needed for the color lookup texture. There |
| // should be enough so that each color stop has a pixel with the color |
| // blended at 100%. However, since the lookup texture will be used with |
| // linear filtering, reducing the texture size will only impact accuracy at |
| // the color stops (but not between them). |
| const float kLookupSizes[] = { |
| // These represent breakpoints that are likely to be used. |
| 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 8.0f, 10.0f, 20.0f, 50.0f, 100.0f, |
| }; |
| const render_tree::ColorStopList& color_stops = brush.color_stops(); |
| |
| int lookup_size_index = 0; |
| for (int i = 0; i < color_stops.size();) { |
| float scaled = color_stops[i].position * kLookupSizes[lookup_size_index]; |
| float fraction = scaled - std::floor(scaled); |
| if (fraction > 0.001f && lookup_size_index < arraysize(kLookupSizes) - 1) { |
| ++lookup_size_index; |
| i = 0; |
| } else { |
| ++i; |
| } |
| } |
| |
| // Create and initialize the lookup texture if needed. Reserve an additional |
| // pixel to represent color stop position 1.0 blended to 100%. |
| float lookup_size = kLookupSizes[lookup_size_index] + 1.0f; |
| TextureInfo texture_info; |
| get_scratch_texture.Run(lookup_size, &texture_info); |
| lookup_texture_ = texture_info.texture; |
| lookup_region_ = texture_info.region; |
| |
| // Add the geometry if a lookup texture was available. |
| if (lookup_texture_) { |
| if (texture_info.is_new) { |
| InitializeLookupTexture(brush); |
| } |
| |
| attributes_.reserve(kVertexCount); |
| math::PointF offset_center(brush.center()); |
| math::PointF offset_scale(1.0f / std::max(brush.radius_x(), 0.001f), |
| 1.0f / std::max(brush.radius_y(), 0.001f)); |
| AddVertex(rect.x(), rect.y(), offset_center, offset_scale); |
| AddVertex(rect.right(), rect.y(), offset_center, offset_scale); |
| AddVertex(rect.right(), rect.bottom(), offset_center, offset_scale); |
| AddVertex(rect.x(), rect.bottom(), offset_center, offset_scale); |
| graphics_state->ReserveVertexData( |
| attributes_.size() * sizeof(VertexAttributes)); |
| } |
| } |
| |
| void DrawRectRadialGradient::ExecuteUpdateVertexBuffer( |
| GraphicsState* graphics_state, |
| ShaderProgramManager* program_manager) { |
| if (attributes_.size() > 0) { |
| vertex_buffer_ = graphics_state->AllocateVertexData( |
| attributes_.size() * sizeof(VertexAttributes)); |
| SbMemoryCopy(vertex_buffer_, &attributes_[0], |
| attributes_.size() * sizeof(VertexAttributes)); |
| } |
| } |
| |
| void DrawRectRadialGradient::ExecuteRasterize( |
| GraphicsState* graphics_state, |
| ShaderProgramManager* program_manager) { |
| if (attributes_.size() > 0) { |
| ShaderProgram<ShaderVertexOffset, |
| ShaderFragmentOpacityTexcoord1d>* program; |
| program_manager->GetProgram(&program); |
| graphics_state->UseProgram(program->GetHandle()); |
| graphics_state->UpdateClipAdjustment( |
| program->GetVertexShader().u_clip_adjustment()); |
| graphics_state->UpdateTransformMatrix( |
| program->GetVertexShader().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( |
| program->GetVertexShader().a_position(), 2, GL_FLOAT, GL_FALSE, |
| sizeof(VertexAttributes), vertex_buffer_ + |
| offsetof(VertexAttributes, position)); |
| graphics_state->VertexAttribPointer( |
| program->GetVertexShader().a_offset(), 2, GL_FLOAT, GL_FALSE, |
| sizeof(VertexAttributes), vertex_buffer_ + |
| offsetof(VertexAttributes, offset)); |
| graphics_state->VertexAttribFinish(); |
| |
| // Map radial length [0, 1] to texture coordinates for the lookup texture. |
| // |u_texcoord_transform| represents (u-scale, u-add, u-max, v-center). |
| const float kTextureWidthScale = 1.0f / lookup_texture_->GetSize().width(); |
| GL_CALL(glUniform4f(program->GetFragmentShader().u_texcoord_transform(), |
| (lookup_region_.width() - 1.0f) * kTextureWidthScale, |
| (lookup_region_.x() + 0.5f) * kTextureWidthScale, |
| (lookup_region_.right() - 0.5f) * kTextureWidthScale, |
| (lookup_region_.y() + 0.5f) / lookup_texture_->GetSize().height())); |
| GL_CALL(glUniform1f(program->GetFragmentShader().u_opacity(), |
| base_state_.opacity)); |
| graphics_state->ActiveBindTexture( |
| program->GetFragmentShader().u_texture_texunit(), |
| lookup_texture_->GetTarget(), lookup_texture_->gl_handle()); |
| GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, attributes_.size())); |
| } |
| } |
| |
| base::TypeId DrawRectRadialGradient::GetTypeId() const { |
| return ShaderProgram<ShaderVertexColorOffset, |
| ShaderFragmentOpacityTexcoord1d>::GetTypeId(); |
| } |
| |
| void DrawRectRadialGradient::InitializeLookupTexture( |
| const render_tree::RadialGradientBrush& brush) { |
| const render_tree::ColorStopList& color_stops = brush.color_stops(); |
| |
| // The lookup texture is a row of RGBA pixels. Ensure the last pixel contains |
| // the last color stop blended to 100%. |
| // NOTE: The base_state_.opacity should not be baked into the lookup texture |
| // as opacity can be animated. |
| const float kTexelToStop = 1.0f / (lookup_region_.width() - 1.0f); |
| const float kStopToTexel = lookup_region_.width() - 1.0f; |
| uint8_t* lookup_buffer = |
| new uint8_t[static_cast<int>(lookup_region_.width()) * 4]; |
| |
| size_t color_index = 0; |
| float position_prev = 0.0f; |
| float position_next = color_stops[color_index].position * kStopToTexel; |
| float position_scale = 1.0f / std::max(position_next - position_prev, 1.0f); |
| render_tree::ColorRGBA color_prev = |
| GetDrawColor(color_stops[color_index].color); |
| render_tree::ColorRGBA color_next = color_prev; |
| uint8_t* pixel = lookup_buffer; |
| |
| // Fill in the blended colors according to the color stops. |
| for (float texel = 0.0f; texel < lookup_region_.width(); texel += 1.0f) { |
| while (texel > position_next) { |
| position_prev = position_next; |
| color_prev = color_next; |
| if (color_index + 1 < color_stops.size()) { |
| // Advance to the next color stop. |
| ++color_index; |
| position_next = color_stops[color_index].position * kStopToTexel; |
| color_next = GetDrawColor(color_stops[color_index].color); |
| } else { |
| // Persist the current color stop to the end. |
| position_next = lookup_region_.width(); |
| } |
| position_scale = 1.0f / std::max(position_next - position_prev, 1.0f); |
| } |
| |
| // Write the blended color to the lookup texture buffer. |
| float blend_ratio = (texel - position_prev) * position_scale; |
| render_tree::ColorRGBA color = color_prev * (1.0f - blend_ratio) + |
| color_next * blend_ratio; |
| pixel[0] = color.rgb8_r(); |
| pixel[1] = color.rgb8_g(); |
| pixel[2] = color.rgb8_b(); |
| pixel[3] = color.rgb8_a(); |
| pixel += 4; |
| } |
| |
| // Update the lookup texture. |
| DCHECK_EQ(lookup_texture_->GetFormat(), GL_RGBA); |
| GL_CALL(glBindTexture(lookup_texture_->GetTarget(), |
| lookup_texture_->gl_handle())); |
| GL_CALL(glTexSubImage2D(lookup_texture_->GetTarget(), 0, |
| static_cast<GLint>(lookup_region_.x()), |
| static_cast<GLint>(lookup_region_.y()), |
| static_cast<GLsizei>(lookup_region_.width()), 1, |
| lookup_texture_->GetFormat(), GL_UNSIGNED_BYTE, |
| lookup_buffer)); |
| GL_CALL(glBindTexture(lookup_texture_->GetTarget(), 0)); |
| |
| delete[] lookup_buffer; |
| } |
| |
| void DrawRectRadialGradient::AddVertex(float x, float y, |
| const math::PointF& offset_center, const math::PointF& offset_scale) { |
| VertexAttributes attributes = { |
| { x, y }, |
| { (x - offset_center.x()) * offset_scale.x(), |
| (y - offset_center.y()) * offset_scale.y() }, |
| }; |
| attributes_.push_back(attributes); |
| } |
| |
| } // namespace egl |
| } // namespace rasterizer |
| } // namespace renderer |
| } // namespace cobalt |