diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc index 5ea7fb9f5507b..23fd0d4dfafc5 100644 --- a/impeller/aiks/aiks_unittests.cc +++ b/impeller/aiks/aiks_unittests.cc @@ -442,6 +442,7 @@ TEST_F(AiksTest, TransformMultipliesCorrectly) { -3, 0, 0, 0, 0, 0, 0, 0, -500, 400, 0, 1)); + // clang-format on } } // namespace testing diff --git a/impeller/entity/contents/contents.cc b/impeller/entity/contents/contents.cc index 43116365d9e45..4f0ba354c4ca7 100644 --- a/impeller/entity/contents/contents.cc +++ b/impeller/entity/contents/contents.cc @@ -3,8 +3,10 @@ // found in the LICENSE file. #include "impeller/entity/contents/contents.h" +#include #include "impeller/entity/contents/content_context.h" +#include "impeller/renderer/command_buffer.h" #include "impeller/renderer/render_pass.h" namespace impeller { @@ -27,4 +29,79 @@ Contents::Contents() = default; Contents::~Contents() = default; +Rect Contents::GetBounds(const Entity& entity) const { + const auto& transform = entity.GetTransformation(); + auto points = entity.GetPath().GetBoundingBox()->GetPoints(); + for (uint i = 0; i < points.size(); i++) { + points[i] = transform * points[i]; + } + return Rect::MakePointBounds({points.begin(), points.end()}); +} + +std::optional Contents::RenderToTexture( + const ContentContext& renderer, + const Entity& entity) const { + auto bounds = GetBounds(entity); + + auto texture = MakeSubpass( + renderer, ISize(bounds.size), + [&contents = *this, &entity, &bounds](const ContentContext& renderer, + RenderPass& pass) -> bool { + Entity sub_entity; + sub_entity.SetPath(entity.GetPath()); + sub_entity.SetBlendMode(Entity::BlendMode::kSource); + sub_entity.SetTransformation( + Matrix::MakeTranslation(Vector3(-bounds.origin)) * + entity.GetTransformation()); + return contents.Render(renderer, sub_entity, pass); + }); + + if (!texture.has_value()) { + return std::nullopt; + } + + return Snapshot{.texture = texture.value(), .position = bounds.origin}; +} + +using SubpassCallback = std::function; + +std::optional> Contents::MakeSubpass( + const ContentContext& renderer, + ISize texture_size, + SubpassCallback subpass_callback) { + auto context = renderer.GetContext(); + + auto subpass_target = RenderTarget::CreateOffscreen(*context, texture_size); + auto subpass_texture = subpass_target.GetRenderTargetTexture(); + if (!subpass_texture) { + return std::nullopt; + } + + auto sub_command_buffer = context->CreateRenderCommandBuffer(); + sub_command_buffer->SetLabel("Offscreen Contents Command Buffer"); + if (!sub_command_buffer) { + return std::nullopt; + } + + auto sub_renderpass = sub_command_buffer->CreateRenderPass(subpass_target); + if (!sub_renderpass) { + return std::nullopt; + } + sub_renderpass->SetLabel("OffscreenContentsPass"); + + if (!subpass_callback(renderer, *sub_renderpass)) { + return std::nullopt; + } + + if (!sub_renderpass->EncodeCommands(*context->GetTransientsAllocator())) { + return std::nullopt; + } + + if (!sub_command_buffer->SubmitCommands()) { + return std::nullopt; + } + + return subpass_texture; +} + } // namespace impeller diff --git a/impeller/entity/contents/contents.h b/impeller/entity/contents/contents.h index 1f1d08b9290a1..915b89c34c308 100644 --- a/impeller/entity/contents/contents.h +++ b/impeller/entity/contents/contents.h @@ -9,6 +9,8 @@ #include #include "flutter/fml/macros.h" +#include "impeller/geometry/rect.h" +#include "impeller/renderer/texture.h" namespace impeller { @@ -25,6 +27,14 @@ ContentContextOptions OptionsFromPassAndEntity(const RenderPass& pass, class Contents { public: + /// Represents a screen space texture and it's intended draw position. + struct Snapshot { + std::shared_ptr texture; + /// The offset from the origin where this texture is intended to be + /// rendered. + Vector2 position; + }; + Contents(); virtual ~Contents(); @@ -33,6 +43,26 @@ class Contents { const Entity& entity, RenderPass& pass) const = 0; + /// @brief Get the bounding rectangle that this contents modifies in screen + /// space. + virtual Rect GetBounds(const Entity& entity) const; + + /// @brief Render this contents to a texture, respecting the entity's + /// transform, path, stencil depth, blend mode, etc. + /// The result texture size is always the size of `GetBounds(entity)`. + virtual std::optional RenderToTexture( + const ContentContext& renderer, + const Entity& entity) const; + + using SubpassCallback = + std::function; + static std::optional> MakeSubpass( + const ContentContext& renderer, + ISize texture_size, + SubpassCallback subpass_callback); + + protected: + private: FML_DISALLOW_COPY_AND_ASSIGN(Contents); }; diff --git a/impeller/entity/contents/filters/blend_filter_contents.cc b/impeller/entity/contents/filters/blend_filter_contents.cc index 5f1e89c0eb7de..81a048e5a1f35 100644 --- a/impeller/entity/contents/filters/blend_filter_contents.cc +++ b/impeller/entity/contents/filters/blend_filter_contents.cc @@ -20,42 +20,28 @@ using PipelineProc = std::shared_ptr (ContentContext::*)(ContentContextOptions) const; template -static void AdvancedBlendPass(std::shared_ptr input_d, - std::shared_ptr input_s, - std::shared_ptr sampler, - const ContentContext& renderer, - RenderPass& pass, - Command& cmd) {} - -template -static bool AdvancedBlend( - const std::vector>& input_textures, - const ContentContext& renderer, - RenderPass& pass, - PipelineProc pipeline_proc) { +static bool AdvancedBlend(const std::vector& input_textures, + const ContentContext& renderer, + RenderPass& pass, + PipelineProc pipeline_proc) { if (input_textures.size() < 2) { return false; } auto& host_buffer = pass.GetTransientsBuffer(); + auto size = pass.GetRenderTargetSize(); VertexBufferBuilder vtx_builder; vtx_builder.AddVertices({ {Point(0, 0), Point(0, 0)}, - {Point(1, 0), Point(1, 0)}, - {Point(1, 1), Point(1, 1)}, + {Point(size.width, 0), Point(1, 0)}, + {Point(size.width, size.height), Point(1, 1)}, {Point(0, 0), Point(0, 0)}, - {Point(1, 1), Point(1, 1)}, - {Point(0, 1), Point(0, 1)}, + {Point(size.width, size.height), Point(1, 1)}, + {Point(0, size.height), Point(0, 1)}, }); auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer); - typename VS::FrameInfo frame_info; - frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1)); - - auto uniform_view = host_buffer.EmplaceUniform(frame_info); - auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({}); - auto options = OptionsFromPass(pass); options.blend_mode = Entity::BlendMode::kSource; std::shared_ptr pipeline = @@ -65,10 +51,27 @@ static bool AdvancedBlend( cmd.label = "Advanced Blend Filter"; cmd.BindVertices(vtx_buffer); cmd.pipeline = std::move(pipeline); - VS::BindFrameInfo(cmd, uniform_view); - FS::BindTextureSamplerDst(cmd, input_textures[0], sampler); - FS::BindTextureSamplerSrc(cmd, input_textures[1], sampler); + auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({}); + typename VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(size); + + auto dst_snapshot = input_textures[1]; + FS::BindTextureSamplerSrc(cmd, dst_snapshot.texture, sampler); + frame_info.dst_uv_transform = + Matrix::MakeTranslation(-dst_snapshot.position / size) * + Matrix::MakeScale( + Vector3(Size(size) / Size(dst_snapshot.texture->GetSize()))); + + auto src_snapshot = input_textures[0]; + FS::BindTextureSamplerDst(cmd, src_snapshot.texture, sampler); + frame_info.src_uv_transform = + Matrix::MakeTranslation(-src_snapshot.position / size) * + Matrix::MakeScale( + Vector3(Size(size) / Size(src_snapshot.texture->GetSize()))); + + auto uniform_view = host_buffer.EmplaceUniform(frame_info); + VS::BindFrameInfo(cmd, uniform_view); pass.AddCommand(cmd); return true; @@ -88,14 +91,14 @@ void BlendFilterContents::SetBlendMode(Entity::BlendMode blend_mode) { switch (blend_mode) { case Entity::BlendMode::kScreen: - advanced_blend_proc_ = - [](const std::vector>& input_textures, - const ContentContext& renderer, RenderPass& pass) { - PipelineProc p = &ContentContext::GetTextureBlendScreenPipeline; - return AdvancedBlend( - input_textures, renderer, pass, p); - }; + advanced_blend_proc_ = [](const std::vector& input_textures, + const ContentContext& renderer, + RenderPass& pass) { + PipelineProc p = &ContentContext::GetTextureBlendScreenPipeline; + return AdvancedBlend( + input_textures, renderer, pass, p); + }; break; default: FML_UNREACHABLE(); @@ -103,31 +106,27 @@ void BlendFilterContents::SetBlendMode(Entity::BlendMode blend_mode) { } } -static bool BasicBlend( - const std::vector>& input_textures, - const ContentContext& renderer, - RenderPass& pass, - Entity::BlendMode basic_blend) { +static bool BasicBlend(const std::vector& input_textures, + const ContentContext& renderer, + RenderPass& pass, + Entity::BlendMode basic_blend) { using VS = TextureBlendPipeline::VertexShader; using FS = TextureBlendPipeline::FragmentShader; auto& host_buffer = pass.GetTransientsBuffer(); + auto size = pass.GetRenderTargetSize(); VertexBufferBuilder vtx_builder; vtx_builder.AddVertices({ {Point(0, 0), Point(0, 0)}, - {Point(1, 0), Point(1, 0)}, - {Point(1, 1), Point(1, 1)}, + {Point(size.width, 0), Point(1, 0)}, + {Point(size.width, size.height), Point(1, 1)}, {Point(0, 0), Point(0, 0)}, - {Point(1, 1), Point(1, 1)}, - {Point(0, 1), Point(0, 1)}, + {Point(size.width, size.height), Point(1, 1)}, + {Point(0, size.height), Point(0, 1)}, }); auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer); - VS::FrameInfo frame_info; - frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1)); - - auto uniform_view = host_buffer.EmplaceUniform(frame_info); auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({}); // Draw the first texture using kSource. @@ -138,8 +137,19 @@ static bool BasicBlend( auto options = OptionsFromPass(pass); options.blend_mode = Entity::BlendMode::kSource; cmd.pipeline = renderer.GetTextureBlendPipeline(options); - FS::BindTextureSamplerSrc(cmd, input_textures[0], sampler); - VS::BindFrameInfo(cmd, uniform_view); + { + auto input = input_textures[0]; + FS::BindTextureSamplerSrc(cmd, input.texture, sampler); + + VS::FrameInfo frame_info; + frame_info.mvp = + Matrix::MakeOrthographic(size) * + Matrix::MakeTranslation(input.position) * + Matrix::MakeScale(Size(input.texture->GetSize()) / Size(size)); + + auto uniform_view = host_buffer.EmplaceUniform(frame_info); + VS::BindFrameInfo(cmd, uniform_view); + } pass.AddCommand(cmd); if (input_textures.size() < 2) { @@ -153,7 +163,17 @@ static bool BasicBlend( for (auto texture_i = input_textures.begin() + 1; texture_i < input_textures.end(); texture_i++) { - FS::BindTextureSamplerSrc(cmd, *texture_i, sampler); + auto input = *texture_i; + FS::BindTextureSamplerSrc(cmd, input.texture, sampler); + + VS::FrameInfo frame_info; + frame_info.mvp = frame_info.mvp = + Matrix::MakeOrthographic(size) * + Matrix::MakeTranslation(input.position) * + Matrix::MakeScale(Size(input.texture->GetSize()) / Size(size)); + + auto uniform_view = host_buffer.EmplaceUniform(frame_info); + VS::BindFrameInfo(cmd, uniform_view); pass.AddCommand(cmd); } @@ -161,9 +181,10 @@ static bool BasicBlend( } bool BlendFilterContents::RenderFilter( - const std::vector>& input_textures, + const std::vector& input_textures, const ContentContext& renderer, - RenderPass& pass) const { + RenderPass& pass, + const Matrix& transform) const { if (input_textures.empty()) { return true; } diff --git a/impeller/entity/contents/filters/blend_filter_contents.h b/impeller/entity/contents/filters/blend_filter_contents.h index 442cc54b3b05e..2346037f7b282 100644 --- a/impeller/entity/contents/filters/blend_filter_contents.h +++ b/impeller/entity/contents/filters/blend_filter_contents.h @@ -10,10 +10,10 @@ namespace impeller { class BlendFilterContents : public FilterContents { public: - using AdvancedBlendProc = std::function>& input_textures, - const ContentContext& renderer, - RenderPass& pass)>; + using AdvancedBlendProc = + std::function& input_textures, + const ContentContext& renderer, + RenderPass& pass)>; BlendFilterContents(); @@ -23,9 +23,10 @@ class BlendFilterContents : public FilterContents { private: // |FilterContents| - bool RenderFilter(const std::vector>& input_textures, + bool RenderFilter(const std::vector& input_textures, const ContentContext& renderer, - RenderPass& pass) const override; + RenderPass& pass, + const Matrix& transform) const override; Entity::BlendMode blend_mode_; AdvancedBlendProc advanced_blend_proc_; diff --git a/impeller/entity/contents/filters/filter_contents.cc b/impeller/entity/contents/filters/filter_contents.cc index 033fdeed3183e..b799fc2ae4be2 100644 --- a/impeller/entity/contents/filters/filter_contents.cc +++ b/impeller/entity/contents/filters/filter_contents.cc @@ -6,7 +6,10 @@ #include #include +#include +#include #include +#include #include "flutter/fml/logging.h" #include "impeller/base/validation.h" @@ -48,7 +51,9 @@ std::shared_ptr FilterContents::MakeBlend( new_blend->SetBlendMode(blend_mode); blend = new_blend; } - return std::get>(blend); + auto contents = std::get>(blend); + // This downcast is safe because we know blend is a BlendFilterContents. + return std::static_pointer_cast(contents); } FML_UNREACHABLE(); @@ -56,24 +61,19 @@ std::shared_ptr FilterContents::MakeBlend( std::shared_ptr FilterContents::MakeDirectionalGaussianBlur( InputVariant input_texture, - Scalar radius, - Vector2 direction, - bool clip_border) { + Vector2 blur_vector) { auto blur = std::make_shared(); blur->SetInputTextures({input_texture}); - blur->SetRadius(radius); - blur->SetDirection(direction); - blur->SetClipBorder(clip_border); + blur->SetBlurVector(blur_vector); return blur; } std::shared_ptr FilterContents::MakeGaussianBlur( InputVariant input_texture, - Scalar radius, - bool clip_border) { - auto x_blur = MakeDirectionalGaussianBlur(input_texture, radius, Point(1, 0), - clip_border); - return MakeDirectionalGaussianBlur(x_blur, radius, Point(0, 1), false); + Scalar sigma_x, + Scalar sigma_y) { + auto x_blur = MakeDirectionalGaussianBlur(input_texture, Point(sigma_x, 0)); + return MakeDirectionalGaussianBlur(x_blur, Point(0, sigma_y)); } FilterContents::FilterContents() = default; @@ -89,106 +89,139 @@ bool FilterContents::Render(const ContentContext& renderer, RenderPass& pass) const { // Run the filter. - auto maybe_texture = RenderFilterToTexture(renderer, entity, pass); - if (!maybe_texture.has_value()) { + auto maybe_snapshot = RenderToTexture(renderer, entity); + if (!maybe_snapshot.has_value()) { return false; } - auto& texture = maybe_texture.value(); + auto& snapshot = maybe_snapshot.value(); - // Draw the resulting texture to the given destination rect, respecting the - // transform and clip stack. + // Draw the result texture, respecting the transform and clip stack. auto contents = std::make_shared(); - contents->SetTexture(texture); - contents->SetSourceRect(IRect::MakeSize(texture->GetSize())); - - return contents->Render(renderer, entity, pass); + contents->SetTexture(snapshot.texture); + contents->SetSourceRect(IRect::MakeSize(snapshot.texture->GetSize())); + + Entity e; + e.SetPath(PathBuilder{}.AddRect(GetBounds(entity)).GetCurrentPath()); + e.SetBlendMode(entity.GetBlendMode()); + e.SetStencilDepth(entity.GetStencilDepth()); + return contents->Render(renderer, e, pass); } -std::optional> FilterContents::RenderFilterToTexture( - const ContentContext& renderer, - const Entity& entity, - RenderPass& pass) const { - auto output_size = GetOutputSize(input_textures_); - if (output_size.IsZero()) { - return std::nullopt; +Rect FilterContents::GetBoundsForInput(const Entity& entity, + const InputVariant& input) { + if (auto contents = std::get_if>(&input)) { + return contents->get()->GetBounds(entity); } - // Resolve all inputs as textures. + if (auto texture = std::get_if>(&input)) { + auto points = entity.GetPath().GetBoundingBox()->GetPoints(); - std::vector> input_textures; - input_textures.reserve(input_textures_.size()); - for (const auto& input : input_textures_) { - if (auto filter = std::get_if>(&input)) { - auto texture = - filter->get()->RenderFilterToTexture(renderer, entity, pass); - if (!texture.has_value()) { - return std::nullopt; - } - input_textures.push_back(std::move(texture.value())); - } else if (auto texture = std::get_if>(&input)) { - input_textures.push_back(*texture); - } else { - FML_UNREACHABLE(); + const auto& transform = entity.GetTransformation(); + for (uint i = 0; i < points.size(); i++) { + points[i] = transform * points[i]; } + return Rect::MakePointBounds({points.begin(), points.end()}); } - // Create a new texture and render the filter to it. + FML_UNREACHABLE(); +} - auto context = renderer.GetContext(); +Rect FilterContents::GetBounds(const Entity& entity) const { + // The default bounds of FilterContents is just the union of its inputs. + // FilterContents implementations may choose to increase the bounds in any + // direction, but it should never - auto subpass_target = RenderTarget::CreateOffscreen(*context, output_size); - auto subpass_texture = subpass_target.GetRenderTargetTexture(); - if (!subpass_texture) { - return std::nullopt; + if (input_textures_.empty()) { + return Rect(); } - auto sub_command_buffer = context->CreateRenderCommandBuffer(); - sub_command_buffer->SetLabel("Offscreen Filter Command Buffer"); - if (!sub_command_buffer) { - return std::nullopt; + Rect result = GetBoundsForInput(entity, input_textures_.front()); + for (auto input_i = input_textures_.begin() + 1; + input_i < input_textures_.end(); input_i++) { + result.Union(GetBoundsForInput(entity, *input_i)); } - auto sub_renderpass = sub_command_buffer->CreateRenderPass(subpass_target); - if (!sub_renderpass) { - return std::nullopt; - } - sub_renderpass->SetLabel("OffscreenFilterPass"); + return result; +} - if (!RenderFilter(input_textures, renderer, *sub_renderpass)) { - return std::nullopt; +static std::optional ResolveSnapshotForInput( + const ContentContext& renderer, + const Entity& entity, + FilterContents::InputVariant input) { + if (auto contents = std::get_if>(&input)) { + return contents->get()->RenderToTexture(renderer, entity); } - if (!sub_renderpass->EncodeCommands(*context->GetTransientsAllocator())) { - return std::nullopt; - } + if (auto input_texture = std::get_if>(&input)) { + auto input_bounds = FilterContents::GetBoundsForInput(entity, input); + // If the input is a texture, render the version of it which is transformed. + auto texture = Contents::MakeSubpass( + renderer, ISize(input_bounds.size), + [texture = *input_texture, entity, input_bounds]( + const ContentContext& renderer, RenderPass& pass) -> bool { + TextureContents contents; + contents.SetTexture(texture); + contents.SetSourceRect(IRect::MakeSize(texture->GetSize())); + Entity sub_entity; + sub_entity.SetPath(entity.GetPath()); + sub_entity.SetBlendMode(Entity::BlendMode::kSource); + sub_entity.SetTransformation( + Matrix::MakeTranslation(Vector3(-input_bounds.origin)) * + entity.GetTransformation()); + return contents.Render(renderer, sub_entity, pass); + }); + if (!texture.has_value()) { + return std::nullopt; + } - if (!sub_command_buffer->SubmitCommands()) { - return std::nullopt; + return Contents::Snapshot{.texture = texture.value(), + .position = input_bounds.origin}; } - return subpass_texture; + FML_UNREACHABLE(); } -ISize FilterContents::GetOutputSize() const { - if (input_textures_.empty()) { - return {}; +std::optional FilterContents::RenderToTexture( + const ContentContext& renderer, + const Entity& entity) const { + auto bounds = GetBounds(entity); + if (bounds.IsZero()) { + return std::nullopt; } - return GetOutputSize(input_textures_); -} -ISize FilterContents::GetOutputSize(const InputTextures& input_textures) const { - if (auto filter = - std::get_if>(&input_textures[0])) { - return filter->get()->GetOutputSize(input_textures); + // Resolve all inputs as textures. + + std::vector input_textures; + + input_textures.reserve(input_textures_.size()); + for (const auto& input : input_textures_) { + auto texture_and_offset = ResolveSnapshotForInput(renderer, entity, input); + if (!texture_and_offset.has_value()) { + continue; + } + + // Make the position of all input snapshots relative to this filter's + // snapshot position. + texture_and_offset->position -= bounds.origin; + + input_textures.push_back(texture_and_offset.value()); } - if (auto texture = - std::get_if>(&input_textures[0])) { - return texture->get()->GetSize(); + // Create a new texture and render the filter to it. + + auto texture = MakeSubpass( + renderer, ISize(GetBounds(entity).size), + [=](const ContentContext& renderer, RenderPass& pass) -> bool { + return RenderFilter(input_textures, renderer, pass, + entity.GetTransformation()); + }); + + if (!texture.has_value()) { + return std::nullopt; } - FML_UNREACHABLE(); + return Snapshot{.texture = texture.value(), .position = bounds.origin}; } } // namespace impeller diff --git a/impeller/entity/contents/filters/filter_contents.h b/impeller/entity/contents/filters/filter_contents.h index 7c5b29ff2858e..fda5b8a7afd78 100644 --- a/impeller/entity/contents/filters/filter_contents.h +++ b/impeller/entity/contents/filters/filter_contents.h @@ -18,7 +18,7 @@ class Pipeline; class FilterContents : public Contents { public: using InputVariant = - std::variant, std::shared_ptr>; + std::variant, std::shared_ptr>; using InputTextures = std::vector; static std::shared_ptr MakeBlend( @@ -27,14 +27,13 @@ class FilterContents : public Contents { static std::shared_ptr MakeDirectionalGaussianBlur( InputVariant input_texture, - Scalar radius, - Vector2 direction, - bool clip_border = false); + Vector2 blur_vector); - static std::shared_ptr MakeGaussianBlur( - InputVariant input_texture, - Scalar radius, - bool clip_border = false); + static std::shared_ptr + MakeGaussianBlur(InputVariant input_texture, Scalar sigma_x, Scalar sigma_y); + + static Rect GetBoundsForInput(const Entity& entity, + const InputVariant& input); FilterContents(); @@ -53,26 +52,21 @@ class FilterContents : public Contents { const Entity& entity, RenderPass& pass) const override; - /// @brief Renders dependency filters, creates a subpass, and calls the - /// `RenderFilter` defined by the subclasses. - std::optional> RenderFilterToTexture( - const ContentContext& renderer, - const Entity& entity, - RenderPass& pass) const; + // |Contents| + Rect GetBounds(const Entity& entity) const override; - /// @brief Fetch the size of the output texture. - ISize GetOutputSize() const; + // |Contents| + virtual std::optional RenderToTexture( + const ContentContext& renderer, + const Entity& entity) const override; private: /// @brief Takes a set of zero or more input textures and writes to an output /// texture. - virtual bool RenderFilter( - const std::vector>& input_textures, - const ContentContext& renderer, - RenderPass& pass) const = 0; - - /// @brief Determines the size of the output texture. - virtual ISize GetOutputSize(const InputTextures& input_textures) const; + virtual bool RenderFilter(const std::vector& input_textures, + const ContentContext& renderer, + RenderPass& pass, + const Matrix& transform) const = 0; InputTextures input_textures_; Rect destination_; diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc index f677f37cf362a..f83b580430179 100644 --- a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc +++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc @@ -4,7 +4,12 @@ #include "impeller/entity/contents/filters/gaussian_blur_filter_contents.h" +#include + #include "impeller/entity/contents/content_context.h" +#include "impeller/entity/contents/filters/filter_contents.h" +#include "impeller/geometry/rect.h" +#include "impeller/geometry/scalar.h" #include "impeller/renderer/render_pass.h" #include "impeller/renderer/sampler_library.h" @@ -16,29 +21,38 @@ DirectionalGaussianBlurFilterContents::DirectionalGaussianBlurFilterContents() = DirectionalGaussianBlurFilterContents:: ~DirectionalGaussianBlurFilterContents() = default; -void DirectionalGaussianBlurFilterContents::SetRadius(Scalar radius) { - radius_ = std::max(radius, 1e-3f); -} - -void DirectionalGaussianBlurFilterContents::SetDirection(Vector2 direction) { - direction_ = direction.Normalize(); -} - -void DirectionalGaussianBlurFilterContents::SetClipBorder(bool clip) { - clip_ = clip; +void DirectionalGaussianBlurFilterContents::SetBlurVector(Vector2 blur_vector) { + if (blur_vector.GetLengthSquared() < kEhCloseEnough) { + blur_vector_ = Vector2(0, kEhCloseEnough); + return; + } + blur_vector_ = blur_vector; } bool DirectionalGaussianBlurFilterContents::RenderFilter( - const std::vector>& input_textures, + const std::vector& input_textures, const ContentContext& renderer, - RenderPass& pass) const { + RenderPass& pass, + const Matrix& transform) const { + if (input_textures.empty()) { + return true; + } + using VS = GaussianBlurPipeline::VertexShader; using FS = GaussianBlurPipeline::FragmentShader; auto& host_buffer = pass.GetTransientsBuffer(); - ISize size = FilterContents::GetOutputSize(); - Point uv_offset = clip_ ? (Point(radius_, radius_) / size) : Point(); + // Because this filter is intended to be used with only one input parameter, + // and GetBounds just increases the input size by a factor of the direction, + // we we can just scale up the UVs by the same amount and don't need to worry + // about mapping the UVs to destination rect (like we do in + // BlendFilterContents). + + auto size = pass.GetRenderTargetSize(); + auto transformed_blur = transform.TransformDirection(blur_vector_); + auto uv_offset = transformed_blur.Abs() / size; + // LTRB Scalar uv[4] = { -uv_offset.x, @@ -59,12 +73,10 @@ bool DirectionalGaussianBlurFilterContents::RenderFilter( auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer); VS::FrameInfo frame_info; - frame_info.mvp = Matrix::MakeOrthographic(size); frame_info.texture_size = Point(size); - frame_info.blur_radius = radius_; - frame_info.blur_direction = direction_; + frame_info.blur_radius = transformed_blur.GetLength(); + frame_info.blur_direction = transformed_blur.Normalize(); - auto uniform_view = host_buffer.EmplaceUniform(frame_info); auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({}); Command cmd; @@ -73,29 +85,26 @@ bool DirectionalGaussianBlurFilterContents::RenderFilter( options.blend_mode = Entity::BlendMode::kSource; cmd.pipeline = renderer.GetGaussianBlurPipeline(options); cmd.BindVertices(vtx_buffer); + + const auto& [texture, _] = input_textures[0]; + FS::BindTextureSampler(cmd, texture, sampler); + + frame_info.mvp = Matrix::MakeOrthographic(size); + auto uniform_view = host_buffer.EmplaceUniform(frame_info); VS::BindFrameInfo(cmd, uniform_view); - for (const auto& texture : input_textures) { - FS::BindTextureSampler(cmd, texture, sampler); - pass.AddCommand(cmd); - } + + pass.AddCommand(cmd); return true; } -ISize DirectionalGaussianBlurFilterContents::GetOutputSize( - const InputTextures& input_textures) const { - ISize size; - if (auto filter = - std::get_if>(&input_textures[0])) { - size = filter->get()->GetOutputSize(); - } else if (auto texture = - std::get_if>(&input_textures[0])) { - size = texture->get()->GetSize(); - } else { - FML_UNREACHABLE(); - } - - return size + (clip_ ? ISize(radius_ * 2, radius_ * 2) : ISize()); +Rect DirectionalGaussianBlurFilterContents::GetBounds( + const Entity& entity) const { + auto bounds = FilterContents::GetBounds(entity); + auto transformed_blur = + entity.GetTransformation().TransformDirection(blur_vector_).Abs(); + auto extent = bounds.size + transformed_blur * 2; + return Rect(bounds.origin - transformed_blur, Size(extent.x, extent.y)); } } // namespace impeller diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents.h b/impeller/entity/contents/filters/gaussian_blur_filter_contents.h index cdf6c470dba7e..ad2e428457416 100644 --- a/impeller/entity/contents/filters/gaussian_blur_filter_contents.h +++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents.h @@ -5,6 +5,7 @@ #pragma once #include "impeller/entity/contents/filters/filter_contents.h" +#include "impeller/geometry/matrix.h" namespace impeller { @@ -14,25 +15,19 @@ class DirectionalGaussianBlurFilterContents final : public FilterContents { ~DirectionalGaussianBlurFilterContents() override; - void SetRadius(Scalar radius); + void SetBlurVector(Vector2 blur_vector); - void SetDirection(Vector2 direction); - - void SetClipBorder(bool clip); + // |Contents| + Rect GetBounds(const Entity& entity) const override; private: // |FilterContents| - bool RenderFilter(const std::vector>& input_textures, + bool RenderFilter(const std::vector& input_textures, const ContentContext& renderer, - RenderPass& pass) const override; - - // |FilterContents| - virtual ISize GetOutputSize( - const InputTextures& input_textures) const override; + RenderPass& pass, + const Matrix& transform) const override; - Scalar radius_ = 0; - Vector2 direction_; - bool clip_ = false; + Vector2 blur_vector_; FML_DISALLOW_COPY_AND_ASSIGN(DirectionalGaussianBlurFilterContents); }; diff --git a/impeller/entity/contents/texture_contents.h b/impeller/entity/contents/texture_contents.h index fd3ff9c13200b..c94be1f39a5ff 100644 --- a/impeller/entity/contents/texture_contents.h +++ b/impeller/entity/contents/texture_contents.h @@ -10,7 +10,6 @@ #include "flutter/fml/macros.h" #include "impeller/entity/contents/contents.h" -#include "impeller/geometry/rect.h" namespace impeller { diff --git a/impeller/entity/entity.cc b/impeller/entity/entity.cc index 37e410960172c..df13ef0bfe685 100644 --- a/impeller/entity/entity.cc +++ b/impeller/entity/entity.cc @@ -39,11 +39,11 @@ bool Entity::AddsToCoverage() const { } std::optional Entity::GetCoverage() const { - if (!adds_to_coverage_) { + if (!adds_to_coverage_ || !contents_) { return std::nullopt; } - return path_.GetBoundingBox(); + return contents_->GetBounds(*this); } void Entity::SetContents(std::shared_ptr contents) { diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index 6f02cf6353cd2..b6d75a3527917 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -681,36 +681,53 @@ TEST_F(EntityTest, GaussianBlurFilter) { auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { if (first_frame) { first_frame = false; - ImGui::SetNextWindowSize({450, 150}); - ImGui::SetNextWindowPos({200, 450}); + ImGui::SetNextWindowSize({500, 170}); + ImGui::SetNextWindowPos({300, 550}); } ImGui::Begin("Controls"); + static float blur_amount[2] = {20, 20}; + ImGui::SliderFloat2("Blur", &blur_amount[0], 0, 200); + static Color cover_color(1, 0, 0, 0.2); + ImGui::ColorEdit4("Cover color", reinterpret_cast(&cover_color)); static float offset[2] = {500, 400}; - ImGui::SliderFloat2("Position offset", &offset[0], 0, 1000); - static float scale = 1; - ImGui::SliderFloat("Scale", &scale, 0, 1); - static float blur_radius = 20; - ImGui::SliderFloat("Blur radius", &blur_radius, 0, 200); - static bool clip_border = true; - ImGui::Checkbox("Clip", &clip_border); + ImGui::SliderFloat2("Translation", &offset[0], 0, + pass.GetRenderTargetSize().width); + static float rotation = 0; + ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2); + static float scale[2] = {0.8, 0.8}; + ImGui::SliderFloat2("Scale", &scale[0], 0, 3); + static float skew[2] = {0, 0}; + ImGui::SliderFloat2("Skew", &skew[0], -3, 3); + ImGui::End(); auto blend = FilterContents::MakeBlend(Entity::BlendMode::kPlus, {boston, bridge, bridge}); auto blur = - FilterContents::MakeGaussianBlur(blend, blur_radius, clip_border); + FilterContents::MakeGaussianBlur(blend, blur_amount[0], blur_amount[1]); - auto output_size = Size(blur->GetOutputSize()); - Rect bounds(Point(offset[0], offset[1]) - output_size / 2 * scale, - output_size * scale); - - ImGui::End(); + auto rect = Rect(-Point(boston->GetSize()) / 2, Size(boston->GetSize())); + auto ctm = Matrix::MakeTranslation(Vector3(offset[0], offset[1])) * + Matrix::MakeRotation(rotation, Vector4(0, 0, 1, 1)) * + Matrix::MakeScale(Vector3(scale[0], scale[1])) * + Matrix::MakeSkew(skew[0], skew[1]); Entity entity; - entity.SetPath(PathBuilder{}.AddRect(bounds).TakePath()); + entity.SetPath(PathBuilder{}.AddRect(rect).TakePath()); entity.SetContents(blur); - return entity.Render(context, pass); + entity.SetTransformation(ctm); + + entity.Render(context, pass); + + // The following entity renders the expected transformed input. + Entity cover_entity; + cover_entity.SetPath(PathBuilder{}.AddRect(rect).TakePath()); + cover_entity.SetContents(SolidColorContents::Make(cover_color)); + cover_entity.SetTransformation(ctm); + + cover_entity.Render(context, pass); + return true; }; ASSERT_TRUE(OpenPlaygroundHere(callback)); } diff --git a/impeller/entity/shaders/texture_blend_screen.frag b/impeller/entity/shaders/texture_blend_screen.frag index 4594592efff9c..90fb52c8f66d8 100644 --- a/impeller/entity/shaders/texture_blend_screen.frag +++ b/impeller/entity/shaders/texture_blend_screen.frag @@ -5,12 +5,21 @@ uniform sampler2D texture_sampler_dst; uniform sampler2D texture_sampler_src; -in vec2 v_texture_coords; +in vec2 v_dst_texture_coords; +in vec2 v_src_texture_coords; out vec4 frag_color; +// Emulate SamplerAddressMode::ClampToBorder. +vec4 SampleWithBorder(sampler2D tex, vec2 uv) { + if (uv.x > 0 && uv.y > 0 && uv.x < 1 && uv.y < 1) { + return texture(tex, uv); + } + return vec4(0); +} + void main() { - vec4 dst = texture(texture_sampler_dst, v_texture_coords); - vec4 src = texture(texture_sampler_src, v_texture_coords); + vec4 dst = SampleWithBorder(texture_sampler_dst, v_dst_texture_coords); + vec4 src = SampleWithBorder(texture_sampler_src, v_src_texture_coords); frag_color = src + dst - src * dst; } diff --git a/impeller/entity/shaders/texture_blend_screen.vert b/impeller/entity/shaders/texture_blend_screen.vert index daa30f5650a3f..c84a1c03ca770 100644 --- a/impeller/entity/shaders/texture_blend_screen.vert +++ b/impeller/entity/shaders/texture_blend_screen.vert @@ -4,14 +4,20 @@ uniform FrameInfo { mat4 mvp; + mat4 dst_uv_transform; + mat4 src_uv_transform; } frame_info; in vec2 vertices; in vec2 texture_coords; -out vec2 v_texture_coords; +out vec2 v_dst_texture_coords; +out vec2 v_src_texture_coords; void main() { gl_Position = frame_info.mvp * vec4(vertices, 0.0, 1.0); - v_texture_coords = texture_coords; + v_dst_texture_coords = + (frame_info.dst_uv_transform * vec4(texture_coords, 1.0, 1.0)).xy; + v_src_texture_coords = + (frame_info.src_uv_transform * vec4(texture_coords, 1.0, 1.0)).xy; } diff --git a/impeller/geometry/geometry_unittests.cc b/impeller/geometry/geometry_unittests.cc index 843b6b59ff5c0..35b6641b574fb 100644 --- a/impeller/geometry/geometry_unittests.cc +++ b/impeller/geometry/geometry_unittests.cc @@ -142,6 +142,76 @@ TEST(GeometryTest, TestRecomposition2) { ASSERT_MATRIX_NEAR(matrix, Matrix{result.value()}); } +TEST(GeometryTest, MatrixVectorMultiplication) { + { + auto matrix = Matrix::MakeTranslation({100, 100, 100}) * + Matrix::MakeRotationZ(Radians{M_PI_2}) * + Matrix::MakeScale({2.0, 2.0, 2.0}); + auto vector = Vector4(10, 20, 30, 2); + + Vector4 result = matrix * vector; + auto expected = Vector4(160, 220, 260, 2); + ASSERT_VECTOR4_NEAR(result, expected); + } + + { + auto matrix = Matrix::MakeTranslation({100, 100, 100}) * + Matrix::MakeRotationZ(Radians{M_PI_2}) * + Matrix::MakeScale({2.0, 2.0, 2.0}); + auto vector = Vector3(10, 20, 30); + + Vector3 result = matrix * vector; + auto expected = Vector3(60, 120, 160); + ASSERT_VECTOR3_NEAR(result, expected); + } + + { + auto matrix = Matrix::MakeTranslation({100, 100, 100}) * + Matrix::MakeRotationZ(Radians{M_PI_2}) * + Matrix::MakeScale({2.0, 2.0, 2.0}); + auto vector = Point(10, 20); + + Point result = matrix * vector; + auto expected = Point(60, 120); + ASSERT_POINT_NEAR(result, expected); + } +} + +TEST(GeometryTest, MatrixTransformDirection) { + { + auto matrix = Matrix::MakeTranslation({100, 100, 100}) * + Matrix::MakeRotationZ(Radians{M_PI_2}) * + Matrix::MakeScale({2.0, 2.0, 2.0}); + auto vector = Vector4(10, 20, 30, 2); + + Vector4 result = matrix.TransformDirection(vector); + auto expected = Vector4(-40, 20, 60, 2); + ASSERT_VECTOR4_NEAR(result, expected); + } + + { + auto matrix = Matrix::MakeTranslation({100, 100, 100}) * + Matrix::MakeRotationZ(Radians{M_PI_2}) * + Matrix::MakeScale({2.0, 2.0, 2.0}); + auto vector = Vector3(10, 20, 30); + + Vector3 result = matrix.TransformDirection(vector); + auto expected = Vector3(-40, 20, 60); + ASSERT_VECTOR3_NEAR(result, expected); + } + + { + auto matrix = Matrix::MakeTranslation({100, 100, 100}) * + Matrix::MakeRotationZ(Radians{M_PI_2}) * + Matrix::MakeScale({2.0, 2.0, 2.0}); + auto vector = Point(10, 20); + + Point result = matrix.TransformDirection(vector); + auto expected = Point(-40, 20); + ASSERT_POINT_NEAR(result, expected); + } +} + TEST(GeometryTest, QuaternionLerp) { auto q1 = Quaternion{{0.0, 0.0, 1.0}, 0.0}; auto q2 = Quaternion{{0.0, 0.0, 1.0}, M_PI_4}; @@ -567,6 +637,13 @@ TEST(GeometryTest, PointReflect) { } } +TEST(GeometryTest, PointAbs) { + Point a(-1, -2); + auto a_abs = a.Abs(); + auto expected = Point(1, 2); + ASSERT_POINT_NEAR(a_abs, expected); +} + TEST(GeometryTest, ColorPremultiply) { { Color a(1.0, 0.5, 0.2, 0.5); @@ -722,6 +799,21 @@ TEST(GeometryTest, RectContainsRect) { } } +TEST(GeometryTest, RectGetPoints) { + Rect r(100, 200, 300, 400); + auto points = r.GetPoints(); + ASSERT_POINT_NEAR(points[0], Point(100, 200)); + ASSERT_POINT_NEAR(points[1], Point(400, 200)); + ASSERT_POINT_NEAR(points[2], Point(100, 600)); + ASSERT_POINT_NEAR(points[3], Point(400, 600)); +} + +TEST(GeometryTest, RectMakePointBounds) { + auto r = Rect::MakePointBounds({Point(1, 5), Point(4, -1), Point(0, 6)}); + auto expected = Rect(0, -1, 4, 7); + ASSERT_RECT_NEAR(r, expected); +} + TEST(GeometryTest, CubicPathComponentPolylineDoesNotIncludePointOne) { CubicPathComponent component({10, 10}, {20, 35}, {35, 20}, {40, 40}); SmoothingApproximation approximation; diff --git a/impeller/geometry/geometry_unittests.h b/impeller/geometry/geometry_unittests.h index 61502f1b38dbe..aea0ab80bb7d1 100644 --- a/impeller/geometry/geometry_unittests.h +++ b/impeller/geometry/geometry_unittests.h @@ -75,6 +75,24 @@ inline ::testing::AssertionResult PointNear(impeller::Point a, : ::testing::AssertionFailure() << "Points are not equal."; } +inline ::testing::AssertionResult Vector3Near(impeller::Vector3 a, + impeller::Vector3 b) { + auto equal = + NumberNear(a.x, b.x) && NumberNear(a.y, b.y) && NumberNear(a.z, b.z); + + return equal ? ::testing::AssertionSuccess() + : ::testing::AssertionFailure() << "Vector3s are not equal."; +} + +inline ::testing::AssertionResult Vector4Near(impeller::Vector4 a, + impeller::Vector4 b) { + auto equal = NumberNear(a.x, b.x) && NumberNear(a.y, b.y) && + NumberNear(a.z, b.z) && NumberNear(a.w, b.w); + + return equal ? ::testing::AssertionSuccess() + : ::testing::AssertionFailure() << "Vector4s are not equal."; +} + inline ::testing::AssertionResult SizeNear(impeller::Size a, impeller::Size b) { auto equal = NumberNear(a.width, b.width) && NumberNear(a.height, b.height); @@ -87,4 +105,6 @@ inline ::testing::AssertionResult SizeNear(impeller::Size a, impeller::Size b) { #define ASSERT_RECT_NEAR(a, b) ASSERT_PRED2(&::RectNear, a, b) #define ASSERT_COLOR_NEAR(a, b) ASSERT_PRED2(&::ColorNear, a, b) #define ASSERT_POINT_NEAR(a, b) ASSERT_PRED2(&::PointNear, a, b) +#define ASSERT_VECTOR3_NEAR(a, b) ASSERT_PRED2(&::Vector3Near, a, b) +#define ASSERT_VECTOR4_NEAR(a, b) ASSERT_PRED2(&::Vector4Near, a, b) #define ASSERT_SIZE_NEAR(a, b) ASSERT_PRED2(&::SizeNear, a, b) diff --git a/impeller/geometry/matrix.cc b/impeller/geometry/matrix.cc index f26411039a097..f5aaa3458b500 100644 --- a/impeller/geometry/matrix.cc +++ b/impeller/geometry/matrix.cc @@ -249,7 +249,7 @@ std::optional Matrix::Decompose() const { * prhs by the inverse. */ - result.perspective = rightHandSide * perpectiveMatrix.Invert().Transpose(); + result.perspective = perpectiveMatrix.Invert().Transpose() * rightHandSide; /* * Clear the perspective partition. diff --git a/impeller/geometry/matrix.h b/impeller/geometry/matrix.h index 6b7e4453645e3..c3178d71953e1 100644 --- a/impeller/geometry/matrix.h +++ b/impeller/geometry/matrix.h @@ -259,12 +259,44 @@ struct Matrix { Matrix operator-(const Vector3& t) const { return Translate(-t); } - Matrix operator*(const Vector3& s) const { return Scale(s); } - Matrix operator*(const Matrix& m) const { return Multiply(m); } Matrix operator+(const Matrix& m) const; + constexpr Vector4 operator*(const Vector4& v) const { + return Vector4(v.x * m[0] + v.y * m[4] + v.z * m[8] + v.w * m[12], + v.x * m[1] + v.y * m[5] + v.z * m[9] + v.w * m[13], + v.x * m[2] + v.y * m[6] + v.z * m[10] + v.w * m[14], + v.x * m[3] + v.y * m[7] + v.z * m[11] + v.w * m[15]); + } + + constexpr Vector3 operator*(const Vector3& v) const { + return Vector3(v.x * m[0] + v.y * m[4] + v.z * m[8] + m[12], + v.x * m[1] + v.y * m[5] + v.z * m[9] + m[13], + v.x * m[2] + v.y * m[6] + v.z * m[10] + m[14]); + } + + constexpr Point operator*(const Point& v) const { + return Point(v.x * m[0] + v.y * m[4] + m[12], + v.x * m[1] + v.y * m[5] + m[13]); + } + + constexpr Vector4 TransformDirection(const Vector4& v) const { + return Vector4(v.x * m[0] + v.y * m[4] + v.z * m[8], + v.x * m[1] + v.y * m[5] + v.z * m[9], + v.x * m[2] + v.y * m[6] + v.z * m[10], v.w); + } + + constexpr Vector3 TransformDirection(const Vector3& v) const { + return Vector3(v.x * m[0] + v.y * m[4] + v.z * m[8], + v.x * m[1] + v.y * m[5] + v.z * m[9], + v.x * m[2] + v.y * m[6] + v.z * m[10]); + } + + constexpr Vector2 TransformDirection(const Vector2& v) const { + return Vector2(v.x * m[0] + v.y * m[4], v.x * m[1] + v.y * m[5]); + } + template static constexpr Matrix MakeOrthographic(TSize size) { // Per assumptions about NDC documented above. @@ -279,17 +311,9 @@ struct Matrix { static_assert(sizeof(struct Matrix) == sizeof(Scalar) * 16, "The matrix must be of consistent size."); -inline Vector4 operator*(const Vector4& v, const Matrix& m) { - return Vector4(v.x * m.m[0] + v.y * m.m[4] + v.z * m.m[8] + v.w * m.m[12], - v.x * m.m[1] + v.y * m.m[5] + v.z * m.m[9] + v.w * m.m[13], - v.x * m.m[2] + v.y * m.m[6] + v.z * m.m[10] + v.w * m.m[14], - v.x * m.m[3] + v.y * m.m[7] + v.z * m.m[11] + v.w * m.m[15]); -} - } // namespace impeller namespace std { - inline std::ostream& operator<<(std::ostream& out, const impeller::Matrix& m) { out << "("; for (size_t i = 0; i < 4u; i++) { diff --git a/impeller/geometry/point.h b/impeller/geometry/point.h index 194bc1815d1df..957ab2ad32d26 100644 --- a/impeller/geometry/point.h +++ b/impeller/geometry/point.h @@ -180,6 +180,8 @@ struct TPoint { return {x / length, y / length}; } + constexpr TPoint Abs() const { return {std::fabs(x), std::fabs(y)}; } + constexpr Type Cross(const TPoint& p) const { return (x * p.y) - (y * p.x); } constexpr Type Dot(const TPoint& p) const { return (x * p.x) + (y * p.y); } diff --git a/impeller/geometry/rect.h b/impeller/geometry/rect.h index bb39b4bcd4759..5d5f4224b1996 100644 --- a/impeller/geometry/rect.h +++ b/impeller/geometry/rect.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -50,6 +51,23 @@ struct TRect { return TRect(0.0, 0.0, size.width, size.height); } + constexpr static TRect MakePointBounds( + const std::vector>& points) { + auto left = points[0].x; + auto top = points[0].y; + auto right = points[0].x; + auto bottom = points[0].y; + if (points.size() > 1) { + for (uint i = 1; i < points.size(); i++) { + left = std::min(left, points[i].x); + top = std::min(top, points[i].y); + right = std::max(right, points[i].x); + bottom = std::max(bottom, points[i].y); + } + } + return TRect::MakeLTRB(left, top, right, bottom); + } + template constexpr explicit TRect(const TRect& other) : origin(static_cast>(other.origin)), @@ -116,6 +134,15 @@ struct TRect { return {left, top, right, bottom}; } + constexpr std::array, 4> GetPoints() const { + const auto left = std::min(origin.x, origin.x + size.width); + const auto top = std::min(origin.y, origin.y + size.height); + const auto right = std::max(origin.x, origin.x + size.width); + const auto bottom = std::max(origin.y, origin.y + size.height); + return {TPoint(left, top), TPoint(right, top), TPoint(left, bottom), + TPoint(right, bottom)}; + } + constexpr TRect Union(const TRect& o) const { auto this_ltrb = GetLTRB(); auto other_ltrb = o.GetLTRB(); diff --git a/impeller/geometry/scalar.h b/impeller/geometry/scalar.h index 3fc11c400be7a..1a27bb3aa5e49 100644 --- a/impeller/geometry/scalar.h +++ b/impeller/geometry/scalar.h @@ -14,6 +14,8 @@ namespace impeller { using Scalar = float; +constexpr Scalar kEhCloseEnough = 1e-3f; + template >> constexpr T Absolute(const T& val) { return val >= T{} ? val : -val; @@ -21,7 +23,7 @@ constexpr T Absolute(const T& val) { constexpr inline bool ScalarNearlyEqual(Scalar x, Scalar y, - Scalar tolerance = 1e-3) { + Scalar tolerance = kEhCloseEnough) { return Absolute(x - y) <= tolerance; } diff --git a/impeller/geometry/size.h b/impeller/geometry/size.h index 9a58f32f68b49..b3dee0cec7140 100644 --- a/impeller/geometry/size.h +++ b/impeller/geometry/size.h @@ -47,6 +47,10 @@ struct TSize { static_cast(height) / scale}; } + constexpr TSize operator/(const TSize& s) const { + return {width / s.width, height / s.height}; + } + constexpr bool operator==(const TSize& s) const { return s.width == width && s.height == height; }