| // 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 <cmath> |
| #include <optional> |
| |
| #include "fml/logging.h" |
| #include "impeller/core/formats.h" |
| #include "impeller/core/vertex_buffer.h" |
| #include "impeller/entity/contents/clip_contents.h" |
| #include "impeller/entity/contents/content_context.h" |
| #include "impeller/entity/entity.h" |
| #include "impeller/renderer/render_pass.h" |
| #include "impeller/renderer/vertex_buffer_builder.h" |
| |
| namespace impeller { |
| |
| static Scalar GetShaderClipDepth(const Entity& entity) { |
| // Draw the clip at the max of the clip entity's depth slice, so that other |
| // draw calls with this same depth value will be culled even if they have a |
| // perspective transform. |
| return std::nextafterf(Entity::GetShaderClipDepth(entity.GetClipDepth() + 1), |
| 0.0f); |
| } |
| |
| /******************************************************************************* |
| ******* ClipContents |
| ******************************************************************************/ |
| |
| ClipContents::ClipContents() = default; |
| |
| ClipContents::~ClipContents() = default; |
| |
| void ClipContents::SetGeometry(const Geometry* geometry) { |
| geometry_ = geometry; |
| } |
| |
| void ClipContents::SetClipOperation(Entity::ClipOperation clip_op) { |
| clip_op_ = clip_op; |
| } |
| |
| std::optional<Rect> ClipContents::GetCoverage(const Entity& entity) const { |
| return std::nullopt; |
| }; |
| |
| Contents::ClipCoverage ClipContents::GetClipCoverage( |
| const Entity& entity, |
| const std::optional<Rect>& current_clip_coverage) const { |
| if (!current_clip_coverage.has_value()) { |
| return {.type = ClipCoverage::Type::kAppend, .coverage = std::nullopt}; |
| } |
| switch (clip_op_) { |
| case Entity::ClipOperation::kDifference: |
| // This can be optimized further by considering cases when the bounds of |
| // the current stencil will shrink. |
| return { |
| .type = ClipCoverage::Type::kAppend, // |
| .is_difference_or_non_square = true, // |
| .coverage = current_clip_coverage // |
| }; |
| case Entity::ClipOperation::kIntersect: |
| if (!geometry_) { |
| return {.type = ClipCoverage::Type::kAppend, .coverage = std::nullopt}; |
| } |
| auto coverage = geometry_->GetCoverage(entity.GetTransform()); |
| if (!coverage.has_value() || !current_clip_coverage.has_value()) { |
| return {.type = ClipCoverage::Type::kAppend, .coverage = std::nullopt}; |
| } |
| return { |
| .type = ClipCoverage::Type::kAppend, // |
| .is_difference_or_non_square = !geometry_->IsAxisAlignedRect(), // |
| .coverage = current_clip_coverage->Intersection(coverage.value()), // |
| }; |
| } |
| FML_UNREACHABLE(); |
| } |
| |
| void ClipContents::SetInheritedOpacity(Scalar opacity) {} |
| |
| bool ClipContents::Render(const ContentContext& renderer, |
| const Entity& entity, |
| RenderPass& pass) const { |
| if (!geometry_) { |
| return true; |
| } |
| |
| using VS = ClipPipeline::VertexShader; |
| |
| VS::FrameInfo info; |
| info.depth = GetShaderClipDepth(entity); |
| |
| auto geometry_result = geometry_->GetPositionBuffer(renderer, entity, pass); |
| auto options = OptionsFromPass(pass); |
| options.blend_mode = BlendMode::kDestination; |
| |
| pass.SetStencilReference(0); |
| |
| /// Stencil preparation draw. |
| |
| options.depth_write_enabled = false; |
| options.primitive_type = geometry_result.type; |
| pass.SetVertexBuffer(std::move(geometry_result.vertex_buffer)); |
| switch (geometry_result.mode) { |
| case GeometryResult::Mode::kNonZero: |
| pass.SetCommandLabel("Clip stencil preparation (NonZero)"); |
| options.stencil_mode = |
| ContentContextOptions::StencilMode::kStencilNonZeroFill; |
| break; |
| case GeometryResult::Mode::kEvenOdd: |
| pass.SetCommandLabel("Clip stencil preparation (EvenOdd)"); |
| options.stencil_mode = |
| ContentContextOptions::StencilMode::kStencilEvenOddFill; |
| break; |
| case GeometryResult::Mode::kNormal: |
| case GeometryResult::Mode::kPreventOverdraw: |
| pass.SetCommandLabel("Clip stencil preparation (Increment)"); |
| options.stencil_mode = |
| ContentContextOptions::StencilMode::kOverdrawPreventionIncrement; |
| break; |
| } |
| pass.SetPipeline(renderer.GetClipPipeline(options)); |
| |
| info.mvp = geometry_result.transform; |
| VS::BindFrameInfo(pass, renderer.GetTransientsBuffer().EmplaceUniform(info)); |
| |
| if (!pass.Draw().ok()) { |
| return false; |
| } |
| |
| /// Write depth. |
| |
| options.depth_write_enabled = true; |
| options.primitive_type = PrimitiveType::kTriangleStrip; |
| Rect cover_area; |
| switch (clip_op_) { |
| case Entity::ClipOperation::kIntersect: |
| pass.SetCommandLabel("Intersect Clip"); |
| options.stencil_mode = |
| ContentContextOptions::StencilMode::kCoverCompareInverted; |
| cover_area = Rect::MakeSize(pass.GetRenderTargetSize()); |
| break; |
| case Entity::ClipOperation::kDifference: |
| pass.SetCommandLabel("Difference Clip"); |
| options.stencil_mode = ContentContextOptions::StencilMode::kCoverCompare; |
| std::optional<Rect> maybe_cover_area = |
| geometry_->GetCoverage(entity.GetTransform()); |
| if (!maybe_cover_area.has_value()) { |
| return true; |
| } |
| cover_area = maybe_cover_area.value(); |
| break; |
| } |
| auto points = cover_area.GetPoints(); |
| pass.SetVertexBuffer( |
| CreateVertexBuffer(points, renderer.GetTransientsBuffer())); |
| |
| pass.SetPipeline(renderer.GetClipPipeline(options)); |
| |
| info.mvp = pass.GetOrthographicTransform(); |
| VS::BindFrameInfo(pass, renderer.GetTransientsBuffer().EmplaceUniform(info)); |
| |
| return pass.Draw().ok(); |
| } |
| |
| /******************************************************************************* |
| ******* ClipRestoreContents |
| ******************************************************************************/ |
| |
| ClipRestoreContents::ClipRestoreContents() = default; |
| |
| ClipRestoreContents::~ClipRestoreContents() = default; |
| |
| void ClipRestoreContents::SetRestoreHeight(size_t clip_height) { |
| restore_height_ = clip_height; |
| } |
| |
| size_t ClipRestoreContents::GetRestoreHeight() const { |
| return restore_height_; |
| } |
| |
| void ClipRestoreContents::SetRestoreCoverage( |
| std::optional<Rect> restore_coverage) { |
| restore_coverage_ = restore_coverage; |
| } |
| |
| std::optional<Rect> ClipRestoreContents::GetCoverage( |
| const Entity& entity) const { |
| return std::nullopt; |
| }; |
| |
| Contents::ClipCoverage ClipRestoreContents::GetClipCoverage( |
| const Entity& entity, |
| const std::optional<Rect>& current_clip_coverage) const { |
| return {.type = ClipCoverage::Type::kRestore, .coverage = std::nullopt}; |
| } |
| |
| void ClipRestoreContents::SetInheritedOpacity(Scalar opacity) {} |
| |
| bool ClipRestoreContents::Render(const ContentContext& renderer, |
| const Entity& entity, |
| RenderPass& pass) const { |
| using VS = ClipPipeline::VertexShader; |
| |
| pass.SetCommandLabel("Restore Clip"); |
| auto options = OptionsFromPass(pass); |
| options.blend_mode = BlendMode::kDestination; |
| options.stencil_mode = |
| ContentContextOptions::StencilMode::kOverdrawPreventionRestore; |
| options.primitive_type = PrimitiveType::kTriangleStrip; |
| pass.SetPipeline(renderer.GetClipPipeline(options)); |
| pass.SetStencilReference(0); |
| |
| // Create a rect that covers either the given restore area, or the whole |
| // render target texture. |
| auto ltrb = |
| restore_coverage_.value_or(Rect::MakeSize(pass.GetRenderTargetSize())) |
| .GetLTRB(); |
| |
| std::array<VS::PerVertexData, 4> vertices = { |
| VS::PerVertexData{Point(ltrb[0], ltrb[1])}, |
| VS::PerVertexData{Point(ltrb[2], ltrb[1])}, |
| VS::PerVertexData{Point(ltrb[0], ltrb[3])}, |
| VS::PerVertexData{Point(ltrb[2], ltrb[3])}, |
| }; |
| pass.SetVertexBuffer( |
| CreateVertexBuffer(vertices, renderer.GetTransientsBuffer())); |
| |
| VS::FrameInfo info; |
| info.depth = GetShaderClipDepth(entity); |
| info.mvp = pass.GetOrthographicTransform(); |
| VS::BindFrameInfo(pass, renderer.GetTransientsBuffer().EmplaceUniform(info)); |
| |
| return pass.Draw().ok(); |
| } |
| |
| } // namespace impeller |