blob: c1140b851ac7ff31b3d5b45a4c27590f8b0f332f [file] [log] [blame]
// 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/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.GetNewClipDepth() + 1), 0.0f);
}
/*******************************************************************************
******* ClipContents
******************************************************************************/
ClipContents::ClipContents() = default;
ClipContents::~ClipContents() = default;
void ClipContents::SetGeometry(const std::shared_ptr<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,
.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,
.coverage = current_clip_coverage->Intersection(coverage.value()),
};
}
FML_UNREACHABLE();
}
bool ClipContents::ShouldRender(const Entity& entity,
const std::optional<Rect> clip_coverage) const {
return true;
}
bool ClipContents::CanInheritOpacity(const Entity& entity) const {
return true;
}
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;
if (clip_op_ == Entity::ClipOperation::kIntersect &&
geometry_->IsAxisAlignedRect() &&
entity.GetTransform().IsTranslationScaleOnly()) {
std::optional<Rect> coverage =
geometry_->GetCoverage(entity.GetTransform());
if (coverage.has_value() &&
coverage->Contains(Rect::MakeSize(pass.GetRenderTargetSize()))) {
// Skip axis-aligned intersect clips that cover the whole render target
// since they won't draw anything to the depth buffer.
return true;
}
}
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::kLegacyClipIncrement;
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();
auto vertices =
VertexBufferBuilder<VS::PerVertexData>{}
.AddVertices({{points[0]}, {points[1]}, {points[2]}, {points[3]}})
.CreateVertexBuffer(renderer.GetTransientsBuffer());
pass.SetVertexBuffer(std::move(vertices));
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::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};
}
bool ClipRestoreContents::ShouldRender(
const Entity& entity,
const std::optional<Rect> clip_coverage) const {
return true;
}
bool ClipRestoreContents::CanInheritOpacity(const Entity& entity) const {
return true;
}
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::kLegacyClipRestore;
options.primitive_type = PrimitiveType::kTriangleStrip;
pass.SetPipeline(renderer.GetClipPipeline(options));
pass.SetStencilReference(entity.GetClipDepth());
// 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();
VertexBufferBuilder<VS::PerVertexData> vtx_builder;
vtx_builder.AddVertices({
{Point(ltrb[0], ltrb[1])},
{Point(ltrb[2], ltrb[1])},
{Point(ltrb[0], ltrb[3])},
{Point(ltrb[2], ltrb[3])},
});
pass.SetVertexBuffer(
vtx_builder.CreateVertexBuffer(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