| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "linear_gradient_contents.h" |
| |
| #include "impeller/core/formats.h" |
| #include "impeller/entity/contents/content_context.h" |
| #include "impeller/entity/contents/gradient_generator.h" |
| #include "impeller/entity/entity.h" |
| #include "impeller/entity/geometry/geometry.h" |
| #include "impeller/geometry/scalar.h" |
| #include "impeller/renderer/render_pass.h" |
| #include "impeller/renderer/vertex_buffer_builder.h" |
| |
| namespace impeller { |
| |
| LinearGradientContents::LinearGradientContents() = default; |
| |
| LinearGradientContents::~LinearGradientContents() = default; |
| |
| void LinearGradientContents::SetEndPoints(Point start_point, Point end_point) { |
| start_point_ = start_point; |
| end_point_ = end_point; |
| } |
| |
| void LinearGradientContents::SetColors(std::vector<Color> colors) { |
| colors_ = std::move(colors); |
| } |
| |
| void LinearGradientContents::SetStops(std::vector<Scalar> stops) { |
| stops_ = std::move(stops); |
| } |
| |
| const std::vector<Color>& LinearGradientContents::GetColors() const { |
| return colors_; |
| } |
| |
| const std::vector<Scalar>& LinearGradientContents::GetStops() const { |
| return stops_; |
| } |
| |
| void LinearGradientContents::SetTileMode(Entity::TileMode tile_mode) { |
| tile_mode_ = tile_mode; |
| } |
| |
| bool LinearGradientContents::IsOpaque(const Matrix& transform) const { |
| if (GetOpacityFactor() < 1 || tile_mode_ == Entity::TileMode::kDecal) { |
| return false; |
| } |
| for (auto color : colors_) { |
| if (!color.IsOpaque()) { |
| return false; |
| } |
| } |
| return !AppliesAlphaForStrokeCoverage(transform); |
| } |
| |
| bool LinearGradientContents::CanApplyFastGradient() const { |
| if (!GetInverseEffectTransform().IsIdentity()) { |
| return false; |
| } |
| std::optional<Rect> maybe_rect = GetGeometry()->GetCoverage(Matrix()); |
| if (!maybe_rect.has_value()) { |
| return false; |
| } |
| Rect rect = maybe_rect.value(); |
| |
| if (ScalarNearlyEqual(start_point_.x, end_point_.x)) { |
| // Sort start and end to make on-rect comparisons easier. |
| Point start = (start_point_.y < end_point_.y) ? start_point_ : end_point_; |
| Point end = (start_point_.y < end_point_.y) ? end_point_ : start_point_; |
| // The exact x positon doesn't matter for a vertical gradient, but the y |
| // position must be nearly on the rectangle. |
| if (ScalarNearlyEqual(start.y, rect.GetTop()) && |
| ScalarNearlyEqual(end.y, rect.GetBottom())) { |
| return true; |
| } |
| return false; |
| } |
| |
| if (ScalarNearlyEqual(start_point_.y, end_point_.y)) { |
| // Sort start and end to make on-rect comparisons easier. |
| Point start = (start_point_.x < end_point_.x) ? start_point_ : end_point_; |
| Point end = (start_point_.x < end_point_.x) ? end_point_ : start_point_; |
| // The exact y positon doesn't matter for a horizontal gradient, but the x |
| // position must be nearly on the rectangle. |
| if (ScalarNearlyEqual(start.x, rect.GetLeft()) && |
| ScalarNearlyEqual(end.x, rect.GetRight())) { |
| return true; |
| } |
| return false; |
| } |
| |
| return false; |
| } |
| |
| // A much faster (in terms of ALU) linear gradient that uses vertex |
| // interpolation to perform all color computation. Requires that the geometry of |
| // the gradient is divided into regions based on the stop values. |
| // Currently restricted to rect geometry where the start and end points are |
| // perfectly horizontal/vertical, but could easily be expanded to StC cases |
| // provided that the start/end are on or outside of the coverage rect. |
| bool LinearGradientContents::FastLinearGradient(const ContentContext& renderer, |
| const Entity& entity, |
| RenderPass& pass) const { |
| using VS = FastGradientPipeline::VertexShader; |
| using FS = FastGradientPipeline::FragmentShader; |
| |
| const Geometry* geometry = GetGeometry(); |
| bool force_stencil = !geometry->IsAxisAlignedRect(); |
| |
| auto geom_callback = [&](const ContentContext& renderer, const Entity& entity, |
| RenderPass& pass, |
| const Geometry* geometry) -> GeometryResult { |
| // We already know this is an axis aligned rectangle, so the coverage will |
| // be approximately the same as the geometry. For non axis-algined |
| // rectangles, we can force stencil then cover (not done here). We give an |
| // identity transform to avoid double transforming the gradient. |
| std::optional<Rect> maybe_rect = geometry->GetCoverage(Matrix()); |
| if (!maybe_rect.has_value()) { |
| return {}; |
| } |
| Rect rect = maybe_rect.value(); |
| bool horizontal_axis = start_point_.y == end_point_.y; |
| |
| // Compute the locations of each breakpoint along the primary axis, then |
| // create a rectangle that joins each segment. There will be two triangles |
| // between each pair of points. |
| VertexBufferBuilder<VS::PerVertexData> vtx_builder; |
| vtx_builder.Reserve(6 * (stops_.size() - 1)); |
| Point prev = start_point_; |
| for (auto i = 1u; i < stops_.size(); i++) { |
| Scalar t = stops_[i]; |
| Point current = (1.0 - t) * start_point_ + t * end_point_; |
| Rect section = horizontal_axis |
| ? Rect::MakeXYWH(prev.x, rect.GetY(), |
| current.x - prev.x, rect.GetHeight()) |
| |
| : Rect::MakeXYWH(rect.GetX(), prev.y, rect.GetWidth(), |
| current.y - prev.y); |
| vtx_builder.AddVertices({ |
| {section.GetLeftTop(), colors_[i - 1]}, |
| {section.GetRightTop(), |
| horizontal_axis ? colors_[i] : colors_[i - 1]}, |
| {section.GetLeftBottom(), |
| horizontal_axis ? colors_[i - 1] : colors_[i]}, |
| {section.GetRightTop(), |
| horizontal_axis ? colors_[i] : colors_[i - 1]}, |
| {section.GetLeftBottom(), |
| horizontal_axis ? colors_[i - 1] : colors_[i]}, |
| {section.GetRightBottom(), colors_[i]}, |
| }); |
| prev = current; |
| } |
| return GeometryResult{ |
| .type = PrimitiveType::kTriangle, |
| .vertex_buffer = |
| vtx_builder.CreateVertexBuffer(renderer.GetTransientsBuffer()), |
| .transform = entity.GetShaderTransform(pass), |
| }; |
| }; |
| |
| pass.SetLabel("LinearGradient"); |
| |
| VS::FrameInfo frame_info; |
| |
| PipelineBuilderCallback pipeline_callback = |
| [&renderer](ContentContextOptions options) { |
| return renderer.GetFastGradientPipeline(options); |
| }; |
| return ColorSourceContents::DrawGeometry<VS>( |
| renderer, entity, pass, pipeline_callback, frame_info, |
| [this, &renderer, &entity](RenderPass& pass) { |
| auto& host_buffer = renderer.GetTransientsBuffer(); |
| |
| FS::FragInfo frag_info; |
| frag_info.alpha = |
| GetOpacityFactor() * |
| GetGeometry()->ComputeAlphaCoverage(entity.GetTransform()); |
| |
| FS::BindFragInfo(pass, host_buffer.EmplaceUniform(frag_info)); |
| |
| return true; |
| }, |
| /*force_stencil=*/force_stencil, geom_callback); |
| } |
| |
| bool LinearGradientContents::Render(const ContentContext& renderer, |
| const Entity& entity, |
| RenderPass& pass) const { |
| // TODO(148651): The fast path is overly restrictive, following the design in |
| // https://github.com/flutter/flutter/issues/148651 support for more cases can |
| // be gradually added. |
| if (CanApplyFastGradient()) { |
| return FastLinearGradient(renderer, entity, pass); |
| } |
| if (renderer.GetDeviceCapabilities().SupportsSSBO()) { |
| return RenderSSBO(renderer, entity, pass); |
| } |
| return RenderTexture(renderer, entity, pass); |
| } |
| |
| bool LinearGradientContents::RenderTexture(const ContentContext& renderer, |
| const Entity& entity, |
| RenderPass& pass) const { |
| using VS = LinearGradientFillPipeline::VertexShader; |
| using FS = LinearGradientFillPipeline::FragmentShader; |
| |
| VS::FrameInfo frame_info; |
| frame_info.matrix = GetInverseEffectTransform(); |
| |
| PipelineBuilderCallback pipeline_callback = |
| [&renderer](ContentContextOptions options) { |
| return renderer.GetLinearGradientFillPipeline(options); |
| }; |
| return ColorSourceContents::DrawGeometry<VS>( |
| renderer, entity, pass, pipeline_callback, frame_info, |
| [this, &renderer, &entity](RenderPass& pass) { |
| auto gradient_data = CreateGradientBuffer(colors_, stops_); |
| auto gradient_texture = |
| CreateGradientTexture(gradient_data, renderer.GetContext()); |
| if (gradient_texture == nullptr) { |
| return false; |
| } |
| |
| FS::FragInfo frag_info; |
| frag_info.start_point = start_point_; |
| frag_info.end_point = end_point_; |
| frag_info.tile_mode = static_cast<Scalar>(tile_mode_); |
| frag_info.decal_border_color = decal_border_color_; |
| frag_info.texture_sampler_y_coord_scale = |
| gradient_texture->GetYCoordScale(); |
| frag_info.alpha = |
| GetOpacityFactor() * |
| GetGeometry()->ComputeAlphaCoverage(entity.GetTransform()); |
| ; |
| frag_info.half_texel = |
| Vector2(0.5 / gradient_texture->GetSize().width, |
| 0.5 / gradient_texture->GetSize().height); |
| |
| pass.SetCommandLabel("LinearGradientFill"); |
| |
| SamplerDescriptor sampler_desc; |
| sampler_desc.min_filter = MinMagFilter::kLinear; |
| sampler_desc.mag_filter = MinMagFilter::kLinear; |
| |
| FS::BindTextureSampler( |
| pass, std::move(gradient_texture), |
| renderer.GetContext()->GetSamplerLibrary()->GetSampler( |
| sampler_desc)); |
| FS::BindFragInfo( |
| pass, renderer.GetTransientsBuffer().EmplaceUniform(frag_info)); |
| return true; |
| }); |
| } |
| |
| namespace { |
| Scalar CalculateInverseDotStartToEnd(Point start_point, Point end_point) { |
| Point start_to_end = end_point - start_point; |
| Scalar dot = |
| (start_to_end.x * start_to_end.x + start_to_end.y * start_to_end.y); |
| return dot == 0.0f ? 0.0f : 1.0f / dot; |
| } |
| } // namespace |
| |
| bool LinearGradientContents::RenderSSBO(const ContentContext& renderer, |
| const Entity& entity, |
| RenderPass& pass) const { |
| using VS = LinearGradientSSBOFillPipeline::VertexShader; |
| using FS = LinearGradientSSBOFillPipeline::FragmentShader; |
| |
| VS::FrameInfo frame_info; |
| frame_info.matrix = GetInverseEffectTransform(); |
| |
| PipelineBuilderCallback pipeline_callback = |
| [&renderer](ContentContextOptions options) { |
| return renderer.GetLinearGradientSSBOFillPipeline(options); |
| }; |
| return ColorSourceContents::DrawGeometry<VS>( |
| renderer, entity, pass, pipeline_callback, frame_info, |
| [this, &renderer, &entity](RenderPass& pass) { |
| FS::FragInfo frag_info; |
| frag_info.start_point = start_point_; |
| frag_info.end_point = end_point_; |
| frag_info.tile_mode = static_cast<Scalar>(tile_mode_); |
| frag_info.decal_border_color = decal_border_color_; |
| frag_info.alpha = |
| GetOpacityFactor() * |
| GetGeometry()->ComputeAlphaCoverage(entity.GetTransform()); |
| frag_info.start_to_end = end_point_ - start_point_; |
| frag_info.inverse_dot_start_to_end = |
| CalculateInverseDotStartToEnd(start_point_, end_point_); |
| |
| auto& host_buffer = renderer.GetTransientsBuffer(); |
| auto colors = CreateGradientColors(colors_, stops_); |
| |
| frag_info.colors_length = colors.size(); |
| auto color_buffer = |
| host_buffer.Emplace(colors.data(), colors.size() * sizeof(StopData), |
| DefaultUniformAlignment()); |
| |
| pass.SetCommandLabel("LinearGradientSSBOFill"); |
| |
| FS::BindFragInfo( |
| pass, renderer.GetTransientsBuffer().EmplaceUniform(frag_info)); |
| FS::BindColorData(pass, color_buffer); |
| |
| return true; |
| }); |
| } |
| |
| bool LinearGradientContents::ApplyColorFilter( |
| const ColorFilterProc& color_filter_proc) { |
| for (Color& color : colors_) { |
| color = color_filter_proc(color); |
| } |
| decal_border_color_ = color_filter_proc(decal_border_color_); |
| return true; |
| } |
| |
| } // namespace impeller |