From 6bd7bc2132af23bb9b87c484df268b1ffe617c34 Mon Sep 17 00:00:00 2001 From: jonahwilliams Date: Sat, 5 Oct 2024 12:27:05 -0700 Subject: [PATCH 1/4] [Impeller] remove heap allocation of most geometry objects. --- impeller/display_list/canvas.cc | 318 +- impeller/display_list/canvas.h | 33 +- impeller/display_list/dl_dispatcher.cc | 37 +- impeller/display_list/paint.cc | 16 +- impeller/display_list/paint.h | 6 +- impeller/entity/contents/clip_contents.cc | 2 +- impeller/entity/contents/clip_contents.h | 8 +- .../contents/clip_contents_unittests.cc | 3 +- .../entity/contents/color_source_contents.cc | 6 +- .../entity/contents/color_source_contents.h | 29 +- .../contents/filters/blend_filter_contents.cc | 5 +- .../contents/filters/filter_contents.cc | 2 +- .../entity/contents/filters/filter_contents.h | 2 +- .../filters/gaussian_blur_filter_contents.cc | 6 +- .../filters/gaussian_blur_filter_contents.h | 13 +- .../contents/linear_gradient_contents.cc | 8 +- .../entity/contents/solid_color_contents.cc | 10 +- .../entity/contents/solid_color_contents.h | 6 - .../tiled_texture_contents_unittests.cc | 3 +- impeller/entity/entity_unittests.cc | 4694 +++++++++-------- impeller/entity/geometry/circle_geometry.cc | 2 + impeller/entity/geometry/circle_geometry.h | 2 +- impeller/entity/geometry/cover_geometry.h | 4 +- impeller/entity/geometry/ellipse_geometry.h | 2 +- .../entity/geometry/fill_path_geometry.cc | 2 + impeller/entity/geometry/fill_path_geometry.h | 2 +- impeller/entity/geometry/geometry.cc | 40 +- impeller/entity/geometry/geometry.h | 22 +- impeller/entity/geometry/line_geometry.cc | 2 + impeller/entity/geometry/line_geometry.h | 5 +- .../entity/geometry/point_field_geometry.cc | 2 + .../entity/geometry/point_field_geometry.h | 2 +- impeller/entity/geometry/rect_geometry.cc | 2 + impeller/entity/geometry/rect_geometry.h | 8 +- .../entity/geometry/round_rect_geometry.cc | 2 + .../entity/geometry/round_rect_geometry.h | 2 +- .../entity/geometry/stroke_path_geometry.h | 2 +- .../entity/geometry/superellipse_geometry.cc | 2 + .../entity/geometry/superellipse_geometry.h | 2 +- 39 files changed, 2662 insertions(+), 2652 deletions(-) diff --git a/impeller/display_list/canvas.cc b/impeller/display_list/canvas.cc index 9c45f8d6dce84..4d19c7a212628 100644 --- a/impeller/display_list/canvas.cc +++ b/impeller/display_list/canvas.cc @@ -25,7 +25,16 @@ #include "impeller/entity/contents/text_contents.h" #include "impeller/entity/contents/texture_contents.h" #include "impeller/entity/contents/vertices_contents.h" +#include "impeller/entity/geometry/circle_geometry.h" +#include "impeller/entity/geometry/cover_geometry.h" +#include "impeller/entity/geometry/ellipse_geometry.h" +#include "impeller/entity/geometry/fill_path_geometry.h" #include "impeller/entity/geometry/geometry.h" +#include "impeller/entity/geometry/line_geometry.h" +#include "impeller/entity/geometry/point_field_geometry.h" +#include "impeller/entity/geometry/rect_geometry.h" +#include "impeller/entity/geometry/round_rect_geometry.h" +#include "impeller/entity/geometry/stroke_path_geometry.h" #include "impeller/entity/save_layer_utils.h" #include "impeller/geometry/color.h" #include "impeller/geometry/constants.h" @@ -35,90 +44,20 @@ namespace impeller { namespace { -static std::shared_ptr CreateContentsForGeometryWithFilters( - const Paint& paint, - std::shared_ptr geometry) { - std::shared_ptr contents = paint.CreateContents(); - - // Attempt to apply the color filter on the CPU first. - // Note: This is not just an optimization; some color sources rely on - // CPU-applied color filters to behave properly. - bool needs_color_filter = paint.color_filter || paint.invert_colors; - if (needs_color_filter && - contents->ApplyColorFilter([&](Color color) -> Color { - if (paint.color_filter) { - color = GetCPUColorFilterProc(paint.color_filter)(color); - } - if (paint.invert_colors) { - color = color.ApplyColorMatrix(kColorInversion); - } - return color; - })) { - needs_color_filter = false; - } - - bool can_apply_mask_filter = geometry->CanApplyMaskFilter(); - contents->SetGeometry(std::move(geometry)); - - if (can_apply_mask_filter && paint.mask_blur_descriptor.has_value()) { - // If there's a mask blur and we need to apply the color filter on the GPU, - // we need to be careful to only apply the color filter to the source - // colors. CreateMaskBlur is able to handle this case. - return paint.mask_blur_descriptor->CreateMaskBlur( - contents, needs_color_filter ? paint.color_filter : nullptr, - needs_color_filter ? paint.invert_colors : false); +static bool UseColorSourceContents( + const std::shared_ptr& vertices, + const Paint& paint) { + // If there are no vertex color or texture coordinates. Or if there + // are vertex coordinates but its just a color. + if (vertices->HasVertexColors()) { + return false; } - - std::shared_ptr contents_copy = std::move(contents); - - // Image input types will directly set their color filter, - // if any. See `TiledTextureContents.SetColorFilter`. - if (needs_color_filter && + if (vertices->HasTextureCoordinates() && (!paint.color_source || - paint.color_source->type() != flutter::DlColorSourceType::kImage)) { - if (paint.color_filter) { - contents_copy = WrapWithGPUColorFilter( - paint.color_filter, FilterInput::Make(std::move(contents_copy)), - ColorFilterContents::AbsorbOpacity::kYes); - } - if (paint.invert_colors) { - contents_copy = - WrapWithInvertColors(FilterInput::Make(contents_copy), - ColorFilterContents::AbsorbOpacity::kYes); - } - } - - if (paint.image_filter) { - std::shared_ptr filter = WrapInput( - paint.image_filter, FilterInput::Make(std::move(contents_copy))); - filter->SetRenderingMode(Entity::RenderingMode::kDirect); - return filter; - } - - return contents_copy; -} - -static std::shared_ptr CreatePathContentsWithFilters( - const Paint& paint, - const Path& path) { - std::shared_ptr geometry; - switch (paint.style) { - case Paint::Style::kFill: - geometry = Geometry::MakeFillPath(path); - break; - case Paint::Style::kStroke: - geometry = - Geometry::MakeStrokePath(path, paint.stroke_width, paint.stroke_miter, - paint.stroke_cap, paint.stroke_join); - break; + paint.color_source->type() == flutter::DlColorSourceType::kColor)) { + return true; } - - return CreateContentsForGeometryWithFilters(paint, std::move(geometry)); -} - -static std::shared_ptr CreateCoverContentsWithFilters( - const Paint& paint) { - return CreateContentsForGeometryWithFilters(paint, Geometry::MakeCover()); + return !vertices->HasTextureCoordinates(); } static void SetClipScissor(std::optional clip_coverage, @@ -448,18 +387,24 @@ void Canvas::DrawPath(const Path& path, const Paint& paint) { Entity entity; entity.SetTransform(GetCurrentTransform()); entity.SetBlendMode(paint.blend_mode); - entity.SetContents(CreatePathContentsWithFilters(paint, path)); - AddRenderEntityToCurrentPass(entity); + if (paint.style == Paint::Style::kFill) { + FillPathGeometry geom(path); + AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint); + } else { + StrokePathGeometry geom(path, paint.stroke_width, paint.stroke_miter, + paint.stroke_cap, paint.stroke_join); + AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint); + } } void Canvas::DrawPaint(const Paint& paint) { Entity entity; entity.SetTransform(GetCurrentTransform()); entity.SetBlendMode(paint.blend_mode); - entity.SetContents(CreateCoverContentsWithFilters(paint)); - AddRenderEntityToCurrentPass(entity); + CoverGeometry geom; + AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint); } bool Canvas::AttemptDrawBlurredRRect(const Rect& rect, @@ -573,18 +518,21 @@ bool Canvas::AttemptDrawBlurredRRect(const Rect& rect, Entity entity; entity.SetTransform(GetCurrentTransform()); entity.SetBlendMode(rrect_paint.blend_mode); - entity.SetContents(CreateContentsForGeometryWithFilters( - rrect_paint, Geometry::MakeRoundRect(rect, corner_radii))); - AddRenderEntityToCurrentPass(entity, true); + + RoundRectGeometry geom(rect, corner_radii); + AddRenderEntityWithFiltersToCurrentPass(entity, &geom, rrect_paint, + /*reuse_depth=*/true); break; } case FilterContents::BlurStyle::kOuter: { - ClipRRect(rect, corner_radii, Entity::ClipOperation::kDifference); + ClipGeometry(Geometry::MakeRoundRect(rect, corner_radii), + Entity::ClipOperation::kDifference); draw_blurred_rrect(); break; } case FilterContents::BlurStyle::kInner: { - ClipRRect(rect, corner_radii, Entity::ClipOperation::kIntersect); + ClipGeometry(Geometry::MakeRoundRect(rect, corner_radii), + Entity::ClipOperation::kIntersect); draw_blurred_rrect(); break; } @@ -599,10 +547,9 @@ void Canvas::DrawLine(const Point& p0, const Point& p1, const Paint& paint) { Entity entity; entity.SetTransform(GetCurrentTransform()); entity.SetBlendMode(paint.blend_mode); - entity.SetContents(CreateContentsForGeometryWithFilters( - paint, Geometry::MakeLine(p0, p1, paint.stroke_width, paint.stroke_cap))); - AddRenderEntityToCurrentPass(entity); + LineGeometry geom(p0, p1, paint.stroke_width, paint.stroke_cap); + AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint); } void Canvas::DrawRect(const Rect& rect, const Paint& paint) { @@ -618,10 +565,9 @@ void Canvas::DrawRect(const Rect& rect, const Paint& paint) { Entity entity; entity.SetTransform(GetCurrentTransform()); entity.SetBlendMode(paint.blend_mode); - entity.SetContents( - CreateContentsForGeometryWithFilters(paint, Geometry::MakeRect(rect))); - AddRenderEntityToCurrentPass(entity); + RectGeometry geom(rect); + AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint); } void Canvas::DrawOval(const Rect& rect, const Paint& paint) { @@ -649,10 +595,9 @@ void Canvas::DrawOval(const Rect& rect, const Paint& paint) { Entity entity; entity.SetTransform(GetCurrentTransform()); entity.SetBlendMode(paint.blend_mode); - entity.SetContents( - CreateContentsForGeometryWithFilters(paint, Geometry::MakeOval(rect))); - AddRenderEntityToCurrentPass(entity); + EllipseGeometry geom(rect); + AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint); } void Canvas::DrawRRect(const Rect& rect, @@ -666,10 +611,9 @@ void Canvas::DrawRRect(const Rect& rect, Entity entity; entity.SetTransform(GetCurrentTransform()); entity.SetBlendMode(paint.blend_mode); - entity.SetContents(CreateContentsForGeometryWithFilters( - paint, Geometry::MakeRoundRect(rect, corner_radii))); - AddRenderEntityToCurrentPass(entity); + RoundRectGeometry geom(rect, corner_radii); + AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint); return; } @@ -694,41 +638,22 @@ void Canvas::DrawCircle(const Point& center, Entity entity; entity.SetTransform(GetCurrentTransform()); entity.SetBlendMode(paint.blend_mode); - auto geometry = - paint.style == Paint::Style::kStroke - ? Geometry::MakeStrokedCircle(center, radius, paint.stroke_width) - : Geometry::MakeCircle(center, radius); - entity.SetContents( - CreateContentsForGeometryWithFilters(paint, std::move(geometry))); - - AddRenderEntityToCurrentPass(entity); -} - -void Canvas::ClipPath(const Path& path, Entity::ClipOperation clip_op) { - ClipGeometry(Geometry::MakeFillPath(path), clip_op); -} -void Canvas::ClipRect(const Rect& rect, Entity::ClipOperation clip_op) { - auto geometry = Geometry::MakeRect(rect); - ClipGeometry(geometry, clip_op); -} - -void Canvas::ClipOval(const Rect& bounds, Entity::ClipOperation clip_op) { - auto geometry = Geometry::MakeOval(bounds); - ClipGeometry(geometry, clip_op); -} - -void Canvas::ClipRRect(const Rect& rect, - const Size& corner_radii, - Entity::ClipOperation clip_op) { - auto geometry = Geometry::MakeRoundRect(rect, corner_radii); - ClipGeometry(geometry, clip_op); + if (paint.style == Paint::Style::kStroke) { + CircleGeometry geom(center, radius, paint.stroke_width); + AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint); + } else { + CircleGeometry geom(center, radius); + AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint); + } } -void Canvas::ClipGeometry(const std::shared_ptr& geometry, +void Canvas::ClipGeometry(std::unique_ptr geometry, Entity::ClipOperation clip_op) { + clip_geometry_.push_back(std::move(geometry)); + auto contents = std::make_shared(); - contents->SetGeometry(geometry); + contents->SetGeometry(clip_geometry_.back().get()); contents->SetClipOperation(clip_op); Entity entity; @@ -764,12 +689,10 @@ void Canvas::DrawPoints(std::vector points, Entity entity; entity.SetTransform(GetCurrentTransform()); entity.SetBlendMode(paint.blend_mode); - entity.SetContents(CreateContentsForGeometryWithFilters( - paint, - Geometry::MakePointField(std::move(points), radius, - /*round=*/point_style == PointStyle::kRound))); - AddRenderEntityToCurrentPass(entity); + PointFieldGeometry geom(std::move(points), radius, + /*round=*/point_style == PointStyle::kRound); + AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint); } void Canvas::DrawImage(const std::shared_ptr& image, @@ -811,16 +734,20 @@ void Canvas::DrawImageRect(const std::shared_ptr& image, texture_contents->SetOpacity(paint.color.alpha); texture_contents->SetDeferApplyingOpacity(paint.HasColorFilter()); - std::shared_ptr contents = texture_contents; - if (paint.mask_blur_descriptor.has_value()) { - contents = paint.mask_blur_descriptor->CreateMaskBlur(texture_contents); - } - Entity entity; entity.SetBlendMode(paint.blend_mode); - entity.SetContents(paint.WithFilters(contents)); entity.SetTransform(GetCurrentTransform()); + if (!paint.mask_blur_descriptor.has_value()) { + entity.SetContents(paint.WithFilters(std::move(texture_contents))); + AddRenderEntityToCurrentPass(entity); + return; + } + + RectGeometry out_rect(Rect{}); + + entity.SetContents(paint.WithFilters( + paint.mask_blur_descriptor->CreateMaskBlur(texture_contents, &out_rect))); AddRenderEntityToCurrentPass(entity); } @@ -828,22 +755,6 @@ size_t Canvas::GetClipHeight() const { return transform_stack_.back().clip_height; } -static bool UseColorSourceContents( - const std::shared_ptr& vertices, - const Paint& paint) { - // If there are no vertex color or texture coordinates. Or if there - // are vertex coordinates but its just a color. - if (vertices->HasVertexColors()) { - return false; - } - if (vertices->HasTextureCoordinates() && - (!paint.color_source || - paint.color_source->type() == flutter::DlColorSourceType::kColor)) { - return true; - } - return !vertices->HasTextureCoordinates(); -} - void Canvas::DrawVertices(const std::shared_ptr& vertices, BlendMode blend_mode, const Paint& paint) { @@ -861,8 +772,7 @@ void Canvas::DrawVertices(const std::shared_ptr& vertices, // If there are no vertex colors. if (UseColorSourceContents(vertices, paint)) { - entity.SetContents(CreateContentsForGeometryWithFilters(paint, vertices)); - AddRenderEntityToCurrentPass(entity); + AddRenderEntityWithFiltersToCurrentPass(entity, vertices.get(), paint); return; } @@ -913,7 +823,7 @@ void Canvas::DrawVertices(const std::shared_ptr& vertices, std::shared_ptr src_contents = src_paint.CreateContents(); - src_contents->SetGeometry(vertices); + src_contents->SetGeometry(vertices.get()); // If the color source has an intrinsic size, then we use that to // create the src contents as a simplification. Otherwise we use @@ -933,7 +843,9 @@ void Canvas::DrawVertices(const std::shared_ptr& vertices, vertices->GetTextureCoordinateCoverge().value_or(cvg.value()); } src_contents = src_paint.CreateContents(); - src_contents->SetGeometry(Geometry::MakeRect(Rect::Round(src_coverage))); + + clip_geometry_.push_back(Geometry::MakeRect(Rect::Round(src_coverage))); + src_contents->SetGeometry(clip_geometry_.back().get()); auto contents = std::make_shared(); contents->SetBlendMode(blend_mode); @@ -1430,6 +1342,85 @@ void Canvas::DrawTextFrame(const std::shared_ptr& text_frame, AddRenderEntityToCurrentPass(entity, false); } +void Canvas::AddRenderEntityWithFiltersToCurrentPass(Entity& entity, + const Geometry* geometry, + const Paint& paint, + bool reuse_depth) { + std::shared_ptr contents = paint.CreateContents(); + if (!paint.color_filter && !paint.invert_colors && !paint.image_filter && + !paint.mask_blur_descriptor.has_value()) { + contents->SetGeometry(geometry); + entity.SetContents(std::move(contents)); + AddRenderEntityToCurrentPass(entity, reuse_depth); + return; + } + + // Attempt to apply the color filter on the CPU first. + // Note: This is not just an optimization; some color sources rely on + // CPU-applied color filters to behave properly. + bool needs_color_filter = paint.color_filter || paint.invert_colors; + if (needs_color_filter && + contents->ApplyColorFilter([&](Color color) -> Color { + if (paint.color_filter) { + color = GetCPUColorFilterProc(paint.color_filter)(color); + } + if (paint.invert_colors) { + color = color.ApplyColorMatrix(kColorInversion); + } + return color; + })) { + needs_color_filter = false; + } + + bool can_apply_mask_filter = geometry->CanApplyMaskFilter(); + contents->SetGeometry(geometry); + + if (can_apply_mask_filter && paint.mask_blur_descriptor.has_value()) { + // If there's a mask blur and we need to apply the color filter on the GPU, + // we need to be careful to only apply the color filter to the source + // colors. CreateMaskBlur is able to handle this case. + RectGeometry out_rect(Rect{}); + auto filter_contents = paint.mask_blur_descriptor->CreateMaskBlur( + contents, needs_color_filter ? paint.color_filter : nullptr, + needs_color_filter ? paint.invert_colors : false, &out_rect); + entity.SetContents(std::move(filter_contents)); + AddRenderEntityToCurrentPass(entity, reuse_depth); + return; + } + + std::shared_ptr contents_copy = std::move(contents); + + // Image input types will directly set their color filter, + // if any. See `TiledTextureContents.SetColorFilter`. + if (needs_color_filter && + (!paint.color_source || + paint.color_source->type() != flutter::DlColorSourceType::kImage)) { + if (paint.color_filter) { + contents_copy = WrapWithGPUColorFilter( + paint.color_filter, FilterInput::Make(std::move(contents_copy)), + ColorFilterContents::AbsorbOpacity::kYes); + } + if (paint.invert_colors) { + contents_copy = + WrapWithInvertColors(FilterInput::Make(contents_copy), + ColorFilterContents::AbsorbOpacity::kYes); + } + } + + if (paint.image_filter) { + std::shared_ptr filter = WrapInput( + paint.image_filter, FilterInput::Make(std::move(contents_copy))); + filter->SetRenderingMode(Entity::RenderingMode::kDirect); + entity.SetContents(filter); + AddRenderEntityToCurrentPass(entity, reuse_depth); + return; + } + + entity.SetContents(std::move(contents_copy)); + AddRenderEntityToCurrentPass(entity, reuse_depth); + return; +} + void Canvas::AddRenderEntityToCurrentPass(Entity& entity, bool reuse_depth) { if (IsSkipping()) { return; @@ -1660,6 +1651,7 @@ void Canvas::EndReplay() { render_passes_.clear(); renderer_.GetRenderTargetCache()->End(); + clip_geometry_.clear(); Reset(); Initialize(initial_cull_rect_); diff --git a/impeller/display_list/canvas.h b/impeller/display_list/canvas.h index e1cc8e2afda4c..387cb5c81dc33 100644 --- a/impeller/display_list/canvas.h +++ b/impeller/display_list/canvas.h @@ -197,23 +197,6 @@ class Canvas { SamplerDescriptor sampler = {}, SourceRectConstraint src_rect_constraint = SourceRectConstraint::kFast); - void ClipPath( - const Path& path, - Entity::ClipOperation clip_op = Entity::ClipOperation::kIntersect); - - void ClipRect( - const Rect& rect, - Entity::ClipOperation clip_op = Entity::ClipOperation::kIntersect); - - void ClipOval( - const Rect& bounds, - Entity::ClipOperation clip_op = Entity::ClipOperation::kIntersect); - - void ClipRRect( - const Rect& rect, - const Size& corner_radii, - Entity::ClipOperation clip_op = Entity::ClipOperation::kIntersect); - void DrawTextFrame(const std::shared_ptr& text_frame, Point position, const Paint& paint); @@ -225,6 +208,9 @@ class Canvas { void DrawAtlas(const std::shared_ptr& atlas_contents, const Paint& paint); + void ClipGeometry(std::unique_ptr geometry, + Entity::ClipOperation clip_op); + void EndReplay(); uint64_t GetOpDepth() const { return current_depth_; } @@ -247,6 +233,11 @@ class Canvas { std::vector render_passes_; std::vector save_layer_state_; + // All geometry objects created for regular draws can be stack allocated, + // but clip geometries must be cached for record/replay for backdrop filters + // and so must be kept alive longer. + std::vector> clip_geometry_; + uint64_t current_depth_ = 0u; Point GetGlobalPassPosition() const; @@ -271,13 +262,15 @@ class Canvas { void Reset(); + void AddRenderEntityWithFiltersToCurrentPass(Entity& entity, + const Geometry* geometry, + const Paint& paint, + bool reuse_depth = false); + void AddRenderEntityToCurrentPass(Entity& entity, bool reuse_depth = false); void AddClipEntityToCurrentPass(Entity& entity); - void ClipGeometry(const std::shared_ptr& geometry, - Entity::ClipOperation clip_op); - void RestoreClip(); bool AttemptDrawBlurredRRect(const Rect& rect, diff --git a/impeller/display_list/dl_dispatcher.cc b/impeller/display_list/dl_dispatcher.cc index 6a06ce5ff5b34..ffdd0c6eca9f8 100644 --- a/impeller/display_list/dl_dispatcher.cc +++ b/impeller/display_list/dl_dispatcher.cc @@ -25,6 +25,7 @@ #include "impeller/entity/contents/filters/filter_contents.h" #include "impeller/entity/contents/filters/inputs/filter_input.h" #include "impeller/entity/entity.h" +#include "impeller/entity/geometry/geometry.h" #include "impeller/geometry/color.h" #include "impeller/geometry/path.h" #include "impeller/geometry/path_builder.h" @@ -430,7 +431,7 @@ void DlDispatcherBase::clipRect(const DlRect& rect, bool is_aa) { AUTO_DEPTH_WATCHER(0u); - GetCanvas().ClipRect(rect, ToClipOperation(clip_op)); + GetCanvas().ClipGeometry(Geometry::MakeRect(rect), ToClipOperation(clip_op)); } // |flutter::DlOpReceiver| @@ -439,7 +440,8 @@ void DlDispatcherBase::clipOval(const DlRect& bounds, bool is_aa) { AUTO_DEPTH_WATCHER(0u); - GetCanvas().ClipOval(bounds, ToClipOperation(clip_op)); + GetCanvas().ClipGeometry(Geometry::MakeOval(bounds), + ToClipOperation(clip_op)); } // |flutter::DlOpReceiver| @@ -450,15 +452,20 @@ void DlDispatcherBase::clipRRect(const SkRRect& rrect, auto clip_op = ToClipOperation(sk_op); if (rrect.isRect()) { - GetCanvas().ClipRect(skia_conversions::ToRect(rrect.rect()), clip_op); + GetCanvas().ClipGeometry( + Geometry::MakeRect(skia_conversions::ToRect(rrect.rect())), clip_op); } else if (rrect.isOval()) { - GetCanvas().ClipOval(skia_conversions::ToRect(rrect.rect()), clip_op); + GetCanvas().ClipGeometry( + Geometry::MakeOval(skia_conversions::ToRect(rrect.rect())), clip_op); } else if (rrect.isSimple()) { - GetCanvas().ClipRRect(skia_conversions::ToRect(rrect.rect()), - skia_conversions::ToSize(rrect.getSimpleRadii()), - clip_op); + GetCanvas().ClipGeometry( + Geometry::MakeRoundRect( + skia_conversions::ToRect(rrect.rect()), + skia_conversions::ToSize(rrect.getSimpleRadii())), + clip_op); } else { - GetCanvas().ClipPath(skia_conversions::ToPath(rrect), clip_op); + GetCanvas().ClipGeometry( + Geometry::MakeFillPath(skia_conversions::ToPath(rrect)), clip_op); } } @@ -470,17 +477,19 @@ void DlDispatcherBase::clipPath(const DlPath& path, ClipOp sk_op, bool is_aa) { DlRect rect; if (path.IsRect(&rect)) { - GetCanvas().ClipRect(rect, clip_op); + GetCanvas().ClipGeometry(Geometry::MakeRect(rect), clip_op); } else if (path.IsOval(&rect)) { - GetCanvas().ClipOval(rect, clip_op); + GetCanvas().ClipGeometry(Geometry::MakeOval(rect), clip_op); } else { SkRRect rrect; if (path.IsSkRRect(&rrect) && rrect.isSimple()) { - GetCanvas().ClipRRect(skia_conversions::ToRect(rrect.rect()), - skia_conversions::ToSize(rrect.getSimpleRadii()), - clip_op); + GetCanvas().ClipGeometry( + Geometry::MakeRoundRect( + skia_conversions::ToRect(rrect.rect()), + skia_conversions::ToSize(rrect.getSimpleRadii())), + clip_op); } else { - GetCanvas().ClipPath(path.GetPath(), clip_op); + GetCanvas().ClipGeometry(Geometry::MakeFillPath(path.GetPath()), clip_op); } } } diff --git a/impeller/display_list/paint.cc b/impeller/display_list/paint.cc index 59105b462ce2d..13bd681fe30fb 100644 --- a/impeller/display_list/paint.cc +++ b/impeller/display_list/paint.cc @@ -24,6 +24,7 @@ #include "impeller/entity/contents/sweep_gradient_contents.h" #include "impeller/entity/contents/tiled_texture_contents.h" #include "impeller/entity/geometry/geometry.h" +#include "impeller/entity/geometry/rect_geometry.h" namespace impeller { @@ -340,7 +341,8 @@ std::shared_ptr Paint::WithColorFilter( } std::shared_ptr Paint::MaskBlurDescriptor::CreateMaskBlur( - std::shared_ptr texture_contents) const { + std::shared_ptr texture_contents, + RectGeometry* rect_geom) const { Scalar expand_amount = GaussianBlurFilterContents::CalculateBlurRadius( GaussianBlurFilterContents::ScaleSigma(sigma.sigma)); texture_contents->SetSourceRect( @@ -348,11 +350,12 @@ std::shared_ptr Paint::MaskBlurDescriptor::CreateMaskBlur( auto mask = std::make_shared(); mask->SetColor(Color::White()); std::optional coverage = texture_contents->GetCoverage({}); - std::shared_ptr geometry; + Geometry* geometry = nullptr; if (coverage) { texture_contents->SetDestinationRect( coverage.value().Expand(expand_amount, expand_amount)); - geometry = Geometry::MakeRect(coverage.value()); + *rect_geom = RectGeometry(coverage.value()); + geometry = rect_geom; } mask->SetGeometry(geometry); auto descriptor = texture_contents->GetSamplerDescriptor(); @@ -370,7 +373,8 @@ std::shared_ptr Paint::MaskBlurDescriptor::CreateMaskBlur( std::shared_ptr Paint::MaskBlurDescriptor::CreateMaskBlur( std::shared_ptr color_source_contents, const flutter::DlColorFilter* color_filter, - bool invert_colors) const { + bool invert_colors, + RectGeometry* rect_geom) const { // If it's a solid color then we can just get away with doing one Gaussian // blur. The color filter will always be applied on the CPU. if (color_source_contents->IsSolidColor()) { @@ -399,8 +403,8 @@ std::shared_ptr Paint::MaskBlurDescriptor::CreateMaskBlur( if (!expanded_local_bounds.has_value()) { expanded_local_bounds = Rect(); } - color_source_contents->SetGeometry( - Geometry::MakeRect(*expanded_local_bounds)); + *rect_geom = RectGeometry(expanded_local_bounds.value()); + color_source_contents->SetGeometry(rect_geom); std::shared_ptr color_contents = color_source_contents; /// 4. Apply the user set color filter on the GPU, if applicable. diff --git a/impeller/display_list/paint.h b/impeller/display_list/paint.h index 620b5126a15f8..830b72673bac7 100644 --- a/impeller/display_list/paint.h +++ b/impeller/display_list/paint.h @@ -58,10 +58,12 @@ struct Paint { std::shared_ptr CreateMaskBlur( std::shared_ptr color_source_contents, const flutter::DlColorFilter* color_filter, - bool invert_colors) const; + bool invert_colors, + RectGeometry* rect_geom) const; std::shared_ptr CreateMaskBlur( - std::shared_ptr texture_contents) const; + std::shared_ptr texture_contents, + RectGeometry* rect_geom) const; std::shared_ptr CreateMaskBlur( const FilterInput::Ref& input, diff --git a/impeller/entity/contents/clip_contents.cc b/impeller/entity/contents/clip_contents.cc index 1eb51b8ee299c..ee4535ed7084e 100644 --- a/impeller/entity/contents/clip_contents.cc +++ b/impeller/entity/contents/clip_contents.cc @@ -32,7 +32,7 @@ ClipContents::ClipContents() = default; ClipContents::~ClipContents() = default; -void ClipContents::SetGeometry(const std::shared_ptr& geometry) { +void ClipContents::SetGeometry(const Geometry* geometry) { geometry_ = geometry; } diff --git a/impeller/entity/contents/clip_contents.h b/impeller/entity/contents/clip_contents.h index eae784ab251c5..51e76644f4ec9 100644 --- a/impeller/entity/contents/clip_contents.h +++ b/impeller/entity/contents/clip_contents.h @@ -5,10 +5,6 @@ #ifndef FLUTTER_IMPELLER_ENTITY_CONTENTS_CLIP_CONTENTS_H_ #define FLUTTER_IMPELLER_ENTITY_CONTENTS_CLIP_CONTENTS_H_ -#include -#include -#include - #include "impeller/entity/contents/contents.h" #include "impeller/entity/entity.h" #include "impeller/entity/geometry/geometry.h" @@ -21,7 +17,7 @@ class ClipContents final : public Contents { ~ClipContents(); - void SetGeometry(const std::shared_ptr& geometry); + void SetGeometry(const Geometry* geometry); void SetClipOperation(Entity::ClipOperation clip_op); @@ -42,7 +38,7 @@ class ClipContents final : public Contents { void SetInheritedOpacity(Scalar opacity) override; private: - std::shared_ptr geometry_; + const Geometry* geometry_ = nullptr; Entity::ClipOperation clip_op_ = Entity::ClipOperation::kIntersect; ClipContents(const ClipContents&) = delete; diff --git a/impeller/entity/contents/clip_contents_unittests.cc b/impeller/entity/contents/clip_contents_unittests.cc index f9c23cb6db26c..21070a0bc5bb5 100644 --- a/impeller/entity/contents/clip_contents_unittests.cc +++ b/impeller/entity/contents/clip_contents_unittests.cc @@ -37,7 +37,8 @@ TEST_P(EntityTest, ClipContentsOptimizesFullScreenIntersectClips) { auto contents = std::make_shared(); contents->SetClipOperation(Entity::ClipOperation::kIntersect); - contents->SetGeometry(Geometry::MakeCover()); + auto geom = Geometry::MakeCover(); + contents->SetGeometry(geom.get()); Entity entity; entity.SetContents(std::move(contents)); diff --git a/impeller/entity/contents/color_source_contents.cc b/impeller/entity/contents/color_source_contents.cc index 009f3146076be..8d42add0a9206 100644 --- a/impeller/entity/contents/color_source_contents.cc +++ b/impeller/entity/contents/color_source_contents.cc @@ -13,11 +13,11 @@ ColorSourceContents::ColorSourceContents() = default; ColorSourceContents::~ColorSourceContents() = default; -void ColorSourceContents::SetGeometry(std::shared_ptr geometry) { - geometry_ = std::move(geometry); +void ColorSourceContents::SetGeometry(const Geometry* geometry) { + geometry_ = geometry; } -const std::shared_ptr& ColorSourceContents::GetGeometry() const { +const Geometry* ColorSourceContents::GetGeometry() const { return geometry_; } diff --git a/impeller/entity/contents/color_source_contents.h b/impeller/entity/contents/color_source_contents.h index 1bade2c369b48..52985493f78aa 100644 --- a/impeller/entity/contents/color_source_contents.h +++ b/impeller/entity/contents/color_source_contents.h @@ -43,12 +43,12 @@ class ColorSourceContents : public Contents { //---------------------------------------------------------------------------- /// @brief Set the geometry that this contents will use to render. /// - void SetGeometry(std::shared_ptr geometry); + void SetGeometry(const Geometry* geometry); //---------------------------------------------------------------------------- /// @brief Get the geometry that this contents will use to render. /// - const std::shared_ptr& GetGeometry() const; + const Geometry* GetGeometry() const; //---------------------------------------------------------------------------- /// @brief Set the effect transform for this color source. @@ -110,14 +110,14 @@ class ColorSourceContents : public Contents { std::function; + const Geometry* geom)>; static GeometryResult DefaultCreateGeometryCallback( const ContentContext& renderer, const Entity& entity, RenderPass& pass, - const Geometry& geom) { - return geom.GetPositionBuffer(renderer, entity, pass); + const Geometry* geom) { + return geom->GetPositionBuffer(renderer, entity, pass); } /// @brief Whether the entity should be treated as non-opaque due to stroke @@ -137,7 +137,8 @@ class ColorSourceContents : public Contents { auto options = OptionsFromPassAndEntity(pass, entity); GeometryResult::Mode geometry_mode = GetGeometry()->GetResultMode(); - Geometry& geometry = *GetGeometry(); + bool do_cover_draw = false; + Rect cover_area = {}; bool is_stencil_then_cover = geometry_mode == GeometryResult::Mode::kNonZero || @@ -200,11 +201,19 @@ class ColorSourceContents : public Contents { if (!maybe_cover_area.has_value()) { return true; } - geometry = RectGeometry(maybe_cover_area.value()); + do_cover_draw = true; + cover_area = maybe_cover_area.value(); + } + + GeometryResult geometry_result; + if (do_cover_draw) { + RectGeometry geom(cover_area); + geometry_result = create_geom_callback(renderer, entity, pass, &geom); + } else { + geometry_result = + create_geom_callback(renderer, entity, pass, GetGeometry()); } - GeometryResult geometry_result = - create_geom_callback(renderer, entity, pass, geometry); if (geometry_result.vertex_buffer.vertex_count == 0u) { return true; } @@ -260,7 +269,7 @@ class ColorSourceContents : public Contents { } private: - std::shared_ptr geometry_; + const Geometry* geometry_ = nullptr; Matrix inverse_matrix_; Scalar opacity_ = 1.0; Scalar inherited_opacity_ = 1.0; diff --git a/impeller/entity/contents/filters/blend_filter_contents.cc b/impeller/entity/contents/filters/blend_filter_contents.cc index 49c7d6b14cd24..08b7b0744f35d 100644 --- a/impeller/entity/contents/filters/blend_filter_contents.cc +++ b/impeller/entity/contents/filters/blend_filter_contents.cc @@ -21,6 +21,7 @@ #include "impeller/entity/contents/filters/inputs/filter_input.h" #include "impeller/entity/contents/solid_color_contents.h" #include "impeller/entity/entity.h" +#include "impeller/entity/geometry/rect_geometry.h" #include "impeller/entity/texture_fill.frag.h" #include "impeller/entity/texture_fill.vert.h" #include "impeller/geometry/color.h" @@ -626,8 +627,8 @@ static std::optional PipelineBlend( if (foreground_color.has_value()) { auto contents = std::make_shared(); - contents->SetGeometry( - Geometry::MakeRect(Rect::MakeSize(pass.GetRenderTargetSize()))); + RectGeometry geom(Rect::MakeSize(pass.GetRenderTargetSize())); + contents->SetGeometry(&geom); contents->SetColor(foreground_color.value()); Entity foreground_entity; diff --git a/impeller/entity/contents/filters/filter_contents.cc b/impeller/entity/contents/filters/filter_contents.cc index 36af904dd14b9..85bcabadc199c 100644 --- a/impeller/entity/contents/filters/filter_contents.cc +++ b/impeller/entity/contents/filters/filter_contents.cc @@ -39,7 +39,7 @@ std::shared_ptr FilterContents::MakeGaussianBlur( Sigma sigma_y, Entity::TileMode tile_mode, FilterContents::BlurStyle mask_blur_style, - const std::shared_ptr& mask_geometry) { + const Geometry* mask_geometry) { auto blur = std::make_shared( sigma_x.sigma, sigma_y.sigma, tile_mode, mask_blur_style, mask_geometry); blur->SetInputs({input}); diff --git a/impeller/entity/contents/filters/filter_contents.h b/impeller/entity/contents/filters/filter_contents.h index 6ffdf13e2a661..3c355bb2494b4 100644 --- a/impeller/entity/contents/filters/filter_contents.h +++ b/impeller/entity/contents/filters/filter_contents.h @@ -44,7 +44,7 @@ class FilterContents : public Contents { Sigma sigma_y, Entity::TileMode tile_mode = Entity::TileMode::kDecal, BlurStyle mask_blur_style = BlurStyle::kNormal, - const std::shared_ptr& mask_geometry = nullptr); + const Geometry* mask_geometry = nullptr); static std::shared_ptr MakeBorderMaskBlur( FilterInput::Ref input, diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc index 4c7af0b485f59..efdd24254f07e 100644 --- a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc +++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc @@ -505,7 +505,7 @@ Entity ApplyClippedBlurStyle(Entity::ClipOperation clip_operation, const std::shared_ptr& input, const Snapshot& input_snapshot, Entity blur_entity, - const std::shared_ptr& geometry) { + const Geometry* geometry) { auto clip_contents = std::make_shared(); clip_contents->SetClipOperation(clip_operation); clip_contents->SetGeometry(geometry); @@ -544,7 +544,7 @@ Entity ApplyBlurStyle(FilterContents::BlurStyle blur_style, const std::shared_ptr& input, const Snapshot& input_snapshot, Entity blur_entity, - const std::shared_ptr& geometry, + const Geometry* geometry, Vector2 source_space_scalar) { switch (blur_style) { case FilterContents::BlurStyle::kNormal: @@ -599,7 +599,7 @@ GaussianBlurFilterContents::GaussianBlurFilterContents( Scalar sigma_y, Entity::TileMode tile_mode, BlurStyle mask_blur_style, - const std::shared_ptr& mask_geometry) + const Geometry* mask_geometry) : sigma_(sigma_x, sigma_y), tile_mode_(tile_mode), mask_blur_style_(mask_blur_style), diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents.h b/impeller/entity/contents/filters/gaussian_blur_filter_contents.h index b24c87f0205d6..1cc43cfaf5c5a 100644 --- a/impeller/entity/contents/filters/gaussian_blur_filter_contents.h +++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents.h @@ -55,12 +55,11 @@ GaussianBlurPipeline::FragmentShader::KernelSamples LerpHackKernelSamples( /// Note: This will replace `DirectionalGaussianBlurFilterContents`. class GaussianBlurFilterContents final : public FilterContents { public: - explicit GaussianBlurFilterContents( - Scalar sigma_x, - Scalar sigma_y, - Entity::TileMode tile_mode, - BlurStyle mask_blur_style, - const std::shared_ptr& mask_geometry); + explicit GaussianBlurFilterContents(Scalar sigma_x, + Scalar sigma_y, + Entity::TileMode tile_mode, + BlurStyle mask_blur_style, + const Geometry* mask_geometry = nullptr); Scalar GetSigmaX() const { return sigma_.x; } Scalar GetSigmaY() const { return sigma_.y; } @@ -117,7 +116,7 @@ class GaussianBlurFilterContents final : public FilterContents { const Vector2 sigma_ = Vector2(0.0, 0.0); const Entity::TileMode tile_mode_; const BlurStyle mask_blur_style_; - std::shared_ptr mask_geometry_; + const Geometry* mask_geometry_ = nullptr; }; } // namespace impeller diff --git a/impeller/entity/contents/linear_gradient_contents.cc b/impeller/entity/contents/linear_gradient_contents.cc index 28ea871b61830..73d533f2d7dbf 100644 --- a/impeller/entity/contents/linear_gradient_contents.cc +++ b/impeller/entity/contents/linear_gradient_contents.cc @@ -107,17 +107,17 @@ bool LinearGradientContents::FastLinearGradient(const ContentContext& renderer, using VS = FastGradientPipeline::VertexShader; using FS = FastGradientPipeline::FragmentShader; - Geometry& geometry = *GetGeometry(); - bool force_stencil = !geometry.IsAxisAlignedRect(); + const Geometry* geometry = GetGeometry(); + bool force_stencil = !geometry->IsAxisAlignedRect(); auto geom_callback = [&](const ContentContext& renderer, const Entity& entity, RenderPass& pass, - const Geometry& geometry) -> GeometryResult { + 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 maybe_rect = geometry.GetCoverage(Matrix()); + std::optional maybe_rect = geometry->GetCoverage(Matrix()); if (!maybe_rect.has_value()) { return {}; } diff --git a/impeller/entity/contents/solid_color_contents.cc b/impeller/entity/contents/solid_color_contents.cc index efddb125f6701..6fdd23fa69a88 100644 --- a/impeller/entity/contents/solid_color_contents.cc +++ b/impeller/entity/contents/solid_color_contents.cc @@ -38,7 +38,7 @@ std::optional SolidColorContents::GetCoverage( return std::nullopt; } - const std::shared_ptr& geometry = GetGeometry(); + const Geometry* geometry = GetGeometry(); if (geometry == nullptr) { return std::nullopt; } @@ -70,14 +70,6 @@ bool SolidColorContents::Render(const ContentContext& renderer, }); } -std::unique_ptr SolidColorContents::Make(const Path& path, - Color color) { - auto contents = std::make_unique(); - contents->SetGeometry(Geometry::MakeFillPath(path)); - contents->SetColor(color); - return contents; -} - std::optional SolidColorContents::AsBackgroundColor( const Entity& entity, ISize target_size) const { diff --git a/impeller/entity/contents/solid_color_contents.h b/impeller/entity/contents/solid_color_contents.h index af6d5048ec22e..c6982206f21a9 100644 --- a/impeller/entity/contents/solid_color_contents.h +++ b/impeller/entity/contents/solid_color_contents.h @@ -5,12 +5,9 @@ #ifndef FLUTTER_IMPELLER_ENTITY_CONTENTS_SOLID_COLOR_CONTENTS_H_ #define FLUTTER_IMPELLER_ENTITY_CONTENTS_SOLID_COLOR_CONTENTS_H_ -#include - #include "impeller/entity/contents/color_source_contents.h" #include "impeller/entity/contents/contents.h" #include "impeller/geometry/color.h" -#include "impeller/geometry/path.h" namespace impeller { @@ -20,9 +17,6 @@ class SolidColorContents final : public ColorSourceContents { ~SolidColorContents() override; - static std::unique_ptr Make(const Path& path, - Color color); - void SetColor(Color color); Color GetColor() const; diff --git a/impeller/entity/contents/tiled_texture_contents_unittests.cc b/impeller/entity/contents/tiled_texture_contents_unittests.cc index dd732e36212a0..ff306e531d784 100644 --- a/impeller/entity/contents/tiled_texture_contents_unittests.cc +++ b/impeller/entity/contents/tiled_texture_contents_unittests.cc @@ -27,9 +27,10 @@ TEST_P(EntityTest, TiledTextureContentsRendersWithCorrectPipeline) { auto texture = GetContext()->GetResourceAllocator()->CreateTexture(texture_desc); + auto geom = Geometry::MakeCover(); TiledTextureContents contents; contents.SetTexture(texture); - contents.SetGeometry(Geometry::MakeCover()); + contents.SetGeometry(geom.get()); auto content_context = GetContentContext(); auto buffer = content_context->GetContext()->CreateCommandBuffer(); diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index b3f9b9940b4f8..16ad4624b9a50 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -62,2347 +62,2359 @@ namespace impeller { namespace testing { -using EntityTest = EntityPlayground; -INSTANTIATE_PLAYGROUND_SUITE(EntityTest); - -TEST_P(EntityTest, CanCreateEntity) { - Entity entity; - ASSERT_TRUE(entity.GetTransform().IsIdentity()); -} - -TEST_P(EntityTest, FilterCoverageRespectsCropRect) { - auto image = CreateTextureForFixture("boston.jpg"); - auto filter = ColorFilterContents::MakeBlend(BlendMode::kSoftLight, - FilterInput::Make({image})); - - // Without the crop rect (default behavior). - { - auto actual = filter->GetCoverage({}); - auto expected = Rect::MakeSize(image->GetSize()); - - ASSERT_TRUE(actual.has_value()); - ASSERT_RECT_NEAR(actual.value(), expected); - } - - // With the crop rect. - { - auto expected = Rect::MakeLTRB(50, 50, 100, 100); - filter->SetCoverageHint(expected); - auto actual = filter->GetCoverage({}); - - ASSERT_TRUE(actual.has_value()); - ASSERT_RECT_NEAR(actual.value(), expected); - } -} - -TEST_P(EntityTest, CanDrawRect) { - auto contents = std::make_shared(); - contents->SetGeometry(Geometry::MakeRect(Rect::MakeXYWH(100, 100, 100, 100))); - contents->SetColor(Color::Red()); - - Entity entity; - entity.SetTransform(Matrix::MakeScale(GetContentScale())); - entity.SetContents(contents); - - ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); -} - -TEST_P(EntityTest, CanDrawRRect) { - auto contents = std::make_shared(); - auto path = PathBuilder{} - .SetConvexity(Convexity::kConvex) - .AddRoundedRect(Rect::MakeXYWH(100, 100, 100, 100), 10.0) - .TakePath(); - contents->SetGeometry(Geometry::MakeFillPath(path)); - contents->SetColor(Color::Red()); - - Entity entity; - entity.SetTransform(Matrix::MakeScale(GetContentScale())); - entity.SetContents(contents); - - ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); -} - -TEST_P(EntityTest, GeometryBoundsAreTransformed) { - auto geometry = Geometry::MakeRect(Rect::MakeXYWH(100, 100, 100, 100)); - auto transform = Matrix::MakeScale({2.0, 2.0, 2.0}); - - ASSERT_RECT_NEAR(geometry->GetCoverage(transform).value(), - Rect::MakeXYWH(200, 200, 200, 200)); -} - -TEST_P(EntityTest, ThreeStrokesInOnePath) { - Path path = PathBuilder{} - .MoveTo({100, 100}) - .LineTo({100, 200}) - .MoveTo({100, 300}) - .LineTo({100, 400}) - .MoveTo({100, 500}) - .LineTo({100, 600}) - .TakePath(); - - Entity entity; - entity.SetTransform(Matrix::MakeScale(GetContentScale())); - auto contents = std::make_unique(); - contents->SetGeometry(Geometry::MakeStrokePath(path, 5.0)); - contents->SetColor(Color::Red()); - entity.SetContents(std::move(contents)); - ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); -} - -TEST_P(EntityTest, StrokeWithTextureContents) { - auto bridge = CreateTextureForFixture("bay_bridge.jpg"); - Path path = PathBuilder{} - .MoveTo({100, 100}) - .LineTo({100, 200}) - .MoveTo({100, 300}) - .LineTo({100, 400}) - .MoveTo({100, 500}) - .LineTo({100, 600}) - .TakePath(); - - Entity entity; - entity.SetTransform(Matrix::MakeScale(GetContentScale())); - auto contents = std::make_unique(); - contents->SetGeometry(Geometry::MakeStrokePath(path, 100.0)); - contents->SetTexture(bridge); - contents->SetTileModes(Entity::TileMode::kClamp, Entity::TileMode::kClamp); - entity.SetContents(std::move(contents)); - ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); -} - -TEST_P(EntityTest, TriangleInsideASquare) { - auto callback = [&](ContentContext& context, RenderPass& pass) { - Point offset(100, 100); - - static PlaygroundPoint point_a(Point(10, 10) + offset, 20, Color::White()); - Point a = DrawPlaygroundPoint(point_a); - static PlaygroundPoint point_b(Point(210, 10) + offset, 20, Color::White()); - Point b = DrawPlaygroundPoint(point_b); - static PlaygroundPoint point_c(Point(210, 210) + offset, 20, - Color::White()); - Point c = DrawPlaygroundPoint(point_c); - static PlaygroundPoint point_d(Point(10, 210) + offset, 20, Color::White()); - Point d = DrawPlaygroundPoint(point_d); - static PlaygroundPoint point_e(Point(50, 50) + offset, 20, Color::White()); - Point e = DrawPlaygroundPoint(point_e); - static PlaygroundPoint point_f(Point(100, 50) + offset, 20, Color::White()); - Point f = DrawPlaygroundPoint(point_f); - static PlaygroundPoint point_g(Point(50, 150) + offset, 20, Color::White()); - Point g = DrawPlaygroundPoint(point_g); - Path path = PathBuilder{} - .MoveTo(a) - .LineTo(b) - .LineTo(c) - .LineTo(d) - .Close() - .MoveTo(e) - .LineTo(f) - .LineTo(g) - .Close() - .TakePath(); - - Entity entity; - entity.SetTransform(Matrix::MakeScale(GetContentScale())); - auto contents = std::make_unique(); - contents->SetGeometry(Geometry::MakeStrokePath(path, 20.0)); - contents->SetColor(Color::Red()); - entity.SetContents(std::move(contents)); - - return entity.Render(context, pass); - }; - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(EntityTest, StrokeCapAndJoinTest) { - const Point padding(300, 250); - const Point margin(140, 180); - - auto callback = [&](ContentContext& context, RenderPass& pass) { - // Slightly above sqrt(2) by default, so that right angles are just below - // the limit and acute angles are over the limit (causing them to get - // beveled). - static Scalar miter_limit = 1.41421357; - static Scalar width = 30; - - ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); - { - ImGui::SliderFloat("Miter limit", &miter_limit, 0, 30); - ImGui::SliderFloat("Stroke width", &width, 0, 100); - if (ImGui::Button("Reset")) { - miter_limit = 1.41421357; - width = 30; - } - } - ImGui::End(); - - auto world_matrix = Matrix::MakeScale(GetContentScale()); - auto render_path = [width = width, &context, &pass, &world_matrix]( - const Path& path, Cap cap, Join join) { - auto contents = std::make_unique(); - contents->SetGeometry( - Geometry::MakeStrokePath(path, width, miter_limit, cap, join)); - contents->SetColor(Color::Red()); - - Entity entity; - entity.SetTransform(world_matrix); - entity.SetContents(std::move(contents)); - - auto coverage = entity.GetCoverage(); - if (coverage.has_value()) { - auto bounds_contents = std::make_unique(); - bounds_contents->SetGeometry(Geometry::MakeFillPath( - PathBuilder{}.AddRect(entity.GetCoverage().value()).TakePath())); - bounds_contents->SetColor(Color::Green().WithAlpha(0.5)); - Entity bounds_entity; - bounds_entity.SetContents(std::move(bounds_contents)); - bounds_entity.Render(context, pass); - } - - entity.Render(context, pass); - }; - - const Point a_def(0, 0), b_def(0, 100), c_def(150, 0), d_def(150, -100), - e_def(75, 75); - const Scalar r = 30; - // Cap::kButt demo. - { - Point off = Point(0, 0) * padding + margin; - static PlaygroundPoint point_a(off + a_def, r, Color::Black()); - static PlaygroundPoint point_b(off + b_def, r, Color::White()); - auto [a, b] = DrawPlaygroundLine(point_a, point_b); - static PlaygroundPoint point_c(off + c_def, r, Color::Black()); - static PlaygroundPoint point_d(off + d_def, r, Color::White()); - auto [c, d] = DrawPlaygroundLine(point_c, point_d); - render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(), - Cap::kButt, Join::kBevel); - } - - // Cap::kSquare demo. - { - Point off = Point(1, 0) * padding + margin; - static PlaygroundPoint point_a(off + a_def, r, Color::Black()); - static PlaygroundPoint point_b(off + b_def, r, Color::White()); - auto [a, b] = DrawPlaygroundLine(point_a, point_b); - static PlaygroundPoint point_c(off + c_def, r, Color::Black()); - static PlaygroundPoint point_d(off + d_def, r, Color::White()); - auto [c, d] = DrawPlaygroundLine(point_c, point_d); - render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(), - Cap::kSquare, Join::kBevel); - } - - // Cap::kRound demo. - { - Point off = Point(2, 0) * padding + margin; - static PlaygroundPoint point_a(off + a_def, r, Color::Black()); - static PlaygroundPoint point_b(off + b_def, r, Color::White()); - auto [a, b] = DrawPlaygroundLine(point_a, point_b); - static PlaygroundPoint point_c(off + c_def, r, Color::Black()); - static PlaygroundPoint point_d(off + d_def, r, Color::White()); - auto [c, d] = DrawPlaygroundLine(point_c, point_d); - render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(), - Cap::kRound, Join::kBevel); - } - - // Join::kBevel demo. - { - Point off = Point(0, 1) * padding + margin; - static PlaygroundPoint point_a = - PlaygroundPoint(off + a_def, r, Color::White()); - static PlaygroundPoint point_b = - PlaygroundPoint(off + e_def, r, Color::White()); - static PlaygroundPoint point_c = - PlaygroundPoint(off + c_def, r, Color::White()); - Point a = DrawPlaygroundPoint(point_a); - Point b = DrawPlaygroundPoint(point_b); - Point c = DrawPlaygroundPoint(point_c); - render_path( - PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(), - Cap::kButt, Join::kBevel); - } - - // Join::kMiter demo. - { - Point off = Point(1, 1) * padding + margin; - static PlaygroundPoint point_a(off + a_def, r, Color::White()); - static PlaygroundPoint point_b(off + e_def, r, Color::White()); - static PlaygroundPoint point_c(off + c_def, r, Color::White()); - Point a = DrawPlaygroundPoint(point_a); - Point b = DrawPlaygroundPoint(point_b); - Point c = DrawPlaygroundPoint(point_c); - render_path( - PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(), - Cap::kButt, Join::kMiter); - } - - // Join::kRound demo. - { - Point off = Point(2, 1) * padding + margin; - static PlaygroundPoint point_a(off + a_def, r, Color::White()); - static PlaygroundPoint point_b(off + e_def, r, Color::White()); - static PlaygroundPoint point_c(off + c_def, r, Color::White()); - Point a = DrawPlaygroundPoint(point_a); - Point b = DrawPlaygroundPoint(point_b); - Point c = DrawPlaygroundPoint(point_c); - render_path( - PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(), - Cap::kButt, Join::kRound); - } - - return true; - }; - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(EntityTest, CubicCurveTest) { - // Compare with https://fiddle.skia.org/c/b3625f26122c9de7afe7794fcf25ead3 - Path path = - PathBuilder{} - .MoveTo({237.164, 125.003}) - .CubicCurveTo({236.709, 125.184}, {236.262, 125.358}, - {235.81, 125.538}) - .CubicCurveTo({235.413, 125.68}, {234.994, 125.832}, - {234.592, 125.977}) - .CubicCurveTo({234.592, 125.977}, {234.591, 125.977}, - {234.59, 125.977}) - .CubicCurveTo({222.206, 130.435}, {207.708, 135.753}, - {192.381, 141.429}) - .CubicCurveTo({162.77, 151.336}, {122.17, 156.894}, {84.1123, 160}) - .Close() - .TakePath(); - Entity entity; - entity.SetTransform(Matrix::MakeScale(GetContentScale())); - entity.SetContents(SolidColorContents::Make(path, Color::Red())); - ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); -} - -TEST_P(EntityTest, CanDrawCorrectlyWithRotatedTransform) { - auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { - const char* input_axis[] = {"X", "Y", "Z"}; - static int rotation_axis_index = 0; - static float rotation = 0; - ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); - ImGui::SliderFloat("Rotation", &rotation, -kPi, kPi); - ImGui::Combo("Rotation Axis", &rotation_axis_index, input_axis, - sizeof(input_axis) / sizeof(char*)); - Matrix rotation_matrix; - switch (rotation_axis_index) { - case 0: - rotation_matrix = Matrix::MakeRotationX(Radians(rotation)); - break; - case 1: - rotation_matrix = Matrix::MakeRotationY(Radians(rotation)); - break; - case 2: - rotation_matrix = Matrix::MakeRotationZ(Radians(rotation)); - break; - default: - rotation_matrix = Matrix{}; - break; - } - - if (ImGui::Button("Reset")) { - rotation = 0; - } - ImGui::End(); - Matrix current_transform = - Matrix::MakeScale(GetContentScale()) - .MakeTranslation( - Vector3(Point(pass.GetRenderTargetSize().width / 2.0, - pass.GetRenderTargetSize().height / 2.0))); - Matrix result_transform = current_transform * rotation_matrix; - Path path = - PathBuilder{}.AddRect(Rect::MakeXYWH(-300, -400, 600, 800)).TakePath(); - - Entity entity; - entity.SetTransform(result_transform); - entity.SetContents(SolidColorContents::Make(path, Color::Red())); - return entity.Render(context, pass); - }; - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(EntityTest, CubicCurveAndOverlapTest) { - // Compare with https://fiddle.skia.org/c/7a05a3e186c65a8dfb732f68020aae06 - Path path = - PathBuilder{} - .MoveTo({359.934, 96.6335}) - .CubicCurveTo({358.189, 96.7055}, {356.436, 96.7908}, - {354.673, 96.8895}) - .CubicCurveTo({354.571, 96.8953}, {354.469, 96.9016}, - {354.367, 96.9075}) - .CubicCurveTo({352.672, 97.0038}, {350.969, 97.113}, - {349.259, 97.2355}) - .CubicCurveTo({349.048, 97.2506}, {348.836, 97.2678}, - {348.625, 97.2834}) - .CubicCurveTo({347.019, 97.4014}, {345.407, 97.5299}, - {343.789, 97.6722}) - .CubicCurveTo({343.428, 97.704}, {343.065, 97.7402}, - {342.703, 97.7734}) - .CubicCurveTo({341.221, 97.9086}, {339.736, 98.0505}, - {338.246, 98.207}) - .CubicCurveTo({337.702, 98.2642}, {337.156, 98.3292}, - {336.612, 98.3894}) - .CubicCurveTo({335.284, 98.5356}, {333.956, 98.6837}, - {332.623, 98.8476}) - .CubicCurveTo({332.495, 98.8635}, {332.366, 98.8818}, - {332.237, 98.8982}) - .LineTo({332.237, 102.601}) - .LineTo({321.778, 102.601}) - .LineTo({321.778, 100.382}) - .CubicCurveTo({321.572, 100.413}, {321.367, 100.442}, - {321.161, 100.476}) - .CubicCurveTo({319.22, 100.79}, {317.277, 101.123}, - {315.332, 101.479}) - .CubicCurveTo({315.322, 101.481}, {315.311, 101.482}, - {315.301, 101.484}) - .LineTo({310.017, 105.94}) - .LineTo({309.779, 105.427}) - .LineTo({314.403, 101.651}) - .CubicCurveTo({314.391, 101.653}, {314.379, 101.656}, - {314.368, 101.658}) - .CubicCurveTo({312.528, 102.001}, {310.687, 102.366}, - {308.846, 102.748}) - .CubicCurveTo({307.85, 102.955}, {306.855, 103.182}, {305.859, 103.4}) - .CubicCurveTo({305.048, 103.579}, {304.236, 103.75}, - {303.425, 103.936}) - .LineTo({299.105, 107.578}) - .LineTo({298.867, 107.065}) - .LineTo({302.394, 104.185}) - .LineTo({302.412, 104.171}) - .CubicCurveTo({301.388, 104.409}, {300.366, 104.67}, - {299.344, 104.921}) - .CubicCurveTo({298.618, 105.1}, {297.89, 105.269}, {297.165, 105.455}) - .CubicCurveTo({295.262, 105.94}, {293.36, 106.445}, - {291.462, 106.979}) - .CubicCurveTo({291.132, 107.072}, {290.802, 107.163}, - {290.471, 107.257}) - .CubicCurveTo({289.463, 107.544}, {288.455, 107.839}, - {287.449, 108.139}) - .CubicCurveTo({286.476, 108.431}, {285.506, 108.73}, - {284.536, 109.035}) - .CubicCurveTo({283.674, 109.304}, {282.812, 109.579}, - {281.952, 109.859}) - .CubicCurveTo({281.177, 110.112}, {280.406, 110.377}, - {279.633, 110.638}) - .CubicCurveTo({278.458, 111.037}, {277.256, 111.449}, - {276.803, 111.607}) - .CubicCurveTo({276.76, 111.622}, {276.716, 111.637}, - {276.672, 111.653}) - .CubicCurveTo({275.017, 112.239}, {273.365, 112.836}, - {271.721, 113.463}) - .LineTo({271.717, 113.449}) - .CubicCurveTo({271.496, 113.496}, {271.238, 113.559}, - {270.963, 113.628}) - .CubicCurveTo({270.893, 113.645}, {270.822, 113.663}, - {270.748, 113.682}) - .CubicCurveTo({270.468, 113.755}, {270.169, 113.834}, - {269.839, 113.926}) - .CubicCurveTo({269.789, 113.94}, {269.732, 113.957}, - {269.681, 113.972}) - .CubicCurveTo({269.391, 114.053}, {269.081, 114.143}, - {268.756, 114.239}) - .CubicCurveTo({268.628, 114.276}, {268.5, 114.314}, - {268.367, 114.354}) - .CubicCurveTo({268.172, 114.412}, {267.959, 114.478}, - {267.752, 114.54}) - .CubicCurveTo({263.349, 115.964}, {258.058, 117.695}, - {253.564, 119.252}) - .CubicCurveTo({253.556, 119.255}, {253.547, 119.258}, - {253.538, 119.261}) - .CubicCurveTo({251.844, 119.849}, {250.056, 120.474}, - {248.189, 121.131}) - .CubicCurveTo({248, 121.197}, {247.812, 121.264}, {247.621, 121.331}) - .CubicCurveTo({247.079, 121.522}, {246.531, 121.715}, - {245.975, 121.912}) - .CubicCurveTo({245.554, 122.06}, {245.126, 122.212}, - {244.698, 122.364}) - .CubicCurveTo({244.071, 122.586}, {243.437, 122.811}, - {242.794, 123.04}) - .CubicCurveTo({242.189, 123.255}, {241.58, 123.472}, - {240.961, 123.693}) - .CubicCurveTo({240.659, 123.801}, {240.357, 123.909}, - {240.052, 124.018}) - .CubicCurveTo({239.12, 124.351}, {238.18, 124.687}, {237.22, 125.032}) - .LineTo({237.164, 125.003}) - .CubicCurveTo({236.709, 125.184}, {236.262, 125.358}, - {235.81, 125.538}) - .CubicCurveTo({235.413, 125.68}, {234.994, 125.832}, - {234.592, 125.977}) - .CubicCurveTo({234.592, 125.977}, {234.591, 125.977}, - {234.59, 125.977}) - .CubicCurveTo({222.206, 130.435}, {207.708, 135.753}, - {192.381, 141.429}) - .CubicCurveTo({162.77, 151.336}, {122.17, 156.894}, {84.1123, 160}) - .LineTo({360, 160}) - .LineTo({360, 119.256}) - .LineTo({360, 106.332}) - .LineTo({360, 96.6307}) - .CubicCurveTo({359.978, 96.6317}, {359.956, 96.6326}, - {359.934, 96.6335}) - .Close() - .MoveTo({337.336, 124.143}) - .CubicCurveTo({337.274, 122.359}, {338.903, 121.511}, - {338.903, 121.511}) - .CubicCurveTo({338.903, 121.511}, {338.96, 123.303}, - {337.336, 124.143}) - .Close() - .MoveTo({340.082, 121.849}) - .CubicCurveTo({340.074, 121.917}, {340.062, 121.992}, - {340.046, 122.075}) - .CubicCurveTo({340.039, 122.109}, {340.031, 122.142}, - {340.023, 122.177}) - .CubicCurveTo({340.005, 122.26}, {339.98, 122.346}, - {339.952, 122.437}) - .CubicCurveTo({339.941, 122.473}, {339.931, 122.507}, - {339.918, 122.544}) - .CubicCurveTo({339.873, 122.672}, {339.819, 122.804}, - {339.75, 122.938}) - .CubicCurveTo({339.747, 122.944}, {339.743, 122.949}, - {339.74, 122.955}) - .CubicCurveTo({339.674, 123.08}, {339.593, 123.205}, - {339.501, 123.328}) - .CubicCurveTo({339.473, 123.366}, {339.441, 123.401}, - {339.41, 123.438}) - .CubicCurveTo({339.332, 123.534}, {339.243, 123.625}, - {339.145, 123.714}) - .CubicCurveTo({339.105, 123.75}, {339.068, 123.786}, - {339.025, 123.821}) - .CubicCurveTo({338.881, 123.937}, {338.724, 124.048}, - {338.539, 124.143}) - .CubicCurveTo({338.532, 123.959}, {338.554, 123.79}, - {338.58, 123.626}) - .CubicCurveTo({338.58, 123.625}, {338.58, 123.625}, {338.58, 123.625}) - .CubicCurveTo({338.607, 123.455}, {338.65, 123.299}, - {338.704, 123.151}) - .CubicCurveTo({338.708, 123.14}, {338.71, 123.127}, - {338.714, 123.117}) - .CubicCurveTo({338.769, 122.971}, {338.833, 122.838}, - {338.905, 122.712}) - .CubicCurveTo({338.911, 122.702}, {338.916, 122.69200000000001}, - {338.922, 122.682}) - .CubicCurveTo({338.996, 122.557}, {339.072, 122.444}, - {339.155, 122.34}) - .CubicCurveTo({339.161, 122.333}, {339.166, 122.326}, - {339.172, 122.319}) - .CubicCurveTo({339.256, 122.215}, {339.339, 122.12}, - {339.425, 122.037}) - .CubicCurveTo({339.428, 122.033}, {339.431, 122.03}, - {339.435, 122.027}) - .CubicCurveTo({339.785, 121.687}, {340.106, 121.511}, - {340.106, 121.511}) - .CubicCurveTo({340.106, 121.511}, {340.107, 121.645}, - {340.082, 121.849}) - .Close() - .MoveTo({340.678, 113.245}) - .CubicCurveTo({340.594, 113.488}, {340.356, 113.655}, - {340.135, 113.775}) - .CubicCurveTo({339.817, 113.948}, {339.465, 114.059}, - {339.115, 114.151}) - .CubicCurveTo({338.251, 114.379}, {337.34, 114.516}, - {336.448, 114.516}) - .CubicCurveTo({335.761, 114.516}, {335.072, 114.527}, - {334.384, 114.513}) - .CubicCurveTo({334.125, 114.508}, {333.862, 114.462}, - {333.605, 114.424}) - .CubicCurveTo({332.865, 114.318}, {332.096, 114.184}, - {331.41, 113.883}) - .CubicCurveTo({330.979, 113.695}, {330.442, 113.34}, - {330.672, 112.813}) - .CubicCurveTo({331.135, 111.755}, {333.219, 112.946}, - {334.526, 113.833}) - .CubicCurveTo({334.54, 113.816}, {334.554, 113.8}, {334.569, 113.784}) - .CubicCurveTo({333.38, 112.708}, {331.749, 110.985}, - {332.76, 110.402}) - .CubicCurveTo({333.769, 109.82}, {334.713, 111.93}, - {335.228, 113.395}) - .CubicCurveTo({334.915, 111.889}, {334.59, 109.636}, - {335.661, 109.592}) - .CubicCurveTo({336.733, 109.636}, {336.408, 111.889}, - {336.07, 113.389}) - .CubicCurveTo({336.609, 111.93}, {337.553, 109.82}, - {338.563, 110.402}) - .CubicCurveTo({339.574, 110.984}, {337.942, 112.708}, - {336.753, 113.784}) - .CubicCurveTo({336.768, 113.8}, {336.782, 113.816}, - {336.796, 113.833}) - .CubicCurveTo({338.104, 112.946}, {340.187, 111.755}, - {340.65, 112.813}) - .CubicCurveTo({340.71, 112.95}, {340.728, 113.102}, - {340.678, 113.245}) - .Close() - .MoveTo({346.357, 106.771}) - .CubicCurveTo({346.295, 104.987}, {347.924, 104.139}, - {347.924, 104.139}) - .CubicCurveTo({347.924, 104.139}, {347.982, 105.931}, - {346.357, 106.771}) - .Close() - .MoveTo({347.56, 106.771}) - .CubicCurveTo({347.498, 104.987}, {349.127, 104.139}, - {349.127, 104.139}) - .CubicCurveTo({349.127, 104.139}, {349.185, 105.931}, - {347.56, 106.771}) - .Close() - .TakePath(); - Entity entity; - entity.SetTransform(Matrix::MakeScale(GetContentScale())); - entity.SetContents(SolidColorContents::Make(path, Color::Red())); - ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); -} - -TEST_P(EntityTest, SolidColorContentsStrokeSetStrokeCapsAndJoins) { - { - auto geometry = Geometry::MakeStrokePath(Path{}); - auto path_geometry = static_cast(geometry.get()); - // Defaults. - ASSERT_EQ(path_geometry->GetStrokeCap(), Cap::kButt); - ASSERT_EQ(path_geometry->GetStrokeJoin(), Join::kMiter); - } - - { - auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, 4.0, Cap::kSquare); - auto path_geometry = static_cast(geometry.get()); - ASSERT_EQ(path_geometry->GetStrokeCap(), Cap::kSquare); - } - - { - auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, 4.0, Cap::kRound); - auto path_geometry = static_cast(geometry.get()); - ASSERT_EQ(path_geometry->GetStrokeCap(), Cap::kRound); - } -} - -TEST_P(EntityTest, SolidColorContentsStrokeSetMiterLimit) { - { - auto geometry = Geometry::MakeStrokePath(Path{}); - auto path_geometry = static_cast(geometry.get()); - ASSERT_FLOAT_EQ(path_geometry->GetMiterLimit(), 4); - } - - { - auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, /*miter_limit=*/8.0); - auto path_geometry = static_cast(geometry.get()); - ASSERT_FLOAT_EQ(path_geometry->GetMiterLimit(), 8); - } - - { - auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, /*miter_limit=*/-1.0); - auto path_geometry = static_cast(geometry.get()); - ASSERT_FLOAT_EQ(path_geometry->GetMiterLimit(), 4); - } -} - -TEST_P(EntityTest, BlendingModeOptions) { - std::vector blend_mode_names; - std::vector blend_mode_values; - { - // Force an exhausiveness check with a switch. When adding blend modes, - // update this switch with a new name/value to make it selectable in the - // test GUI. - - const BlendMode b{}; - static_assert(b == BlendMode::kClear); // Ensure the first item in - // the switch is the first - // item in the enum. - static_assert(Entity::kLastPipelineBlendMode == BlendMode::kModulate); - switch (b) { - case BlendMode::kClear: - blend_mode_names.push_back("Clear"); - blend_mode_values.push_back(BlendMode::kClear); - case BlendMode::kSource: - blend_mode_names.push_back("Source"); - blend_mode_values.push_back(BlendMode::kSource); - case BlendMode::kDestination: - blend_mode_names.push_back("Destination"); - blend_mode_values.push_back(BlendMode::kDestination); - case BlendMode::kSourceOver: - blend_mode_names.push_back("SourceOver"); - blend_mode_values.push_back(BlendMode::kSourceOver); - case BlendMode::kDestinationOver: - blend_mode_names.push_back("DestinationOver"); - blend_mode_values.push_back(BlendMode::kDestinationOver); - case BlendMode::kSourceIn: - blend_mode_names.push_back("SourceIn"); - blend_mode_values.push_back(BlendMode::kSourceIn); - case BlendMode::kDestinationIn: - blend_mode_names.push_back("DestinationIn"); - blend_mode_values.push_back(BlendMode::kDestinationIn); - case BlendMode::kSourceOut: - blend_mode_names.push_back("SourceOut"); - blend_mode_values.push_back(BlendMode::kSourceOut); - case BlendMode::kDestinationOut: - blend_mode_names.push_back("DestinationOut"); - blend_mode_values.push_back(BlendMode::kDestinationOut); - case BlendMode::kSourceATop: - blend_mode_names.push_back("SourceATop"); - blend_mode_values.push_back(BlendMode::kSourceATop); - case BlendMode::kDestinationATop: - blend_mode_names.push_back("DestinationATop"); - blend_mode_values.push_back(BlendMode::kDestinationATop); - case BlendMode::kXor: - blend_mode_names.push_back("Xor"); - blend_mode_values.push_back(BlendMode::kXor); - case BlendMode::kPlus: - blend_mode_names.push_back("Plus"); - blend_mode_values.push_back(BlendMode::kPlus); - case BlendMode::kModulate: - blend_mode_names.push_back("Modulate"); - blend_mode_values.push_back(BlendMode::kModulate); - }; - } - - auto callback = [&](ContentContext& context, RenderPass& pass) { - auto world_matrix = Matrix::MakeScale(GetContentScale()); - auto draw_rect = [&context, &pass, &world_matrix]( - Rect rect, Color color, BlendMode blend_mode) -> bool { - using VS = SolidFillPipeline::VertexShader; - using FS = SolidFillPipeline::FragmentShader; - - VertexBufferBuilder vtx_builder; - { - auto r = rect.GetLTRB(); - vtx_builder.AddVertices({ - {Point(r[0], r[1])}, - {Point(r[2], r[1])}, - {Point(r[2], r[3])}, - {Point(r[0], r[1])}, - {Point(r[2], r[3])}, - {Point(r[0], r[3])}, - }); - } - - pass.SetCommandLabel("Blended Rectangle"); - auto options = OptionsFromPass(pass); - options.blend_mode = blend_mode; - options.primitive_type = PrimitiveType::kTriangle; - pass.SetPipeline(context.GetSolidFillPipeline(options)); - pass.SetVertexBuffer( - vtx_builder.CreateVertexBuffer(context.GetTransientsBuffer())); - - VS::FrameInfo frame_info; - frame_info.mvp = pass.GetOrthographicTransform() * world_matrix; - VS::BindFrameInfo( - pass, context.GetTransientsBuffer().EmplaceUniform(frame_info)); - FS::FragInfo frag_info; - frag_info.color = color.Premultiply(); - FS::BindFragInfo( - pass, context.GetTransientsBuffer().EmplaceUniform(frame_info)); - return pass.Draw().ok(); - }; - - ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); - static Color color1(1, 0, 0, 0.5), color2(0, 1, 0, 0.5); - ImGui::ColorEdit4("Color 1", reinterpret_cast(&color1)); - ImGui::ColorEdit4("Color 2", reinterpret_cast(&color2)); - static int current_blend_index = 3; - ImGui::ListBox("Blending mode", ¤t_blend_index, - blend_mode_names.data(), blend_mode_names.size()); - ImGui::End(); - - BlendMode selected_mode = blend_mode_values[current_blend_index]; - - Point a, b, c, d; - static PlaygroundPoint point_a(Point(400, 100), 20, Color::White()); - static PlaygroundPoint point_b(Point(200, 300), 20, Color::White()); - std::tie(a, b) = DrawPlaygroundLine(point_a, point_b); - static PlaygroundPoint point_c(Point(470, 190), 20, Color::White()); - static PlaygroundPoint point_d(Point(270, 390), 20, Color::White()); - std::tie(c, d) = DrawPlaygroundLine(point_c, point_d); - - bool result = true; - result = result && - draw_rect(Rect::MakeXYWH(0, 0, pass.GetRenderTargetSize().width, - pass.GetRenderTargetSize().height), - Color(), BlendMode::kClear); - result = result && draw_rect(Rect::MakeLTRB(a.x, a.y, b.x, b.y), color1, - BlendMode::kSourceOver); - result = result && draw_rect(Rect::MakeLTRB(c.x, c.y, d.x, d.y), color2, - selected_mode); - return result; - }; - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(EntityTest, BezierCircleScaled) { - auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { - static float scale = 20; - - ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); - ImGui::SliderFloat("Scale", &scale, 1, 100); - ImGui::End(); - - Entity entity; - entity.SetTransform(Matrix::MakeScale(GetContentScale())); - auto path = PathBuilder{} - .MoveTo({97.325, 34.818}) - .CubicCurveTo({98.50862885295136, 34.81812293973836}, - {99.46822048142015, 33.85863261475589}, - {99.46822048142015, 32.67499810206613}) - .CubicCurveTo({99.46822048142015, 31.491363589376355}, - {98.50862885295136, 30.53187326439389}, - {97.32499434685802, 30.531998226542708}) - .CubicCurveTo({96.14153655073771, 30.532123170035373}, - {95.18222070648729, 31.491540299350355}, - {95.18222070648729, 32.67499810206613}) - .CubicCurveTo({95.18222070648729, 33.85845590478189}, - {96.14153655073771, 34.81787303409686}, - {97.32499434685802, 34.81799797758954}) - .Close() - .TakePath(); - entity.SetTransform( - Matrix::MakeScale({scale, scale, 1.0}).Translate({-90, -20, 0})); - entity.SetContents(SolidColorContents::Make(path, Color::Red())); - return entity.Render(context, pass); - }; - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(EntityTest, Filters) { - auto bridge = CreateTextureForFixture("bay_bridge.jpg"); - auto boston = CreateTextureForFixture("boston.jpg"); - auto kalimba = CreateTextureForFixture("kalimba.jpg"); - ASSERT_TRUE(bridge && boston && kalimba); - - auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { - auto fi_bridge = FilterInput::Make(bridge); - auto fi_boston = FilterInput::Make(boston); - auto fi_kalimba = FilterInput::Make(kalimba); - - std::shared_ptr blend0 = ColorFilterContents::MakeBlend( - BlendMode::kModulate, {fi_kalimba, fi_boston}); - - auto blend1 = ColorFilterContents::MakeBlend( - BlendMode::kScreen, - {FilterInput::Make(blend0), fi_bridge, fi_bridge, fi_bridge}); - - Entity entity; - entity.SetTransform(Matrix::MakeScale(GetContentScale()) * - Matrix::MakeTranslation({500, 300}) * - Matrix::MakeScale(Vector2{0.5, 0.5})); - entity.SetContents(blend1); - return entity.Render(context, pass); - }; - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(EntityTest, GaussianBlurFilter) { - auto boston = - CreateTextureForFixture("boston.jpg", /*enable_mipmapping=*/true); - ASSERT_TRUE(boston); - - auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { - const char* input_type_names[] = {"Texture", "Solid Color"}; - const char* blur_type_names[] = {"Image blur", "Mask blur"}; - const char* pass_variation_names[] = {"New"}; - const char* blur_style_names[] = {"Normal", "Solid", "Outer", "Inner"}; - const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"}; - const FilterContents::BlurStyle blur_styles[] = { - FilterContents::BlurStyle::kNormal, FilterContents::BlurStyle::kSolid, - FilterContents::BlurStyle::kOuter, FilterContents::BlurStyle::kInner}; - const Entity::TileMode tile_modes[] = { - Entity::TileMode::kClamp, Entity::TileMode::kRepeat, - Entity::TileMode::kMirror, Entity::TileMode::kDecal}; - - // UI state. - static int selected_input_type = 0; - static Color input_color = Color::Black(); - static int selected_blur_type = 0; - static int selected_pass_variation = 0; - static bool combined_sigma = false; - static float blur_amount_coarse[2] = {0, 0}; - static float blur_amount_fine[2] = {10, 10}; - static int selected_blur_style = 0; - static int selected_tile_mode = 3; - static Color cover_color(1, 0, 0, 0.2); - static Color bounds_color(0, 1, 0, 0.1); - static float offset[2] = {500, 400}; - static float rotation = 0; - static float scale[2] = {0.65, 0.65}; - static float skew[2] = {0, 0}; - static float path_rect[4] = {0, 0, - static_cast(boston->GetSize().width), - static_cast(boston->GetSize().height)}; - - ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); - { - ImGui::Combo("Input type", &selected_input_type, input_type_names, - sizeof(input_type_names) / sizeof(char*)); - if (selected_input_type == 0) { - ImGui::SliderFloat("Input opacity", &input_color.alpha, 0, 1); - } else { - ImGui::ColorEdit4("Input color", - reinterpret_cast(&input_color)); - } - ImGui::Combo("Blur type", &selected_blur_type, blur_type_names, - sizeof(blur_type_names) / sizeof(char*)); - if (selected_blur_type == 0) { - ImGui::Combo("Pass variation", &selected_pass_variation, - pass_variation_names, - sizeof(pass_variation_names) / sizeof(char*)); - } - ImGui::Checkbox("Combined sigma", &combined_sigma); - if (combined_sigma) { - ImGui::SliderFloat("Sigma (coarse)", blur_amount_coarse, 0, 1000); - ImGui::SliderFloat("Sigma (fine)", blur_amount_fine, 0, 10); - blur_amount_coarse[1] = blur_amount_coarse[0]; - blur_amount_fine[1] = blur_amount_fine[0]; - } else { - ImGui::SliderFloat2("Sigma (coarse)", blur_amount_coarse, 0, 1000); - ImGui::SliderFloat2("Sigma (fine)", blur_amount_fine, 0, 10); - } - ImGui::Combo("Blur style", &selected_blur_style, blur_style_names, - sizeof(blur_style_names) / sizeof(char*)); - ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names, - sizeof(tile_mode_names) / sizeof(char*)); - ImGui::ColorEdit4("Cover color", reinterpret_cast(&cover_color)); - ImGui::ColorEdit4("Bounds color", - reinterpret_cast(&bounds_color)); - ImGui::SliderFloat2("Translation", offset, 0, - pass.GetRenderTargetSize().width); - ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2); - ImGui::SliderFloat2("Scale", scale, 0, 3); - ImGui::SliderFloat2("Skew", skew, -3, 3); - ImGui::SliderFloat4("Path XYWH", path_rect, -1000, 1000); - } - ImGui::End(); - - auto blur_sigma_x = Sigma{blur_amount_coarse[0] + blur_amount_fine[0]}; - auto blur_sigma_y = Sigma{blur_amount_coarse[1] + blur_amount_fine[1]}; - - std::shared_ptr input; - Size input_size; - - auto input_rect = - Rect::MakeXYWH(path_rect[0], path_rect[1], path_rect[2], path_rect[3]); - if (selected_input_type == 0) { - auto texture = std::make_shared(); - texture->SetSourceRect(Rect::MakeSize(boston->GetSize())); - texture->SetDestinationRect(input_rect); - texture->SetTexture(boston); - texture->SetOpacity(input_color.alpha); - - input = texture; - input_size = input_rect.GetSize(); - } else { - auto fill = std::make_shared(); - fill->SetColor(input_color); - fill->SetGeometry( - Geometry::MakeFillPath(PathBuilder{}.AddRect(input_rect).TakePath())); - - input = fill; - input_size = input_rect.GetSize(); - } - - std::shared_ptr blur; - switch (selected_pass_variation) { - case 0: - blur = std::make_shared( - blur_sigma_x.sigma, blur_sigma_y.sigma, - tile_modes[selected_tile_mode], blur_styles[selected_blur_style], - /*geometry=*/nullptr); - blur->SetInputs({FilterInput::Make(input)}); - break; - case 1: - blur = FilterContents::MakeGaussianBlur( - FilterInput::Make(input), blur_sigma_x, blur_sigma_y, - tile_modes[selected_tile_mode], blur_styles[selected_blur_style]); - break; - }; - FML_CHECK(blur); - - auto mask_blur = FilterContents::MakeBorderMaskBlur( - FilterInput::Make(input), blur_sigma_x, blur_sigma_y, - blur_styles[selected_blur_style]); - - auto ctm = Matrix::MakeScale(GetContentScale()) * - Matrix::MakeTranslation(Vector3(offset[0], offset[1])) * - Matrix::MakeRotationZ(Radians(rotation)) * - Matrix::MakeScale(Vector2(scale[0], scale[1])) * - Matrix::MakeSkew(skew[0], skew[1]) * - Matrix::MakeTranslation(-Point(input_size) / 2); - - auto target_contents = selected_blur_type == 0 ? blur : mask_blur; - - Entity entity; - entity.SetContents(target_contents); - entity.SetTransform(ctm); - - entity.Render(context, pass); - - // Renders a red "cover" rectangle that shows the original position of the - // unfiltered input. - Entity cover_entity; - cover_entity.SetContents(SolidColorContents::Make( - PathBuilder{}.AddRect(input_rect).TakePath(), cover_color)); - cover_entity.SetTransform(ctm); - - cover_entity.Render(context, pass); - - // Renders a green bounding rect of the target filter. - Entity bounds_entity; - std::optional target_contents_coverage = - target_contents->GetCoverage(entity); - if (target_contents_coverage.has_value()) { - bounds_entity.SetContents(SolidColorContents::Make( - PathBuilder{} - .AddRect(target_contents->GetCoverage(entity).value()) - .TakePath(), - bounds_color)); - bounds_entity.SetTransform(Matrix()); - - bounds_entity.Render(context, pass); - } - - return true; - }; - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(EntityTest, MorphologyFilter) { - auto boston = CreateTextureForFixture("boston.jpg"); - ASSERT_TRUE(boston); - - auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { - const char* morphology_type_names[] = {"Dilate", "Erode"}; - const FilterContents::MorphType morphology_types[] = { - FilterContents::MorphType::kDilate, FilterContents::MorphType::kErode}; - static Color input_color = Color::Black(); - // UI state. - static int selected_morphology_type = 0; - static float radius[2] = {20, 20}; - static Color cover_color(1, 0, 0, 0.2); - static Color bounds_color(0, 1, 0, 0.1); - static float offset[2] = {500, 400}; - static float rotation = 0; - static float scale[2] = {0.65, 0.65}; - static float skew[2] = {0, 0}; - static float path_rect[4] = {0, 0, - static_cast(boston->GetSize().width), - static_cast(boston->GetSize().height)}; - static float effect_transform_scale = 1; - - ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); - { - ImGui::Combo("Morphology type", &selected_morphology_type, - morphology_type_names, - sizeof(morphology_type_names) / sizeof(char*)); - ImGui::SliderFloat2("Radius", radius, 0, 200); - ImGui::SliderFloat("Input opacity", &input_color.alpha, 0, 1); - ImGui::ColorEdit4("Cover color", reinterpret_cast(&cover_color)); - ImGui::ColorEdit4("Bounds color", - reinterpret_cast(&bounds_color)); - ImGui::SliderFloat2("Translation", offset, 0, - pass.GetRenderTargetSize().width); - ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2); - ImGui::SliderFloat2("Scale", scale, 0, 3); - ImGui::SliderFloat2("Skew", skew, -3, 3); - ImGui::SliderFloat4("Path XYWH", path_rect, -1000, 1000); - ImGui::SliderFloat("Effect transform scale", &effect_transform_scale, 0, - 3); - } - ImGui::End(); - - std::shared_ptr input; - Size input_size; - - auto input_rect = - Rect::MakeXYWH(path_rect[0], path_rect[1], path_rect[2], path_rect[3]); - auto texture = std::make_shared(); - texture->SetSourceRect(Rect::MakeSize(boston->GetSize())); - texture->SetDestinationRect(input_rect); - texture->SetTexture(boston); - texture->SetOpacity(input_color.alpha); - - input = texture; - input_size = input_rect.GetSize(); - - auto contents = FilterContents::MakeMorphology( - FilterInput::Make(input), Radius{radius[0]}, Radius{radius[1]}, - morphology_types[selected_morphology_type]); - contents->SetEffectTransform(Matrix::MakeScale( - Vector2{effect_transform_scale, effect_transform_scale})); - - auto ctm = Matrix::MakeScale(GetContentScale()) * - Matrix::MakeTranslation(Vector3(offset[0], offset[1])) * - Matrix::MakeRotationZ(Radians(rotation)) * - Matrix::MakeScale(Vector2(scale[0], scale[1])) * - Matrix::MakeSkew(skew[0], skew[1]) * - Matrix::MakeTranslation(-Point(input_size) / 2); - - Entity entity; - entity.SetContents(contents); - entity.SetTransform(ctm); - - entity.Render(context, pass); - - // Renders a red "cover" rectangle that shows the original position of the - // unfiltered input. - Entity cover_entity; - cover_entity.SetContents(SolidColorContents::Make( - PathBuilder{}.AddRect(input_rect).TakePath(), cover_color)); - cover_entity.SetTransform(ctm); - - cover_entity.Render(context, pass); - - // Renders a green bounding rect of the target filter. - Entity bounds_entity; - bounds_entity.SetContents(SolidColorContents::Make( - PathBuilder{}.AddRect(contents->GetCoverage(entity).value()).TakePath(), - bounds_color)); - bounds_entity.SetTransform(Matrix()); - - bounds_entity.Render(context, pass); - - return true; - }; - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(EntityTest, SetBlendMode) { - Entity entity; - ASSERT_EQ(entity.GetBlendMode(), BlendMode::kSourceOver); - entity.SetBlendMode(BlendMode::kClear); - ASSERT_EQ(entity.GetBlendMode(), BlendMode::kClear); -} - -TEST_P(EntityTest, ContentsGetBoundsForEmptyPathReturnsNullopt) { - Entity entity; - entity.SetContents(std::make_shared()); - ASSERT_FALSE(entity.GetCoverage().has_value()); -} - -TEST_P(EntityTest, SolidStrokeCoverageIsCorrect) { - { - auto geometry = Geometry::MakeStrokePath( - PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath(), 4.0, 4.0, - Cap::kButt, Join::kBevel); - - Entity entity; - auto contents = std::make_unique(); - contents->SetGeometry(std::move(geometry)); - contents->SetColor(Color::Black()); - entity.SetContents(std::move(contents)); - auto actual = entity.GetCoverage(); - auto expected = Rect::MakeLTRB(-2, -2, 12, 12); - ASSERT_TRUE(actual.has_value()); - ASSERT_RECT_NEAR(actual.value(), expected); - } - - // Cover the Cap::kSquare case. - { - auto geometry = Geometry::MakeStrokePath( - PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath(), 4.0, 4.0, - Cap::kSquare, Join::kBevel); - - Entity entity; - auto contents = std::make_unique(); - contents->SetGeometry(std::move(geometry)); - contents->SetColor(Color::Black()); - entity.SetContents(std::move(contents)); - auto actual = entity.GetCoverage(); - auto expected = - Rect::MakeLTRB(-sqrt(8), -sqrt(8), 10 + sqrt(8), 10 + sqrt(8)); - ASSERT_TRUE(actual.has_value()); - ASSERT_RECT_NEAR(actual.value(), expected); - } - - // Cover the Join::kMiter case. - { - auto geometry = Geometry::MakeStrokePath( - PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath(), 4.0, 2.0, - Cap::kSquare, Join::kMiter); - - Entity entity; - auto contents = std::make_unique(); - contents->SetGeometry(std::move(geometry)); - contents->SetColor(Color::Black()); - entity.SetContents(std::move(contents)); - auto actual = entity.GetCoverage(); - auto expected = Rect::MakeLTRB(-4, -4, 14, 14); - ASSERT_TRUE(actual.has_value()); - ASSERT_RECT_NEAR(actual.value(), expected); - } -} - -TEST_P(EntityTest, BorderMaskBlurCoverageIsCorrect) { - auto fill = std::make_shared(); - fill->SetGeometry(Geometry::MakeFillPath( - PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath())); - fill->SetColor(Color::CornflowerBlue()); - auto border_mask_blur = FilterContents::MakeBorderMaskBlur( - FilterInput::Make(fill), Radius{3}, Radius{4}); - - { - Entity e; - e.SetTransform(Matrix()); - auto actual = border_mask_blur->GetCoverage(e); - auto expected = Rect::MakeXYWH(-3, -4, 306, 408); - ASSERT_TRUE(actual.has_value()); - ASSERT_RECT_NEAR(actual.value(), expected); - } - - { - Entity e; - e.SetTransform(Matrix::MakeRotationZ(Radians{kPi / 4})); - auto actual = border_mask_blur->GetCoverage(e); - auto expected = Rect::MakeXYWH(-287.792, -4.94975, 504.874, 504.874); - ASSERT_TRUE(actual.has_value()); - ASSERT_RECT_NEAR(actual.value(), expected); - } -} - -TEST_P(EntityTest, SolidFillCoverageIsCorrect) { - // No transform - { - auto fill = std::make_shared(); - fill->SetColor(Color::CornflowerBlue()); - auto expected = Rect::MakeLTRB(100, 110, 200, 220); - fill->SetGeometry( - Geometry::MakeFillPath(PathBuilder{}.AddRect(expected).TakePath())); - - auto coverage = fill->GetCoverage({}); - ASSERT_TRUE(coverage.has_value()); - ASSERT_RECT_NEAR(coverage.value(), expected); - } - - // Entity transform - { - auto fill = std::make_shared(); - fill->SetColor(Color::CornflowerBlue()); - fill->SetGeometry(Geometry::MakeFillPath( - PathBuilder{}.AddRect(Rect::MakeLTRB(100, 110, 200, 220)).TakePath())); - - Entity entity; - entity.SetTransform(Matrix::MakeTranslation(Vector2(4, 5))); - entity.SetContents(std::move(fill)); - - auto coverage = entity.GetCoverage(); - auto expected = Rect::MakeLTRB(104, 115, 204, 225); - ASSERT_TRUE(coverage.has_value()); - ASSERT_RECT_NEAR(coverage.value(), expected); - } - - // No coverage for fully transparent colors - { - auto fill = std::make_shared(); - fill->SetColor(Color::WhiteTransparent()); - fill->SetGeometry(Geometry::MakeFillPath( - PathBuilder{}.AddRect(Rect::MakeLTRB(100, 110, 200, 220)).TakePath())); - - auto coverage = fill->GetCoverage({}); - ASSERT_FALSE(coverage.has_value()); - } -} - -TEST_P(EntityTest, ClipContentsGetClipCoverageIsCorrect) { - // Intersection: No stencil coverage, no geometry. - { - auto clip = std::make_shared(); - clip->SetClipOperation(Entity::ClipOperation::kIntersect); - auto result = clip->GetClipCoverage(Entity{}, Rect{}); - - ASSERT_FALSE(result.coverage.has_value()); - } - - // Intersection: No stencil coverage, with geometry. - { - auto clip = std::make_shared(); - clip->SetClipOperation(Entity::ClipOperation::kIntersect); - clip->SetGeometry(Geometry::MakeFillPath( - PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 100, 100)).TakePath())); - auto result = clip->GetClipCoverage(Entity{}, Rect{}); - - ASSERT_FALSE(result.coverage.has_value()); - } - - // Intersection: With stencil coverage, no geometry. - { - auto clip = std::make_shared(); - clip->SetClipOperation(Entity::ClipOperation::kIntersect); - auto result = - clip->GetClipCoverage(Entity{}, Rect::MakeLTRB(0, 0, 100, 100)); - - ASSERT_FALSE(result.coverage.has_value()); - } - - // Intersection: With stencil coverage, with geometry. - { - auto clip = std::make_shared(); - clip->SetClipOperation(Entity::ClipOperation::kIntersect); - clip->SetGeometry(Geometry::MakeFillPath( - PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 50, 50)).TakePath())); - auto result = - clip->GetClipCoverage(Entity{}, Rect::MakeLTRB(0, 0, 100, 100)); - - ASSERT_TRUE(result.coverage.has_value()); - ASSERT_RECT_NEAR(result.coverage.value(), Rect::MakeLTRB(0, 0, 50, 50)); - ASSERT_EQ(result.type, Contents::ClipCoverage::Type::kAppend); - } - - // Difference: With stencil coverage, with geometry. - { - auto clip = std::make_shared(); - clip->SetClipOperation(Entity::ClipOperation::kDifference); - clip->SetGeometry(Geometry::MakeFillPath( - PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 50, 50)).TakePath())); - auto result = - clip->GetClipCoverage(Entity{}, Rect::MakeLTRB(0, 0, 100, 100)); - - ASSERT_TRUE(result.coverage.has_value()); - ASSERT_RECT_NEAR(result.coverage.value(), Rect::MakeLTRB(0, 0, 100, 100)); - ASSERT_EQ(result.type, Contents::ClipCoverage::Type::kAppend); - } -} - -TEST_P(EntityTest, RRectShadowTest) { - auto callback = [&](ContentContext& context, RenderPass& pass) { - static Color color = Color::Red(); - static float corner_radius = 100; - static float blur_radius = 100; - static bool show_coverage = false; - static Color coverage_color = Color::Green().WithAlpha(0.2); - - ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); - ImGui::SliderFloat("Corner radius", &corner_radius, 0, 300); - ImGui::SliderFloat("Blur radius", &blur_radius, 0, 300); - ImGui::ColorEdit4("Color", reinterpret_cast(&color)); - ImGui::Checkbox("Show coverage", &show_coverage); - if (show_coverage) { - ImGui::ColorEdit4("Coverage color", - reinterpret_cast(&coverage_color)); - } - ImGui::End(); - - static PlaygroundPoint top_left_point(Point(200, 200), 30, Color::White()); - static PlaygroundPoint bottom_right_point(Point(600, 400), 30, - Color::White()); - auto [top_left, bottom_right] = - DrawPlaygroundLine(top_left_point, bottom_right_point); - auto rect = - Rect::MakeLTRB(top_left.x, top_left.y, bottom_right.x, bottom_right.y); - - auto contents = std::make_unique(); - contents->SetRRect(rect, {corner_radius, corner_radius}); - contents->SetColor(color); - contents->SetSigma(Radius(blur_radius)); - - Entity entity; - entity.SetTransform(Matrix::MakeScale(GetContentScale())); - entity.SetContents(std::move(contents)); - entity.Render(context, pass); - - auto coverage = entity.GetCoverage(); - if (show_coverage && coverage.has_value()) { - auto bounds_contents = std::make_unique(); - bounds_contents->SetGeometry(Geometry::MakeFillPath( - PathBuilder{}.AddRect(entity.GetCoverage().value()).TakePath())); - bounds_contents->SetColor(coverage_color.Premultiply()); - Entity bounds_entity; - bounds_entity.SetContents(std::move(bounds_contents)); - bounds_entity.Render(context, pass); - } - - return true; - }; - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(EntityTest, ColorMatrixFilterCoverageIsCorrect) { - // Set up a simple color background. - auto fill = std::make_shared(); - fill->SetGeometry(Geometry::MakeFillPath( - PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath())); - fill->SetColor(Color::Coral()); - - // Set the color matrix filter. - ColorMatrix matrix = { - 1, 1, 1, 1, 1, // - 1, 1, 1, 1, 1, // - 1, 1, 1, 1, 1, // - 1, 1, 1, 1, 1, // - }; - - auto filter = - ColorFilterContents::MakeColorMatrix(FilterInput::Make(fill), matrix); - - Entity e; - e.SetTransform(Matrix()); - - // Confirm that the actual filter coverage matches the expected coverage. - auto actual = filter->GetCoverage(e); - auto expected = Rect::MakeXYWH(0, 0, 300, 400); - - ASSERT_TRUE(actual.has_value()); - ASSERT_RECT_NEAR(actual.value(), expected); -} - -TEST_P(EntityTest, ColorMatrixFilterEditable) { - auto bay_bridge = CreateTextureForFixture("bay_bridge.jpg"); - ASSERT_TRUE(bay_bridge); - - auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { - // UI state. - static ColorMatrix color_matrix = { - 1, 0, 0, 0, 0, // - 0, 3, 0, 0, 0, // - 0, 0, 1, 0, 0, // - 0, 0, 0, 1, 0, // - }; - static float offset[2] = {500, 400}; - static float rotation = 0; - static float scale[2] = {0.65, 0.65}; - static float skew[2] = {0, 0}; - - // Define the ImGui - ImGui::Begin("Color Matrix", nullptr, ImGuiWindowFlags_AlwaysAutoResize); - { - std::string label = "##1"; - for (int i = 0; i < 20; i += 5) { - ImGui::InputScalarN(label.c_str(), ImGuiDataType_Float, - &(color_matrix.array[i]), 5, nullptr, nullptr, - "%.2f", 0); - label[2]++; - } - - ImGui::SliderFloat2("Translation", &offset[0], 0, - pass.GetRenderTargetSize().width); - ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2); - ImGui::SliderFloat2("Scale", &scale[0], 0, 3); - ImGui::SliderFloat2("Skew", &skew[0], -3, 3); - } - ImGui::End(); - - // Set the color matrix filter. - auto filter = ColorFilterContents::MakeColorMatrix( - FilterInput::Make(bay_bridge), color_matrix); - - // Define the entity with the color matrix filter. - Entity entity; - entity.SetTransform( - Matrix::MakeScale(GetContentScale()) * - Matrix::MakeTranslation(Vector3(offset[0], offset[1])) * - Matrix::MakeRotationZ(Radians(rotation)) * - Matrix::MakeScale(Vector2(scale[0], scale[1])) * - Matrix::MakeSkew(skew[0], skew[1]) * - Matrix::MakeTranslation(-Point(bay_bridge->GetSize()) / 2)); - entity.SetContents(filter); - entity.Render(context, pass); - - return true; - }; - - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(EntityTest, LinearToSrgbFilterCoverageIsCorrect) { - // Set up a simple color background. - auto fill = std::make_shared(); - fill->SetGeometry(Geometry::MakeFillPath( - PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath())); - fill->SetColor(Color::MintCream()); - - auto filter = - ColorFilterContents::MakeLinearToSrgbFilter(FilterInput::Make(fill)); - - Entity e; - e.SetTransform(Matrix()); - - // Confirm that the actual filter coverage matches the expected coverage. - auto actual = filter->GetCoverage(e); - auto expected = Rect::MakeXYWH(0, 0, 300, 400); - - ASSERT_TRUE(actual.has_value()); - ASSERT_RECT_NEAR(actual.value(), expected); -} - -TEST_P(EntityTest, LinearToSrgbFilter) { - auto image = CreateTextureForFixture("kalimba.jpg"); - ASSERT_TRUE(image); - - auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { - auto filtered = - ColorFilterContents::MakeLinearToSrgbFilter(FilterInput::Make(image)); - - // Define the entity that will serve as the control image as a Gaussian blur - // filter with no filter at all. - Entity entity_left; - entity_left.SetTransform(Matrix::MakeScale(GetContentScale()) * - Matrix::MakeTranslation({100, 300}) * - Matrix::MakeScale(Vector2{0.5, 0.5})); - auto unfiltered = FilterContents::MakeGaussianBlur(FilterInput::Make(image), - Sigma{0}, Sigma{0}); - entity_left.SetContents(unfiltered); - - // Define the entity that will be filtered from linear to sRGB. - Entity entity_right; - entity_right.SetTransform(Matrix::MakeScale(GetContentScale()) * - Matrix::MakeTranslation({500, 300}) * - Matrix::MakeScale(Vector2{0.5, 0.5})); - entity_right.SetContents(filtered); - return entity_left.Render(context, pass) && - entity_right.Render(context, pass); - }; - - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(EntityTest, SrgbToLinearFilterCoverageIsCorrect) { - // Set up a simple color background. - auto fill = std::make_shared(); - fill->SetGeometry(Geometry::MakeFillPath( - PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath())); - fill->SetColor(Color::DeepPink()); - - auto filter = - ColorFilterContents::MakeSrgbToLinearFilter(FilterInput::Make(fill)); - - Entity e; - e.SetTransform(Matrix()); - - // Confirm that the actual filter coverage matches the expected coverage. - auto actual = filter->GetCoverage(e); - auto expected = Rect::MakeXYWH(0, 0, 300, 400); - - ASSERT_TRUE(actual.has_value()); - ASSERT_RECT_NEAR(actual.value(), expected); -} - -TEST_P(EntityTest, SrgbToLinearFilter) { - auto image = CreateTextureForFixture("embarcadero.jpg"); - ASSERT_TRUE(image); - - auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { - auto filtered = - ColorFilterContents::MakeSrgbToLinearFilter(FilterInput::Make(image)); - - // Define the entity that will serve as the control image as a Gaussian blur - // filter with no filter at all. - Entity entity_left; - entity_left.SetTransform(Matrix::MakeScale(GetContentScale()) * - Matrix::MakeTranslation({100, 300}) * - Matrix::MakeScale(Vector2{0.5, 0.5})); - auto unfiltered = FilterContents::MakeGaussianBlur(FilterInput::Make(image), - Sigma{0}, Sigma{0}); - entity_left.SetContents(unfiltered); - - // Define the entity that will be filtered from sRGB to linear. - Entity entity_right; - entity_right.SetTransform(Matrix::MakeScale(GetContentScale()) * - Matrix::MakeTranslation({500, 300}) * - Matrix::MakeScale(Vector2{0.5, 0.5})); - entity_right.SetContents(filtered); - return entity_left.Render(context, pass) && - entity_right.Render(context, pass); - }; - - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -static Vector3 RGBToYUV(Vector3 rgb, YUVColorSpace yuv_color_space) { - Vector3 yuv; - switch (yuv_color_space) { - case YUVColorSpace::kBT601FullRange: - yuv.x = rgb.x * 0.299 + rgb.y * 0.587 + rgb.z * 0.114; - yuv.y = rgb.x * -0.169 + rgb.y * -0.331 + rgb.z * 0.5 + 0.5; - yuv.z = rgb.x * 0.5 + rgb.y * -0.419 + rgb.z * -0.081 + 0.5; - break; - case YUVColorSpace::kBT601LimitedRange: - yuv.x = rgb.x * 0.257 + rgb.y * 0.516 + rgb.z * 0.100 + 0.063; - yuv.y = rgb.x * -0.145 + rgb.y * -0.291 + rgb.z * 0.439 + 0.5; - yuv.z = rgb.x * 0.429 + rgb.y * -0.368 + rgb.z * -0.071 + 0.5; - break; - } - return yuv; -} - -static std::vector> CreateTestYUVTextures( - Context* context, - YUVColorSpace yuv_color_space) { - Vector3 red = {244.0 / 255.0, 67.0 / 255.0, 54.0 / 255.0}; - Vector3 green = {76.0 / 255.0, 175.0 / 255.0, 80.0 / 255.0}; - Vector3 blue = {33.0 / 255.0, 150.0 / 255.0, 243.0 / 255.0}; - Vector3 white = {1.0, 1.0, 1.0}; - Vector3 red_yuv = RGBToYUV(red, yuv_color_space); - Vector3 green_yuv = RGBToYUV(green, yuv_color_space); - Vector3 blue_yuv = RGBToYUV(blue, yuv_color_space); - Vector3 white_yuv = RGBToYUV(white, yuv_color_space); - std::vector yuvs{red_yuv, green_yuv, blue_yuv, white_yuv}; - std::vector y_data; - std::vector uv_data; - for (int i = 0; i < 4; i++) { - auto yuv = yuvs[i]; - uint8_t y = std::round(yuv.x * 255.0); - uint8_t u = std::round(yuv.y * 255.0); - uint8_t v = std::round(yuv.z * 255.0); - for (int j = 0; j < 16; j++) { - y_data.push_back(y); - } - for (int j = 0; j < 8; j++) { - uv_data.push_back(j % 2 == 0 ? u : v); - } - } - auto cmd_buffer = context->CreateCommandBuffer(); - auto blit_pass = cmd_buffer->CreateBlitPass(); - - impeller::TextureDescriptor y_texture_descriptor; - y_texture_descriptor.storage_mode = impeller::StorageMode::kHostVisible; - y_texture_descriptor.format = PixelFormat::kR8UNormInt; - y_texture_descriptor.size = {8, 8}; - auto y_texture = - context->GetResourceAllocator()->CreateTexture(y_texture_descriptor); - auto y_mapping = std::make_shared(y_data); - auto y_mapping_buffer = - context->GetResourceAllocator()->CreateBufferWithCopy(*y_mapping); - - blit_pass->AddCopy(DeviceBuffer::AsBufferView(y_mapping_buffer), y_texture); - - impeller::TextureDescriptor uv_texture_descriptor; - uv_texture_descriptor.storage_mode = impeller::StorageMode::kHostVisible; - uv_texture_descriptor.format = PixelFormat::kR8G8UNormInt; - uv_texture_descriptor.size = {4, 4}; - auto uv_texture = - context->GetResourceAllocator()->CreateTexture(uv_texture_descriptor); - auto uv_mapping = std::make_shared(uv_data); - auto uv_mapping_buffer = - context->GetResourceAllocator()->CreateBufferWithCopy(*uv_mapping); - - blit_pass->AddCopy(DeviceBuffer::AsBufferView(uv_mapping_buffer), uv_texture); - - if (!blit_pass->EncodeCommands(context->GetResourceAllocator()) || - !context->GetCommandQueue()->Submit({cmd_buffer}).ok()) { - FML_DLOG(ERROR) << "Could not copy contents into Y/UV texture."; - } - - return {y_texture, uv_texture}; -} - -TEST_P(EntityTest, YUVToRGBFilter) { - if (GetParam() == PlaygroundBackend::kOpenGLES) { - // TODO(114588) : Support YUV to RGB filter on OpenGLES backend. - GTEST_SKIP() - << "YUV to RGB filter is not supported on OpenGLES backend yet."; - } - - auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { - YUVColorSpace yuv_color_space_array[2]{YUVColorSpace::kBT601FullRange, - YUVColorSpace::kBT601LimitedRange}; - for (int i = 0; i < 2; i++) { - auto yuv_color_space = yuv_color_space_array[i]; - auto textures = - CreateTestYUVTextures(GetContext().get(), yuv_color_space); - auto filter_contents = FilterContents::MakeYUVToRGBFilter( - textures[0], textures[1], yuv_color_space); - Entity filter_entity; - filter_entity.SetContents(filter_contents); - auto snapshot = filter_contents->RenderToSnapshot(context, filter_entity); - - Entity entity; - auto contents = TextureContents::MakeRect(Rect::MakeLTRB(0, 0, 256, 256)); - contents->SetTexture(snapshot->texture); - contents->SetSourceRect(Rect::MakeSize(snapshot->texture->GetSize())); - entity.SetContents(contents); - entity.SetTransform( - Matrix::MakeTranslation({static_cast(100 + 400 * i), 300})); - entity.Render(context, pass); - } - return true; - }; - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(EntityTest, RuntimeEffect) { - auto runtime_stages = - OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); - auto runtime_stage = - runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; - ASSERT_TRUE(runtime_stage); - ASSERT_TRUE(runtime_stage->IsDirty()); - - bool expect_dirty = true; - Pipeline* first_pipeline; - auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { - EXPECT_EQ(runtime_stage->IsDirty(), expect_dirty); - - auto contents = std::make_shared(); - contents->SetGeometry(Geometry::MakeCover()); - contents->SetRuntimeStage(runtime_stage); - - struct FragUniforms { - Vector2 iResolution; - Scalar iTime; - } frag_uniforms = { - .iResolution = Vector2(GetWindowSize().width, GetWindowSize().height), - .iTime = static_cast(GetSecondsElapsed()), - }; - auto uniform_data = std::make_shared>(); - uniform_data->resize(sizeof(FragUniforms)); - memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms)); - contents->SetUniformData(uniform_data); - - Entity entity; - entity.SetContents(contents); - bool result = contents->Render(context, entity, pass); - - if (expect_dirty) { - EXPECT_NE(first_pipeline, pass.GetCommands().back().pipeline.get()); - first_pipeline = pass.GetCommands().back().pipeline.get(); - } else { - EXPECT_EQ(pass.GetCommands().back().pipeline.get(), first_pipeline); - } - - expect_dirty = false; - return result; - }; - - // Simulate some renders and hot reloading of the shader. - auto content_context = GetContentContext(); - { - RenderTarget target = - content_context->GetRenderTargetCache()->CreateOffscreen( - *content_context->GetContext(), {1, 1}, 1u); - - testing::MockRenderPass mock_pass(GetContext(), target); - callback(*content_context, mock_pass); - callback(*content_context, mock_pass); - - // Dirty the runtime stage. - runtime_stages = OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); - runtime_stage = - runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; - - ASSERT_TRUE(runtime_stage->IsDirty()); - expect_dirty = true; - - callback(*content_context, mock_pass); - } -} - -TEST_P(EntityTest, RuntimeEffectCanSuccessfullyRender) { - auto runtime_stages = - OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); - auto runtime_stage = - runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; - ASSERT_TRUE(runtime_stage); - ASSERT_TRUE(runtime_stage->IsDirty()); - - auto contents = std::make_shared(); - contents->SetGeometry(Geometry::MakeCover()); - - contents->SetRuntimeStage(runtime_stage); - - struct FragUniforms { - Vector2 iResolution; - Scalar iTime; - } frag_uniforms = { - .iResolution = Vector2(GetWindowSize().width, GetWindowSize().height), - .iTime = static_cast(GetSecondsElapsed()), - }; - auto uniform_data = std::make_shared>(); - uniform_data->resize(sizeof(FragUniforms)); - memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms)); - contents->SetUniformData(uniform_data); - - Entity entity; - entity.SetContents(contents); - - // Create a render target with a depth-stencil, similar to how EntityPass - // does. - RenderTarget target = - GetContentContext()->GetRenderTargetCache()->CreateOffscreenMSAA( - *GetContext(), {GetWindowSize().width, GetWindowSize().height}, 1, - "RuntimeEffect Texture"); - testing::MockRenderPass pass(GetContext(), target); - - ASSERT_TRUE(contents->Render(*GetContentContext(), entity, pass)); - ASSERT_EQ(pass.GetCommands().size(), 1u); - const auto& command = pass.GetCommands()[0]; - ASSERT_TRUE(command.pipeline->GetDescriptor() - .GetDepthStencilAttachmentDescriptor() - .has_value()); - ASSERT_TRUE(command.pipeline->GetDescriptor() - .GetFrontStencilAttachmentDescriptor() - .has_value()); -} - -TEST_P(EntityTest, RuntimeEffectCanPrecache) { - auto runtime_stages = - OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); - auto runtime_stage = - runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; - ASSERT_TRUE(runtime_stage); - ASSERT_TRUE(runtime_stage->IsDirty()); - - auto contents = std::make_shared(); - contents->SetRuntimeStage(runtime_stage); - - EXPECT_TRUE(contents->BootstrapShader(*GetContentContext())); -} - -TEST_P(EntityTest, RuntimeEffectSetsRightSizeWhenUniformIsStruct) { - if (GetBackend() != PlaygroundBackend::kVulkan) { - GTEST_SKIP() << "Test only applies to Vulkan"; - } - - auto runtime_stages = - OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); - auto runtime_stage = - runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; - ASSERT_TRUE(runtime_stage); - ASSERT_TRUE(runtime_stage->IsDirty()); - - auto contents = std::make_shared(); - contents->SetGeometry(Geometry::MakeCover()); - contents->SetRuntimeStage(runtime_stage); - - struct FragUniforms { - Vector2 iResolution; - Scalar iTime; - } frag_uniforms = { - .iResolution = Vector2(GetWindowSize().width, GetWindowSize().height), - .iTime = static_cast(GetSecondsElapsed()), - }; - auto uniform_data = std::make_shared>(); - uniform_data->resize(sizeof(FragUniforms)); - memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms)); - contents->SetUniformData(uniform_data); - - Entity entity; - entity.SetContents(contents); - - auto context = GetContentContext(); - RenderTarget target = context->GetRenderTargetCache()->CreateOffscreen( - *context->GetContext(), {1, 1}, 1u); - - testing::MockRenderPass pass(GetContext(), target); - ASSERT_TRUE(contents->Render(*context, entity, pass)); - ASSERT_EQ(pass.GetCommands().size(), 1u); - const auto& command = pass.GetCommands()[0]; - ASSERT_EQ(command.fragment_bindings.buffers.size(), 1u); - // 16 bytes: - // 8 bytes for iResolution - // 4 bytes for iTime - // 4 bytes padding - EXPECT_EQ(command.fragment_bindings.buffers[0].view.resource.range.length, - 16u); -} - -TEST_P(EntityTest, InheritOpacityTest) { - Entity entity; - - // Texture contents can always accept opacity. - auto texture_contents = std::make_shared(); - texture_contents->SetOpacity(0.5); - - texture_contents->SetInheritedOpacity(0.5); - ASSERT_EQ(texture_contents->GetOpacity(), 0.25); - texture_contents->SetInheritedOpacity(0.5); - ASSERT_EQ(texture_contents->GetOpacity(), 0.25); - - // Solid color contents can accept opacity if their geometry - // doesn't overlap. - auto solid_color = std::make_shared(); - solid_color->SetGeometry( - Geometry::MakeRect(Rect::MakeLTRB(100, 100, 200, 200))); - solid_color->SetColor(Color::Blue().WithAlpha(0.5)); - - solid_color->SetInheritedOpacity(0.5); - ASSERT_EQ(solid_color->GetColor().alpha, 0.25); - solid_color->SetInheritedOpacity(0.5); - ASSERT_EQ(solid_color->GetColor().alpha, 0.25); - - // Color source contents can accept opacity if their geometry - // doesn't overlap. - auto tiled_texture = std::make_shared(); - tiled_texture->SetGeometry( - Geometry::MakeRect(Rect::MakeLTRB(100, 100, 200, 200))); - tiled_texture->SetOpacityFactor(0.5); - - tiled_texture->SetInheritedOpacity(0.5); - ASSERT_EQ(tiled_texture->GetOpacityFactor(), 0.25); - tiled_texture->SetInheritedOpacity(0.5); - ASSERT_EQ(tiled_texture->GetOpacityFactor(), 0.25); -} - -TEST_P(EntityTest, ColorFilterWithForegroundColorAdvancedBlend) { - auto image = CreateTextureForFixture("boston.jpg"); - auto filter = ColorFilterContents::MakeBlend( - BlendMode::kColorBurn, FilterInput::Make({image}), Color::Red()); - - auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { - Entity entity; - entity.SetTransform(Matrix::MakeScale(GetContentScale()) * - Matrix::MakeTranslation({500, 300}) * - Matrix::MakeScale(Vector2{0.5, 0.5})); - entity.SetContents(filter); - return entity.Render(context, pass); - }; - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(EntityTest, ColorFilterWithForegroundColorClearBlend) { - auto image = CreateTextureForFixture("boston.jpg"); - auto filter = ColorFilterContents::MakeBlend( - BlendMode::kClear, FilterInput::Make({image}), Color::Red()); - - auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { - Entity entity; - entity.SetTransform(Matrix::MakeScale(GetContentScale()) * - Matrix::MakeTranslation({500, 300}) * - Matrix::MakeScale(Vector2{0.5, 0.5})); - entity.SetContents(filter); - return entity.Render(context, pass); - }; - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(EntityTest, ColorFilterWithForegroundColorSrcBlend) { - auto image = CreateTextureForFixture("boston.jpg"); - auto filter = ColorFilterContents::MakeBlend( - BlendMode::kSource, FilterInput::Make({image}), Color::Red()); - - auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { - Entity entity; - entity.SetTransform(Matrix::MakeScale(GetContentScale()) * - Matrix::MakeTranslation({500, 300}) * - Matrix::MakeScale(Vector2{0.5, 0.5})); - entity.SetContents(filter); - return entity.Render(context, pass); - }; - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(EntityTest, ColorFilterWithForegroundColorDstBlend) { - auto image = CreateTextureForFixture("boston.jpg"); - auto filter = ColorFilterContents::MakeBlend( - BlendMode::kDestination, FilterInput::Make({image}), Color::Red()); - - auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { - Entity entity; - entity.SetTransform(Matrix::MakeScale(GetContentScale()) * - Matrix::MakeTranslation({500, 300}) * - Matrix::MakeScale(Vector2{0.5, 0.5})); - entity.SetContents(filter); - return entity.Render(context, pass); - }; - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(EntityTest, ColorFilterWithForegroundColorSrcInBlend) { - auto image = CreateTextureForFixture("boston.jpg"); - auto filter = ColorFilterContents::MakeBlend( - BlendMode::kSourceIn, FilterInput::Make({image}), Color::Red()); - - auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { - Entity entity; - entity.SetTransform(Matrix::MakeScale(GetContentScale()) * - Matrix::MakeTranslation({500, 300}) * - Matrix::MakeScale(Vector2{0.5, 0.5})); - entity.SetContents(filter); - return entity.Render(context, pass); - }; - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(EntityTest, CoverageForStrokePathWithNegativeValuesInTransform) { - auto arrow_head = PathBuilder{} - .MoveTo({50, 120}) - .LineTo({120, 190}) - .LineTo({190, 120}) - .TakePath(); - auto geometry = Geometry::MakeStrokePath(arrow_head, 15.0, 4.0, Cap::kRound, - Join::kRound); - - auto transform = Matrix::MakeTranslation({300, 300}) * - Matrix::MakeRotationZ(Radians(kPiOver2)); - // Note that e[0][0] used to be tested here, but it was -epsilon solely - // due to floating point inaccuracy in the transcendental trig functions. - // e[1][0] is the intended negative value that we care about (-1.0) as it - // comes from the rotation of pi/2. - EXPECT_LT(transform.e[1][0], 0.0f); - auto coverage = geometry->GetCoverage(transform); - ASSERT_RECT_NEAR(coverage.value(), Rect::MakeXYWH(102.5, 342.5, 85, 155)); -} - -TEST_P(EntityTest, SolidColorContentsIsOpaque) { - Matrix matrix; - SolidColorContents contents; - contents.SetGeometry(Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10))); - - contents.SetColor(Color::CornflowerBlue()); - EXPECT_TRUE(contents.IsOpaque(matrix)); - contents.SetColor(Color::CornflowerBlue().WithAlpha(0.5)); - EXPECT_FALSE(contents.IsOpaque(matrix)); - - // Create stroked path that required alpha coverage. - contents.SetGeometry(Geometry::MakeStrokePath( - PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), - /*stroke_width=*/0.05)); - contents.SetColor(Color::CornflowerBlue()); - - EXPECT_FALSE(contents.IsOpaque(matrix)); -} - -TEST_P(EntityTest, ConicalGradientContentsIsOpaque) { - Matrix matrix; - ConicalGradientContents contents; - contents.SetGeometry(Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10))); - - contents.SetColors({Color::CornflowerBlue()}); - EXPECT_FALSE(contents.IsOpaque(matrix)); - contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)}); - EXPECT_FALSE(contents.IsOpaque(matrix)); - - // Create stroked path that required alpha coverage. - contents.SetGeometry(Geometry::MakeStrokePath( - PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), - /*stroke_width=*/0.05)); - contents.SetColors({Color::CornflowerBlue()}); - - EXPECT_FALSE(contents.IsOpaque(matrix)); -} - -TEST_P(EntityTest, LinearGradientContentsIsOpaque) { - Matrix matrix; - LinearGradientContents contents; - contents.SetGeometry(Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10))); - - contents.SetColors({Color::CornflowerBlue()}); - EXPECT_TRUE(contents.IsOpaque(matrix)); - contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)}); - EXPECT_FALSE(contents.IsOpaque(matrix)); - contents.SetColors({Color::CornflowerBlue()}); - contents.SetTileMode(Entity::TileMode::kDecal); - EXPECT_FALSE(contents.IsOpaque(matrix)); - - // Create stroked path that required alpha coverage. - contents.SetGeometry(Geometry::MakeStrokePath( - PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), - /*stroke_width=*/0.05)); - contents.SetColors({Color::CornflowerBlue()}); - - EXPECT_FALSE(contents.IsOpaque(matrix)); -} - -TEST_P(EntityTest, RadialGradientContentsIsOpaque) { - Matrix matrix; - RadialGradientContents contents; - contents.SetGeometry(Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10))); - - contents.SetColors({Color::CornflowerBlue()}); - EXPECT_TRUE(contents.IsOpaque(matrix)); - contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)}); - EXPECT_FALSE(contents.IsOpaque(matrix)); - contents.SetColors({Color::CornflowerBlue()}); - contents.SetTileMode(Entity::TileMode::kDecal); - EXPECT_FALSE(contents.IsOpaque(matrix)); - - // Create stroked path that required alpha coverage. - contents.SetGeometry(Geometry::MakeStrokePath( - PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), - /*stroke_width=*/0.05)); - contents.SetColors({Color::CornflowerBlue()}); - - EXPECT_FALSE(contents.IsOpaque(matrix)); -} - -TEST_P(EntityTest, SweepGradientContentsIsOpaque) { - Matrix matrix; - RadialGradientContents contents; - contents.SetGeometry(Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10))); - - contents.SetColors({Color::CornflowerBlue()}); - EXPECT_TRUE(contents.IsOpaque(matrix)); - contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)}); - EXPECT_FALSE(contents.IsOpaque(matrix)); - contents.SetColors({Color::CornflowerBlue()}); - contents.SetTileMode(Entity::TileMode::kDecal); - EXPECT_FALSE(contents.IsOpaque(matrix)); - - // Create stroked path that required alpha coverage. - contents.SetGeometry(Geometry::MakeStrokePath( - PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), - /*stroke_width=*/0.05)); - contents.SetColors({Color::CornflowerBlue()}); - - EXPECT_FALSE(contents.IsOpaque(matrix)); -} - -TEST_P(EntityTest, TiledTextureContentsIsOpaque) { - Matrix matrix; - auto bay_bridge = CreateTextureForFixture("bay_bridge.jpg"); - TiledTextureContents contents; - contents.SetTexture(bay_bridge); - // This is a placeholder test. Images currently never decompress as opaque - // (whether in Flutter or the playground), and so this should currently always - // return false in practice. - EXPECT_FALSE(contents.IsOpaque(matrix)); -} - -TEST_P(EntityTest, PointFieldGeometryCoverage) { - std::vector points = {{10, 20}, {100, 200}}; - auto geometry = Geometry::MakePointField(points, 5.0, false); - ASSERT_EQ(*geometry->GetCoverage(Matrix()), Rect::MakeLTRB(5, 15, 105, 205)); - ASSERT_EQ(*geometry->GetCoverage(Matrix::MakeTranslation({30, 0, 0})), - Rect::MakeLTRB(35, 15, 135, 205)); -} - -TEST_P(EntityTest, ColorFilterContentsWithLargeGeometry) { - Entity entity; - entity.SetTransform(Matrix::MakeScale(GetContentScale())); - auto src_contents = std::make_shared(); - src_contents->SetGeometry( - Geometry::MakeRect(Rect::MakeLTRB(-300, -500, 30000, 50000))); - src_contents->SetColor(Color::Red()); - - auto dst_contents = std::make_shared(); - dst_contents->SetGeometry( - Geometry::MakeRect(Rect::MakeLTRB(300, 500, 20000, 30000))); - dst_contents->SetColor(Color::Blue()); - - auto contents = ColorFilterContents::MakeBlend( - BlendMode::kSourceOver, {FilterInput::Make(dst_contents, false), - FilterInput::Make(src_contents, false)}); - entity.SetContents(std::move(contents)); - ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); -} - -TEST_P(EntityTest, TextContentsCeilsGlyphScaleToDecimal) { - ASSERT_EQ(TextFrame::RoundScaledFontSize(0.4321111f, 12), 0.43f); - ASSERT_EQ(TextFrame::RoundScaledFontSize(0.5321111f, 12), 0.53f); - ASSERT_EQ(TextFrame::RoundScaledFontSize(2.1f, 12), 2.1f); - ASSERT_EQ(TextFrame::RoundScaledFontSize(0.0f, 12), 0.0f); - ASSERT_EQ(TextFrame::RoundScaledFontSize(100000000.0f, 12), 48.0f); -} - -TEST_P(EntityTest, SpecializationConstantsAreAppliedToVariants) { - auto content_context = GetContentContext(); - - auto default_gyph = content_context->GetGlyphAtlasPipeline({ - .color_attachment_pixel_format = PixelFormat::kR8G8B8A8UNormInt, - .has_depth_stencil_attachments = false, - }); - auto alt_gyph = content_context->GetGlyphAtlasPipeline( - {.color_attachment_pixel_format = PixelFormat::kR8G8B8A8UNormInt, - .has_depth_stencil_attachments = true}); - - EXPECT_NE(default_gyph, alt_gyph); - EXPECT_EQ(default_gyph->GetDescriptor().GetSpecializationConstants(), - alt_gyph->GetDescriptor().GetSpecializationConstants()); - - auto use_a8 = GetContext()->GetCapabilities()->GetDefaultGlyphAtlasFormat() == - PixelFormat::kA8UNormInt; - - std::vector expected_constants = {static_cast(use_a8)}; - EXPECT_EQ(default_gyph->GetDescriptor().GetSpecializationConstants(), - expected_constants); -} - -TEST_P(EntityTest, DecalSpecializationAppliedToMorphologyFilter) { - auto content_context = GetContentContext(); - auto default_color_burn = content_context->GetMorphologyFilterPipeline({ - .color_attachment_pixel_format = PixelFormat::kR8G8B8A8UNormInt, - }); - - auto decal_supported = static_cast( - GetContext()->GetCapabilities()->SupportsDecalSamplerAddressMode()); - std::vector expected_constants = {decal_supported}; - ASSERT_EQ(default_color_burn->GetDescriptor().GetSpecializationConstants(), - expected_constants); -} - -// This doesn't really tell you if the hashes will have frequent -// collisions, but since this type is only used to hash a bounded -// set of options, we can just compare benchmarks. -TEST_P(EntityTest, ContentContextOptionsHasReasonableHashFunctions) { - ContentContextOptions opts; - auto hash_a = ContentContextOptions::Hash{}(opts); - - opts.blend_mode = BlendMode::kColorBurn; - auto hash_b = ContentContextOptions::Hash{}(opts); - - opts.has_depth_stencil_attachments = false; - auto hash_c = ContentContextOptions::Hash{}(opts); - - opts.primitive_type = PrimitiveType::kPoint; - auto hash_d = ContentContextOptions::Hash{}(opts); - - EXPECT_NE(hash_a, hash_b); - EXPECT_NE(hash_b, hash_c); - EXPECT_NE(hash_c, hash_d); -} - -#ifdef FML_OS_LINUX -TEST_P(EntityTest, FramebufferFetchVulkanBindingOffsetIsTheSame) { - // Using framebuffer fetch on Vulkan requires that we maintain a subpass input - // binding that we don't have a good route for configuring with the current - // metadata approach. This test verifies that the binding value doesn't change - // from the expected constant. - // See also: - // * impeller/renderer/backend/vulkan/binding_helpers_vk.cc - // * impeller/entity/shaders/blending/framebuffer_blend.frag - // This test only works on Linux because macOS hosts incorrectly populate the - // Vulkan descriptor sets based on the MSL compiler settings. - - bool expected_layout = false; - for (const DescriptorSetLayout& layout : FramebufferBlendColorBurnPipeline:: - FragmentShader::kDescriptorSetLayouts) { - if (layout.binding == 64 && - layout.descriptor_type == DescriptorType::kInputAttachment) { - expected_layout = true; - } - } - EXPECT_TRUE(expected_layout); -} -#endif - -TEST_P(EntityTest, FillPathGeometryGetPositionBufferReturnsExpectedMode) { - RenderTarget target; - testing::MockRenderPass mock_pass(GetContext(), target); - - auto get_result = [this, &mock_pass](const Path& path) { - auto geometry = Geometry::MakeFillPath( - path, /* inner rect */ Rect::MakeLTRB(0, 0, 100, 100)); - return geometry->GetPositionBuffer(*GetContentContext(), {}, mock_pass); - }; - - // Convex path - { - GeometryResult result = - get_result(PathBuilder{} - .AddRect(Rect::MakeLTRB(0, 0, 100, 100)) - .SetConvexity(Convexity::kConvex) - .TakePath()); - EXPECT_EQ(result.mode, GeometryResult::Mode::kNormal); - } - - // Concave path - { - Path path = PathBuilder{} - .MoveTo({0, 0}) - .LineTo({100, 0}) - .LineTo({100, 100}) - .LineTo({50, 50}) - .Close() - .TakePath(); - GeometryResult result = get_result(path); - EXPECT_EQ(result.mode, GeometryResult::Mode::kNonZero); - } -} - -TEST_P(EntityTest, FailOnValidationError) { - if (GetParam() != PlaygroundBackend::kVulkan) { - GTEST_SKIP() << "Validation is only fatal on Vulkan backend."; - } - EXPECT_DEATH( - // The easiest way to trigger a validation error is to try to compile - // a shader with an unsupported pixel format. - GetContentContext()->GetBlendColorBurnPipeline({ - .color_attachment_pixel_format = PixelFormat::kUnknown, - .has_depth_stencil_attachments = false, - }), - ""); -} - -TEST_P(EntityTest, CanComputeGeometryForEmptyPathsWithoutCrashing) { - PathBuilder builder = {}; - builder.AddRect(Rect::MakeLTRB(0, 0, 0, 0)); - Path path = builder.TakePath(); - - EXPECT_TRUE(path.GetBoundingBox()->IsEmpty()); - - auto geom = Geometry::MakeFillPath(path); - - Entity entity; - RenderTarget target = - GetContentContext()->GetRenderTargetCache()->CreateOffscreen( - *GetContext(), {1, 1}, 1u); - testing::MockRenderPass render_pass(GetContext(), target); - auto position_result = - geom->GetPositionBuffer(*GetContentContext(), entity, render_pass); - - EXPECT_EQ(position_result.vertex_buffer.vertex_count, 0u); - - EXPECT_EQ(geom->GetResultMode(), GeometryResult::Mode::kNormal); -} - -TEST_P(EntityTest, CanRenderEmptyPathsWithoutCrashing) { - PathBuilder builder = {}; - builder.AddRect(Rect::MakeLTRB(0, 0, 0, 0)); - Path path = builder.TakePath(); - - EXPECT_TRUE(path.GetBoundingBox()->IsEmpty()); - - auto contents = std::make_shared(); - contents->SetGeometry(Geometry::MakeFillPath(path)); - contents->SetColor(Color::Red()); - - Entity entity; - entity.SetTransform(Matrix::MakeScale(GetContentScale())); - entity.SetContents(contents); - - ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); -} - -TEST_P(EntityTest, DrawSuperEllipse) { - auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { - // UI state. - static float alpha = 10; - static float beta = 10; - static float radius = 40; - static int degree = 4; - static Color color = Color::Red(); - - ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); - ImGui::SliderFloat("Alpha", &alpha, 0, 100); - ImGui::SliderFloat("Beta", &beta, 0, 100); - ImGui::SliderInt("Degreee", °ree, 1, 20); - ImGui::SliderFloat("Radius", &radius, 0, 400); - ImGui::ColorEdit4("Color", reinterpret_cast(&color)); - ImGui::End(); - - auto contents = std::make_shared(); - contents->SetColor(color); - contents->SetGeometry(std::make_shared( - Point{400, 400}, radius, degree, alpha, beta)); - - Entity entity; - entity.SetContents(contents); - - return entity.Render(context, pass); - }; - - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(EntityTest, SolidColorApplyColorFilter) { - auto contents = SolidColorContents(); - contents.SetColor(Color::CornflowerBlue().WithAlpha(0.75)); - auto result = contents.ApplyColorFilter([](const Color& color) { - return color.Blend(Color::LimeGreen().WithAlpha(0.75), BlendMode::kScreen); - }); - ASSERT_TRUE(result); - ASSERT_COLOR_NEAR(contents.GetColor(), - Color(0.424452, 0.828743, 0.79105, 0.9375)); -} - -#define APPLY_COLOR_FILTER_GRADIENT_TEST(name) \ - TEST_P(EntityTest, name##GradientApplyColorFilter) { \ - auto contents = name##GradientContents(); \ - contents.SetColors({Color::CornflowerBlue().WithAlpha(0.75)}); \ - auto result = contents.ApplyColorFilter([](const Color& color) { \ - return color.Blend(Color::LimeGreen().WithAlpha(0.75), \ - BlendMode::kScreen); \ - }); \ - ASSERT_TRUE(result); \ - \ - std::vector expected = {Color(0.433247, 0.879523, 0.825324, 0.75)}; \ - ASSERT_COLORS_NEAR(contents.GetColors(), expected); \ - } - -APPLY_COLOR_FILTER_GRADIENT_TEST(Linear); -APPLY_COLOR_FILTER_GRADIENT_TEST(Radial); -APPLY_COLOR_FILTER_GRADIENT_TEST(Conical); -APPLY_COLOR_FILTER_GRADIENT_TEST(Sweep); +// using EntityTest = EntityPlayground; +// INSTANTIATE_PLAYGROUND_SUITE(EntityTest); + +// TEST_P(EntityTest, CanCreateEntity) { +// Entity entity; +// ASSERT_TRUE(entity.GetTransform().IsIdentity()); +// } + +// TEST_P(EntityTest, FilterCoverageRespectsCropRect) { +// auto image = CreateTextureForFixture("boston.jpg"); +// auto filter = ColorFilterContents::MakeBlend(BlendMode::kSoftLight, +// FilterInput::Make({image})); + +// // Without the crop rect (default behavior). +// { +// auto actual = filter->GetCoverage({}); +// auto expected = Rect::MakeSize(image->GetSize()); + +// ASSERT_TRUE(actual.has_value()); +// ASSERT_RECT_NEAR(actual.value(), expected); +// } + +// // With the crop rect. +// { +// auto expected = Rect::MakeLTRB(50, 50, 100, 100); +// filter->SetCoverageHint(expected); +// auto actual = filter->GetCoverage({}); + +// ASSERT_TRUE(actual.has_value()); +// ASSERT_RECT_NEAR(actual.value(), expected); +// } +// } + +// TEST_P(EntityTest, GeometryBoundsAreTransformed) { +// auto geometry = Geometry::MakeRect(Rect::MakeXYWH(100, 100, 100, 100)); +// auto transform = Matrix::MakeScale({2.0, 2.0, 2.0}); + +// ASSERT_RECT_NEAR(geometry->GetCoverage(transform).value(), +// Rect::MakeXYWH(200, 200, 200, 200)); +// } + +// TEST_P(EntityTest, ThreeStrokesInOnePath) { +// Path path = PathBuilder{} +// .MoveTo({100, 100}) +// .LineTo({100, 200}) +// .MoveTo({100, 300}) +// .LineTo({100, 400}) +// .MoveTo({100, 500}) +// .LineTo({100, 600}) +// .TakePath(); + +// Entity entity; +// entity.SetTransform(Matrix::MakeScale(GetContentScale())); +// auto contents = std::make_unique(); + +// auto geom = Geometry::MakeStrokePath(path, 5.0); +// contents->SetGeometry(geom.get()); +// contents->SetColor(Color::Red()); +// entity.SetContents(std::move(contents)); +// ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); +// } + +// TEST_P(EntityTest, StrokeWithTextureContents) { +// auto bridge = CreateTextureForFixture("bay_bridge.jpg"); +// Path path = PathBuilder{} +// .MoveTo({100, 100}) +// .LineTo({100, 200}) +// .MoveTo({100, 300}) +// .LineTo({100, 400}) +// .MoveTo({100, 500}) +// .LineTo({100, 600}) +// .TakePath(); + +// Entity entity; +// entity.SetTransform(Matrix::MakeScale(GetContentScale())); +// auto contents = std::make_unique(); +// auto geom = Geometry::MakeStrokePath(path, 100.0); +// contents->SetGeometry(geom.get()); +// contents->SetTexture(bridge); +// contents->SetTileModes(Entity::TileMode::kClamp, Entity::TileMode::kClamp); +// entity.SetContents(std::move(contents)); +// ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); +// } + +// TEST_P(EntityTest, TriangleInsideASquare) { +// auto callback = [&](ContentContext& context, RenderPass& pass) { +// Point offset(100, 100); + +// static PlaygroundPoint point_a(Point(10, 10) + offset, 20, +// Color::White()); Point a = DrawPlaygroundPoint(point_a); static +// PlaygroundPoint point_b(Point(210, 10) + offset, 20, Color::White()); +// Point b = DrawPlaygroundPoint(point_b); +// static PlaygroundPoint point_c(Point(210, 210) + offset, 20, +// Color::White()); +// Point c = DrawPlaygroundPoint(point_c); +// static PlaygroundPoint point_d(Point(10, 210) + offset, 20, +// Color::White()); Point d = DrawPlaygroundPoint(point_d); static +// PlaygroundPoint point_e(Point(50, 50) + offset, 20, Color::White()); +// Point e = DrawPlaygroundPoint(point_e); +// static PlaygroundPoint point_f(Point(100, 50) + offset, 20, +// Color::White()); Point f = DrawPlaygroundPoint(point_f); static +// PlaygroundPoint point_g(Point(50, 150) + offset, 20, Color::White()); +// Point g = DrawPlaygroundPoint(point_g); +// Path path = PathBuilder{} +// .MoveTo(a) +// .LineTo(b) +// .LineTo(c) +// .LineTo(d) +// .Close() +// .MoveTo(e) +// .LineTo(f) +// .LineTo(g) +// .Close() +// .TakePath(); + +// Entity entity; +// entity.SetTransform(Matrix::MakeScale(GetContentScale())); +// auto contents = std::make_unique(); +// contents->SetGeometry(Geometry::MakeStrokePath(path, 20.0)); +// contents->SetColor(Color::Red()); +// entity.SetContents(std::move(contents)); + +// return entity.Render(context, pass); +// }; +// ASSERT_TRUE(OpenPlaygroundHere(callback)); +// } + +// TEST_P(EntityTest, StrokeCapAndJoinTest) { +// const Point padding(300, 250); +// const Point margin(140, 180); + +// auto callback = [&](ContentContext& context, RenderPass& pass) { +// // Slightly above sqrt(2) by default, so that right angles are just below +// // the limit and acute angles are over the limit (causing them to get +// // beveled). +// static Scalar miter_limit = 1.41421357; +// static Scalar width = 30; + +// ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); +// { +// ImGui::SliderFloat("Miter limit", &miter_limit, 0, 30); +// ImGui::SliderFloat("Stroke width", &width, 0, 100); +// if (ImGui::Button("Reset")) { +// miter_limit = 1.41421357; +// width = 30; +// } +// } +// ImGui::End(); + +// auto world_matrix = Matrix::MakeScale(GetContentScale()); +// auto render_path = [width = width, &context, &pass, &world_matrix]( +// const Path& path, Cap cap, Join join) { +// auto contents = std::make_unique(); +// contents->SetGeometry( +// Geometry::MakeStrokePath(path, width, miter_limit, cap, join)); +// contents->SetColor(Color::Red()); + +// Entity entity; +// entity.SetTransform(world_matrix); +// entity.SetContents(std::move(contents)); + +// auto coverage = entity.GetCoverage(); +// if (coverage.has_value()) { +// auto bounds_contents = std::make_unique(); +// bounds_contents->SetGeometry(Geometry::MakeFillPath( +// PathBuilder{}.AddRect(entity.GetCoverage().value()).TakePath())); +// bounds_contents->SetColor(Color::Green().WithAlpha(0.5)); +// Entity bounds_entity; +// bounds_entity.SetContents(std::move(bounds_contents)); +// bounds_entity.Render(context, pass); +// } + +// entity.Render(context, pass); +// }; + +// const Point a_def(0, 0), b_def(0, 100), c_def(150, 0), d_def(150, -100), +// e_def(75, 75); +// const Scalar r = 30; +// // Cap::kButt demo. +// { +// Point off = Point(0, 0) * padding + margin; +// static PlaygroundPoint point_a(off + a_def, r, Color::Black()); +// static PlaygroundPoint point_b(off + b_def, r, Color::White()); +// auto [a, b] = DrawPlaygroundLine(point_a, point_b); +// static PlaygroundPoint point_c(off + c_def, r, Color::Black()); +// static PlaygroundPoint point_d(off + d_def, r, Color::White()); +// auto [c, d] = DrawPlaygroundLine(point_c, point_d); +// render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(), +// Cap::kButt, Join::kBevel); +// } + +// // Cap::kSquare demo. +// { +// Point off = Point(1, 0) * padding + margin; +// static PlaygroundPoint point_a(off + a_def, r, Color::Black()); +// static PlaygroundPoint point_b(off + b_def, r, Color::White()); +// auto [a, b] = DrawPlaygroundLine(point_a, point_b); +// static PlaygroundPoint point_c(off + c_def, r, Color::Black()); +// static PlaygroundPoint point_d(off + d_def, r, Color::White()); +// auto [c, d] = DrawPlaygroundLine(point_c, point_d); +// render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(), +// Cap::kSquare, Join::kBevel); +// } + +// // Cap::kRound demo. +// { +// Point off = Point(2, 0) * padding + margin; +// static PlaygroundPoint point_a(off + a_def, r, Color::Black()); +// static PlaygroundPoint point_b(off + b_def, r, Color::White()); +// auto [a, b] = DrawPlaygroundLine(point_a, point_b); +// static PlaygroundPoint point_c(off + c_def, r, Color::Black()); +// static PlaygroundPoint point_d(off + d_def, r, Color::White()); +// auto [c, d] = DrawPlaygroundLine(point_c, point_d); +// render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(), +// Cap::kRound, Join::kBevel); +// } + +// // Join::kBevel demo. +// { +// Point off = Point(0, 1) * padding + margin; +// static PlaygroundPoint point_a = +// PlaygroundPoint(off + a_def, r, Color::White()); +// static PlaygroundPoint point_b = +// PlaygroundPoint(off + e_def, r, Color::White()); +// static PlaygroundPoint point_c = +// PlaygroundPoint(off + c_def, r, Color::White()); +// Point a = DrawPlaygroundPoint(point_a); +// Point b = DrawPlaygroundPoint(point_b); +// Point c = DrawPlaygroundPoint(point_c); +// render_path( +// PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(), +// Cap::kButt, Join::kBevel); +// } + +// // Join::kMiter demo. +// { +// Point off = Point(1, 1) * padding + margin; +// static PlaygroundPoint point_a(off + a_def, r, Color::White()); +// static PlaygroundPoint point_b(off + e_def, r, Color::White()); +// static PlaygroundPoint point_c(off + c_def, r, Color::White()); +// Point a = DrawPlaygroundPoint(point_a); +// Point b = DrawPlaygroundPoint(point_b); +// Point c = DrawPlaygroundPoint(point_c); +// render_path( +// PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(), +// Cap::kButt, Join::kMiter); +// } + +// // Join::kRound demo. +// { +// Point off = Point(2, 1) * padding + margin; +// static PlaygroundPoint point_a(off + a_def, r, Color::White()); +// static PlaygroundPoint point_b(off + e_def, r, Color::White()); +// static PlaygroundPoint point_c(off + c_def, r, Color::White()); +// Point a = DrawPlaygroundPoint(point_a); +// Point b = DrawPlaygroundPoint(point_b); +// Point c = DrawPlaygroundPoint(point_c); +// render_path( +// PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(), +// Cap::kButt, Join::kRound); +// } + +// return true; +// }; +// ASSERT_TRUE(OpenPlaygroundHere(callback)); +// } + +// TEST_P(EntityTest, CubicCurveTest) { +// // Compare with https://fiddle.skia.org/c/b3625f26122c9de7afe7794fcf25ead3 +// Path path = +// PathBuilder{} +// .MoveTo({237.164, 125.003}) +// .CubicCurveTo({236.709, 125.184}, {236.262, 125.358}, +// {235.81, 125.538}) +// .CubicCurveTo({235.413, 125.68}, {234.994, 125.832}, +// {234.592, 125.977}) +// .CubicCurveTo({234.592, 125.977}, {234.591, 125.977}, +// {234.59, 125.977}) +// .CubicCurveTo({222.206, 130.435}, {207.708, 135.753}, +// {192.381, 141.429}) +// .CubicCurveTo({162.77, 151.336}, {122.17, 156.894}, {84.1123, 160}) +// .Close() +// .TakePath(); +// Entity entity; +// entity.SetTransform(Matrix::MakeScale(GetContentScale())); +// entity.SetContents(SolidColorContents::Make(path, Color::Red())); +// ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); +// } + +// TEST_P(EntityTest, CanDrawCorrectlyWithRotatedTransform) { +// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { +// const char* input_axis[] = {"X", "Y", "Z"}; +// static int rotation_axis_index = 0; +// static float rotation = 0; +// ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); +// ImGui::SliderFloat("Rotation", &rotation, -kPi, kPi); +// ImGui::Combo("Rotation Axis", &rotation_axis_index, input_axis, +// sizeof(input_axis) / sizeof(char*)); +// Matrix rotation_matrix; +// switch (rotation_axis_index) { +// case 0: +// rotation_matrix = Matrix::MakeRotationX(Radians(rotation)); +// break; +// case 1: +// rotation_matrix = Matrix::MakeRotationY(Radians(rotation)); +// break; +// case 2: +// rotation_matrix = Matrix::MakeRotationZ(Radians(rotation)); +// break; +// default: +// rotation_matrix = Matrix{}; +// break; +// } + +// if (ImGui::Button("Reset")) { +// rotation = 0; +// } +// ImGui::End(); +// Matrix current_transform = +// Matrix::MakeScale(GetContentScale()) +// .MakeTranslation( +// Vector3(Point(pass.GetRenderTargetSize().width / 2.0, +// pass.GetRenderTargetSize().height / 2.0))); +// Matrix result_transform = current_transform * rotation_matrix; +// Path path = +// PathBuilder{}.AddRect(Rect::MakeXYWH(-300, -400, 600, +// 800)).TakePath(); + +// Entity entity; +// entity.SetTransform(result_transform); +// entity.SetContents(SolidColorContents::Make(path, Color::Red())); +// return entity.Render(context, pass); +// }; +// ASSERT_TRUE(OpenPlaygroundHere(callback)); +// } + +// TEST_P(EntityTest, CubicCurveAndOverlapTest) { +// // Compare with https://fiddle.skia.org/c/7a05a3e186c65a8dfb732f68020aae06 +// Path path = +// PathBuilder{} +// .MoveTo({359.934, 96.6335}) +// .CubicCurveTo({358.189, 96.7055}, {356.436, 96.7908}, +// {354.673, 96.8895}) +// .CubicCurveTo({354.571, 96.8953}, {354.469, 96.9016}, +// {354.367, 96.9075}) +// .CubicCurveTo({352.672, 97.0038}, {350.969, 97.113}, +// {349.259, 97.2355}) +// .CubicCurveTo({349.048, 97.2506}, {348.836, 97.2678}, +// {348.625, 97.2834}) +// .CubicCurveTo({347.019, 97.4014}, {345.407, 97.5299}, +// {343.789, 97.6722}) +// .CubicCurveTo({343.428, 97.704}, {343.065, 97.7402}, +// {342.703, 97.7734}) +// .CubicCurveTo({341.221, 97.9086}, {339.736, 98.0505}, +// {338.246, 98.207}) +// .CubicCurveTo({337.702, 98.2642}, {337.156, 98.3292}, +// {336.612, 98.3894}) +// .CubicCurveTo({335.284, 98.5356}, {333.956, 98.6837}, +// {332.623, 98.8476}) +// .CubicCurveTo({332.495, 98.8635}, {332.366, 98.8818}, +// {332.237, 98.8982}) +// .LineTo({332.237, 102.601}) +// .LineTo({321.778, 102.601}) +// .LineTo({321.778, 100.382}) +// .CubicCurveTo({321.572, 100.413}, {321.367, 100.442}, +// {321.161, 100.476}) +// .CubicCurveTo({319.22, 100.79}, {317.277, 101.123}, +// {315.332, 101.479}) +// .CubicCurveTo({315.322, 101.481}, {315.311, 101.482}, +// {315.301, 101.484}) +// .LineTo({310.017, 105.94}) +// .LineTo({309.779, 105.427}) +// .LineTo({314.403, 101.651}) +// .CubicCurveTo({314.391, 101.653}, {314.379, 101.656}, +// {314.368, 101.658}) +// .CubicCurveTo({312.528, 102.001}, {310.687, 102.366}, +// {308.846, 102.748}) +// .CubicCurveTo({307.85, 102.955}, {306.855, 103.182}, {305.859, +// 103.4}) .CubicCurveTo({305.048, 103.579}, {304.236, 103.75}, +// {303.425, 103.936}) +// .LineTo({299.105, 107.578}) +// .LineTo({298.867, 107.065}) +// .LineTo({302.394, 104.185}) +// .LineTo({302.412, 104.171}) +// .CubicCurveTo({301.388, 104.409}, {300.366, 104.67}, +// {299.344, 104.921}) +// .CubicCurveTo({298.618, 105.1}, {297.89, 105.269}, {297.165, +// 105.455}) .CubicCurveTo({295.262, 105.94}, {293.36, 106.445}, +// {291.462, 106.979}) +// .CubicCurveTo({291.132, 107.072}, {290.802, 107.163}, +// {290.471, 107.257}) +// .CubicCurveTo({289.463, 107.544}, {288.455, 107.839}, +// {287.449, 108.139}) +// .CubicCurveTo({286.476, 108.431}, {285.506, 108.73}, +// {284.536, 109.035}) +// .CubicCurveTo({283.674, 109.304}, {282.812, 109.579}, +// {281.952, 109.859}) +// .CubicCurveTo({281.177, 110.112}, {280.406, 110.377}, +// {279.633, 110.638}) +// .CubicCurveTo({278.458, 111.037}, {277.256, 111.449}, +// {276.803, 111.607}) +// .CubicCurveTo({276.76, 111.622}, {276.716, 111.637}, +// {276.672, 111.653}) +// .CubicCurveTo({275.017, 112.239}, {273.365, 112.836}, +// {271.721, 113.463}) +// .LineTo({271.717, 113.449}) +// .CubicCurveTo({271.496, 113.496}, {271.238, 113.559}, +// {270.963, 113.628}) +// .CubicCurveTo({270.893, 113.645}, {270.822, 113.663}, +// {270.748, 113.682}) +// .CubicCurveTo({270.468, 113.755}, {270.169, 113.834}, +// {269.839, 113.926}) +// .CubicCurveTo({269.789, 113.94}, {269.732, 113.957}, +// {269.681, 113.972}) +// .CubicCurveTo({269.391, 114.053}, {269.081, 114.143}, +// {268.756, 114.239}) +// .CubicCurveTo({268.628, 114.276}, {268.5, 114.314}, +// {268.367, 114.354}) +// .CubicCurveTo({268.172, 114.412}, {267.959, 114.478}, +// {267.752, 114.54}) +// .CubicCurveTo({263.349, 115.964}, {258.058, 117.695}, +// {253.564, 119.252}) +// .CubicCurveTo({253.556, 119.255}, {253.547, 119.258}, +// {253.538, 119.261}) +// .CubicCurveTo({251.844, 119.849}, {250.056, 120.474}, +// {248.189, 121.131}) +// .CubicCurveTo({248, 121.197}, {247.812, 121.264}, {247.621, +// 121.331}) .CubicCurveTo({247.079, 121.522}, {246.531, 121.715}, +// {245.975, 121.912}) +// .CubicCurveTo({245.554, 122.06}, {245.126, 122.212}, +// {244.698, 122.364}) +// .CubicCurveTo({244.071, 122.586}, {243.437, 122.811}, +// {242.794, 123.04}) +// .CubicCurveTo({242.189, 123.255}, {241.58, 123.472}, +// {240.961, 123.693}) +// .CubicCurveTo({240.659, 123.801}, {240.357, 123.909}, +// {240.052, 124.018}) +// .CubicCurveTo({239.12, 124.351}, {238.18, 124.687}, {237.22, +// 125.032}) .LineTo({237.164, 125.003}) .CubicCurveTo({236.709, +// 125.184}, {236.262, 125.358}, +// {235.81, 125.538}) +// .CubicCurveTo({235.413, 125.68}, {234.994, 125.832}, +// {234.592, 125.977}) +// .CubicCurveTo({234.592, 125.977}, {234.591, 125.977}, +// {234.59, 125.977}) +// .CubicCurveTo({222.206, 130.435}, {207.708, 135.753}, +// {192.381, 141.429}) +// .CubicCurveTo({162.77, 151.336}, {122.17, 156.894}, {84.1123, 160}) +// .LineTo({360, 160}) +// .LineTo({360, 119.256}) +// .LineTo({360, 106.332}) +// .LineTo({360, 96.6307}) +// .CubicCurveTo({359.978, 96.6317}, {359.956, 96.6326}, +// {359.934, 96.6335}) +// .Close() +// .MoveTo({337.336, 124.143}) +// .CubicCurveTo({337.274, 122.359}, {338.903, 121.511}, +// {338.903, 121.511}) +// .CubicCurveTo({338.903, 121.511}, {338.96, 123.303}, +// {337.336, 124.143}) +// .Close() +// .MoveTo({340.082, 121.849}) +// .CubicCurveTo({340.074, 121.917}, {340.062, 121.992}, +// {340.046, 122.075}) +// .CubicCurveTo({340.039, 122.109}, {340.031, 122.142}, +// {340.023, 122.177}) +// .CubicCurveTo({340.005, 122.26}, {339.98, 122.346}, +// {339.952, 122.437}) +// .CubicCurveTo({339.941, 122.473}, {339.931, 122.507}, +// {339.918, 122.544}) +// .CubicCurveTo({339.873, 122.672}, {339.819, 122.804}, +// {339.75, 122.938}) +// .CubicCurveTo({339.747, 122.944}, {339.743, 122.949}, +// {339.74, 122.955}) +// .CubicCurveTo({339.674, 123.08}, {339.593, 123.205}, +// {339.501, 123.328}) +// .CubicCurveTo({339.473, 123.366}, {339.441, 123.401}, +// {339.41, 123.438}) +// .CubicCurveTo({339.332, 123.534}, {339.243, 123.625}, +// {339.145, 123.714}) +// .CubicCurveTo({339.105, 123.75}, {339.068, 123.786}, +// {339.025, 123.821}) +// .CubicCurveTo({338.881, 123.937}, {338.724, 124.048}, +// {338.539, 124.143}) +// .CubicCurveTo({338.532, 123.959}, {338.554, 123.79}, +// {338.58, 123.626}) +// .CubicCurveTo({338.58, 123.625}, {338.58, 123.625}, {338.58, +// 123.625}) .CubicCurveTo({338.607, 123.455}, {338.65, 123.299}, +// {338.704, 123.151}) +// .CubicCurveTo({338.708, 123.14}, {338.71, 123.127}, +// {338.714, 123.117}) +// .CubicCurveTo({338.769, 122.971}, {338.833, 122.838}, +// {338.905, 122.712}) +// .CubicCurveTo({338.911, 122.702}, {338.916, 122.69200000000001}, +// {338.922, 122.682}) +// .CubicCurveTo({338.996, 122.557}, {339.072, 122.444}, +// {339.155, 122.34}) +// .CubicCurveTo({339.161, 122.333}, {339.166, 122.326}, +// {339.172, 122.319}) +// .CubicCurveTo({339.256, 122.215}, {339.339, 122.12}, +// {339.425, 122.037}) +// .CubicCurveTo({339.428, 122.033}, {339.431, 122.03}, +// {339.435, 122.027}) +// .CubicCurveTo({339.785, 121.687}, {340.106, 121.511}, +// {340.106, 121.511}) +// .CubicCurveTo({340.106, 121.511}, {340.107, 121.645}, +// {340.082, 121.849}) +// .Close() +// .MoveTo({340.678, 113.245}) +// .CubicCurveTo({340.594, 113.488}, {340.356, 113.655}, +// {340.135, 113.775}) +// .CubicCurveTo({339.817, 113.948}, {339.465, 114.059}, +// {339.115, 114.151}) +// .CubicCurveTo({338.251, 114.379}, {337.34, 114.516}, +// {336.448, 114.516}) +// .CubicCurveTo({335.761, 114.516}, {335.072, 114.527}, +// {334.384, 114.513}) +// .CubicCurveTo({334.125, 114.508}, {333.862, 114.462}, +// {333.605, 114.424}) +// .CubicCurveTo({332.865, 114.318}, {332.096, 114.184}, +// {331.41, 113.883}) +// .CubicCurveTo({330.979, 113.695}, {330.442, 113.34}, +// {330.672, 112.813}) +// .CubicCurveTo({331.135, 111.755}, {333.219, 112.946}, +// {334.526, 113.833}) +// .CubicCurveTo({334.54, 113.816}, {334.554, 113.8}, {334.569, +// 113.784}) .CubicCurveTo({333.38, 112.708}, {331.749, 110.985}, +// {332.76, 110.402}) +// .CubicCurveTo({333.769, 109.82}, {334.713, 111.93}, +// {335.228, 113.395}) +// .CubicCurveTo({334.915, 111.889}, {334.59, 109.636}, +// {335.661, 109.592}) +// .CubicCurveTo({336.733, 109.636}, {336.408, 111.889}, +// {336.07, 113.389}) +// .CubicCurveTo({336.609, 111.93}, {337.553, 109.82}, +// {338.563, 110.402}) +// .CubicCurveTo({339.574, 110.984}, {337.942, 112.708}, +// {336.753, 113.784}) +// .CubicCurveTo({336.768, 113.8}, {336.782, 113.816}, +// {336.796, 113.833}) +// .CubicCurveTo({338.104, 112.946}, {340.187, 111.755}, +// {340.65, 112.813}) +// .CubicCurveTo({340.71, 112.95}, {340.728, 113.102}, +// {340.678, 113.245}) +// .Close() +// .MoveTo({346.357, 106.771}) +// .CubicCurveTo({346.295, 104.987}, {347.924, 104.139}, +// {347.924, 104.139}) +// .CubicCurveTo({347.924, 104.139}, {347.982, 105.931}, +// {346.357, 106.771}) +// .Close() +// .MoveTo({347.56, 106.771}) +// .CubicCurveTo({347.498, 104.987}, {349.127, 104.139}, +// {349.127, 104.139}) +// .CubicCurveTo({349.127, 104.139}, {349.185, 105.931}, +// {347.56, 106.771}) +// .Close() +// .TakePath(); +// Entity entity; +// entity.SetTransform(Matrix::MakeScale(GetContentScale())); +// entity.SetContents(SolidColorContents::Make(path, Color::Red())); +// ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); +// } + +// TEST_P(EntityTest, SolidColorContentsStrokeSetStrokeCapsAndJoins) { +// { +// auto geometry = Geometry::MakeStrokePath(Path{}); +// auto path_geometry = static_cast(geometry.get()); +// // Defaults. +// ASSERT_EQ(path_geometry->GetStrokeCap(), Cap::kButt); +// ASSERT_EQ(path_geometry->GetStrokeJoin(), Join::kMiter); +// } + +// { +// auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, 4.0, Cap::kSquare); +// auto path_geometry = static_cast(geometry.get()); +// ASSERT_EQ(path_geometry->GetStrokeCap(), Cap::kSquare); +// } + +// { +// auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, 4.0, Cap::kRound); +// auto path_geometry = static_cast(geometry.get()); +// ASSERT_EQ(path_geometry->GetStrokeCap(), Cap::kRound); +// } +// } + +// TEST_P(EntityTest, SolidColorContentsStrokeSetMiterLimit) { +// { +// auto geometry = Geometry::MakeStrokePath(Path{}); +// auto path_geometry = static_cast(geometry.get()); +// ASSERT_FLOAT_EQ(path_geometry->GetMiterLimit(), 4); +// } + +// { +// auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, +// /*miter_limit=*/8.0); auto path_geometry = +// static_cast(geometry.get()); +// ASSERT_FLOAT_EQ(path_geometry->GetMiterLimit(), 8); +// } + +// { +// auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, +// /*miter_limit=*/-1.0); auto path_geometry = +// static_cast(geometry.get()); +// ASSERT_FLOAT_EQ(path_geometry->GetMiterLimit(), 4); +// } +// } + +// TEST_P(EntityTest, BlendingModeOptions) { +// std::vector blend_mode_names; +// std::vector blend_mode_values; +// { +// // Force an exhausiveness check with a switch. When adding blend modes, +// // update this switch with a new name/value to make it selectable in the +// // test GUI. + +// const BlendMode b{}; +// static_assert(b == BlendMode::kClear); // Ensure the first item in +// // the switch is the first +// // item in the enum. +// static_assert(Entity::kLastPipelineBlendMode == BlendMode::kModulate); +// switch (b) { +// case BlendMode::kClear: +// blend_mode_names.push_back("Clear"); +// blend_mode_values.push_back(BlendMode::kClear); +// case BlendMode::kSource: +// blend_mode_names.push_back("Source"); +// blend_mode_values.push_back(BlendMode::kSource); +// case BlendMode::kDestination: +// blend_mode_names.push_back("Destination"); +// blend_mode_values.push_back(BlendMode::kDestination); +// case BlendMode::kSourceOver: +// blend_mode_names.push_back("SourceOver"); +// blend_mode_values.push_back(BlendMode::kSourceOver); +// case BlendMode::kDestinationOver: +// blend_mode_names.push_back("DestinationOver"); +// blend_mode_values.push_back(BlendMode::kDestinationOver); +// case BlendMode::kSourceIn: +// blend_mode_names.push_back("SourceIn"); +// blend_mode_values.push_back(BlendMode::kSourceIn); +// case BlendMode::kDestinationIn: +// blend_mode_names.push_back("DestinationIn"); +// blend_mode_values.push_back(BlendMode::kDestinationIn); +// case BlendMode::kSourceOut: +// blend_mode_names.push_back("SourceOut"); +// blend_mode_values.push_back(BlendMode::kSourceOut); +// case BlendMode::kDestinationOut: +// blend_mode_names.push_back("DestinationOut"); +// blend_mode_values.push_back(BlendMode::kDestinationOut); +// case BlendMode::kSourceATop: +// blend_mode_names.push_back("SourceATop"); +// blend_mode_values.push_back(BlendMode::kSourceATop); +// case BlendMode::kDestinationATop: +// blend_mode_names.push_back("DestinationATop"); +// blend_mode_values.push_back(BlendMode::kDestinationATop); +// case BlendMode::kXor: +// blend_mode_names.push_back("Xor"); +// blend_mode_values.push_back(BlendMode::kXor); +// case BlendMode::kPlus: +// blend_mode_names.push_back("Plus"); +// blend_mode_values.push_back(BlendMode::kPlus); +// case BlendMode::kModulate: +// blend_mode_names.push_back("Modulate"); +// blend_mode_values.push_back(BlendMode::kModulate); +// }; +// } + +// auto callback = [&](ContentContext& context, RenderPass& pass) { +// auto world_matrix = Matrix::MakeScale(GetContentScale()); +// auto draw_rect = [&context, &pass, &world_matrix]( +// Rect rect, Color color, BlendMode blend_mode) -> +// bool { +// using VS = SolidFillPipeline::VertexShader; +// using FS = SolidFillPipeline::FragmentShader; + +// VertexBufferBuilder vtx_builder; +// { +// auto r = rect.GetLTRB(); +// vtx_builder.AddVertices({ +// {Point(r[0], r[1])}, +// {Point(r[2], r[1])}, +// {Point(r[2], r[3])}, +// {Point(r[0], r[1])}, +// {Point(r[2], r[3])}, +// {Point(r[0], r[3])}, +// }); +// } + +// pass.SetCommandLabel("Blended Rectangle"); +// auto options = OptionsFromPass(pass); +// options.blend_mode = blend_mode; +// options.primitive_type = PrimitiveType::kTriangle; +// pass.SetPipeline(context.GetSolidFillPipeline(options)); +// pass.SetVertexBuffer( +// vtx_builder.CreateVertexBuffer(context.GetTransientsBuffer())); + +// VS::FrameInfo frame_info; +// frame_info.mvp = pass.GetOrthographicTransform() * world_matrix; +// VS::BindFrameInfo( +// pass, context.GetTransientsBuffer().EmplaceUniform(frame_info)); +// FS::FragInfo frag_info; +// frag_info.color = color.Premultiply(); +// FS::BindFragInfo( +// pass, context.GetTransientsBuffer().EmplaceUniform(frame_info)); +// return pass.Draw().ok(); +// }; + +// ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); +// static Color color1(1, 0, 0, 0.5), color2(0, 1, 0, 0.5); +// ImGui::ColorEdit4("Color 1", reinterpret_cast(&color1)); +// ImGui::ColorEdit4("Color 2", reinterpret_cast(&color2)); +// static int current_blend_index = 3; +// ImGui::ListBox("Blending mode", ¤t_blend_index, +// blend_mode_names.data(), blend_mode_names.size()); +// ImGui::End(); + +// BlendMode selected_mode = blend_mode_values[current_blend_index]; + +// Point a, b, c, d; +// static PlaygroundPoint point_a(Point(400, 100), 20, Color::White()); +// static PlaygroundPoint point_b(Point(200, 300), 20, Color::White()); +// std::tie(a, b) = DrawPlaygroundLine(point_a, point_b); +// static PlaygroundPoint point_c(Point(470, 190), 20, Color::White()); +// static PlaygroundPoint point_d(Point(270, 390), 20, Color::White()); +// std::tie(c, d) = DrawPlaygroundLine(point_c, point_d); + +// bool result = true; +// result = result && +// draw_rect(Rect::MakeXYWH(0, 0, pass.GetRenderTargetSize().width, +// pass.GetRenderTargetSize().height), +// Color(), BlendMode::kClear); +// result = result && draw_rect(Rect::MakeLTRB(a.x, a.y, b.x, b.y), color1, +// BlendMode::kSourceOver); +// result = result && draw_rect(Rect::MakeLTRB(c.x, c.y, d.x, d.y), color2, +// selected_mode); +// return result; +// }; +// ASSERT_TRUE(OpenPlaygroundHere(callback)); +// } + +// TEST_P(EntityTest, BezierCircleScaled) { +// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { +// static float scale = 20; + +// ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); +// ImGui::SliderFloat("Scale", &scale, 1, 100); +// ImGui::End(); + +// Entity entity; +// entity.SetTransform(Matrix::MakeScale(GetContentScale())); +// auto path = PathBuilder{} +// .MoveTo({97.325, 34.818}) +// .CubicCurveTo({98.50862885295136, 34.81812293973836}, +// {99.46822048142015, 33.85863261475589}, +// {99.46822048142015, 32.67499810206613}) +// .CubicCurveTo({99.46822048142015, 31.491363589376355}, +// {98.50862885295136, 30.53187326439389}, +// {97.32499434685802, 30.531998226542708}) +// .CubicCurveTo({96.14153655073771, 30.532123170035373}, +// {95.18222070648729, 31.491540299350355}, +// {95.18222070648729, 32.67499810206613}) +// .CubicCurveTo({95.18222070648729, 33.85845590478189}, +// {96.14153655073771, 34.81787303409686}, +// {97.32499434685802, 34.81799797758954}) +// .Close() +// .TakePath(); +// entity.SetTransform( +// Matrix::MakeScale({scale, scale, 1.0}).Translate({-90, -20, 0})); +// entity.SetContents(SolidColorContents::Make(path, Color::Red())); +// return entity.Render(context, pass); +// }; +// ASSERT_TRUE(OpenPlaygroundHere(callback)); +// } + +// TEST_P(EntityTest, Filters) { +// auto bridge = CreateTextureForFixture("bay_bridge.jpg"); +// auto boston = CreateTextureForFixture("boston.jpg"); +// auto kalimba = CreateTextureForFixture("kalimba.jpg"); +// ASSERT_TRUE(bridge && boston && kalimba); + +// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { +// auto fi_bridge = FilterInput::Make(bridge); +// auto fi_boston = FilterInput::Make(boston); +// auto fi_kalimba = FilterInput::Make(kalimba); + +// std::shared_ptr blend0 = ColorFilterContents::MakeBlend( +// BlendMode::kModulate, {fi_kalimba, fi_boston}); + +// auto blend1 = ColorFilterContents::MakeBlend( +// BlendMode::kScreen, +// {FilterInput::Make(blend0), fi_bridge, fi_bridge, fi_bridge}); + +// Entity entity; +// entity.SetTransform(Matrix::MakeScale(GetContentScale()) * +// Matrix::MakeTranslation({500, 300}) * +// Matrix::MakeScale(Vector2{0.5, 0.5})); +// entity.SetContents(blend1); +// return entity.Render(context, pass); +// }; +// ASSERT_TRUE(OpenPlaygroundHere(callback)); +// } + +// TEST_P(EntityTest, GaussianBlurFilter) { +// auto boston = +// CreateTextureForFixture("boston.jpg", /*enable_mipmapping=*/true); +// ASSERT_TRUE(boston); + +// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { +// const char* input_type_names[] = {"Texture", "Solid Color"}; +// const char* blur_type_names[] = {"Image blur", "Mask blur"}; +// const char* pass_variation_names[] = {"New"}; +// const char* blur_style_names[] = {"Normal", "Solid", "Outer", "Inner"}; +// const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"}; +// const FilterContents::BlurStyle blur_styles[] = { +// FilterContents::BlurStyle::kNormal, +// FilterContents::BlurStyle::kSolid, FilterContents::BlurStyle::kOuter, +// FilterContents::BlurStyle::kInner}; +// const Entity::TileMode tile_modes[] = { +// Entity::TileMode::kClamp, Entity::TileMode::kRepeat, +// Entity::TileMode::kMirror, Entity::TileMode::kDecal}; + +// // UI state. +// static int selected_input_type = 0; +// static Color input_color = Color::Black(); +// static int selected_blur_type = 0; +// static int selected_pass_variation = 0; +// static bool combined_sigma = false; +// static float blur_amount_coarse[2] = {0, 0}; +// static float blur_amount_fine[2] = {10, 10}; +// static int selected_blur_style = 0; +// static int selected_tile_mode = 3; +// static Color cover_color(1, 0, 0, 0.2); +// static Color bounds_color(0, 1, 0, 0.1); +// static float offset[2] = {500, 400}; +// static float rotation = 0; +// static float scale[2] = {0.65, 0.65}; +// static float skew[2] = {0, 0}; +// static float path_rect[4] = {0, 0, +// static_cast(boston->GetSize().width), +// static_cast(boston->GetSize().height)}; + +// ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); +// { +// ImGui::Combo("Input type", &selected_input_type, input_type_names, +// sizeof(input_type_names) / sizeof(char*)); +// if (selected_input_type == 0) { +// ImGui::SliderFloat("Input opacity", &input_color.alpha, 0, 1); +// } else { +// ImGui::ColorEdit4("Input color", +// reinterpret_cast(&input_color)); +// } +// ImGui::Combo("Blur type", &selected_blur_type, blur_type_names, +// sizeof(blur_type_names) / sizeof(char*)); +// if (selected_blur_type == 0) { +// ImGui::Combo("Pass variation", &selected_pass_variation, +// pass_variation_names, +// sizeof(pass_variation_names) / sizeof(char*)); +// } +// ImGui::Checkbox("Combined sigma", &combined_sigma); +// if (combined_sigma) { +// ImGui::SliderFloat("Sigma (coarse)", blur_amount_coarse, 0, 1000); +// ImGui::SliderFloat("Sigma (fine)", blur_amount_fine, 0, 10); +// blur_amount_coarse[1] = blur_amount_coarse[0]; +// blur_amount_fine[1] = blur_amount_fine[0]; +// } else { +// ImGui::SliderFloat2("Sigma (coarse)", blur_amount_coarse, 0, 1000); +// ImGui::SliderFloat2("Sigma (fine)", blur_amount_fine, 0, 10); +// } +// ImGui::Combo("Blur style", &selected_blur_style, blur_style_names, +// sizeof(blur_style_names) / sizeof(char*)); +// ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names, +// sizeof(tile_mode_names) / sizeof(char*)); +// ImGui::ColorEdit4("Cover color", +// reinterpret_cast(&cover_color)); ImGui::ColorEdit4("Bounds +// color", +// reinterpret_cast(&bounds_color)); +// ImGui::SliderFloat2("Translation", offset, 0, +// pass.GetRenderTargetSize().width); +// ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2); +// ImGui::SliderFloat2("Scale", scale, 0, 3); +// ImGui::SliderFloat2("Skew", skew, -3, 3); +// ImGui::SliderFloat4("Path XYWH", path_rect, -1000, 1000); +// } +// ImGui::End(); + +// auto blur_sigma_x = Sigma{blur_amount_coarse[0] + blur_amount_fine[0]}; +// auto blur_sigma_y = Sigma{blur_amount_coarse[1] + blur_amount_fine[1]}; + +// std::shared_ptr input; +// Size input_size; + +// auto input_rect = +// Rect::MakeXYWH(path_rect[0], path_rect[1], path_rect[2], +// path_rect[3]); +// if (selected_input_type == 0) { +// auto texture = std::make_shared(); +// texture->SetSourceRect(Rect::MakeSize(boston->GetSize())); +// texture->SetDestinationRect(input_rect); +// texture->SetTexture(boston); +// texture->SetOpacity(input_color.alpha); + +// input = texture; +// input_size = input_rect.GetSize(); +// } else { +// auto fill = std::make_shared(); +// fill->SetColor(input_color); +// fill->SetGeometry( +// Geometry::MakeFillPath(PathBuilder{}.AddRect(input_rect).TakePath())); + +// input = fill; +// input_size = input_rect.GetSize(); +// } + +// std::shared_ptr blur; +// switch (selected_pass_variation) { +// case 0: +// blur = std::make_shared( +// blur_sigma_x.sigma, blur_sigma_y.sigma, +// tile_modes[selected_tile_mode], blur_styles[selected_blur_style], +// /*geometry=*/nullptr); +// blur->SetInputs({FilterInput::Make(input)}); +// break; +// case 1: +// blur = FilterContents::MakeGaussianBlur( +// FilterInput::Make(input), blur_sigma_x, blur_sigma_y, +// tile_modes[selected_tile_mode], +// blur_styles[selected_blur_style]); +// break; +// }; +// FML_CHECK(blur); + +// auto mask_blur = FilterContents::MakeBorderMaskBlur( +// FilterInput::Make(input), blur_sigma_x, blur_sigma_y, +// blur_styles[selected_blur_style]); + +// auto ctm = Matrix::MakeScale(GetContentScale()) * +// Matrix::MakeTranslation(Vector3(offset[0], offset[1])) * +// Matrix::MakeRotationZ(Radians(rotation)) * +// Matrix::MakeScale(Vector2(scale[0], scale[1])) * +// Matrix::MakeSkew(skew[0], skew[1]) * +// Matrix::MakeTranslation(-Point(input_size) / 2); + +// auto target_contents = selected_blur_type == 0 ? blur : mask_blur; + +// Entity entity; +// entity.SetContents(target_contents); +// entity.SetTransform(ctm); + +// entity.Render(context, pass); + +// // Renders a red "cover" rectangle that shows the original position of +// the +// // unfiltered input. +// Entity cover_entity; +// cover_entity.SetContents(SolidColorContents::Make( +// PathBuilder{}.AddRect(input_rect).TakePath(), cover_color)); +// cover_entity.SetTransform(ctm); + +// cover_entity.Render(context, pass); + +// // Renders a green bounding rect of the target filter. +// Entity bounds_entity; +// std::optional target_contents_coverage = +// target_contents->GetCoverage(entity); +// if (target_contents_coverage.has_value()) { +// bounds_entity.SetContents(SolidColorContents::Make( +// PathBuilder{} +// .AddRect(target_contents->GetCoverage(entity).value()) +// .TakePath(), +// bounds_color)); +// bounds_entity.SetTransform(Matrix()); + +// bounds_entity.Render(context, pass); +// } + +// return true; +// }; +// ASSERT_TRUE(OpenPlaygroundHere(callback)); +// } + +// TEST_P(EntityTest, MorphologyFilter) { +// auto boston = CreateTextureForFixture("boston.jpg"); +// ASSERT_TRUE(boston); + +// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { +// const char* morphology_type_names[] = {"Dilate", "Erode"}; +// const FilterContents::MorphType morphology_types[] = { +// FilterContents::MorphType::kDilate, +// FilterContents::MorphType::kErode}; +// static Color input_color = Color::Black(); +// // UI state. +// static int selected_morphology_type = 0; +// static float radius[2] = {20, 20}; +// static Color cover_color(1, 0, 0, 0.2); +// static Color bounds_color(0, 1, 0, 0.1); +// static float offset[2] = {500, 400}; +// static float rotation = 0; +// static float scale[2] = {0.65, 0.65}; +// static float skew[2] = {0, 0}; +// static float path_rect[4] = {0, 0, +// static_cast(boston->GetSize().width), +// static_cast(boston->GetSize().height)}; +// static float effect_transform_scale = 1; + +// ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); +// { +// ImGui::Combo("Morphology type", &selected_morphology_type, +// morphology_type_names, +// sizeof(morphology_type_names) / sizeof(char*)); +// ImGui::SliderFloat2("Radius", radius, 0, 200); +// ImGui::SliderFloat("Input opacity", &input_color.alpha, 0, 1); +// ImGui::ColorEdit4("Cover color", +// reinterpret_cast(&cover_color)); ImGui::ColorEdit4("Bounds +// color", +// reinterpret_cast(&bounds_color)); +// ImGui::SliderFloat2("Translation", offset, 0, +// pass.GetRenderTargetSize().width); +// ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2); +// ImGui::SliderFloat2("Scale", scale, 0, 3); +// ImGui::SliderFloat2("Skew", skew, -3, 3); +// ImGui::SliderFloat4("Path XYWH", path_rect, -1000, 1000); +// ImGui::SliderFloat("Effect transform scale", &effect_transform_scale, +// 0, +// 3); +// } +// ImGui::End(); + +// std::shared_ptr input; +// Size input_size; + +// auto input_rect = +// Rect::MakeXYWH(path_rect[0], path_rect[1], path_rect[2], +// path_rect[3]); +// auto texture = std::make_shared(); +// texture->SetSourceRect(Rect::MakeSize(boston->GetSize())); +// texture->SetDestinationRect(input_rect); +// texture->SetTexture(boston); +// texture->SetOpacity(input_color.alpha); + +// input = texture; +// input_size = input_rect.GetSize(); + +// auto contents = FilterContents::MakeMorphology( +// FilterInput::Make(input), Radius{radius[0]}, Radius{radius[1]}, +// morphology_types[selected_morphology_type]); +// contents->SetEffectTransform(Matrix::MakeScale( +// Vector2{effect_transform_scale, effect_transform_scale})); + +// auto ctm = Matrix::MakeScale(GetContentScale()) * +// Matrix::MakeTranslation(Vector3(offset[0], offset[1])) * +// Matrix::MakeRotationZ(Radians(rotation)) * +// Matrix::MakeScale(Vector2(scale[0], scale[1])) * +// Matrix::MakeSkew(skew[0], skew[1]) * +// Matrix::MakeTranslation(-Point(input_size) / 2); + +// Entity entity; +// entity.SetContents(contents); +// entity.SetTransform(ctm); + +// entity.Render(context, pass); + +// // Renders a red "cover" rectangle that shows the original position of +// the +// // unfiltered input. +// Entity cover_entity; +// cover_entity.SetContents(SolidColorContents::Make( +// PathBuilder{}.AddRect(input_rect).TakePath(), cover_color)); +// cover_entity.SetTransform(ctm); + +// cover_entity.Render(context, pass); + +// // Renders a green bounding rect of the target filter. +// Entity bounds_entity; +// bounds_entity.SetContents(SolidColorContents::Make( +// PathBuilder{}.AddRect(contents->GetCoverage(entity).value()).TakePath(), +// bounds_color)); +// bounds_entity.SetTransform(Matrix()); + +// bounds_entity.Render(context, pass); + +// return true; +// }; +// ASSERT_TRUE(OpenPlaygroundHere(callback)); +// } + +// TEST_P(EntityTest, SetBlendMode) { +// Entity entity; +// ASSERT_EQ(entity.GetBlendMode(), BlendMode::kSourceOver); +// entity.SetBlendMode(BlendMode::kClear); +// ASSERT_EQ(entity.GetBlendMode(), BlendMode::kClear); +// } + +// TEST_P(EntityTest, ContentsGetBoundsForEmptyPathReturnsNullopt) { +// Entity entity; +// entity.SetContents(std::make_shared()); +// ASSERT_FALSE(entity.GetCoverage().has_value()); +// } + +// TEST_P(EntityTest, SolidStrokeCoverageIsCorrect) { +// { +// auto geometry = Geometry::MakeStrokePath( +// PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath(), 4.0, 4.0, +// Cap::kButt, Join::kBevel); + +// Entity entity; +// auto contents = std::make_unique(); +// contents->SetGeometry(std::move(geometry)); +// contents->SetColor(Color::Black()); +// entity.SetContents(std::move(contents)); +// auto actual = entity.GetCoverage(); +// auto expected = Rect::MakeLTRB(-2, -2, 12, 12); +// ASSERT_TRUE(actual.has_value()); +// ASSERT_RECT_NEAR(actual.value(), expected); +// } + +// // Cover the Cap::kSquare case. +// { +// auto geometry = Geometry::MakeStrokePath( +// PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath(), 4.0, 4.0, +// Cap::kSquare, Join::kBevel); + +// Entity entity; +// auto contents = std::make_unique(); +// contents->SetGeometry(std::move(geometry)); +// contents->SetColor(Color::Black()); +// entity.SetContents(std::move(contents)); +// auto actual = entity.GetCoverage(); +// auto expected = +// Rect::MakeLTRB(-sqrt(8), -sqrt(8), 10 + sqrt(8), 10 + sqrt(8)); +// ASSERT_TRUE(actual.has_value()); +// ASSERT_RECT_NEAR(actual.value(), expected); +// } + +// // Cover the Join::kMiter case. +// { +// auto geometry = Geometry::MakeStrokePath( +// PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath(), 4.0, 2.0, +// Cap::kSquare, Join::kMiter); + +// Entity entity; +// auto contents = std::make_unique(); +// contents->SetGeometry(std::move(geometry)); +// contents->SetColor(Color::Black()); +// entity.SetContents(std::move(contents)); +// auto actual = entity.GetCoverage(); +// auto expected = Rect::MakeLTRB(-4, -4, 14, 14); +// ASSERT_TRUE(actual.has_value()); +// ASSERT_RECT_NEAR(actual.value(), expected); +// } +// } + +// TEST_P(EntityTest, BorderMaskBlurCoverageIsCorrect) { +// auto fill = std::make_shared(); +// fill->SetGeometry(Geometry::MakeFillPath( +// PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath())); +// fill->SetColor(Color::CornflowerBlue()); +// auto border_mask_blur = FilterContents::MakeBorderMaskBlur( +// FilterInput::Make(fill), Radius{3}, Radius{4}); + +// { +// Entity e; +// e.SetTransform(Matrix()); +// auto actual = border_mask_blur->GetCoverage(e); +// auto expected = Rect::MakeXYWH(-3, -4, 306, 408); +// ASSERT_TRUE(actual.has_value()); +// ASSERT_RECT_NEAR(actual.value(), expected); +// } + +// { +// Entity e; +// e.SetTransform(Matrix::MakeRotationZ(Radians{kPi / 4})); +// auto actual = border_mask_blur->GetCoverage(e); +// auto expected = Rect::MakeXYWH(-287.792, -4.94975, 504.874, 504.874); +// ASSERT_TRUE(actual.has_value()); +// ASSERT_RECT_NEAR(actual.value(), expected); +// } +// } + +// TEST_P(EntityTest, SolidFillCoverageIsCorrect) { +// // No transform +// { +// auto fill = std::make_shared(); +// fill->SetColor(Color::CornflowerBlue()); +// auto expected = Rect::MakeLTRB(100, 110, 200, 220); +// fill->SetGeometry( +// Geometry::MakeFillPath(PathBuilder{}.AddRect(expected).TakePath())); + +// auto coverage = fill->GetCoverage({}); +// ASSERT_TRUE(coverage.has_value()); +// ASSERT_RECT_NEAR(coverage.value(), expected); +// } + +// // Entity transform +// { +// auto fill = std::make_shared(); +// fill->SetColor(Color::CornflowerBlue()); +// fill->SetGeometry(Geometry::MakeFillPath( +// PathBuilder{}.AddRect(Rect::MakeLTRB(100, 110, 200, +// 220)).TakePath())); + +// Entity entity; +// entity.SetTransform(Matrix::MakeTranslation(Vector2(4, 5))); +// entity.SetContents(std::move(fill)); + +// auto coverage = entity.GetCoverage(); +// auto expected = Rect::MakeLTRB(104, 115, 204, 225); +// ASSERT_TRUE(coverage.has_value()); +// ASSERT_RECT_NEAR(coverage.value(), expected); +// } + +// // No coverage for fully transparent colors +// { +// auto fill = std::make_shared(); +// fill->SetColor(Color::WhiteTransparent()); +// fill->SetGeometry(Geometry::MakeFillPath( +// PathBuilder{}.AddRect(Rect::MakeLTRB(100, 110, 200, +// 220)).TakePath())); + +// auto coverage = fill->GetCoverage({}); +// ASSERT_FALSE(coverage.has_value()); +// } +// } + +// TEST_P(EntityTest, ClipContentsGetClipCoverageIsCorrect) { +// // Intersection: No stencil coverage, no geometry. +// { +// auto clip = std::make_shared(); +// clip->SetClipOperation(Entity::ClipOperation::kIntersect); +// auto result = clip->GetClipCoverage(Entity{}, Rect{}); + +// ASSERT_FALSE(result.coverage.has_value()); +// } + +// // Intersection: No stencil coverage, with geometry. +// { +// auto clip = std::make_shared(); +// clip->SetClipOperation(Entity::ClipOperation::kIntersect); +// clip->SetGeometry(Geometry::MakeFillPath( +// PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 100, 100)).TakePath())); +// auto result = clip->GetClipCoverage(Entity{}, Rect{}); + +// ASSERT_FALSE(result.coverage.has_value()); +// } + +// // Intersection: With stencil coverage, no geometry. +// { +// auto clip = std::make_shared(); +// clip->SetClipOperation(Entity::ClipOperation::kIntersect); +// auto result = +// clip->GetClipCoverage(Entity{}, Rect::MakeLTRB(0, 0, 100, 100)); + +// ASSERT_FALSE(result.coverage.has_value()); +// } + +// // Intersection: With stencil coverage, with geometry. +// { +// auto clip = std::make_shared(); +// clip->SetClipOperation(Entity::ClipOperation::kIntersect); +// clip->SetGeometry(Geometry::MakeFillPath( +// PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 50, 50)).TakePath())); +// auto result = +// clip->GetClipCoverage(Entity{}, Rect::MakeLTRB(0, 0, 100, 100)); + +// ASSERT_TRUE(result.coverage.has_value()); +// ASSERT_RECT_NEAR(result.coverage.value(), Rect::MakeLTRB(0, 0, 50, 50)); +// ASSERT_EQ(result.type, Contents::ClipCoverage::Type::kAppend); +// } + +// // Difference: With stencil coverage, with geometry. +// { +// auto clip = std::make_shared(); +// clip->SetClipOperation(Entity::ClipOperation::kDifference); +// clip->SetGeometry(Geometry::MakeFillPath( +// PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 50, 50)).TakePath())); +// auto result = +// clip->GetClipCoverage(Entity{}, Rect::MakeLTRB(0, 0, 100, 100)); + +// ASSERT_TRUE(result.coverage.has_value()); +// ASSERT_RECT_NEAR(result.coverage.value(), Rect::MakeLTRB(0, 0, 100, +// 100)); ASSERT_EQ(result.type, Contents::ClipCoverage::Type::kAppend); +// } +// } + +// TEST_P(EntityTest, RRectShadowTest) { +// auto callback = [&](ContentContext& context, RenderPass& pass) { +// static Color color = Color::Red(); +// static float corner_radius = 100; +// static float blur_radius = 100; +// static bool show_coverage = false; +// static Color coverage_color = Color::Green().WithAlpha(0.2); + +// ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); +// ImGui::SliderFloat("Corner radius", &corner_radius, 0, 300); +// ImGui::SliderFloat("Blur radius", &blur_radius, 0, 300); +// ImGui::ColorEdit4("Color", reinterpret_cast(&color)); +// ImGui::Checkbox("Show coverage", &show_coverage); +// if (show_coverage) { +// ImGui::ColorEdit4("Coverage color", +// reinterpret_cast(&coverage_color)); +// } +// ImGui::End(); + +// static PlaygroundPoint top_left_point(Point(200, 200), 30, +// Color::White()); static PlaygroundPoint bottom_right_point(Point(600, +// 400), 30, +// Color::White()); +// auto [top_left, bottom_right] = +// DrawPlaygroundLine(top_left_point, bottom_right_point); +// auto rect = +// Rect::MakeLTRB(top_left.x, top_left.y, bottom_right.x, +// bottom_right.y); + +// auto contents = std::make_unique(); +// contents->SetRRect(rect, {corner_radius, corner_radius}); +// contents->SetColor(color); +// contents->SetSigma(Radius(blur_radius)); + +// Entity entity; +// entity.SetTransform(Matrix::MakeScale(GetContentScale())); +// entity.SetContents(std::move(contents)); +// entity.Render(context, pass); + +// auto coverage = entity.GetCoverage(); +// if (show_coverage && coverage.has_value()) { +// auto bounds_contents = std::make_unique(); +// bounds_contents->SetGeometry(Geometry::MakeFillPath( +// PathBuilder{}.AddRect(entity.GetCoverage().value()).TakePath())); +// bounds_contents->SetColor(coverage_color.Premultiply()); +// Entity bounds_entity; +// bounds_entity.SetContents(std::move(bounds_contents)); +// bounds_entity.Render(context, pass); +// } + +// return true; +// }; +// ASSERT_TRUE(OpenPlaygroundHere(callback)); +// } + +// TEST_P(EntityTest, ColorMatrixFilterCoverageIsCorrect) { +// // Set up a simple color background. +// auto fill = std::make_shared(); +// fill->SetGeometry(Geometry::MakeFillPath( +// PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath())); +// fill->SetColor(Color::Coral()); + +// // Set the color matrix filter. +// ColorMatrix matrix = { +// 1, 1, 1, 1, 1, // +// 1, 1, 1, 1, 1, // +// 1, 1, 1, 1, 1, // +// 1, 1, 1, 1, 1, // +// }; + +// auto filter = +// ColorFilterContents::MakeColorMatrix(FilterInput::Make(fill), matrix); + +// Entity e; +// e.SetTransform(Matrix()); + +// // Confirm that the actual filter coverage matches the expected coverage. +// auto actual = filter->GetCoverage(e); +// auto expected = Rect::MakeXYWH(0, 0, 300, 400); + +// ASSERT_TRUE(actual.has_value()); +// ASSERT_RECT_NEAR(actual.value(), expected); +// } + +// TEST_P(EntityTest, ColorMatrixFilterEditable) { +// auto bay_bridge = CreateTextureForFixture("bay_bridge.jpg"); +// ASSERT_TRUE(bay_bridge); + +// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { +// // UI state. +// static ColorMatrix color_matrix = { +// 1, 0, 0, 0, 0, // +// 0, 3, 0, 0, 0, // +// 0, 0, 1, 0, 0, // +// 0, 0, 0, 1, 0, // +// }; +// static float offset[2] = {500, 400}; +// static float rotation = 0; +// static float scale[2] = {0.65, 0.65}; +// static float skew[2] = {0, 0}; + +// // Define the ImGui +// ImGui::Begin("Color Matrix", nullptr, ImGuiWindowFlags_AlwaysAutoResize); +// { +// std::string label = "##1"; +// for (int i = 0; i < 20; i += 5) { +// ImGui::InputScalarN(label.c_str(), ImGuiDataType_Float, +// &(color_matrix.array[i]), 5, nullptr, nullptr, +// "%.2f", 0); +// label[2]++; +// } + +// ImGui::SliderFloat2("Translation", &offset[0], 0, +// pass.GetRenderTargetSize().width); +// ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2); +// ImGui::SliderFloat2("Scale", &scale[0], 0, 3); +// ImGui::SliderFloat2("Skew", &skew[0], -3, 3); +// } +// ImGui::End(); + +// // Set the color matrix filter. +// auto filter = ColorFilterContents::MakeColorMatrix( +// FilterInput::Make(bay_bridge), color_matrix); + +// // Define the entity with the color matrix filter. +// Entity entity; +// entity.SetTransform( +// Matrix::MakeScale(GetContentScale()) * +// Matrix::MakeTranslation(Vector3(offset[0], offset[1])) * +// Matrix::MakeRotationZ(Radians(rotation)) * +// Matrix::MakeScale(Vector2(scale[0], scale[1])) * +// Matrix::MakeSkew(skew[0], skew[1]) * +// Matrix::MakeTranslation(-Point(bay_bridge->GetSize()) / 2)); +// entity.SetContents(filter); +// entity.Render(context, pass); + +// return true; +// }; + +// ASSERT_TRUE(OpenPlaygroundHere(callback)); +// } + +// TEST_P(EntityTest, LinearToSrgbFilterCoverageIsCorrect) { +// // Set up a simple color background. +// auto fill = std::make_shared(); +// fill->SetGeometry(Geometry::MakeFillPath( +// PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath())); +// fill->SetColor(Color::MintCream()); + +// auto filter = +// ColorFilterContents::MakeLinearToSrgbFilter(FilterInput::Make(fill)); + +// Entity e; +// e.SetTransform(Matrix()); + +// // Confirm that the actual filter coverage matches the expected coverage. +// auto actual = filter->GetCoverage(e); +// auto expected = Rect::MakeXYWH(0, 0, 300, 400); + +// ASSERT_TRUE(actual.has_value()); +// ASSERT_RECT_NEAR(actual.value(), expected); +// } + +// TEST_P(EntityTest, LinearToSrgbFilter) { +// auto image = CreateTextureForFixture("kalimba.jpg"); +// ASSERT_TRUE(image); + +// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { +// auto filtered = +// ColorFilterContents::MakeLinearToSrgbFilter(FilterInput::Make(image)); + +// // Define the entity that will serve as the control image as a Gaussian +// blur +// // filter with no filter at all. +// Entity entity_left; +// entity_left.SetTransform(Matrix::MakeScale(GetContentScale()) * +// Matrix::MakeTranslation({100, 300}) * +// Matrix::MakeScale(Vector2{0.5, 0.5})); +// auto unfiltered = +// FilterContents::MakeGaussianBlur(FilterInput::Make(image), +// Sigma{0}, Sigma{0}); +// entity_left.SetContents(unfiltered); + +// // Define the entity that will be filtered from linear to sRGB. +// Entity entity_right; +// entity_right.SetTransform(Matrix::MakeScale(GetContentScale()) * +// Matrix::MakeTranslation({500, 300}) * +// Matrix::MakeScale(Vector2{0.5, 0.5})); +// entity_right.SetContents(filtered); +// return entity_left.Render(context, pass) && +// entity_right.Render(context, pass); +// }; + +// ASSERT_TRUE(OpenPlaygroundHere(callback)); +// } + +// TEST_P(EntityTest, SrgbToLinearFilterCoverageIsCorrect) { +// // Set up a simple color background. +// auto fill = std::make_shared(); +// fill->SetGeometry(Geometry::MakeFillPath( +// PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath())); +// fill->SetColor(Color::DeepPink()); + +// auto filter = +// ColorFilterContents::MakeSrgbToLinearFilter(FilterInput::Make(fill)); + +// Entity e; +// e.SetTransform(Matrix()); + +// // Confirm that the actual filter coverage matches the expected coverage. +// auto actual = filter->GetCoverage(e); +// auto expected = Rect::MakeXYWH(0, 0, 300, 400); + +// ASSERT_TRUE(actual.has_value()); +// ASSERT_RECT_NEAR(actual.value(), expected); +// } + +// TEST_P(EntityTest, SrgbToLinearFilter) { +// auto image = CreateTextureForFixture("embarcadero.jpg"); +// ASSERT_TRUE(image); + +// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { +// auto filtered = +// ColorFilterContents::MakeSrgbToLinearFilter(FilterInput::Make(image)); + +// // Define the entity that will serve as the control image as a Gaussian +// blur +// // filter with no filter at all. +// Entity entity_left; +// entity_left.SetTransform(Matrix::MakeScale(GetContentScale()) * +// Matrix::MakeTranslation({100, 300}) * +// Matrix::MakeScale(Vector2{0.5, 0.5})); +// auto unfiltered = +// FilterContents::MakeGaussianBlur(FilterInput::Make(image), +// Sigma{0}, Sigma{0}); +// entity_left.SetContents(unfiltered); + +// // Define the entity that will be filtered from sRGB to linear. +// Entity entity_right; +// entity_right.SetTransform(Matrix::MakeScale(GetContentScale()) * +// Matrix::MakeTranslation({500, 300}) * +// Matrix::MakeScale(Vector2{0.5, 0.5})); +// entity_right.SetContents(filtered); +// return entity_left.Render(context, pass) && +// entity_right.Render(context, pass); +// }; + +// ASSERT_TRUE(OpenPlaygroundHere(callback)); +// } + +// static Vector3 RGBToYUV(Vector3 rgb, YUVColorSpace yuv_color_space) { +// Vector3 yuv; +// switch (yuv_color_space) { +// case YUVColorSpace::kBT601FullRange: +// yuv.x = rgb.x * 0.299 + rgb.y * 0.587 + rgb.z * 0.114; +// yuv.y = rgb.x * -0.169 + rgb.y * -0.331 + rgb.z * 0.5 + 0.5; +// yuv.z = rgb.x * 0.5 + rgb.y * -0.419 + rgb.z * -0.081 + 0.5; +// break; +// case YUVColorSpace::kBT601LimitedRange: +// yuv.x = rgb.x * 0.257 + rgb.y * 0.516 + rgb.z * 0.100 + 0.063; +// yuv.y = rgb.x * -0.145 + rgb.y * -0.291 + rgb.z * 0.439 + 0.5; +// yuv.z = rgb.x * 0.429 + rgb.y * -0.368 + rgb.z * -0.071 + 0.5; +// break; +// } +// return yuv; +// } + +// static std::vector> CreateTestYUVTextures( +// Context* context, +// YUVColorSpace yuv_color_space) { +// Vector3 red = {244.0 / 255.0, 67.0 / 255.0, 54.0 / 255.0}; +// Vector3 green = {76.0 / 255.0, 175.0 / 255.0, 80.0 / 255.0}; +// Vector3 blue = {33.0 / 255.0, 150.0 / 255.0, 243.0 / 255.0}; +// Vector3 white = {1.0, 1.0, 1.0}; +// Vector3 red_yuv = RGBToYUV(red, yuv_color_space); +// Vector3 green_yuv = RGBToYUV(green, yuv_color_space); +// Vector3 blue_yuv = RGBToYUV(blue, yuv_color_space); +// Vector3 white_yuv = RGBToYUV(white, yuv_color_space); +// std::vector yuvs{red_yuv, green_yuv, blue_yuv, white_yuv}; +// std::vector y_data; +// std::vector uv_data; +// for (int i = 0; i < 4; i++) { +// auto yuv = yuvs[i]; +// uint8_t y = std::round(yuv.x * 255.0); +// uint8_t u = std::round(yuv.y * 255.0); +// uint8_t v = std::round(yuv.z * 255.0); +// for (int j = 0; j < 16; j++) { +// y_data.push_back(y); +// } +// for (int j = 0; j < 8; j++) { +// uv_data.push_back(j % 2 == 0 ? u : v); +// } +// } +// auto cmd_buffer = context->CreateCommandBuffer(); +// auto blit_pass = cmd_buffer->CreateBlitPass(); + +// impeller::TextureDescriptor y_texture_descriptor; +// y_texture_descriptor.storage_mode = impeller::StorageMode::kHostVisible; +// y_texture_descriptor.format = PixelFormat::kR8UNormInt; +// y_texture_descriptor.size = {8, 8}; +// auto y_texture = +// context->GetResourceAllocator()->CreateTexture(y_texture_descriptor); +// auto y_mapping = std::make_shared(y_data); +// auto y_mapping_buffer = +// context->GetResourceAllocator()->CreateBufferWithCopy(*y_mapping); + +// blit_pass->AddCopy(DeviceBuffer::AsBufferView(y_mapping_buffer), +// y_texture); + +// impeller::TextureDescriptor uv_texture_descriptor; +// uv_texture_descriptor.storage_mode = impeller::StorageMode::kHostVisible; +// uv_texture_descriptor.format = PixelFormat::kR8G8UNormInt; +// uv_texture_descriptor.size = {4, 4}; +// auto uv_texture = +// context->GetResourceAllocator()->CreateTexture(uv_texture_descriptor); +// auto uv_mapping = std::make_shared(uv_data); +// auto uv_mapping_buffer = +// context->GetResourceAllocator()->CreateBufferWithCopy(*uv_mapping); + +// blit_pass->AddCopy(DeviceBuffer::AsBufferView(uv_mapping_buffer), +// uv_texture); + +// if (!blit_pass->EncodeCommands(context->GetResourceAllocator()) || +// !context->GetCommandQueue()->Submit({cmd_buffer}).ok()) { +// FML_DLOG(ERROR) << "Could not copy contents into Y/UV texture."; +// } + +// return {y_texture, uv_texture}; +// } + +// TEST_P(EntityTest, YUVToRGBFilter) { +// if (GetParam() == PlaygroundBackend::kOpenGLES) { +// // TODO(114588) : Support YUV to RGB filter on OpenGLES backend. +// GTEST_SKIP() +// << "YUV to RGB filter is not supported on OpenGLES backend yet."; +// } + +// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { +// YUVColorSpace yuv_color_space_array[2]{YUVColorSpace::kBT601FullRange, +// YUVColorSpace::kBT601LimitedRange}; +// for (int i = 0; i < 2; i++) { +// auto yuv_color_space = yuv_color_space_array[i]; +// auto textures = +// CreateTestYUVTextures(GetContext().get(), yuv_color_space); +// auto filter_contents = FilterContents::MakeYUVToRGBFilter( +// textures[0], textures[1], yuv_color_space); +// Entity filter_entity; +// filter_entity.SetContents(filter_contents); +// auto snapshot = filter_contents->RenderToSnapshot(context, +// filter_entity); + +// Entity entity; +// auto contents = TextureContents::MakeRect(Rect::MakeLTRB(0, 0, 256, +// 256)); contents->SetTexture(snapshot->texture); +// contents->SetSourceRect(Rect::MakeSize(snapshot->texture->GetSize())); +// entity.SetContents(contents); +// entity.SetTransform( +// Matrix::MakeTranslation({static_cast(100 + 400 * i), +// 300})); +// entity.Render(context, pass); +// } +// return true; +// }; +// ASSERT_TRUE(OpenPlaygroundHere(callback)); +// } + +// TEST_P(EntityTest, RuntimeEffect) { +// auto runtime_stages = +// OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); +// auto runtime_stage = +// runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; +// ASSERT_TRUE(runtime_stage); +// ASSERT_TRUE(runtime_stage->IsDirty()); + +// bool expect_dirty = true; +// Pipeline* first_pipeline; +// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { +// EXPECT_EQ(runtime_stage->IsDirty(), expect_dirty); + +// auto contents = std::make_shared(); +// contents->SetGeometry(Geometry::MakeCover()); +// contents->SetRuntimeStage(runtime_stage); + +// struct FragUniforms { +// Vector2 iResolution; +// Scalar iTime; +// } frag_uniforms = { +// .iResolution = Vector2(GetWindowSize().width, +// GetWindowSize().height), .iTime = +// static_cast(GetSecondsElapsed()), +// }; +// auto uniform_data = std::make_shared>(); +// uniform_data->resize(sizeof(FragUniforms)); +// memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms)); +// contents->SetUniformData(uniform_data); + +// Entity entity; +// entity.SetContents(contents); +// bool result = contents->Render(context, entity, pass); + +// if (expect_dirty) { +// EXPECT_NE(first_pipeline, pass.GetCommands().back().pipeline.get()); +// first_pipeline = pass.GetCommands().back().pipeline.get(); +// } else { +// EXPECT_EQ(pass.GetCommands().back().pipeline.get(), first_pipeline); +// } + +// expect_dirty = false; +// return result; +// }; + +// // Simulate some renders and hot reloading of the shader. +// auto content_context = GetContentContext(); +// { +// RenderTarget target = +// content_context->GetRenderTargetCache()->CreateOffscreen( +// *content_context->GetContext(), {1, 1}, 1u); + +// testing::MockRenderPass mock_pass(GetContext(), target); +// callback(*content_context, mock_pass); +// callback(*content_context, mock_pass); + +// // Dirty the runtime stage. +// runtime_stages = +// OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); runtime_stage +// = +// runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; + +// ASSERT_TRUE(runtime_stage->IsDirty()); +// expect_dirty = true; + +// callback(*content_context, mock_pass); +// } +// } + +// TEST_P(EntityTest, RuntimeEffectCanSuccessfullyRender) { +// auto runtime_stages = +// OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); +// auto runtime_stage = +// runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; +// ASSERT_TRUE(runtime_stage); +// ASSERT_TRUE(runtime_stage->IsDirty()); + +// auto contents = std::make_shared(); +// contents->SetGeometry(Geometry::MakeCover()); + +// contents->SetRuntimeStage(runtime_stage); + +// struct FragUniforms { +// Vector2 iResolution; +// Scalar iTime; +// } frag_uniforms = { +// .iResolution = Vector2(GetWindowSize().width, GetWindowSize().height), +// .iTime = static_cast(GetSecondsElapsed()), +// }; +// auto uniform_data = std::make_shared>(); +// uniform_data->resize(sizeof(FragUniforms)); +// memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms)); +// contents->SetUniformData(uniform_data); + +// Entity entity; +// entity.SetContents(contents); + +// // Create a render target with a depth-stencil, similar to how EntityPass +// // does. +// RenderTarget target = +// GetContentContext()->GetRenderTargetCache()->CreateOffscreenMSAA( +// *GetContext(), {GetWindowSize().width, GetWindowSize().height}, 1, +// "RuntimeEffect Texture"); +// testing::MockRenderPass pass(GetContext(), target); + +// ASSERT_TRUE(contents->Render(*GetContentContext(), entity, pass)); +// ASSERT_EQ(pass.GetCommands().size(), 1u); +// const auto& command = pass.GetCommands()[0]; +// ASSERT_TRUE(command.pipeline->GetDescriptor() +// .GetDepthStencilAttachmentDescriptor() +// .has_value()); +// ASSERT_TRUE(command.pipeline->GetDescriptor() +// .GetFrontStencilAttachmentDescriptor() +// .has_value()); +// } + +// TEST_P(EntityTest, RuntimeEffectCanPrecache) { +// auto runtime_stages = +// OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); +// auto runtime_stage = +// runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; +// ASSERT_TRUE(runtime_stage); +// ASSERT_TRUE(runtime_stage->IsDirty()); + +// auto contents = std::make_shared(); +// contents->SetRuntimeStage(runtime_stage); + +// EXPECT_TRUE(contents->BootstrapShader(*GetContentContext())); +// } + +// TEST_P(EntityTest, RuntimeEffectSetsRightSizeWhenUniformIsStruct) { +// if (GetBackend() != PlaygroundBackend::kVulkan) { +// GTEST_SKIP() << "Test only applies to Vulkan"; +// } + +// auto runtime_stages = +// OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); +// auto runtime_stage = +// runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; +// ASSERT_TRUE(runtime_stage); +// ASSERT_TRUE(runtime_stage->IsDirty()); + +// auto contents = std::make_shared(); +// contents->SetGeometry(Geometry::MakeCover()); +// contents->SetRuntimeStage(runtime_stage); + +// struct FragUniforms { +// Vector2 iResolution; +// Scalar iTime; +// } frag_uniforms = { +// .iResolution = Vector2(GetWindowSize().width, GetWindowSize().height), +// .iTime = static_cast(GetSecondsElapsed()), +// }; +// auto uniform_data = std::make_shared>(); +// uniform_data->resize(sizeof(FragUniforms)); +// memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms)); +// contents->SetUniformData(uniform_data); + +// Entity entity; +// entity.SetContents(contents); + +// auto context = GetContentContext(); +// RenderTarget target = context->GetRenderTargetCache()->CreateOffscreen( +// *context->GetContext(), {1, 1}, 1u); + +// testing::MockRenderPass pass(GetContext(), target); +// ASSERT_TRUE(contents->Render(*context, entity, pass)); +// ASSERT_EQ(pass.GetCommands().size(), 1u); +// const auto& command = pass.GetCommands()[0]; +// ASSERT_EQ(command.fragment_bindings.buffers.size(), 1u); +// // 16 bytes: +// // 8 bytes for iResolution +// // 4 bytes for iTime +// // 4 bytes padding +// EXPECT_EQ(command.fragment_bindings.buffers[0].view.resource.range.length, +// 16u); +// } + +// TEST_P(EntityTest, InheritOpacityTest) { +// Entity entity; + +// // Texture contents can always accept opacity. +// auto texture_contents = std::make_shared(); +// texture_contents->SetOpacity(0.5); + +// texture_contents->SetInheritedOpacity(0.5); +// ASSERT_EQ(texture_contents->GetOpacity(), 0.25); +// texture_contents->SetInheritedOpacity(0.5); +// ASSERT_EQ(texture_contents->GetOpacity(), 0.25); + +// // Solid color contents can accept opacity if their geometry +// // doesn't overlap. +// auto solid_color = std::make_shared(); +// solid_color->SetGeometry( +// Geometry::MakeRect(Rect::MakeLTRB(100, 100, 200, 200))); +// solid_color->SetColor(Color::Blue().WithAlpha(0.5)); + +// solid_color->SetInheritedOpacity(0.5); +// ASSERT_EQ(solid_color->GetColor().alpha, 0.25); +// solid_color->SetInheritedOpacity(0.5); +// ASSERT_EQ(solid_color->GetColor().alpha, 0.25); + +// // Color source contents can accept opacity if their geometry +// // doesn't overlap. +// auto tiled_texture = std::make_shared(); +// tiled_texture->SetGeometry( +// Geometry::MakeRect(Rect::MakeLTRB(100, 100, 200, 200))); +// tiled_texture->SetOpacityFactor(0.5); + +// tiled_texture->SetInheritedOpacity(0.5); +// ASSERT_EQ(tiled_texture->GetOpacityFactor(), 0.25); +// tiled_texture->SetInheritedOpacity(0.5); +// ASSERT_EQ(tiled_texture->GetOpacityFactor(), 0.25); +// } + +// TEST_P(EntityTest, ColorFilterWithForegroundColorAdvancedBlend) { +// auto image = CreateTextureForFixture("boston.jpg"); +// auto filter = ColorFilterContents::MakeBlend( +// BlendMode::kColorBurn, FilterInput::Make({image}), Color::Red()); + +// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { +// Entity entity; +// entity.SetTransform(Matrix::MakeScale(GetContentScale()) * +// Matrix::MakeTranslation({500, 300}) * +// Matrix::MakeScale(Vector2{0.5, 0.5})); +// entity.SetContents(filter); +// return entity.Render(context, pass); +// }; +// ASSERT_TRUE(OpenPlaygroundHere(callback)); +// } + +// TEST_P(EntityTest, ColorFilterWithForegroundColorClearBlend) { +// auto image = CreateTextureForFixture("boston.jpg"); +// auto filter = ColorFilterContents::MakeBlend( +// BlendMode::kClear, FilterInput::Make({image}), Color::Red()); + +// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { +// Entity entity; +// entity.SetTransform(Matrix::MakeScale(GetContentScale()) * +// Matrix::MakeTranslation({500, 300}) * +// Matrix::MakeScale(Vector2{0.5, 0.5})); +// entity.SetContents(filter); +// return entity.Render(context, pass); +// }; +// ASSERT_TRUE(OpenPlaygroundHere(callback)); +// } + +// TEST_P(EntityTest, ColorFilterWithForegroundColorSrcBlend) { +// auto image = CreateTextureForFixture("boston.jpg"); +// auto filter = ColorFilterContents::MakeBlend( +// BlendMode::kSource, FilterInput::Make({image}), Color::Red()); + +// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { +// Entity entity; +// entity.SetTransform(Matrix::MakeScale(GetContentScale()) * +// Matrix::MakeTranslation({500, 300}) * +// Matrix::MakeScale(Vector2{0.5, 0.5})); +// entity.SetContents(filter); +// return entity.Render(context, pass); +// }; +// ASSERT_TRUE(OpenPlaygroundHere(callback)); +// } + +// TEST_P(EntityTest, ColorFilterWithForegroundColorDstBlend) { +// auto image = CreateTextureForFixture("boston.jpg"); +// auto filter = ColorFilterContents::MakeBlend( +// BlendMode::kDestination, FilterInput::Make({image}), Color::Red()); + +// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { +// Entity entity; +// entity.SetTransform(Matrix::MakeScale(GetContentScale()) * +// Matrix::MakeTranslation({500, 300}) * +// Matrix::MakeScale(Vector2{0.5, 0.5})); +// entity.SetContents(filter); +// return entity.Render(context, pass); +// }; +// ASSERT_TRUE(OpenPlaygroundHere(callback)); +// } + +// TEST_P(EntityTest, ColorFilterWithForegroundColorSrcInBlend) { +// auto image = CreateTextureForFixture("boston.jpg"); +// auto filter = ColorFilterContents::MakeBlend( +// BlendMode::kSourceIn, FilterInput::Make({image}), Color::Red()); + +// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { +// Entity entity; +// entity.SetTransform(Matrix::MakeScale(GetContentScale()) * +// Matrix::MakeTranslation({500, 300}) * +// Matrix::MakeScale(Vector2{0.5, 0.5})); +// entity.SetContents(filter); +// return entity.Render(context, pass); +// }; +// ASSERT_TRUE(OpenPlaygroundHere(callback)); +// } + +// TEST_P(EntityTest, CoverageForStrokePathWithNegativeValuesInTransform) { +// auto arrow_head = PathBuilder{} +// .MoveTo({50, 120}) +// .LineTo({120, 190}) +// .LineTo({190, 120}) +// .TakePath(); +// auto geometry = Geometry::MakeStrokePath(arrow_head, 15.0, 4.0, +// Cap::kRound, +// Join::kRound); + +// auto transform = Matrix::MakeTranslation({300, 300}) * +// Matrix::MakeRotationZ(Radians(kPiOver2)); +// // Note that e[0][0] used to be tested here, but it was -epsilon solely +// // due to floating point inaccuracy in the transcendental trig functions. +// // e[1][0] is the intended negative value that we care about (-1.0) as it +// // comes from the rotation of pi/2. +// EXPECT_LT(transform.e[1][0], 0.0f); +// auto coverage = geometry->GetCoverage(transform); +// ASSERT_RECT_NEAR(coverage.value(), Rect::MakeXYWH(102.5, 342.5, 85, 155)); +// } + +// TEST_P(EntityTest, SolidColorContentsIsOpaque) { +// Matrix matrix; +// SolidColorContents contents; +// contents.SetGeometry(Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10))); + +// contents.SetColor(Color::CornflowerBlue()); +// EXPECT_TRUE(contents.IsOpaque(matrix)); +// contents.SetColor(Color::CornflowerBlue().WithAlpha(0.5)); +// EXPECT_FALSE(contents.IsOpaque(matrix)); + +// // Create stroked path that required alpha coverage. +// contents.SetGeometry(Geometry::MakeStrokePath( +// PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), +// /*stroke_width=*/0.05)); +// contents.SetColor(Color::CornflowerBlue()); + +// EXPECT_FALSE(contents.IsOpaque(matrix)); +// } + +// TEST_P(EntityTest, ConicalGradientContentsIsOpaque) { +// Matrix matrix; +// ConicalGradientContents contents; +// contents.SetGeometry(Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10))); + +// contents.SetColors({Color::CornflowerBlue()}); +// EXPECT_FALSE(contents.IsOpaque(matrix)); +// contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)}); +// EXPECT_FALSE(contents.IsOpaque(matrix)); + +// // Create stroked path that required alpha coverage. +// contents.SetGeometry(Geometry::MakeStrokePath( +// PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), +// /*stroke_width=*/0.05)); +// contents.SetColors({Color::CornflowerBlue()}); + +// EXPECT_FALSE(contents.IsOpaque(matrix)); +// } + +// TEST_P(EntityTest, LinearGradientContentsIsOpaque) { +// Matrix matrix; +// LinearGradientContents contents; +// contents.SetGeometry(Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10))); + +// contents.SetColors({Color::CornflowerBlue()}); +// EXPECT_TRUE(contents.IsOpaque(matrix)); +// contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)}); +// EXPECT_FALSE(contents.IsOpaque(matrix)); +// contents.SetColors({Color::CornflowerBlue()}); +// contents.SetTileMode(Entity::TileMode::kDecal); +// EXPECT_FALSE(contents.IsOpaque(matrix)); + +// // Create stroked path that required alpha coverage. +// contents.SetGeometry(Geometry::MakeStrokePath( +// PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), +// /*stroke_width=*/0.05)); +// contents.SetColors({Color::CornflowerBlue()}); + +// EXPECT_FALSE(contents.IsOpaque(matrix)); +// } + +// TEST_P(EntityTest, RadialGradientContentsIsOpaque) { +// Matrix matrix; +// RadialGradientContents contents; +// contents.SetGeometry(Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10))); + +// contents.SetColors({Color::CornflowerBlue()}); +// EXPECT_TRUE(contents.IsOpaque(matrix)); +// contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)}); +// EXPECT_FALSE(contents.IsOpaque(matrix)); +// contents.SetColors({Color::CornflowerBlue()}); +// contents.SetTileMode(Entity::TileMode::kDecal); +// EXPECT_FALSE(contents.IsOpaque(matrix)); + +// // Create stroked path that required alpha coverage. +// contents.SetGeometry(Geometry::MakeStrokePath( +// PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), +// /*stroke_width=*/0.05)); +// contents.SetColors({Color::CornflowerBlue()}); + +// EXPECT_FALSE(contents.IsOpaque(matrix)); +// } + +// TEST_P(EntityTest, SweepGradientContentsIsOpaque) { +// Matrix matrix; +// RadialGradientContents contents; +// contents.SetGeometry(Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10))); + +// contents.SetColors({Color::CornflowerBlue()}); +// EXPECT_TRUE(contents.IsOpaque(matrix)); +// contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)}); +// EXPECT_FALSE(contents.IsOpaque(matrix)); +// contents.SetColors({Color::CornflowerBlue()}); +// contents.SetTileMode(Entity::TileMode::kDecal); +// EXPECT_FALSE(contents.IsOpaque(matrix)); + +// // Create stroked path that required alpha coverage. +// contents.SetGeometry(Geometry::MakeStrokePath( +// PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), +// /*stroke_width=*/0.05)); +// contents.SetColors({Color::CornflowerBlue()}); + +// EXPECT_FALSE(contents.IsOpaque(matrix)); +// } + +// TEST_P(EntityTest, TiledTextureContentsIsOpaque) { +// Matrix matrix; +// auto bay_bridge = CreateTextureForFixture("bay_bridge.jpg"); +// TiledTextureContents contents; +// contents.SetTexture(bay_bridge); +// // This is a placeholder test. Images currently never decompress as opaque +// // (whether in Flutter or the playground), and so this should currently +// always +// // return false in practice. +// EXPECT_FALSE(contents.IsOpaque(matrix)); +// } + +// TEST_P(EntityTest, PointFieldGeometryCoverage) { +// std::vector points = {{10, 20}, {100, 200}}; +// auto geometry = Geometry::MakePointField(points, 5.0, false); +// ASSERT_EQ(*geometry->GetCoverage(Matrix()), Rect::MakeLTRB(5, 15, 105, +// 205)); ASSERT_EQ(*geometry->GetCoverage(Matrix::MakeTranslation({30, 0, +// 0})), +// Rect::MakeLTRB(35, 15, 135, 205)); +// } + +// TEST_P(EntityTest, ColorFilterContentsWithLargeGeometry) { +// Entity entity; +// entity.SetTransform(Matrix::MakeScale(GetContentScale())); +// auto src_contents = std::make_shared(); +// src_contents->SetGeometry( +// Geometry::MakeRect(Rect::MakeLTRB(-300, -500, 30000, 50000))); +// src_contents->SetColor(Color::Red()); + +// auto dst_contents = std::make_shared(); +// dst_contents->SetGeometry( +// Geometry::MakeRect(Rect::MakeLTRB(300, 500, 20000, 30000))); +// dst_contents->SetColor(Color::Blue()); + +// auto contents = ColorFilterContents::MakeBlend( +// BlendMode::kSourceOver, {FilterInput::Make(dst_contents, false), +// FilterInput::Make(src_contents, false)}); +// entity.SetContents(std::move(contents)); +// ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); +// } + +// TEST_P(EntityTest, TextContentsCeilsGlyphScaleToDecimal) { +// ASSERT_EQ(TextFrame::RoundScaledFontSize(0.4321111f, 12), 0.43f); +// ASSERT_EQ(TextFrame::RoundScaledFontSize(0.5321111f, 12), 0.53f); +// ASSERT_EQ(TextFrame::RoundScaledFontSize(2.1f, 12), 2.1f); +// ASSERT_EQ(TextFrame::RoundScaledFontSize(0.0f, 12), 0.0f); +// ASSERT_EQ(TextFrame::RoundScaledFontSize(100000000.0f, 12), 48.0f); +// } + +// TEST_P(EntityTest, SpecializationConstantsAreAppliedToVariants) { +// auto content_context = GetContentContext(); + +// auto default_gyph = content_context->GetGlyphAtlasPipeline({ +// .color_attachment_pixel_format = PixelFormat::kR8G8B8A8UNormInt, +// .has_depth_stencil_attachments = false, +// }); +// auto alt_gyph = content_context->GetGlyphAtlasPipeline( +// {.color_attachment_pixel_format = PixelFormat::kR8G8B8A8UNormInt, +// .has_depth_stencil_attachments = true}); + +// EXPECT_NE(default_gyph, alt_gyph); +// EXPECT_EQ(default_gyph->GetDescriptor().GetSpecializationConstants(), +// alt_gyph->GetDescriptor().GetSpecializationConstants()); + +// auto use_a8 = GetContext()->GetCapabilities()->GetDefaultGlyphAtlasFormat() +// == +// PixelFormat::kA8UNormInt; + +// std::vector expected_constants = {static_cast(use_a8)}; +// EXPECT_EQ(default_gyph->GetDescriptor().GetSpecializationConstants(), +// expected_constants); +// } + +// TEST_P(EntityTest, DecalSpecializationAppliedToMorphologyFilter) { +// auto content_context = GetContentContext(); +// auto default_color_burn = content_context->GetMorphologyFilterPipeline({ +// .color_attachment_pixel_format = PixelFormat::kR8G8B8A8UNormInt, +// }); + +// auto decal_supported = static_cast( +// GetContext()->GetCapabilities()->SupportsDecalSamplerAddressMode()); +// std::vector expected_constants = {decal_supported}; +// ASSERT_EQ(default_color_burn->GetDescriptor().GetSpecializationConstants(), +// expected_constants); +// } + +// // This doesn't really tell you if the hashes will have frequent +// // collisions, but since this type is only used to hash a bounded +// // set of options, we can just compare benchmarks. +// TEST_P(EntityTest, ContentContextOptionsHasReasonableHashFunctions) { +// ContentContextOptions opts; +// auto hash_a = ContentContextOptions::Hash{}(opts); + +// opts.blend_mode = BlendMode::kColorBurn; +// auto hash_b = ContentContextOptions::Hash{}(opts); + +// opts.has_depth_stencil_attachments = false; +// auto hash_c = ContentContextOptions::Hash{}(opts); + +// opts.primitive_type = PrimitiveType::kPoint; +// auto hash_d = ContentContextOptions::Hash{}(opts); + +// EXPECT_NE(hash_a, hash_b); +// EXPECT_NE(hash_b, hash_c); +// EXPECT_NE(hash_c, hash_d); +// } + +// #ifdef FML_OS_LINUX +// TEST_P(EntityTest, FramebufferFetchVulkanBindingOffsetIsTheSame) { +// // Using framebuffer fetch on Vulkan requires that we maintain a subpass +// input +// // binding that we don't have a good route for configuring with the current +// // metadata approach. This test verifies that the binding value doesn't +// change +// // from the expected constant. +// // See also: +// // * impeller/renderer/backend/vulkan/binding_helpers_vk.cc +// // * impeller/entity/shaders/blending/framebuffer_blend.frag +// // This test only works on Linux because macOS hosts incorrectly populate +// the +// // Vulkan descriptor sets based on the MSL compiler settings. + +// bool expected_layout = false; +// for (const DescriptorSetLayout& layout : +// FramebufferBlendColorBurnPipeline:: +// FragmentShader::kDescriptorSetLayouts) { +// if (layout.binding == 64 && +// layout.descriptor_type == DescriptorType::kInputAttachment) { +// expected_layout = true; +// } +// } +// EXPECT_TRUE(expected_layout); +// } +// #endif + +// TEST_P(EntityTest, FillPathGeometryGetPositionBufferReturnsExpectedMode) { +// RenderTarget target; +// testing::MockRenderPass mock_pass(GetContext(), target); + +// auto get_result = [this, &mock_pass](const Path& path) { +// auto geometry = Geometry::MakeFillPath( +// path, /* inner rect */ Rect::MakeLTRB(0, 0, 100, 100)); +// return geometry->GetPositionBuffer(*GetContentContext(), {}, mock_pass); +// }; + +// // Convex path +// { +// GeometryResult result = +// get_result(PathBuilder{} +// .AddRect(Rect::MakeLTRB(0, 0, 100, 100)) +// .SetConvexity(Convexity::kConvex) +// .TakePath()); +// EXPECT_EQ(result.mode, GeometryResult::Mode::kNormal); +// } + +// // Concave path +// { +// Path path = PathBuilder{} +// .MoveTo({0, 0}) +// .LineTo({100, 0}) +// .LineTo({100, 100}) +// .LineTo({50, 50}) +// .Close() +// .TakePath(); +// GeometryResult result = get_result(path); +// EXPECT_EQ(result.mode, GeometryResult::Mode::kNonZero); +// } +// } + +// TEST_P(EntityTest, FailOnValidationError) { +// if (GetParam() != PlaygroundBackend::kVulkan) { +// GTEST_SKIP() << "Validation is only fatal on Vulkan backend."; +// } +// EXPECT_DEATH( +// // The easiest way to trigger a validation error is to try to compile +// // a shader with an unsupported pixel format. +// GetContentContext()->GetBlendColorBurnPipeline({ +// .color_attachment_pixel_format = PixelFormat::kUnknown, +// .has_depth_stencil_attachments = false, +// }), +// ""); +// } + +// TEST_P(EntityTest, CanComputeGeometryForEmptyPathsWithoutCrashing) { +// PathBuilder builder = {}; +// builder.AddRect(Rect::MakeLTRB(0, 0, 0, 0)); +// Path path = builder.TakePath(); + +// EXPECT_TRUE(path.GetBoundingBox()->IsEmpty()); + +// auto geom = Geometry::MakeFillPath(path); + +// Entity entity; +// RenderTarget target = +// GetContentContext()->GetRenderTargetCache()->CreateOffscreen( +// *GetContext(), {1, 1}, 1u); +// testing::MockRenderPass render_pass(GetContext(), target); +// auto position_result = +// geom->GetPositionBuffer(*GetContentContext(), entity, render_pass); + +// EXPECT_EQ(position_result.vertex_buffer.vertex_count, 0u); + +// EXPECT_EQ(geom->GetResultMode(), GeometryResult::Mode::kNormal); +// } + +// TEST_P(EntityTest, CanRenderEmptyPathsWithoutCrashing) { +// PathBuilder builder = {}; +// builder.AddRect(Rect::MakeLTRB(0, 0, 0, 0)); +// Path path = builder.TakePath(); + +// EXPECT_TRUE(path.GetBoundingBox()->IsEmpty()); + +// auto contents = std::make_shared(); +// contents->SetGeometry(Geometry::MakeFillPath(path)); +// contents->SetColor(Color::Red()); + +// Entity entity; +// entity.SetTransform(Matrix::MakeScale(GetContentScale())); +// entity.SetContents(contents); + +// ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); +// } + +// TEST_P(EntityTest, DrawSuperEllipse) { +// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { +// // UI state. +// static float alpha = 10; +// static float beta = 10; +// static float radius = 40; +// static int degree = 4; +// static Color color = Color::Red(); + +// ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); +// ImGui::SliderFloat("Alpha", &alpha, 0, 100); +// ImGui::SliderFloat("Beta", &beta, 0, 100); +// ImGui::SliderInt("Degreee", °ree, 1, 20); +// ImGui::SliderFloat("Radius", &radius, 0, 400); +// ImGui::ColorEdit4("Color", reinterpret_cast(&color)); +// ImGui::End(); + +// auto contents = std::make_shared(); +// contents->SetColor(color); +// contents->SetGeometry(std::make_shared( +// Point{400, 400}, radius, degree, alpha, beta)); + +// Entity entity; +// entity.SetContents(contents); + +// return entity.Render(context, pass); +// }; + +// ASSERT_TRUE(OpenPlaygroundHere(callback)); +// } + +// TEST_P(EntityTest, SolidColorApplyColorFilter) { +// auto contents = SolidColorContents(); +// contents.SetColor(Color::CornflowerBlue().WithAlpha(0.75)); +// auto result = contents.ApplyColorFilter([](const Color& color) { +// return color.Blend(Color::LimeGreen().WithAlpha(0.75), +// BlendMode::kScreen); +// }); +// ASSERT_TRUE(result); +// ASSERT_COLOR_NEAR(contents.GetColor(), +// Color(0.424452, 0.828743, 0.79105, 0.9375)); +// } + +// #define APPLY_COLOR_FILTER_GRADIENT_TEST(name) \ +// TEST_P(EntityTest, name##GradientApplyColorFilter) { \ +// auto contents = name##GradientContents(); \ +// contents.SetColors({Color::CornflowerBlue().WithAlpha(0.75)}); \ +// auto result = contents.ApplyColorFilter([](const Color& color) { \ +// return color.Blend(Color::LimeGreen().WithAlpha(0.75), \ +// BlendMode::kScreen); \ +// }); \ +// ASSERT_TRUE(result); \ +// \ +// std::vector expected = {Color(0.433247, 0.879523, 0.825324, 0.75)}; \ +// ASSERT_COLORS_NEAR(contents.GetColors(), expected); \ +// } + +// APPLY_COLOR_FILTER_GRADIENT_TEST(Linear); +// APPLY_COLOR_FILTER_GRADIENT_TEST(Radial); +// APPLY_COLOR_FILTER_GRADIENT_TEST(Conical); +// APPLY_COLOR_FILTER_GRADIENT_TEST(Sweep); } // namespace testing } // namespace impeller diff --git a/impeller/entity/geometry/circle_geometry.cc b/impeller/entity/geometry/circle_geometry.cc index b4075757780a3..8934d11d586cd 100644 --- a/impeller/entity/geometry/circle_geometry.cc +++ b/impeller/entity/geometry/circle_geometry.cc @@ -17,6 +17,8 @@ CircleGeometry::CircleGeometry(const Point& center, Scalar radius) FML_DCHECK(radius >= 0); } +CircleGeometry::~CircleGeometry() {} + CircleGeometry::CircleGeometry(const Point& center, Scalar radius, Scalar stroke_width) diff --git a/impeller/entity/geometry/circle_geometry.h b/impeller/entity/geometry/circle_geometry.h index 7d33fdd390a92..cbbef0d119ba6 100644 --- a/impeller/entity/geometry/circle_geometry.h +++ b/impeller/entity/geometry/circle_geometry.h @@ -19,7 +19,7 @@ class CircleGeometry final : public Geometry { Scalar radius, Scalar stroke_width); - ~CircleGeometry() = default; + ~CircleGeometry() override; // |Geometry| bool CoversArea(const Matrix& transform, const Rect& rect) const override; diff --git a/impeller/entity/geometry/cover_geometry.h b/impeller/entity/geometry/cover_geometry.h index 1d238fbc9949b..4be6359acf946 100644 --- a/impeller/entity/geometry/cover_geometry.h +++ b/impeller/entity/geometry/cover_geometry.h @@ -15,7 +15,7 @@ class CoverGeometry final : public Geometry { public: CoverGeometry(); - ~CoverGeometry() = default; + ~CoverGeometry() override = default; // |Geometry| bool CoversArea(const Matrix& transform, const Rect& rect) const override; @@ -36,8 +36,6 @@ class CoverGeometry final : public Geometry { CoverGeometry& operator=(const CoverGeometry&) = delete; }; -static_assert(std::is_trivially_destructible::value); - } // namespace impeller #endif // FLUTTER_IMPELLER_ENTITY_GEOMETRY_COVER_GEOMETRY_H_ diff --git a/impeller/entity/geometry/ellipse_geometry.h b/impeller/entity/geometry/ellipse_geometry.h index 941f7a2b58882..924727072d048 100644 --- a/impeller/entity/geometry/ellipse_geometry.h +++ b/impeller/entity/geometry/ellipse_geometry.h @@ -17,7 +17,7 @@ class EllipseGeometry final : public Geometry { public: explicit EllipseGeometry(Rect bounds); - ~EllipseGeometry() = default; + ~EllipseGeometry() override = default; // |Geometry| bool CoversArea(const Matrix& transform, const Rect& rect) const override; diff --git a/impeller/entity/geometry/fill_path_geometry.cc b/impeller/entity/geometry/fill_path_geometry.cc index 731618274907a..347aa3ed42607 100644 --- a/impeller/entity/geometry/fill_path_geometry.cc +++ b/impeller/entity/geometry/fill_path_geometry.cc @@ -16,6 +16,8 @@ FillPathGeometry::FillPathGeometry(const Path& path, std::optional inner_rect) : path_(path), inner_rect_(inner_rect) {} +FillPathGeometry::~FillPathGeometry() {} + GeometryResult FillPathGeometry::GetPositionBuffer( const ContentContext& renderer, const Entity& entity, diff --git a/impeller/entity/geometry/fill_path_geometry.h b/impeller/entity/geometry/fill_path_geometry.h index 104ba0a6811e6..7b930d0557f3c 100644 --- a/impeller/entity/geometry/fill_path_geometry.h +++ b/impeller/entity/geometry/fill_path_geometry.h @@ -18,7 +18,7 @@ class FillPathGeometry final : public Geometry { explicit FillPathGeometry(const Path& path, std::optional inner_rect = std::nullopt); - ~FillPathGeometry() = default; + ~FillPathGeometry() override; // |Geometry| bool CoversArea(const Matrix& transform, const Rect& rect) const override; diff --git a/impeller/entity/geometry/geometry.cc b/impeller/entity/geometry/geometry.cc index c92e02dad9aec..40540dd649a7f 100644 --- a/impeller/entity/geometry/geometry.cc +++ b/impeller/entity/geometry/geometry.cc @@ -57,19 +57,19 @@ GeometryResult::Mode Geometry::GetResultMode() const { return GeometryResult::Mode::kNormal; } -std::shared_ptr Geometry::MakeFillPath( +std::unique_ptr Geometry::MakeFillPath( const Path& path, std::optional inner_rect) { - return std::make_shared(path, inner_rect); + return std::make_unique(path, inner_rect); } -std::shared_ptr Geometry::MakePointField(std::vector points, +std::unique_ptr Geometry::MakePointField(std::vector points, Scalar radius, bool round) { - return std::make_shared(std::move(points), radius, round); + return std::make_unique(std::move(points), radius, round); } -std::shared_ptr Geometry::MakeStrokePath(const Path& path, +std::unique_ptr Geometry::MakeStrokePath(const Path& path, Scalar stroke_width, Scalar miter_limit, Cap stroke_cap, @@ -78,43 +78,43 @@ std::shared_ptr Geometry::MakeStrokePath(const Path& path, if (miter_limit < 0) { miter_limit = 4.0; } - return std::make_shared(path, stroke_width, miter_limit, + return std::make_unique(path, stroke_width, miter_limit, stroke_cap, stroke_join); } -std::shared_ptr Geometry::MakeCover() { - return std::make_shared(); +std::unique_ptr Geometry::MakeCover() { + return std::make_unique(); } -std::shared_ptr Geometry::MakeRect(const Rect& rect) { - return std::make_shared(rect); +std::unique_ptr Geometry::MakeRect(const Rect& rect) { + return std::make_unique(rect); } -std::shared_ptr Geometry::MakeOval(const Rect& rect) { - return std::make_shared(rect); +std::unique_ptr Geometry::MakeOval(const Rect& rect) { + return std::make_unique(rect); } -std::shared_ptr Geometry::MakeLine(const Point& p0, +std::unique_ptr Geometry::MakeLine(const Point& p0, const Point& p1, Scalar width, Cap cap) { - return std::make_shared(p0, p1, width, cap); + return std::make_unique(p0, p1, width, cap); } -std::shared_ptr Geometry::MakeCircle(const Point& center, +std::unique_ptr Geometry::MakeCircle(const Point& center, Scalar radius) { - return std::make_shared(center, radius); + return std::make_unique(center, radius); } -std::shared_ptr Geometry::MakeStrokedCircle(const Point& center, +std::unique_ptr Geometry::MakeStrokedCircle(const Point& center, Scalar radius, Scalar stroke_width) { - return std::make_shared(center, radius, stroke_width); + return std::make_unique(center, radius, stroke_width); } -std::shared_ptr Geometry::MakeRoundRect(const Rect& rect, +std::unique_ptr Geometry::MakeRoundRect(const Rect& rect, const Size& radii) { - return std::make_shared(rect, radii); + return std::make_unique(rect, radii); } bool Geometry::CoversArea(const Matrix& transform, const Rect& rect) const { diff --git a/impeller/entity/geometry/geometry.h b/impeller/entity/geometry/geometry.h index 09d4384882a1b..8a24936366377 100644 --- a/impeller/entity/geometry/geometry.h +++ b/impeller/entity/geometry/geometry.h @@ -54,39 +54,41 @@ static const GeometryResult kEmptyResult = { class Geometry { public: - static std::shared_ptr MakeFillPath( + virtual ~Geometry() {} + + static std::unique_ptr MakeFillPath( const Path& path, std::optional inner_rect = std::nullopt); - static std::shared_ptr MakeStrokePath( + static std::unique_ptr MakeStrokePath( const Path& path, Scalar stroke_width = 0.0, Scalar miter_limit = 4.0, Cap stroke_cap = Cap::kButt, Join stroke_join = Join::kMiter); - static std::shared_ptr MakeCover(); + static std::unique_ptr MakeCover(); - static std::shared_ptr MakeRect(const Rect& rect); + static std::unique_ptr MakeRect(const Rect& rect); - static std::shared_ptr MakeOval(const Rect& rect); + static std::unique_ptr MakeOval(const Rect& rect); - static std::shared_ptr MakeLine(const Point& p0, + static std::unique_ptr MakeLine(const Point& p0, const Point& p1, Scalar width, Cap cap); - static std::shared_ptr MakeCircle(const Point& center, + static std::unique_ptr MakeCircle(const Point& center, Scalar radius); - static std::shared_ptr MakeStrokedCircle(const Point& center, + static std::unique_ptr MakeStrokedCircle(const Point& center, Scalar radius, Scalar stroke_width); - static std::shared_ptr MakeRoundRect(const Rect& rect, + static std::unique_ptr MakeRoundRect(const Rect& rect, const Size& radii); - static std::shared_ptr MakePointField(std::vector points, + static std::unique_ptr MakePointField(std::vector points, Scalar radius, bool round); diff --git a/impeller/entity/geometry/line_geometry.cc b/impeller/entity/geometry/line_geometry.cc index 72c20cd83e436..281c32b4ada36 100644 --- a/impeller/entity/geometry/line_geometry.cc +++ b/impeller/entity/geometry/line_geometry.cc @@ -12,6 +12,8 @@ LineGeometry::LineGeometry(Point p0, Point p1, Scalar width, Cap cap) FML_DCHECK(width >= 0); } +LineGeometry::~LineGeometry() {} + Scalar LineGeometry::ComputePixelHalfWidth(const Matrix& transform, Scalar width, bool msaa) { diff --git a/impeller/entity/geometry/line_geometry.h b/impeller/entity/geometry/line_geometry.h index 861ae4a0d8624..5f9a0306a1f22 100644 --- a/impeller/entity/geometry/line_geometry.h +++ b/impeller/entity/geometry/line_geometry.h @@ -5,7 +5,6 @@ #ifndef FLUTTER_IMPELLER_ENTITY_GEOMETRY_LINE_GEOMETRY_H_ #define FLUTTER_IMPELLER_ENTITY_GEOMETRY_LINE_GEOMETRY_H_ -#include #include "impeller/entity/geometry/geometry.h" namespace impeller { @@ -14,7 +13,7 @@ class LineGeometry final : public Geometry { public: explicit LineGeometry(Point p0, Point p1, Scalar width, Cap cap); - ~LineGeometry() = default; + ~LineGeometry() override; static Scalar ComputePixelHalfWidth(const Matrix& transform, Scalar width, @@ -70,8 +69,6 @@ class LineGeometry final : public Geometry { LineGeometry& operator=(const LineGeometry&) = delete; }; -static_assert(std::is_trivially_destructible::value); - } // namespace impeller #endif // FLUTTER_IMPELLER_ENTITY_GEOMETRY_LINE_GEOMETRY_H_ diff --git a/impeller/entity/geometry/point_field_geometry.cc b/impeller/entity/geometry/point_field_geometry.cc index 9cf412e1e1340..0b2a037bc9361 100644 --- a/impeller/entity/geometry/point_field_geometry.cc +++ b/impeller/entity/geometry/point_field_geometry.cc @@ -15,6 +15,8 @@ PointFieldGeometry::PointFieldGeometry(std::vector points, bool round) : points_(std::move(points)), radius_(radius), round_(round) {} +PointFieldGeometry::~PointFieldGeometry() {} + GeometryResult PointFieldGeometry::GetPositionBuffer( const ContentContext& renderer, const Entity& entity, diff --git a/impeller/entity/geometry/point_field_geometry.h b/impeller/entity/geometry/point_field_geometry.h index 14696a1652c9f..eb47345bf8306 100644 --- a/impeller/entity/geometry/point_field_geometry.h +++ b/impeller/entity/geometry/point_field_geometry.h @@ -13,7 +13,7 @@ class PointFieldGeometry final : public Geometry { public: PointFieldGeometry(std::vector points, Scalar radius, bool round); - ~PointFieldGeometry() = default; + ~PointFieldGeometry() override; private: // |Geometry| diff --git a/impeller/entity/geometry/rect_geometry.cc b/impeller/entity/geometry/rect_geometry.cc index 6c88015079294..58513cb45d18e 100644 --- a/impeller/entity/geometry/rect_geometry.cc +++ b/impeller/entity/geometry/rect_geometry.cc @@ -8,6 +8,8 @@ namespace impeller { RectGeometry::RectGeometry(Rect rect) : rect_(rect) {} +RectGeometry::~RectGeometry() {} + GeometryResult RectGeometry::GetPositionBuffer(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { diff --git a/impeller/entity/geometry/rect_geometry.h b/impeller/entity/geometry/rect_geometry.h index d7b90d41424a1..40457939ad63f 100644 --- a/impeller/entity/geometry/rect_geometry.h +++ b/impeller/entity/geometry/rect_geometry.h @@ -13,7 +13,7 @@ class RectGeometry final : public Geometry { public: explicit RectGeometry(Rect rect); - ~RectGeometry() = default; + ~RectGeometry() override; // |Geometry| bool CoversArea(const Matrix& transform, const Rect& rect) const override; @@ -31,14 +31,8 @@ class RectGeometry final : public Geometry { private: Rect rect_; - - RectGeometry(const RectGeometry&) = delete; - - RectGeometry& operator=(const RectGeometry&) = delete; }; -static_assert(std::is_trivially_destructible::value); - } // namespace impeller #endif // FLUTTER_IMPELLER_ENTITY_GEOMETRY_RECT_GEOMETRY_H_ diff --git a/impeller/entity/geometry/round_rect_geometry.cc b/impeller/entity/geometry/round_rect_geometry.cc index fea43dbb38200..e9a5016d077bf 100644 --- a/impeller/entity/geometry/round_rect_geometry.cc +++ b/impeller/entity/geometry/round_rect_geometry.cc @@ -9,6 +9,8 @@ namespace impeller { RoundRectGeometry::RoundRectGeometry(const Rect& bounds, const Size& radii) : bounds_(bounds), radii_(radii) {} +RoundRectGeometry::~RoundRectGeometry() {} + GeometryResult RoundRectGeometry::GetPositionBuffer( const ContentContext& renderer, const Entity& entity, diff --git a/impeller/entity/geometry/round_rect_geometry.h b/impeller/entity/geometry/round_rect_geometry.h index b3b7957aca2e3..1cf73b812bd5c 100644 --- a/impeller/entity/geometry/round_rect_geometry.h +++ b/impeller/entity/geometry/round_rect_geometry.h @@ -17,7 +17,7 @@ class RoundRectGeometry final : public Geometry { public: explicit RoundRectGeometry(const Rect& bounds, const Size& radii); - ~RoundRectGeometry() = default; + ~RoundRectGeometry() override; // |Geometry| bool CoversArea(const Matrix& transform, const Rect& rect) const override; diff --git a/impeller/entity/geometry/stroke_path_geometry.h b/impeller/entity/geometry/stroke_path_geometry.h index e5575f8147ed6..4265710e6941a 100644 --- a/impeller/entity/geometry/stroke_path_geometry.h +++ b/impeller/entity/geometry/stroke_path_geometry.h @@ -19,7 +19,7 @@ class StrokePathGeometry final : public Geometry { Cap stroke_cap, Join stroke_join); - ~StrokePathGeometry(); + ~StrokePathGeometry() override; Scalar GetStrokeWidth() const; diff --git a/impeller/entity/geometry/superellipse_geometry.cc b/impeller/entity/geometry/superellipse_geometry.cc index 578126b03c89d..5fe5fd660afe9 100644 --- a/impeller/entity/geometry/superellipse_geometry.cc +++ b/impeller/entity/geometry/superellipse_geometry.cc @@ -21,6 +21,8 @@ SuperellipseGeometry::SuperellipseGeometry(const Point& center, alpha_(alpha), beta_(beta) {} +SuperellipseGeometry::~SuperellipseGeometry() {} + GeometryResult SuperellipseGeometry::GetPositionBuffer( const ContentContext& renderer, const Entity& entity, diff --git a/impeller/entity/geometry/superellipse_geometry.h b/impeller/entity/geometry/superellipse_geometry.h index 19c049f2962ff..b01845bac57b1 100644 --- a/impeller/entity/geometry/superellipse_geometry.h +++ b/impeller/entity/geometry/superellipse_geometry.h @@ -28,7 +28,7 @@ class SuperellipseGeometry final : public Geometry { Scalar alpha, Scalar beta); - ~SuperellipseGeometry() = default; + ~SuperellipseGeometry() override; // |Geometry| bool CoversArea(const Matrix& transform, const Rect& rect) const override; From 721b40539a6265a6c922e367c0298f3ca47a6433 Mon Sep 17 00:00:00 2001 From: jonahwilliams Date: Sat, 5 Oct 2024 13:07:18 -0700 Subject: [PATCH 2/4] update testing. --- impeller/entity/entity_unittests.cc | 4719 ++++++++++++++------------- 1 file changed, 2366 insertions(+), 2353 deletions(-) diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index 16ad4624b9a50..568ba93d3c07a 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -62,2359 +62,2372 @@ namespace impeller { namespace testing { -// using EntityTest = EntityPlayground; -// INSTANTIATE_PLAYGROUND_SUITE(EntityTest); - -// TEST_P(EntityTest, CanCreateEntity) { -// Entity entity; -// ASSERT_TRUE(entity.GetTransform().IsIdentity()); -// } - -// TEST_P(EntityTest, FilterCoverageRespectsCropRect) { -// auto image = CreateTextureForFixture("boston.jpg"); -// auto filter = ColorFilterContents::MakeBlend(BlendMode::kSoftLight, -// FilterInput::Make({image})); - -// // Without the crop rect (default behavior). -// { -// auto actual = filter->GetCoverage({}); -// auto expected = Rect::MakeSize(image->GetSize()); - -// ASSERT_TRUE(actual.has_value()); -// ASSERT_RECT_NEAR(actual.value(), expected); -// } - -// // With the crop rect. -// { -// auto expected = Rect::MakeLTRB(50, 50, 100, 100); -// filter->SetCoverageHint(expected); -// auto actual = filter->GetCoverage({}); - -// ASSERT_TRUE(actual.has_value()); -// ASSERT_RECT_NEAR(actual.value(), expected); -// } -// } - -// TEST_P(EntityTest, GeometryBoundsAreTransformed) { -// auto geometry = Geometry::MakeRect(Rect::MakeXYWH(100, 100, 100, 100)); -// auto transform = Matrix::MakeScale({2.0, 2.0, 2.0}); - -// ASSERT_RECT_NEAR(geometry->GetCoverage(transform).value(), -// Rect::MakeXYWH(200, 200, 200, 200)); -// } - -// TEST_P(EntityTest, ThreeStrokesInOnePath) { -// Path path = PathBuilder{} -// .MoveTo({100, 100}) -// .LineTo({100, 200}) -// .MoveTo({100, 300}) -// .LineTo({100, 400}) -// .MoveTo({100, 500}) -// .LineTo({100, 600}) -// .TakePath(); - -// Entity entity; -// entity.SetTransform(Matrix::MakeScale(GetContentScale())); -// auto contents = std::make_unique(); - -// auto geom = Geometry::MakeStrokePath(path, 5.0); -// contents->SetGeometry(geom.get()); -// contents->SetColor(Color::Red()); -// entity.SetContents(std::move(contents)); -// ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); -// } - -// TEST_P(EntityTest, StrokeWithTextureContents) { -// auto bridge = CreateTextureForFixture("bay_bridge.jpg"); -// Path path = PathBuilder{} -// .MoveTo({100, 100}) -// .LineTo({100, 200}) -// .MoveTo({100, 300}) -// .LineTo({100, 400}) -// .MoveTo({100, 500}) -// .LineTo({100, 600}) -// .TakePath(); - -// Entity entity; -// entity.SetTransform(Matrix::MakeScale(GetContentScale())); -// auto contents = std::make_unique(); -// auto geom = Geometry::MakeStrokePath(path, 100.0); -// contents->SetGeometry(geom.get()); -// contents->SetTexture(bridge); -// contents->SetTileModes(Entity::TileMode::kClamp, Entity::TileMode::kClamp); -// entity.SetContents(std::move(contents)); -// ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); -// } - -// TEST_P(EntityTest, TriangleInsideASquare) { -// auto callback = [&](ContentContext& context, RenderPass& pass) { -// Point offset(100, 100); - -// static PlaygroundPoint point_a(Point(10, 10) + offset, 20, -// Color::White()); Point a = DrawPlaygroundPoint(point_a); static -// PlaygroundPoint point_b(Point(210, 10) + offset, 20, Color::White()); -// Point b = DrawPlaygroundPoint(point_b); -// static PlaygroundPoint point_c(Point(210, 210) + offset, 20, -// Color::White()); -// Point c = DrawPlaygroundPoint(point_c); -// static PlaygroundPoint point_d(Point(10, 210) + offset, 20, -// Color::White()); Point d = DrawPlaygroundPoint(point_d); static -// PlaygroundPoint point_e(Point(50, 50) + offset, 20, Color::White()); -// Point e = DrawPlaygroundPoint(point_e); -// static PlaygroundPoint point_f(Point(100, 50) + offset, 20, -// Color::White()); Point f = DrawPlaygroundPoint(point_f); static -// PlaygroundPoint point_g(Point(50, 150) + offset, 20, Color::White()); -// Point g = DrawPlaygroundPoint(point_g); -// Path path = PathBuilder{} -// .MoveTo(a) -// .LineTo(b) -// .LineTo(c) -// .LineTo(d) -// .Close() -// .MoveTo(e) -// .LineTo(f) -// .LineTo(g) -// .Close() -// .TakePath(); - -// Entity entity; -// entity.SetTransform(Matrix::MakeScale(GetContentScale())); -// auto contents = std::make_unique(); -// contents->SetGeometry(Geometry::MakeStrokePath(path, 20.0)); -// contents->SetColor(Color::Red()); -// entity.SetContents(std::move(contents)); - -// return entity.Render(context, pass); -// }; -// ASSERT_TRUE(OpenPlaygroundHere(callback)); -// } - -// TEST_P(EntityTest, StrokeCapAndJoinTest) { -// const Point padding(300, 250); -// const Point margin(140, 180); - -// auto callback = [&](ContentContext& context, RenderPass& pass) { -// // Slightly above sqrt(2) by default, so that right angles are just below -// // the limit and acute angles are over the limit (causing them to get -// // beveled). -// static Scalar miter_limit = 1.41421357; -// static Scalar width = 30; - -// ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); -// { -// ImGui::SliderFloat("Miter limit", &miter_limit, 0, 30); -// ImGui::SliderFloat("Stroke width", &width, 0, 100); -// if (ImGui::Button("Reset")) { -// miter_limit = 1.41421357; -// width = 30; -// } -// } -// ImGui::End(); - -// auto world_matrix = Matrix::MakeScale(GetContentScale()); -// auto render_path = [width = width, &context, &pass, &world_matrix]( -// const Path& path, Cap cap, Join join) { -// auto contents = std::make_unique(); -// contents->SetGeometry( -// Geometry::MakeStrokePath(path, width, miter_limit, cap, join)); -// contents->SetColor(Color::Red()); - -// Entity entity; -// entity.SetTransform(world_matrix); -// entity.SetContents(std::move(contents)); - -// auto coverage = entity.GetCoverage(); -// if (coverage.has_value()) { -// auto bounds_contents = std::make_unique(); -// bounds_contents->SetGeometry(Geometry::MakeFillPath( -// PathBuilder{}.AddRect(entity.GetCoverage().value()).TakePath())); -// bounds_contents->SetColor(Color::Green().WithAlpha(0.5)); -// Entity bounds_entity; -// bounds_entity.SetContents(std::move(bounds_contents)); -// bounds_entity.Render(context, pass); -// } - -// entity.Render(context, pass); -// }; - -// const Point a_def(0, 0), b_def(0, 100), c_def(150, 0), d_def(150, -100), -// e_def(75, 75); -// const Scalar r = 30; -// // Cap::kButt demo. -// { -// Point off = Point(0, 0) * padding + margin; -// static PlaygroundPoint point_a(off + a_def, r, Color::Black()); -// static PlaygroundPoint point_b(off + b_def, r, Color::White()); -// auto [a, b] = DrawPlaygroundLine(point_a, point_b); -// static PlaygroundPoint point_c(off + c_def, r, Color::Black()); -// static PlaygroundPoint point_d(off + d_def, r, Color::White()); -// auto [c, d] = DrawPlaygroundLine(point_c, point_d); -// render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(), -// Cap::kButt, Join::kBevel); -// } - -// // Cap::kSquare demo. -// { -// Point off = Point(1, 0) * padding + margin; -// static PlaygroundPoint point_a(off + a_def, r, Color::Black()); -// static PlaygroundPoint point_b(off + b_def, r, Color::White()); -// auto [a, b] = DrawPlaygroundLine(point_a, point_b); -// static PlaygroundPoint point_c(off + c_def, r, Color::Black()); -// static PlaygroundPoint point_d(off + d_def, r, Color::White()); -// auto [c, d] = DrawPlaygroundLine(point_c, point_d); -// render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(), -// Cap::kSquare, Join::kBevel); -// } - -// // Cap::kRound demo. -// { -// Point off = Point(2, 0) * padding + margin; -// static PlaygroundPoint point_a(off + a_def, r, Color::Black()); -// static PlaygroundPoint point_b(off + b_def, r, Color::White()); -// auto [a, b] = DrawPlaygroundLine(point_a, point_b); -// static PlaygroundPoint point_c(off + c_def, r, Color::Black()); -// static PlaygroundPoint point_d(off + d_def, r, Color::White()); -// auto [c, d] = DrawPlaygroundLine(point_c, point_d); -// render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(), -// Cap::kRound, Join::kBevel); -// } - -// // Join::kBevel demo. -// { -// Point off = Point(0, 1) * padding + margin; -// static PlaygroundPoint point_a = -// PlaygroundPoint(off + a_def, r, Color::White()); -// static PlaygroundPoint point_b = -// PlaygroundPoint(off + e_def, r, Color::White()); -// static PlaygroundPoint point_c = -// PlaygroundPoint(off + c_def, r, Color::White()); -// Point a = DrawPlaygroundPoint(point_a); -// Point b = DrawPlaygroundPoint(point_b); -// Point c = DrawPlaygroundPoint(point_c); -// render_path( -// PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(), -// Cap::kButt, Join::kBevel); -// } - -// // Join::kMiter demo. -// { -// Point off = Point(1, 1) * padding + margin; -// static PlaygroundPoint point_a(off + a_def, r, Color::White()); -// static PlaygroundPoint point_b(off + e_def, r, Color::White()); -// static PlaygroundPoint point_c(off + c_def, r, Color::White()); -// Point a = DrawPlaygroundPoint(point_a); -// Point b = DrawPlaygroundPoint(point_b); -// Point c = DrawPlaygroundPoint(point_c); -// render_path( -// PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(), -// Cap::kButt, Join::kMiter); -// } - -// // Join::kRound demo. -// { -// Point off = Point(2, 1) * padding + margin; -// static PlaygroundPoint point_a(off + a_def, r, Color::White()); -// static PlaygroundPoint point_b(off + e_def, r, Color::White()); -// static PlaygroundPoint point_c(off + c_def, r, Color::White()); -// Point a = DrawPlaygroundPoint(point_a); -// Point b = DrawPlaygroundPoint(point_b); -// Point c = DrawPlaygroundPoint(point_c); -// render_path( -// PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(), -// Cap::kButt, Join::kRound); -// } - -// return true; -// }; -// ASSERT_TRUE(OpenPlaygroundHere(callback)); -// } - -// TEST_P(EntityTest, CubicCurveTest) { -// // Compare with https://fiddle.skia.org/c/b3625f26122c9de7afe7794fcf25ead3 -// Path path = -// PathBuilder{} -// .MoveTo({237.164, 125.003}) -// .CubicCurveTo({236.709, 125.184}, {236.262, 125.358}, -// {235.81, 125.538}) -// .CubicCurveTo({235.413, 125.68}, {234.994, 125.832}, -// {234.592, 125.977}) -// .CubicCurveTo({234.592, 125.977}, {234.591, 125.977}, -// {234.59, 125.977}) -// .CubicCurveTo({222.206, 130.435}, {207.708, 135.753}, -// {192.381, 141.429}) -// .CubicCurveTo({162.77, 151.336}, {122.17, 156.894}, {84.1123, 160}) -// .Close() -// .TakePath(); -// Entity entity; -// entity.SetTransform(Matrix::MakeScale(GetContentScale())); -// entity.SetContents(SolidColorContents::Make(path, Color::Red())); -// ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); -// } - -// TEST_P(EntityTest, CanDrawCorrectlyWithRotatedTransform) { -// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { -// const char* input_axis[] = {"X", "Y", "Z"}; -// static int rotation_axis_index = 0; -// static float rotation = 0; -// ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); -// ImGui::SliderFloat("Rotation", &rotation, -kPi, kPi); -// ImGui::Combo("Rotation Axis", &rotation_axis_index, input_axis, -// sizeof(input_axis) / sizeof(char*)); -// Matrix rotation_matrix; -// switch (rotation_axis_index) { -// case 0: -// rotation_matrix = Matrix::MakeRotationX(Radians(rotation)); -// break; -// case 1: -// rotation_matrix = Matrix::MakeRotationY(Radians(rotation)); -// break; -// case 2: -// rotation_matrix = Matrix::MakeRotationZ(Radians(rotation)); -// break; -// default: -// rotation_matrix = Matrix{}; -// break; -// } - -// if (ImGui::Button("Reset")) { -// rotation = 0; -// } -// ImGui::End(); -// Matrix current_transform = -// Matrix::MakeScale(GetContentScale()) -// .MakeTranslation( -// Vector3(Point(pass.GetRenderTargetSize().width / 2.0, -// pass.GetRenderTargetSize().height / 2.0))); -// Matrix result_transform = current_transform * rotation_matrix; -// Path path = -// PathBuilder{}.AddRect(Rect::MakeXYWH(-300, -400, 600, -// 800)).TakePath(); - -// Entity entity; -// entity.SetTransform(result_transform); -// entity.SetContents(SolidColorContents::Make(path, Color::Red())); -// return entity.Render(context, pass); -// }; -// ASSERT_TRUE(OpenPlaygroundHere(callback)); -// } - -// TEST_P(EntityTest, CubicCurveAndOverlapTest) { -// // Compare with https://fiddle.skia.org/c/7a05a3e186c65a8dfb732f68020aae06 -// Path path = -// PathBuilder{} -// .MoveTo({359.934, 96.6335}) -// .CubicCurveTo({358.189, 96.7055}, {356.436, 96.7908}, -// {354.673, 96.8895}) -// .CubicCurveTo({354.571, 96.8953}, {354.469, 96.9016}, -// {354.367, 96.9075}) -// .CubicCurveTo({352.672, 97.0038}, {350.969, 97.113}, -// {349.259, 97.2355}) -// .CubicCurveTo({349.048, 97.2506}, {348.836, 97.2678}, -// {348.625, 97.2834}) -// .CubicCurveTo({347.019, 97.4014}, {345.407, 97.5299}, -// {343.789, 97.6722}) -// .CubicCurveTo({343.428, 97.704}, {343.065, 97.7402}, -// {342.703, 97.7734}) -// .CubicCurveTo({341.221, 97.9086}, {339.736, 98.0505}, -// {338.246, 98.207}) -// .CubicCurveTo({337.702, 98.2642}, {337.156, 98.3292}, -// {336.612, 98.3894}) -// .CubicCurveTo({335.284, 98.5356}, {333.956, 98.6837}, -// {332.623, 98.8476}) -// .CubicCurveTo({332.495, 98.8635}, {332.366, 98.8818}, -// {332.237, 98.8982}) -// .LineTo({332.237, 102.601}) -// .LineTo({321.778, 102.601}) -// .LineTo({321.778, 100.382}) -// .CubicCurveTo({321.572, 100.413}, {321.367, 100.442}, -// {321.161, 100.476}) -// .CubicCurveTo({319.22, 100.79}, {317.277, 101.123}, -// {315.332, 101.479}) -// .CubicCurveTo({315.322, 101.481}, {315.311, 101.482}, -// {315.301, 101.484}) -// .LineTo({310.017, 105.94}) -// .LineTo({309.779, 105.427}) -// .LineTo({314.403, 101.651}) -// .CubicCurveTo({314.391, 101.653}, {314.379, 101.656}, -// {314.368, 101.658}) -// .CubicCurveTo({312.528, 102.001}, {310.687, 102.366}, -// {308.846, 102.748}) -// .CubicCurveTo({307.85, 102.955}, {306.855, 103.182}, {305.859, -// 103.4}) .CubicCurveTo({305.048, 103.579}, {304.236, 103.75}, -// {303.425, 103.936}) -// .LineTo({299.105, 107.578}) -// .LineTo({298.867, 107.065}) -// .LineTo({302.394, 104.185}) -// .LineTo({302.412, 104.171}) -// .CubicCurveTo({301.388, 104.409}, {300.366, 104.67}, -// {299.344, 104.921}) -// .CubicCurveTo({298.618, 105.1}, {297.89, 105.269}, {297.165, -// 105.455}) .CubicCurveTo({295.262, 105.94}, {293.36, 106.445}, -// {291.462, 106.979}) -// .CubicCurveTo({291.132, 107.072}, {290.802, 107.163}, -// {290.471, 107.257}) -// .CubicCurveTo({289.463, 107.544}, {288.455, 107.839}, -// {287.449, 108.139}) -// .CubicCurveTo({286.476, 108.431}, {285.506, 108.73}, -// {284.536, 109.035}) -// .CubicCurveTo({283.674, 109.304}, {282.812, 109.579}, -// {281.952, 109.859}) -// .CubicCurveTo({281.177, 110.112}, {280.406, 110.377}, -// {279.633, 110.638}) -// .CubicCurveTo({278.458, 111.037}, {277.256, 111.449}, -// {276.803, 111.607}) -// .CubicCurveTo({276.76, 111.622}, {276.716, 111.637}, -// {276.672, 111.653}) -// .CubicCurveTo({275.017, 112.239}, {273.365, 112.836}, -// {271.721, 113.463}) -// .LineTo({271.717, 113.449}) -// .CubicCurveTo({271.496, 113.496}, {271.238, 113.559}, -// {270.963, 113.628}) -// .CubicCurveTo({270.893, 113.645}, {270.822, 113.663}, -// {270.748, 113.682}) -// .CubicCurveTo({270.468, 113.755}, {270.169, 113.834}, -// {269.839, 113.926}) -// .CubicCurveTo({269.789, 113.94}, {269.732, 113.957}, -// {269.681, 113.972}) -// .CubicCurveTo({269.391, 114.053}, {269.081, 114.143}, -// {268.756, 114.239}) -// .CubicCurveTo({268.628, 114.276}, {268.5, 114.314}, -// {268.367, 114.354}) -// .CubicCurveTo({268.172, 114.412}, {267.959, 114.478}, -// {267.752, 114.54}) -// .CubicCurveTo({263.349, 115.964}, {258.058, 117.695}, -// {253.564, 119.252}) -// .CubicCurveTo({253.556, 119.255}, {253.547, 119.258}, -// {253.538, 119.261}) -// .CubicCurveTo({251.844, 119.849}, {250.056, 120.474}, -// {248.189, 121.131}) -// .CubicCurveTo({248, 121.197}, {247.812, 121.264}, {247.621, -// 121.331}) .CubicCurveTo({247.079, 121.522}, {246.531, 121.715}, -// {245.975, 121.912}) -// .CubicCurveTo({245.554, 122.06}, {245.126, 122.212}, -// {244.698, 122.364}) -// .CubicCurveTo({244.071, 122.586}, {243.437, 122.811}, -// {242.794, 123.04}) -// .CubicCurveTo({242.189, 123.255}, {241.58, 123.472}, -// {240.961, 123.693}) -// .CubicCurveTo({240.659, 123.801}, {240.357, 123.909}, -// {240.052, 124.018}) -// .CubicCurveTo({239.12, 124.351}, {238.18, 124.687}, {237.22, -// 125.032}) .LineTo({237.164, 125.003}) .CubicCurveTo({236.709, -// 125.184}, {236.262, 125.358}, -// {235.81, 125.538}) -// .CubicCurveTo({235.413, 125.68}, {234.994, 125.832}, -// {234.592, 125.977}) -// .CubicCurveTo({234.592, 125.977}, {234.591, 125.977}, -// {234.59, 125.977}) -// .CubicCurveTo({222.206, 130.435}, {207.708, 135.753}, -// {192.381, 141.429}) -// .CubicCurveTo({162.77, 151.336}, {122.17, 156.894}, {84.1123, 160}) -// .LineTo({360, 160}) -// .LineTo({360, 119.256}) -// .LineTo({360, 106.332}) -// .LineTo({360, 96.6307}) -// .CubicCurveTo({359.978, 96.6317}, {359.956, 96.6326}, -// {359.934, 96.6335}) -// .Close() -// .MoveTo({337.336, 124.143}) -// .CubicCurveTo({337.274, 122.359}, {338.903, 121.511}, -// {338.903, 121.511}) -// .CubicCurveTo({338.903, 121.511}, {338.96, 123.303}, -// {337.336, 124.143}) -// .Close() -// .MoveTo({340.082, 121.849}) -// .CubicCurveTo({340.074, 121.917}, {340.062, 121.992}, -// {340.046, 122.075}) -// .CubicCurveTo({340.039, 122.109}, {340.031, 122.142}, -// {340.023, 122.177}) -// .CubicCurveTo({340.005, 122.26}, {339.98, 122.346}, -// {339.952, 122.437}) -// .CubicCurveTo({339.941, 122.473}, {339.931, 122.507}, -// {339.918, 122.544}) -// .CubicCurveTo({339.873, 122.672}, {339.819, 122.804}, -// {339.75, 122.938}) -// .CubicCurveTo({339.747, 122.944}, {339.743, 122.949}, -// {339.74, 122.955}) -// .CubicCurveTo({339.674, 123.08}, {339.593, 123.205}, -// {339.501, 123.328}) -// .CubicCurveTo({339.473, 123.366}, {339.441, 123.401}, -// {339.41, 123.438}) -// .CubicCurveTo({339.332, 123.534}, {339.243, 123.625}, -// {339.145, 123.714}) -// .CubicCurveTo({339.105, 123.75}, {339.068, 123.786}, -// {339.025, 123.821}) -// .CubicCurveTo({338.881, 123.937}, {338.724, 124.048}, -// {338.539, 124.143}) -// .CubicCurveTo({338.532, 123.959}, {338.554, 123.79}, -// {338.58, 123.626}) -// .CubicCurveTo({338.58, 123.625}, {338.58, 123.625}, {338.58, -// 123.625}) .CubicCurveTo({338.607, 123.455}, {338.65, 123.299}, -// {338.704, 123.151}) -// .CubicCurveTo({338.708, 123.14}, {338.71, 123.127}, -// {338.714, 123.117}) -// .CubicCurveTo({338.769, 122.971}, {338.833, 122.838}, -// {338.905, 122.712}) -// .CubicCurveTo({338.911, 122.702}, {338.916, 122.69200000000001}, -// {338.922, 122.682}) -// .CubicCurveTo({338.996, 122.557}, {339.072, 122.444}, -// {339.155, 122.34}) -// .CubicCurveTo({339.161, 122.333}, {339.166, 122.326}, -// {339.172, 122.319}) -// .CubicCurveTo({339.256, 122.215}, {339.339, 122.12}, -// {339.425, 122.037}) -// .CubicCurveTo({339.428, 122.033}, {339.431, 122.03}, -// {339.435, 122.027}) -// .CubicCurveTo({339.785, 121.687}, {340.106, 121.511}, -// {340.106, 121.511}) -// .CubicCurveTo({340.106, 121.511}, {340.107, 121.645}, -// {340.082, 121.849}) -// .Close() -// .MoveTo({340.678, 113.245}) -// .CubicCurveTo({340.594, 113.488}, {340.356, 113.655}, -// {340.135, 113.775}) -// .CubicCurveTo({339.817, 113.948}, {339.465, 114.059}, -// {339.115, 114.151}) -// .CubicCurveTo({338.251, 114.379}, {337.34, 114.516}, -// {336.448, 114.516}) -// .CubicCurveTo({335.761, 114.516}, {335.072, 114.527}, -// {334.384, 114.513}) -// .CubicCurveTo({334.125, 114.508}, {333.862, 114.462}, -// {333.605, 114.424}) -// .CubicCurveTo({332.865, 114.318}, {332.096, 114.184}, -// {331.41, 113.883}) -// .CubicCurveTo({330.979, 113.695}, {330.442, 113.34}, -// {330.672, 112.813}) -// .CubicCurveTo({331.135, 111.755}, {333.219, 112.946}, -// {334.526, 113.833}) -// .CubicCurveTo({334.54, 113.816}, {334.554, 113.8}, {334.569, -// 113.784}) .CubicCurveTo({333.38, 112.708}, {331.749, 110.985}, -// {332.76, 110.402}) -// .CubicCurveTo({333.769, 109.82}, {334.713, 111.93}, -// {335.228, 113.395}) -// .CubicCurveTo({334.915, 111.889}, {334.59, 109.636}, -// {335.661, 109.592}) -// .CubicCurveTo({336.733, 109.636}, {336.408, 111.889}, -// {336.07, 113.389}) -// .CubicCurveTo({336.609, 111.93}, {337.553, 109.82}, -// {338.563, 110.402}) -// .CubicCurveTo({339.574, 110.984}, {337.942, 112.708}, -// {336.753, 113.784}) -// .CubicCurveTo({336.768, 113.8}, {336.782, 113.816}, -// {336.796, 113.833}) -// .CubicCurveTo({338.104, 112.946}, {340.187, 111.755}, -// {340.65, 112.813}) -// .CubicCurveTo({340.71, 112.95}, {340.728, 113.102}, -// {340.678, 113.245}) -// .Close() -// .MoveTo({346.357, 106.771}) -// .CubicCurveTo({346.295, 104.987}, {347.924, 104.139}, -// {347.924, 104.139}) -// .CubicCurveTo({347.924, 104.139}, {347.982, 105.931}, -// {346.357, 106.771}) -// .Close() -// .MoveTo({347.56, 106.771}) -// .CubicCurveTo({347.498, 104.987}, {349.127, 104.139}, -// {349.127, 104.139}) -// .CubicCurveTo({349.127, 104.139}, {349.185, 105.931}, -// {347.56, 106.771}) -// .Close() -// .TakePath(); -// Entity entity; -// entity.SetTransform(Matrix::MakeScale(GetContentScale())); -// entity.SetContents(SolidColorContents::Make(path, Color::Red())); -// ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); -// } - -// TEST_P(EntityTest, SolidColorContentsStrokeSetStrokeCapsAndJoins) { -// { -// auto geometry = Geometry::MakeStrokePath(Path{}); -// auto path_geometry = static_cast(geometry.get()); -// // Defaults. -// ASSERT_EQ(path_geometry->GetStrokeCap(), Cap::kButt); -// ASSERT_EQ(path_geometry->GetStrokeJoin(), Join::kMiter); -// } - -// { -// auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, 4.0, Cap::kSquare); -// auto path_geometry = static_cast(geometry.get()); -// ASSERT_EQ(path_geometry->GetStrokeCap(), Cap::kSquare); -// } - -// { -// auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, 4.0, Cap::kRound); -// auto path_geometry = static_cast(geometry.get()); -// ASSERT_EQ(path_geometry->GetStrokeCap(), Cap::kRound); -// } -// } - -// TEST_P(EntityTest, SolidColorContentsStrokeSetMiterLimit) { -// { -// auto geometry = Geometry::MakeStrokePath(Path{}); -// auto path_geometry = static_cast(geometry.get()); -// ASSERT_FLOAT_EQ(path_geometry->GetMiterLimit(), 4); -// } - -// { -// auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, -// /*miter_limit=*/8.0); auto path_geometry = -// static_cast(geometry.get()); -// ASSERT_FLOAT_EQ(path_geometry->GetMiterLimit(), 8); -// } - -// { -// auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, -// /*miter_limit=*/-1.0); auto path_geometry = -// static_cast(geometry.get()); -// ASSERT_FLOAT_EQ(path_geometry->GetMiterLimit(), 4); -// } -// } - -// TEST_P(EntityTest, BlendingModeOptions) { -// std::vector blend_mode_names; -// std::vector blend_mode_values; -// { -// // Force an exhausiveness check with a switch. When adding blend modes, -// // update this switch with a new name/value to make it selectable in the -// // test GUI. - -// const BlendMode b{}; -// static_assert(b == BlendMode::kClear); // Ensure the first item in -// // the switch is the first -// // item in the enum. -// static_assert(Entity::kLastPipelineBlendMode == BlendMode::kModulate); -// switch (b) { -// case BlendMode::kClear: -// blend_mode_names.push_back("Clear"); -// blend_mode_values.push_back(BlendMode::kClear); -// case BlendMode::kSource: -// blend_mode_names.push_back("Source"); -// blend_mode_values.push_back(BlendMode::kSource); -// case BlendMode::kDestination: -// blend_mode_names.push_back("Destination"); -// blend_mode_values.push_back(BlendMode::kDestination); -// case BlendMode::kSourceOver: -// blend_mode_names.push_back("SourceOver"); -// blend_mode_values.push_back(BlendMode::kSourceOver); -// case BlendMode::kDestinationOver: -// blend_mode_names.push_back("DestinationOver"); -// blend_mode_values.push_back(BlendMode::kDestinationOver); -// case BlendMode::kSourceIn: -// blend_mode_names.push_back("SourceIn"); -// blend_mode_values.push_back(BlendMode::kSourceIn); -// case BlendMode::kDestinationIn: -// blend_mode_names.push_back("DestinationIn"); -// blend_mode_values.push_back(BlendMode::kDestinationIn); -// case BlendMode::kSourceOut: -// blend_mode_names.push_back("SourceOut"); -// blend_mode_values.push_back(BlendMode::kSourceOut); -// case BlendMode::kDestinationOut: -// blend_mode_names.push_back("DestinationOut"); -// blend_mode_values.push_back(BlendMode::kDestinationOut); -// case BlendMode::kSourceATop: -// blend_mode_names.push_back("SourceATop"); -// blend_mode_values.push_back(BlendMode::kSourceATop); -// case BlendMode::kDestinationATop: -// blend_mode_names.push_back("DestinationATop"); -// blend_mode_values.push_back(BlendMode::kDestinationATop); -// case BlendMode::kXor: -// blend_mode_names.push_back("Xor"); -// blend_mode_values.push_back(BlendMode::kXor); -// case BlendMode::kPlus: -// blend_mode_names.push_back("Plus"); -// blend_mode_values.push_back(BlendMode::kPlus); -// case BlendMode::kModulate: -// blend_mode_names.push_back("Modulate"); -// blend_mode_values.push_back(BlendMode::kModulate); -// }; -// } - -// auto callback = [&](ContentContext& context, RenderPass& pass) { -// auto world_matrix = Matrix::MakeScale(GetContentScale()); -// auto draw_rect = [&context, &pass, &world_matrix]( -// Rect rect, Color color, BlendMode blend_mode) -> -// bool { -// using VS = SolidFillPipeline::VertexShader; -// using FS = SolidFillPipeline::FragmentShader; - -// VertexBufferBuilder vtx_builder; -// { -// auto r = rect.GetLTRB(); -// vtx_builder.AddVertices({ -// {Point(r[0], r[1])}, -// {Point(r[2], r[1])}, -// {Point(r[2], r[3])}, -// {Point(r[0], r[1])}, -// {Point(r[2], r[3])}, -// {Point(r[0], r[3])}, -// }); -// } - -// pass.SetCommandLabel("Blended Rectangle"); -// auto options = OptionsFromPass(pass); -// options.blend_mode = blend_mode; -// options.primitive_type = PrimitiveType::kTriangle; -// pass.SetPipeline(context.GetSolidFillPipeline(options)); -// pass.SetVertexBuffer( -// vtx_builder.CreateVertexBuffer(context.GetTransientsBuffer())); - -// VS::FrameInfo frame_info; -// frame_info.mvp = pass.GetOrthographicTransform() * world_matrix; -// VS::BindFrameInfo( -// pass, context.GetTransientsBuffer().EmplaceUniform(frame_info)); -// FS::FragInfo frag_info; -// frag_info.color = color.Premultiply(); -// FS::BindFragInfo( -// pass, context.GetTransientsBuffer().EmplaceUniform(frame_info)); -// return pass.Draw().ok(); -// }; - -// ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); -// static Color color1(1, 0, 0, 0.5), color2(0, 1, 0, 0.5); -// ImGui::ColorEdit4("Color 1", reinterpret_cast(&color1)); -// ImGui::ColorEdit4("Color 2", reinterpret_cast(&color2)); -// static int current_blend_index = 3; -// ImGui::ListBox("Blending mode", ¤t_blend_index, -// blend_mode_names.data(), blend_mode_names.size()); -// ImGui::End(); - -// BlendMode selected_mode = blend_mode_values[current_blend_index]; - -// Point a, b, c, d; -// static PlaygroundPoint point_a(Point(400, 100), 20, Color::White()); -// static PlaygroundPoint point_b(Point(200, 300), 20, Color::White()); -// std::tie(a, b) = DrawPlaygroundLine(point_a, point_b); -// static PlaygroundPoint point_c(Point(470, 190), 20, Color::White()); -// static PlaygroundPoint point_d(Point(270, 390), 20, Color::White()); -// std::tie(c, d) = DrawPlaygroundLine(point_c, point_d); - -// bool result = true; -// result = result && -// draw_rect(Rect::MakeXYWH(0, 0, pass.GetRenderTargetSize().width, -// pass.GetRenderTargetSize().height), -// Color(), BlendMode::kClear); -// result = result && draw_rect(Rect::MakeLTRB(a.x, a.y, b.x, b.y), color1, -// BlendMode::kSourceOver); -// result = result && draw_rect(Rect::MakeLTRB(c.x, c.y, d.x, d.y), color2, -// selected_mode); -// return result; -// }; -// ASSERT_TRUE(OpenPlaygroundHere(callback)); -// } - -// TEST_P(EntityTest, BezierCircleScaled) { -// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { -// static float scale = 20; - -// ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); -// ImGui::SliderFloat("Scale", &scale, 1, 100); -// ImGui::End(); - -// Entity entity; -// entity.SetTransform(Matrix::MakeScale(GetContentScale())); -// auto path = PathBuilder{} -// .MoveTo({97.325, 34.818}) -// .CubicCurveTo({98.50862885295136, 34.81812293973836}, -// {99.46822048142015, 33.85863261475589}, -// {99.46822048142015, 32.67499810206613}) -// .CubicCurveTo({99.46822048142015, 31.491363589376355}, -// {98.50862885295136, 30.53187326439389}, -// {97.32499434685802, 30.531998226542708}) -// .CubicCurveTo({96.14153655073771, 30.532123170035373}, -// {95.18222070648729, 31.491540299350355}, -// {95.18222070648729, 32.67499810206613}) -// .CubicCurveTo({95.18222070648729, 33.85845590478189}, -// {96.14153655073771, 34.81787303409686}, -// {97.32499434685802, 34.81799797758954}) -// .Close() -// .TakePath(); -// entity.SetTransform( -// Matrix::MakeScale({scale, scale, 1.0}).Translate({-90, -20, 0})); -// entity.SetContents(SolidColorContents::Make(path, Color::Red())); -// return entity.Render(context, pass); -// }; -// ASSERT_TRUE(OpenPlaygroundHere(callback)); -// } - -// TEST_P(EntityTest, Filters) { -// auto bridge = CreateTextureForFixture("bay_bridge.jpg"); -// auto boston = CreateTextureForFixture("boston.jpg"); -// auto kalimba = CreateTextureForFixture("kalimba.jpg"); -// ASSERT_TRUE(bridge && boston && kalimba); - -// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { -// auto fi_bridge = FilterInput::Make(bridge); -// auto fi_boston = FilterInput::Make(boston); -// auto fi_kalimba = FilterInput::Make(kalimba); - -// std::shared_ptr blend0 = ColorFilterContents::MakeBlend( -// BlendMode::kModulate, {fi_kalimba, fi_boston}); - -// auto blend1 = ColorFilterContents::MakeBlend( -// BlendMode::kScreen, -// {FilterInput::Make(blend0), fi_bridge, fi_bridge, fi_bridge}); - -// Entity entity; -// entity.SetTransform(Matrix::MakeScale(GetContentScale()) * -// Matrix::MakeTranslation({500, 300}) * -// Matrix::MakeScale(Vector2{0.5, 0.5})); -// entity.SetContents(blend1); -// return entity.Render(context, pass); -// }; -// ASSERT_TRUE(OpenPlaygroundHere(callback)); -// } - -// TEST_P(EntityTest, GaussianBlurFilter) { -// auto boston = -// CreateTextureForFixture("boston.jpg", /*enable_mipmapping=*/true); -// ASSERT_TRUE(boston); - -// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { -// const char* input_type_names[] = {"Texture", "Solid Color"}; -// const char* blur_type_names[] = {"Image blur", "Mask blur"}; -// const char* pass_variation_names[] = {"New"}; -// const char* blur_style_names[] = {"Normal", "Solid", "Outer", "Inner"}; -// const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"}; -// const FilterContents::BlurStyle blur_styles[] = { -// FilterContents::BlurStyle::kNormal, -// FilterContents::BlurStyle::kSolid, FilterContents::BlurStyle::kOuter, -// FilterContents::BlurStyle::kInner}; -// const Entity::TileMode tile_modes[] = { -// Entity::TileMode::kClamp, Entity::TileMode::kRepeat, -// Entity::TileMode::kMirror, Entity::TileMode::kDecal}; - -// // UI state. -// static int selected_input_type = 0; -// static Color input_color = Color::Black(); -// static int selected_blur_type = 0; -// static int selected_pass_variation = 0; -// static bool combined_sigma = false; -// static float blur_amount_coarse[2] = {0, 0}; -// static float blur_amount_fine[2] = {10, 10}; -// static int selected_blur_style = 0; -// static int selected_tile_mode = 3; -// static Color cover_color(1, 0, 0, 0.2); -// static Color bounds_color(0, 1, 0, 0.1); -// static float offset[2] = {500, 400}; -// static float rotation = 0; -// static float scale[2] = {0.65, 0.65}; -// static float skew[2] = {0, 0}; -// static float path_rect[4] = {0, 0, -// static_cast(boston->GetSize().width), -// static_cast(boston->GetSize().height)}; - -// ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); -// { -// ImGui::Combo("Input type", &selected_input_type, input_type_names, -// sizeof(input_type_names) / sizeof(char*)); -// if (selected_input_type == 0) { -// ImGui::SliderFloat("Input opacity", &input_color.alpha, 0, 1); -// } else { -// ImGui::ColorEdit4("Input color", -// reinterpret_cast(&input_color)); -// } -// ImGui::Combo("Blur type", &selected_blur_type, blur_type_names, -// sizeof(blur_type_names) / sizeof(char*)); -// if (selected_blur_type == 0) { -// ImGui::Combo("Pass variation", &selected_pass_variation, -// pass_variation_names, -// sizeof(pass_variation_names) / sizeof(char*)); -// } -// ImGui::Checkbox("Combined sigma", &combined_sigma); -// if (combined_sigma) { -// ImGui::SliderFloat("Sigma (coarse)", blur_amount_coarse, 0, 1000); -// ImGui::SliderFloat("Sigma (fine)", blur_amount_fine, 0, 10); -// blur_amount_coarse[1] = blur_amount_coarse[0]; -// blur_amount_fine[1] = blur_amount_fine[0]; -// } else { -// ImGui::SliderFloat2("Sigma (coarse)", blur_amount_coarse, 0, 1000); -// ImGui::SliderFloat2("Sigma (fine)", blur_amount_fine, 0, 10); -// } -// ImGui::Combo("Blur style", &selected_blur_style, blur_style_names, -// sizeof(blur_style_names) / sizeof(char*)); -// ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names, -// sizeof(tile_mode_names) / sizeof(char*)); -// ImGui::ColorEdit4("Cover color", -// reinterpret_cast(&cover_color)); ImGui::ColorEdit4("Bounds -// color", -// reinterpret_cast(&bounds_color)); -// ImGui::SliderFloat2("Translation", offset, 0, -// pass.GetRenderTargetSize().width); -// ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2); -// ImGui::SliderFloat2("Scale", scale, 0, 3); -// ImGui::SliderFloat2("Skew", skew, -3, 3); -// ImGui::SliderFloat4("Path XYWH", path_rect, -1000, 1000); -// } -// ImGui::End(); - -// auto blur_sigma_x = Sigma{blur_amount_coarse[0] + blur_amount_fine[0]}; -// auto blur_sigma_y = Sigma{blur_amount_coarse[1] + blur_amount_fine[1]}; - -// std::shared_ptr input; -// Size input_size; - -// auto input_rect = -// Rect::MakeXYWH(path_rect[0], path_rect[1], path_rect[2], -// path_rect[3]); -// if (selected_input_type == 0) { -// auto texture = std::make_shared(); -// texture->SetSourceRect(Rect::MakeSize(boston->GetSize())); -// texture->SetDestinationRect(input_rect); -// texture->SetTexture(boston); -// texture->SetOpacity(input_color.alpha); - -// input = texture; -// input_size = input_rect.GetSize(); -// } else { -// auto fill = std::make_shared(); -// fill->SetColor(input_color); -// fill->SetGeometry( -// Geometry::MakeFillPath(PathBuilder{}.AddRect(input_rect).TakePath())); - -// input = fill; -// input_size = input_rect.GetSize(); -// } - -// std::shared_ptr blur; -// switch (selected_pass_variation) { -// case 0: -// blur = std::make_shared( -// blur_sigma_x.sigma, blur_sigma_y.sigma, -// tile_modes[selected_tile_mode], blur_styles[selected_blur_style], -// /*geometry=*/nullptr); -// blur->SetInputs({FilterInput::Make(input)}); -// break; -// case 1: -// blur = FilterContents::MakeGaussianBlur( -// FilterInput::Make(input), blur_sigma_x, blur_sigma_y, -// tile_modes[selected_tile_mode], -// blur_styles[selected_blur_style]); -// break; -// }; -// FML_CHECK(blur); - -// auto mask_blur = FilterContents::MakeBorderMaskBlur( -// FilterInput::Make(input), blur_sigma_x, blur_sigma_y, -// blur_styles[selected_blur_style]); - -// auto ctm = Matrix::MakeScale(GetContentScale()) * -// Matrix::MakeTranslation(Vector3(offset[0], offset[1])) * -// Matrix::MakeRotationZ(Radians(rotation)) * -// Matrix::MakeScale(Vector2(scale[0], scale[1])) * -// Matrix::MakeSkew(skew[0], skew[1]) * -// Matrix::MakeTranslation(-Point(input_size) / 2); - -// auto target_contents = selected_blur_type == 0 ? blur : mask_blur; - -// Entity entity; -// entity.SetContents(target_contents); -// entity.SetTransform(ctm); - -// entity.Render(context, pass); - -// // Renders a red "cover" rectangle that shows the original position of -// the -// // unfiltered input. -// Entity cover_entity; -// cover_entity.SetContents(SolidColorContents::Make( -// PathBuilder{}.AddRect(input_rect).TakePath(), cover_color)); -// cover_entity.SetTransform(ctm); - -// cover_entity.Render(context, pass); - -// // Renders a green bounding rect of the target filter. -// Entity bounds_entity; -// std::optional target_contents_coverage = -// target_contents->GetCoverage(entity); -// if (target_contents_coverage.has_value()) { -// bounds_entity.SetContents(SolidColorContents::Make( -// PathBuilder{} -// .AddRect(target_contents->GetCoverage(entity).value()) -// .TakePath(), -// bounds_color)); -// bounds_entity.SetTransform(Matrix()); - -// bounds_entity.Render(context, pass); -// } - -// return true; -// }; -// ASSERT_TRUE(OpenPlaygroundHere(callback)); -// } - -// TEST_P(EntityTest, MorphologyFilter) { -// auto boston = CreateTextureForFixture("boston.jpg"); -// ASSERT_TRUE(boston); - -// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { -// const char* morphology_type_names[] = {"Dilate", "Erode"}; -// const FilterContents::MorphType morphology_types[] = { -// FilterContents::MorphType::kDilate, -// FilterContents::MorphType::kErode}; -// static Color input_color = Color::Black(); -// // UI state. -// static int selected_morphology_type = 0; -// static float radius[2] = {20, 20}; -// static Color cover_color(1, 0, 0, 0.2); -// static Color bounds_color(0, 1, 0, 0.1); -// static float offset[2] = {500, 400}; -// static float rotation = 0; -// static float scale[2] = {0.65, 0.65}; -// static float skew[2] = {0, 0}; -// static float path_rect[4] = {0, 0, -// static_cast(boston->GetSize().width), -// static_cast(boston->GetSize().height)}; -// static float effect_transform_scale = 1; - -// ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); -// { -// ImGui::Combo("Morphology type", &selected_morphology_type, -// morphology_type_names, -// sizeof(morphology_type_names) / sizeof(char*)); -// ImGui::SliderFloat2("Radius", radius, 0, 200); -// ImGui::SliderFloat("Input opacity", &input_color.alpha, 0, 1); -// ImGui::ColorEdit4("Cover color", -// reinterpret_cast(&cover_color)); ImGui::ColorEdit4("Bounds -// color", -// reinterpret_cast(&bounds_color)); -// ImGui::SliderFloat2("Translation", offset, 0, -// pass.GetRenderTargetSize().width); -// ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2); -// ImGui::SliderFloat2("Scale", scale, 0, 3); -// ImGui::SliderFloat2("Skew", skew, -3, 3); -// ImGui::SliderFloat4("Path XYWH", path_rect, -1000, 1000); -// ImGui::SliderFloat("Effect transform scale", &effect_transform_scale, -// 0, -// 3); -// } -// ImGui::End(); - -// std::shared_ptr input; -// Size input_size; - -// auto input_rect = -// Rect::MakeXYWH(path_rect[0], path_rect[1], path_rect[2], -// path_rect[3]); -// auto texture = std::make_shared(); -// texture->SetSourceRect(Rect::MakeSize(boston->GetSize())); -// texture->SetDestinationRect(input_rect); -// texture->SetTexture(boston); -// texture->SetOpacity(input_color.alpha); - -// input = texture; -// input_size = input_rect.GetSize(); - -// auto contents = FilterContents::MakeMorphology( -// FilterInput::Make(input), Radius{radius[0]}, Radius{radius[1]}, -// morphology_types[selected_morphology_type]); -// contents->SetEffectTransform(Matrix::MakeScale( -// Vector2{effect_transform_scale, effect_transform_scale})); - -// auto ctm = Matrix::MakeScale(GetContentScale()) * -// Matrix::MakeTranslation(Vector3(offset[0], offset[1])) * -// Matrix::MakeRotationZ(Radians(rotation)) * -// Matrix::MakeScale(Vector2(scale[0], scale[1])) * -// Matrix::MakeSkew(skew[0], skew[1]) * -// Matrix::MakeTranslation(-Point(input_size) / 2); - -// Entity entity; -// entity.SetContents(contents); -// entity.SetTransform(ctm); - -// entity.Render(context, pass); - -// // Renders a red "cover" rectangle that shows the original position of -// the -// // unfiltered input. -// Entity cover_entity; -// cover_entity.SetContents(SolidColorContents::Make( -// PathBuilder{}.AddRect(input_rect).TakePath(), cover_color)); -// cover_entity.SetTransform(ctm); - -// cover_entity.Render(context, pass); - -// // Renders a green bounding rect of the target filter. -// Entity bounds_entity; -// bounds_entity.SetContents(SolidColorContents::Make( -// PathBuilder{}.AddRect(contents->GetCoverage(entity).value()).TakePath(), -// bounds_color)); -// bounds_entity.SetTransform(Matrix()); - -// bounds_entity.Render(context, pass); - -// return true; -// }; -// ASSERT_TRUE(OpenPlaygroundHere(callback)); -// } - -// TEST_P(EntityTest, SetBlendMode) { -// Entity entity; -// ASSERT_EQ(entity.GetBlendMode(), BlendMode::kSourceOver); -// entity.SetBlendMode(BlendMode::kClear); -// ASSERT_EQ(entity.GetBlendMode(), BlendMode::kClear); -// } - -// TEST_P(EntityTest, ContentsGetBoundsForEmptyPathReturnsNullopt) { -// Entity entity; -// entity.SetContents(std::make_shared()); -// ASSERT_FALSE(entity.GetCoverage().has_value()); -// } - -// TEST_P(EntityTest, SolidStrokeCoverageIsCorrect) { -// { -// auto geometry = Geometry::MakeStrokePath( -// PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath(), 4.0, 4.0, -// Cap::kButt, Join::kBevel); - -// Entity entity; -// auto contents = std::make_unique(); -// contents->SetGeometry(std::move(geometry)); -// contents->SetColor(Color::Black()); -// entity.SetContents(std::move(contents)); -// auto actual = entity.GetCoverage(); -// auto expected = Rect::MakeLTRB(-2, -2, 12, 12); -// ASSERT_TRUE(actual.has_value()); -// ASSERT_RECT_NEAR(actual.value(), expected); -// } - -// // Cover the Cap::kSquare case. -// { -// auto geometry = Geometry::MakeStrokePath( -// PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath(), 4.0, 4.0, -// Cap::kSquare, Join::kBevel); - -// Entity entity; -// auto contents = std::make_unique(); -// contents->SetGeometry(std::move(geometry)); -// contents->SetColor(Color::Black()); -// entity.SetContents(std::move(contents)); -// auto actual = entity.GetCoverage(); -// auto expected = -// Rect::MakeLTRB(-sqrt(8), -sqrt(8), 10 + sqrt(8), 10 + sqrt(8)); -// ASSERT_TRUE(actual.has_value()); -// ASSERT_RECT_NEAR(actual.value(), expected); -// } - -// // Cover the Join::kMiter case. -// { -// auto geometry = Geometry::MakeStrokePath( -// PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath(), 4.0, 2.0, -// Cap::kSquare, Join::kMiter); - -// Entity entity; -// auto contents = std::make_unique(); -// contents->SetGeometry(std::move(geometry)); -// contents->SetColor(Color::Black()); -// entity.SetContents(std::move(contents)); -// auto actual = entity.GetCoverage(); -// auto expected = Rect::MakeLTRB(-4, -4, 14, 14); -// ASSERT_TRUE(actual.has_value()); -// ASSERT_RECT_NEAR(actual.value(), expected); -// } -// } - -// TEST_P(EntityTest, BorderMaskBlurCoverageIsCorrect) { -// auto fill = std::make_shared(); -// fill->SetGeometry(Geometry::MakeFillPath( -// PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath())); -// fill->SetColor(Color::CornflowerBlue()); -// auto border_mask_blur = FilterContents::MakeBorderMaskBlur( -// FilterInput::Make(fill), Radius{3}, Radius{4}); - -// { -// Entity e; -// e.SetTransform(Matrix()); -// auto actual = border_mask_blur->GetCoverage(e); -// auto expected = Rect::MakeXYWH(-3, -4, 306, 408); -// ASSERT_TRUE(actual.has_value()); -// ASSERT_RECT_NEAR(actual.value(), expected); -// } - -// { -// Entity e; -// e.SetTransform(Matrix::MakeRotationZ(Radians{kPi / 4})); -// auto actual = border_mask_blur->GetCoverage(e); -// auto expected = Rect::MakeXYWH(-287.792, -4.94975, 504.874, 504.874); -// ASSERT_TRUE(actual.has_value()); -// ASSERT_RECT_NEAR(actual.value(), expected); -// } -// } - -// TEST_P(EntityTest, SolidFillCoverageIsCorrect) { -// // No transform -// { -// auto fill = std::make_shared(); -// fill->SetColor(Color::CornflowerBlue()); -// auto expected = Rect::MakeLTRB(100, 110, 200, 220); -// fill->SetGeometry( -// Geometry::MakeFillPath(PathBuilder{}.AddRect(expected).TakePath())); - -// auto coverage = fill->GetCoverage({}); -// ASSERT_TRUE(coverage.has_value()); -// ASSERT_RECT_NEAR(coverage.value(), expected); -// } - -// // Entity transform -// { -// auto fill = std::make_shared(); -// fill->SetColor(Color::CornflowerBlue()); -// fill->SetGeometry(Geometry::MakeFillPath( -// PathBuilder{}.AddRect(Rect::MakeLTRB(100, 110, 200, -// 220)).TakePath())); - -// Entity entity; -// entity.SetTransform(Matrix::MakeTranslation(Vector2(4, 5))); -// entity.SetContents(std::move(fill)); - -// auto coverage = entity.GetCoverage(); -// auto expected = Rect::MakeLTRB(104, 115, 204, 225); -// ASSERT_TRUE(coverage.has_value()); -// ASSERT_RECT_NEAR(coverage.value(), expected); -// } - -// // No coverage for fully transparent colors -// { -// auto fill = std::make_shared(); -// fill->SetColor(Color::WhiteTransparent()); -// fill->SetGeometry(Geometry::MakeFillPath( -// PathBuilder{}.AddRect(Rect::MakeLTRB(100, 110, 200, -// 220)).TakePath())); - -// auto coverage = fill->GetCoverage({}); -// ASSERT_FALSE(coverage.has_value()); -// } -// } - -// TEST_P(EntityTest, ClipContentsGetClipCoverageIsCorrect) { -// // Intersection: No stencil coverage, no geometry. -// { -// auto clip = std::make_shared(); -// clip->SetClipOperation(Entity::ClipOperation::kIntersect); -// auto result = clip->GetClipCoverage(Entity{}, Rect{}); - -// ASSERT_FALSE(result.coverage.has_value()); -// } - -// // Intersection: No stencil coverage, with geometry. -// { -// auto clip = std::make_shared(); -// clip->SetClipOperation(Entity::ClipOperation::kIntersect); -// clip->SetGeometry(Geometry::MakeFillPath( -// PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 100, 100)).TakePath())); -// auto result = clip->GetClipCoverage(Entity{}, Rect{}); - -// ASSERT_FALSE(result.coverage.has_value()); -// } - -// // Intersection: With stencil coverage, no geometry. -// { -// auto clip = std::make_shared(); -// clip->SetClipOperation(Entity::ClipOperation::kIntersect); -// auto result = -// clip->GetClipCoverage(Entity{}, Rect::MakeLTRB(0, 0, 100, 100)); - -// ASSERT_FALSE(result.coverage.has_value()); -// } - -// // Intersection: With stencil coverage, with geometry. -// { -// auto clip = std::make_shared(); -// clip->SetClipOperation(Entity::ClipOperation::kIntersect); -// clip->SetGeometry(Geometry::MakeFillPath( -// PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 50, 50)).TakePath())); -// auto result = -// clip->GetClipCoverage(Entity{}, Rect::MakeLTRB(0, 0, 100, 100)); - -// ASSERT_TRUE(result.coverage.has_value()); -// ASSERT_RECT_NEAR(result.coverage.value(), Rect::MakeLTRB(0, 0, 50, 50)); -// ASSERT_EQ(result.type, Contents::ClipCoverage::Type::kAppend); -// } - -// // Difference: With stencil coverage, with geometry. -// { -// auto clip = std::make_shared(); -// clip->SetClipOperation(Entity::ClipOperation::kDifference); -// clip->SetGeometry(Geometry::MakeFillPath( -// PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 50, 50)).TakePath())); -// auto result = -// clip->GetClipCoverage(Entity{}, Rect::MakeLTRB(0, 0, 100, 100)); - -// ASSERT_TRUE(result.coverage.has_value()); -// ASSERT_RECT_NEAR(result.coverage.value(), Rect::MakeLTRB(0, 0, 100, -// 100)); ASSERT_EQ(result.type, Contents::ClipCoverage::Type::kAppend); -// } -// } - -// TEST_P(EntityTest, RRectShadowTest) { -// auto callback = [&](ContentContext& context, RenderPass& pass) { -// static Color color = Color::Red(); -// static float corner_radius = 100; -// static float blur_radius = 100; -// static bool show_coverage = false; -// static Color coverage_color = Color::Green().WithAlpha(0.2); - -// ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); -// ImGui::SliderFloat("Corner radius", &corner_radius, 0, 300); -// ImGui::SliderFloat("Blur radius", &blur_radius, 0, 300); -// ImGui::ColorEdit4("Color", reinterpret_cast(&color)); -// ImGui::Checkbox("Show coverage", &show_coverage); -// if (show_coverage) { -// ImGui::ColorEdit4("Coverage color", -// reinterpret_cast(&coverage_color)); -// } -// ImGui::End(); - -// static PlaygroundPoint top_left_point(Point(200, 200), 30, -// Color::White()); static PlaygroundPoint bottom_right_point(Point(600, -// 400), 30, -// Color::White()); -// auto [top_left, bottom_right] = -// DrawPlaygroundLine(top_left_point, bottom_right_point); -// auto rect = -// Rect::MakeLTRB(top_left.x, top_left.y, bottom_right.x, -// bottom_right.y); - -// auto contents = std::make_unique(); -// contents->SetRRect(rect, {corner_radius, corner_radius}); -// contents->SetColor(color); -// contents->SetSigma(Radius(blur_radius)); - -// Entity entity; -// entity.SetTransform(Matrix::MakeScale(GetContentScale())); -// entity.SetContents(std::move(contents)); -// entity.Render(context, pass); - -// auto coverage = entity.GetCoverage(); -// if (show_coverage && coverage.has_value()) { -// auto bounds_contents = std::make_unique(); -// bounds_contents->SetGeometry(Geometry::MakeFillPath( -// PathBuilder{}.AddRect(entity.GetCoverage().value()).TakePath())); -// bounds_contents->SetColor(coverage_color.Premultiply()); -// Entity bounds_entity; -// bounds_entity.SetContents(std::move(bounds_contents)); -// bounds_entity.Render(context, pass); -// } - -// return true; -// }; -// ASSERT_TRUE(OpenPlaygroundHere(callback)); -// } - -// TEST_P(EntityTest, ColorMatrixFilterCoverageIsCorrect) { -// // Set up a simple color background. -// auto fill = std::make_shared(); -// fill->SetGeometry(Geometry::MakeFillPath( -// PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath())); -// fill->SetColor(Color::Coral()); - -// // Set the color matrix filter. -// ColorMatrix matrix = { -// 1, 1, 1, 1, 1, // -// 1, 1, 1, 1, 1, // -// 1, 1, 1, 1, 1, // -// 1, 1, 1, 1, 1, // -// }; - -// auto filter = -// ColorFilterContents::MakeColorMatrix(FilterInput::Make(fill), matrix); - -// Entity e; -// e.SetTransform(Matrix()); - -// // Confirm that the actual filter coverage matches the expected coverage. -// auto actual = filter->GetCoverage(e); -// auto expected = Rect::MakeXYWH(0, 0, 300, 400); - -// ASSERT_TRUE(actual.has_value()); -// ASSERT_RECT_NEAR(actual.value(), expected); -// } - -// TEST_P(EntityTest, ColorMatrixFilterEditable) { -// auto bay_bridge = CreateTextureForFixture("bay_bridge.jpg"); -// ASSERT_TRUE(bay_bridge); - -// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { -// // UI state. -// static ColorMatrix color_matrix = { -// 1, 0, 0, 0, 0, // -// 0, 3, 0, 0, 0, // -// 0, 0, 1, 0, 0, // -// 0, 0, 0, 1, 0, // -// }; -// static float offset[2] = {500, 400}; -// static float rotation = 0; -// static float scale[2] = {0.65, 0.65}; -// static float skew[2] = {0, 0}; - -// // Define the ImGui -// ImGui::Begin("Color Matrix", nullptr, ImGuiWindowFlags_AlwaysAutoResize); -// { -// std::string label = "##1"; -// for (int i = 0; i < 20; i += 5) { -// ImGui::InputScalarN(label.c_str(), ImGuiDataType_Float, -// &(color_matrix.array[i]), 5, nullptr, nullptr, -// "%.2f", 0); -// label[2]++; -// } - -// ImGui::SliderFloat2("Translation", &offset[0], 0, -// pass.GetRenderTargetSize().width); -// ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2); -// ImGui::SliderFloat2("Scale", &scale[0], 0, 3); -// ImGui::SliderFloat2("Skew", &skew[0], -3, 3); -// } -// ImGui::End(); - -// // Set the color matrix filter. -// auto filter = ColorFilterContents::MakeColorMatrix( -// FilterInput::Make(bay_bridge), color_matrix); - -// // Define the entity with the color matrix filter. -// Entity entity; -// entity.SetTransform( -// Matrix::MakeScale(GetContentScale()) * -// Matrix::MakeTranslation(Vector3(offset[0], offset[1])) * -// Matrix::MakeRotationZ(Radians(rotation)) * -// Matrix::MakeScale(Vector2(scale[0], scale[1])) * -// Matrix::MakeSkew(skew[0], skew[1]) * -// Matrix::MakeTranslation(-Point(bay_bridge->GetSize()) / 2)); -// entity.SetContents(filter); -// entity.Render(context, pass); - -// return true; -// }; - -// ASSERT_TRUE(OpenPlaygroundHere(callback)); -// } - -// TEST_P(EntityTest, LinearToSrgbFilterCoverageIsCorrect) { -// // Set up a simple color background. -// auto fill = std::make_shared(); -// fill->SetGeometry(Geometry::MakeFillPath( -// PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath())); -// fill->SetColor(Color::MintCream()); - -// auto filter = -// ColorFilterContents::MakeLinearToSrgbFilter(FilterInput::Make(fill)); - -// Entity e; -// e.SetTransform(Matrix()); - -// // Confirm that the actual filter coverage matches the expected coverage. -// auto actual = filter->GetCoverage(e); -// auto expected = Rect::MakeXYWH(0, 0, 300, 400); - -// ASSERT_TRUE(actual.has_value()); -// ASSERT_RECT_NEAR(actual.value(), expected); -// } - -// TEST_P(EntityTest, LinearToSrgbFilter) { -// auto image = CreateTextureForFixture("kalimba.jpg"); -// ASSERT_TRUE(image); - -// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { -// auto filtered = -// ColorFilterContents::MakeLinearToSrgbFilter(FilterInput::Make(image)); - -// // Define the entity that will serve as the control image as a Gaussian -// blur -// // filter with no filter at all. -// Entity entity_left; -// entity_left.SetTransform(Matrix::MakeScale(GetContentScale()) * -// Matrix::MakeTranslation({100, 300}) * -// Matrix::MakeScale(Vector2{0.5, 0.5})); -// auto unfiltered = -// FilterContents::MakeGaussianBlur(FilterInput::Make(image), -// Sigma{0}, Sigma{0}); -// entity_left.SetContents(unfiltered); - -// // Define the entity that will be filtered from linear to sRGB. -// Entity entity_right; -// entity_right.SetTransform(Matrix::MakeScale(GetContentScale()) * -// Matrix::MakeTranslation({500, 300}) * -// Matrix::MakeScale(Vector2{0.5, 0.5})); -// entity_right.SetContents(filtered); -// return entity_left.Render(context, pass) && -// entity_right.Render(context, pass); -// }; - -// ASSERT_TRUE(OpenPlaygroundHere(callback)); -// } - -// TEST_P(EntityTest, SrgbToLinearFilterCoverageIsCorrect) { -// // Set up a simple color background. -// auto fill = std::make_shared(); -// fill->SetGeometry(Geometry::MakeFillPath( -// PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath())); -// fill->SetColor(Color::DeepPink()); - -// auto filter = -// ColorFilterContents::MakeSrgbToLinearFilter(FilterInput::Make(fill)); - -// Entity e; -// e.SetTransform(Matrix()); - -// // Confirm that the actual filter coverage matches the expected coverage. -// auto actual = filter->GetCoverage(e); -// auto expected = Rect::MakeXYWH(0, 0, 300, 400); - -// ASSERT_TRUE(actual.has_value()); -// ASSERT_RECT_NEAR(actual.value(), expected); -// } - -// TEST_P(EntityTest, SrgbToLinearFilter) { -// auto image = CreateTextureForFixture("embarcadero.jpg"); -// ASSERT_TRUE(image); - -// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { -// auto filtered = -// ColorFilterContents::MakeSrgbToLinearFilter(FilterInput::Make(image)); - -// // Define the entity that will serve as the control image as a Gaussian -// blur -// // filter with no filter at all. -// Entity entity_left; -// entity_left.SetTransform(Matrix::MakeScale(GetContentScale()) * -// Matrix::MakeTranslation({100, 300}) * -// Matrix::MakeScale(Vector2{0.5, 0.5})); -// auto unfiltered = -// FilterContents::MakeGaussianBlur(FilterInput::Make(image), -// Sigma{0}, Sigma{0}); -// entity_left.SetContents(unfiltered); - -// // Define the entity that will be filtered from sRGB to linear. -// Entity entity_right; -// entity_right.SetTransform(Matrix::MakeScale(GetContentScale()) * -// Matrix::MakeTranslation({500, 300}) * -// Matrix::MakeScale(Vector2{0.5, 0.5})); -// entity_right.SetContents(filtered); -// return entity_left.Render(context, pass) && -// entity_right.Render(context, pass); -// }; - -// ASSERT_TRUE(OpenPlaygroundHere(callback)); -// } - -// static Vector3 RGBToYUV(Vector3 rgb, YUVColorSpace yuv_color_space) { -// Vector3 yuv; -// switch (yuv_color_space) { -// case YUVColorSpace::kBT601FullRange: -// yuv.x = rgb.x * 0.299 + rgb.y * 0.587 + rgb.z * 0.114; -// yuv.y = rgb.x * -0.169 + rgb.y * -0.331 + rgb.z * 0.5 + 0.5; -// yuv.z = rgb.x * 0.5 + rgb.y * -0.419 + rgb.z * -0.081 + 0.5; -// break; -// case YUVColorSpace::kBT601LimitedRange: -// yuv.x = rgb.x * 0.257 + rgb.y * 0.516 + rgb.z * 0.100 + 0.063; -// yuv.y = rgb.x * -0.145 + rgb.y * -0.291 + rgb.z * 0.439 + 0.5; -// yuv.z = rgb.x * 0.429 + rgb.y * -0.368 + rgb.z * -0.071 + 0.5; -// break; -// } -// return yuv; -// } - -// static std::vector> CreateTestYUVTextures( -// Context* context, -// YUVColorSpace yuv_color_space) { -// Vector3 red = {244.0 / 255.0, 67.0 / 255.0, 54.0 / 255.0}; -// Vector3 green = {76.0 / 255.0, 175.0 / 255.0, 80.0 / 255.0}; -// Vector3 blue = {33.0 / 255.0, 150.0 / 255.0, 243.0 / 255.0}; -// Vector3 white = {1.0, 1.0, 1.0}; -// Vector3 red_yuv = RGBToYUV(red, yuv_color_space); -// Vector3 green_yuv = RGBToYUV(green, yuv_color_space); -// Vector3 blue_yuv = RGBToYUV(blue, yuv_color_space); -// Vector3 white_yuv = RGBToYUV(white, yuv_color_space); -// std::vector yuvs{red_yuv, green_yuv, blue_yuv, white_yuv}; -// std::vector y_data; -// std::vector uv_data; -// for (int i = 0; i < 4; i++) { -// auto yuv = yuvs[i]; -// uint8_t y = std::round(yuv.x * 255.0); -// uint8_t u = std::round(yuv.y * 255.0); -// uint8_t v = std::round(yuv.z * 255.0); -// for (int j = 0; j < 16; j++) { -// y_data.push_back(y); -// } -// for (int j = 0; j < 8; j++) { -// uv_data.push_back(j % 2 == 0 ? u : v); -// } -// } -// auto cmd_buffer = context->CreateCommandBuffer(); -// auto blit_pass = cmd_buffer->CreateBlitPass(); - -// impeller::TextureDescriptor y_texture_descriptor; -// y_texture_descriptor.storage_mode = impeller::StorageMode::kHostVisible; -// y_texture_descriptor.format = PixelFormat::kR8UNormInt; -// y_texture_descriptor.size = {8, 8}; -// auto y_texture = -// context->GetResourceAllocator()->CreateTexture(y_texture_descriptor); -// auto y_mapping = std::make_shared(y_data); -// auto y_mapping_buffer = -// context->GetResourceAllocator()->CreateBufferWithCopy(*y_mapping); - -// blit_pass->AddCopy(DeviceBuffer::AsBufferView(y_mapping_buffer), -// y_texture); - -// impeller::TextureDescriptor uv_texture_descriptor; -// uv_texture_descriptor.storage_mode = impeller::StorageMode::kHostVisible; -// uv_texture_descriptor.format = PixelFormat::kR8G8UNormInt; -// uv_texture_descriptor.size = {4, 4}; -// auto uv_texture = -// context->GetResourceAllocator()->CreateTexture(uv_texture_descriptor); -// auto uv_mapping = std::make_shared(uv_data); -// auto uv_mapping_buffer = -// context->GetResourceAllocator()->CreateBufferWithCopy(*uv_mapping); - -// blit_pass->AddCopy(DeviceBuffer::AsBufferView(uv_mapping_buffer), -// uv_texture); - -// if (!blit_pass->EncodeCommands(context->GetResourceAllocator()) || -// !context->GetCommandQueue()->Submit({cmd_buffer}).ok()) { -// FML_DLOG(ERROR) << "Could not copy contents into Y/UV texture."; -// } - -// return {y_texture, uv_texture}; -// } - -// TEST_P(EntityTest, YUVToRGBFilter) { -// if (GetParam() == PlaygroundBackend::kOpenGLES) { -// // TODO(114588) : Support YUV to RGB filter on OpenGLES backend. -// GTEST_SKIP() -// << "YUV to RGB filter is not supported on OpenGLES backend yet."; -// } - -// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { -// YUVColorSpace yuv_color_space_array[2]{YUVColorSpace::kBT601FullRange, -// YUVColorSpace::kBT601LimitedRange}; -// for (int i = 0; i < 2; i++) { -// auto yuv_color_space = yuv_color_space_array[i]; -// auto textures = -// CreateTestYUVTextures(GetContext().get(), yuv_color_space); -// auto filter_contents = FilterContents::MakeYUVToRGBFilter( -// textures[0], textures[1], yuv_color_space); -// Entity filter_entity; -// filter_entity.SetContents(filter_contents); -// auto snapshot = filter_contents->RenderToSnapshot(context, -// filter_entity); - -// Entity entity; -// auto contents = TextureContents::MakeRect(Rect::MakeLTRB(0, 0, 256, -// 256)); contents->SetTexture(snapshot->texture); -// contents->SetSourceRect(Rect::MakeSize(snapshot->texture->GetSize())); -// entity.SetContents(contents); -// entity.SetTransform( -// Matrix::MakeTranslation({static_cast(100 + 400 * i), -// 300})); -// entity.Render(context, pass); -// } -// return true; -// }; -// ASSERT_TRUE(OpenPlaygroundHere(callback)); -// } - -// TEST_P(EntityTest, RuntimeEffect) { -// auto runtime_stages = -// OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); -// auto runtime_stage = -// runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; -// ASSERT_TRUE(runtime_stage); -// ASSERT_TRUE(runtime_stage->IsDirty()); - -// bool expect_dirty = true; -// Pipeline* first_pipeline; -// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { -// EXPECT_EQ(runtime_stage->IsDirty(), expect_dirty); - -// auto contents = std::make_shared(); -// contents->SetGeometry(Geometry::MakeCover()); -// contents->SetRuntimeStage(runtime_stage); - -// struct FragUniforms { -// Vector2 iResolution; -// Scalar iTime; -// } frag_uniforms = { -// .iResolution = Vector2(GetWindowSize().width, -// GetWindowSize().height), .iTime = -// static_cast(GetSecondsElapsed()), -// }; -// auto uniform_data = std::make_shared>(); -// uniform_data->resize(sizeof(FragUniforms)); -// memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms)); -// contents->SetUniformData(uniform_data); - -// Entity entity; -// entity.SetContents(contents); -// bool result = contents->Render(context, entity, pass); - -// if (expect_dirty) { -// EXPECT_NE(first_pipeline, pass.GetCommands().back().pipeline.get()); -// first_pipeline = pass.GetCommands().back().pipeline.get(); -// } else { -// EXPECT_EQ(pass.GetCommands().back().pipeline.get(), first_pipeline); -// } - -// expect_dirty = false; -// return result; -// }; - -// // Simulate some renders and hot reloading of the shader. -// auto content_context = GetContentContext(); -// { -// RenderTarget target = -// content_context->GetRenderTargetCache()->CreateOffscreen( -// *content_context->GetContext(), {1, 1}, 1u); - -// testing::MockRenderPass mock_pass(GetContext(), target); -// callback(*content_context, mock_pass); -// callback(*content_context, mock_pass); - -// // Dirty the runtime stage. -// runtime_stages = -// OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); runtime_stage -// = -// runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; - -// ASSERT_TRUE(runtime_stage->IsDirty()); -// expect_dirty = true; - -// callback(*content_context, mock_pass); -// } -// } - -// TEST_P(EntityTest, RuntimeEffectCanSuccessfullyRender) { -// auto runtime_stages = -// OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); -// auto runtime_stage = -// runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; -// ASSERT_TRUE(runtime_stage); -// ASSERT_TRUE(runtime_stage->IsDirty()); - -// auto contents = std::make_shared(); -// contents->SetGeometry(Geometry::MakeCover()); - -// contents->SetRuntimeStage(runtime_stage); - -// struct FragUniforms { -// Vector2 iResolution; -// Scalar iTime; -// } frag_uniforms = { -// .iResolution = Vector2(GetWindowSize().width, GetWindowSize().height), -// .iTime = static_cast(GetSecondsElapsed()), -// }; -// auto uniform_data = std::make_shared>(); -// uniform_data->resize(sizeof(FragUniforms)); -// memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms)); -// contents->SetUniformData(uniform_data); - -// Entity entity; -// entity.SetContents(contents); - -// // Create a render target with a depth-stencil, similar to how EntityPass -// // does. -// RenderTarget target = -// GetContentContext()->GetRenderTargetCache()->CreateOffscreenMSAA( -// *GetContext(), {GetWindowSize().width, GetWindowSize().height}, 1, -// "RuntimeEffect Texture"); -// testing::MockRenderPass pass(GetContext(), target); - -// ASSERT_TRUE(contents->Render(*GetContentContext(), entity, pass)); -// ASSERT_EQ(pass.GetCommands().size(), 1u); -// const auto& command = pass.GetCommands()[0]; -// ASSERT_TRUE(command.pipeline->GetDescriptor() -// .GetDepthStencilAttachmentDescriptor() -// .has_value()); -// ASSERT_TRUE(command.pipeline->GetDescriptor() -// .GetFrontStencilAttachmentDescriptor() -// .has_value()); -// } - -// TEST_P(EntityTest, RuntimeEffectCanPrecache) { -// auto runtime_stages = -// OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); -// auto runtime_stage = -// runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; -// ASSERT_TRUE(runtime_stage); -// ASSERT_TRUE(runtime_stage->IsDirty()); - -// auto contents = std::make_shared(); -// contents->SetRuntimeStage(runtime_stage); - -// EXPECT_TRUE(contents->BootstrapShader(*GetContentContext())); -// } - -// TEST_P(EntityTest, RuntimeEffectSetsRightSizeWhenUniformIsStruct) { -// if (GetBackend() != PlaygroundBackend::kVulkan) { -// GTEST_SKIP() << "Test only applies to Vulkan"; -// } - -// auto runtime_stages = -// OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); -// auto runtime_stage = -// runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; -// ASSERT_TRUE(runtime_stage); -// ASSERT_TRUE(runtime_stage->IsDirty()); - -// auto contents = std::make_shared(); -// contents->SetGeometry(Geometry::MakeCover()); -// contents->SetRuntimeStage(runtime_stage); - -// struct FragUniforms { -// Vector2 iResolution; -// Scalar iTime; -// } frag_uniforms = { -// .iResolution = Vector2(GetWindowSize().width, GetWindowSize().height), -// .iTime = static_cast(GetSecondsElapsed()), -// }; -// auto uniform_data = std::make_shared>(); -// uniform_data->resize(sizeof(FragUniforms)); -// memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms)); -// contents->SetUniformData(uniform_data); - -// Entity entity; -// entity.SetContents(contents); - -// auto context = GetContentContext(); -// RenderTarget target = context->GetRenderTargetCache()->CreateOffscreen( -// *context->GetContext(), {1, 1}, 1u); - -// testing::MockRenderPass pass(GetContext(), target); -// ASSERT_TRUE(contents->Render(*context, entity, pass)); -// ASSERT_EQ(pass.GetCommands().size(), 1u); -// const auto& command = pass.GetCommands()[0]; -// ASSERT_EQ(command.fragment_bindings.buffers.size(), 1u); -// // 16 bytes: -// // 8 bytes for iResolution -// // 4 bytes for iTime -// // 4 bytes padding -// EXPECT_EQ(command.fragment_bindings.buffers[0].view.resource.range.length, -// 16u); -// } - -// TEST_P(EntityTest, InheritOpacityTest) { -// Entity entity; - -// // Texture contents can always accept opacity. -// auto texture_contents = std::make_shared(); -// texture_contents->SetOpacity(0.5); - -// texture_contents->SetInheritedOpacity(0.5); -// ASSERT_EQ(texture_contents->GetOpacity(), 0.25); -// texture_contents->SetInheritedOpacity(0.5); -// ASSERT_EQ(texture_contents->GetOpacity(), 0.25); - -// // Solid color contents can accept opacity if their geometry -// // doesn't overlap. -// auto solid_color = std::make_shared(); -// solid_color->SetGeometry( -// Geometry::MakeRect(Rect::MakeLTRB(100, 100, 200, 200))); -// solid_color->SetColor(Color::Blue().WithAlpha(0.5)); - -// solid_color->SetInheritedOpacity(0.5); -// ASSERT_EQ(solid_color->GetColor().alpha, 0.25); -// solid_color->SetInheritedOpacity(0.5); -// ASSERT_EQ(solid_color->GetColor().alpha, 0.25); - -// // Color source contents can accept opacity if their geometry -// // doesn't overlap. -// auto tiled_texture = std::make_shared(); -// tiled_texture->SetGeometry( -// Geometry::MakeRect(Rect::MakeLTRB(100, 100, 200, 200))); -// tiled_texture->SetOpacityFactor(0.5); - -// tiled_texture->SetInheritedOpacity(0.5); -// ASSERT_EQ(tiled_texture->GetOpacityFactor(), 0.25); -// tiled_texture->SetInheritedOpacity(0.5); -// ASSERT_EQ(tiled_texture->GetOpacityFactor(), 0.25); -// } - -// TEST_P(EntityTest, ColorFilterWithForegroundColorAdvancedBlend) { -// auto image = CreateTextureForFixture("boston.jpg"); -// auto filter = ColorFilterContents::MakeBlend( -// BlendMode::kColorBurn, FilterInput::Make({image}), Color::Red()); - -// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { -// Entity entity; -// entity.SetTransform(Matrix::MakeScale(GetContentScale()) * -// Matrix::MakeTranslation({500, 300}) * -// Matrix::MakeScale(Vector2{0.5, 0.5})); -// entity.SetContents(filter); -// return entity.Render(context, pass); -// }; -// ASSERT_TRUE(OpenPlaygroundHere(callback)); -// } - -// TEST_P(EntityTest, ColorFilterWithForegroundColorClearBlend) { -// auto image = CreateTextureForFixture("boston.jpg"); -// auto filter = ColorFilterContents::MakeBlend( -// BlendMode::kClear, FilterInput::Make({image}), Color::Red()); - -// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { -// Entity entity; -// entity.SetTransform(Matrix::MakeScale(GetContentScale()) * -// Matrix::MakeTranslation({500, 300}) * -// Matrix::MakeScale(Vector2{0.5, 0.5})); -// entity.SetContents(filter); -// return entity.Render(context, pass); -// }; -// ASSERT_TRUE(OpenPlaygroundHere(callback)); -// } - -// TEST_P(EntityTest, ColorFilterWithForegroundColorSrcBlend) { -// auto image = CreateTextureForFixture("boston.jpg"); -// auto filter = ColorFilterContents::MakeBlend( -// BlendMode::kSource, FilterInput::Make({image}), Color::Red()); - -// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { -// Entity entity; -// entity.SetTransform(Matrix::MakeScale(GetContentScale()) * -// Matrix::MakeTranslation({500, 300}) * -// Matrix::MakeScale(Vector2{0.5, 0.5})); -// entity.SetContents(filter); -// return entity.Render(context, pass); -// }; -// ASSERT_TRUE(OpenPlaygroundHere(callback)); -// } - -// TEST_P(EntityTest, ColorFilterWithForegroundColorDstBlend) { -// auto image = CreateTextureForFixture("boston.jpg"); -// auto filter = ColorFilterContents::MakeBlend( -// BlendMode::kDestination, FilterInput::Make({image}), Color::Red()); - -// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { -// Entity entity; -// entity.SetTransform(Matrix::MakeScale(GetContentScale()) * -// Matrix::MakeTranslation({500, 300}) * -// Matrix::MakeScale(Vector2{0.5, 0.5})); -// entity.SetContents(filter); -// return entity.Render(context, pass); -// }; -// ASSERT_TRUE(OpenPlaygroundHere(callback)); -// } - -// TEST_P(EntityTest, ColorFilterWithForegroundColorSrcInBlend) { -// auto image = CreateTextureForFixture("boston.jpg"); -// auto filter = ColorFilterContents::MakeBlend( -// BlendMode::kSourceIn, FilterInput::Make({image}), Color::Red()); - -// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { -// Entity entity; -// entity.SetTransform(Matrix::MakeScale(GetContentScale()) * -// Matrix::MakeTranslation({500, 300}) * -// Matrix::MakeScale(Vector2{0.5, 0.5})); -// entity.SetContents(filter); -// return entity.Render(context, pass); -// }; -// ASSERT_TRUE(OpenPlaygroundHere(callback)); -// } - -// TEST_P(EntityTest, CoverageForStrokePathWithNegativeValuesInTransform) { -// auto arrow_head = PathBuilder{} -// .MoveTo({50, 120}) -// .LineTo({120, 190}) -// .LineTo({190, 120}) -// .TakePath(); -// auto geometry = Geometry::MakeStrokePath(arrow_head, 15.0, 4.0, -// Cap::kRound, -// Join::kRound); - -// auto transform = Matrix::MakeTranslation({300, 300}) * -// Matrix::MakeRotationZ(Radians(kPiOver2)); -// // Note that e[0][0] used to be tested here, but it was -epsilon solely -// // due to floating point inaccuracy in the transcendental trig functions. -// // e[1][0] is the intended negative value that we care about (-1.0) as it -// // comes from the rotation of pi/2. -// EXPECT_LT(transform.e[1][0], 0.0f); -// auto coverage = geometry->GetCoverage(transform); -// ASSERT_RECT_NEAR(coverage.value(), Rect::MakeXYWH(102.5, 342.5, 85, 155)); -// } - -// TEST_P(EntityTest, SolidColorContentsIsOpaque) { -// Matrix matrix; -// SolidColorContents contents; -// contents.SetGeometry(Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10))); - -// contents.SetColor(Color::CornflowerBlue()); -// EXPECT_TRUE(contents.IsOpaque(matrix)); -// contents.SetColor(Color::CornflowerBlue().WithAlpha(0.5)); -// EXPECT_FALSE(contents.IsOpaque(matrix)); - -// // Create stroked path that required alpha coverage. -// contents.SetGeometry(Geometry::MakeStrokePath( -// PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), -// /*stroke_width=*/0.05)); -// contents.SetColor(Color::CornflowerBlue()); - -// EXPECT_FALSE(contents.IsOpaque(matrix)); -// } - -// TEST_P(EntityTest, ConicalGradientContentsIsOpaque) { -// Matrix matrix; -// ConicalGradientContents contents; -// contents.SetGeometry(Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10))); - -// contents.SetColors({Color::CornflowerBlue()}); -// EXPECT_FALSE(contents.IsOpaque(matrix)); -// contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)}); -// EXPECT_FALSE(contents.IsOpaque(matrix)); - -// // Create stroked path that required alpha coverage. -// contents.SetGeometry(Geometry::MakeStrokePath( -// PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), -// /*stroke_width=*/0.05)); -// contents.SetColors({Color::CornflowerBlue()}); - -// EXPECT_FALSE(contents.IsOpaque(matrix)); -// } - -// TEST_P(EntityTest, LinearGradientContentsIsOpaque) { -// Matrix matrix; -// LinearGradientContents contents; -// contents.SetGeometry(Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10))); - -// contents.SetColors({Color::CornflowerBlue()}); -// EXPECT_TRUE(contents.IsOpaque(matrix)); -// contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)}); -// EXPECT_FALSE(contents.IsOpaque(matrix)); -// contents.SetColors({Color::CornflowerBlue()}); -// contents.SetTileMode(Entity::TileMode::kDecal); -// EXPECT_FALSE(contents.IsOpaque(matrix)); - -// // Create stroked path that required alpha coverage. -// contents.SetGeometry(Geometry::MakeStrokePath( -// PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), -// /*stroke_width=*/0.05)); -// contents.SetColors({Color::CornflowerBlue()}); - -// EXPECT_FALSE(contents.IsOpaque(matrix)); -// } - -// TEST_P(EntityTest, RadialGradientContentsIsOpaque) { -// Matrix matrix; -// RadialGradientContents contents; -// contents.SetGeometry(Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10))); - -// contents.SetColors({Color::CornflowerBlue()}); -// EXPECT_TRUE(contents.IsOpaque(matrix)); -// contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)}); -// EXPECT_FALSE(contents.IsOpaque(matrix)); -// contents.SetColors({Color::CornflowerBlue()}); -// contents.SetTileMode(Entity::TileMode::kDecal); -// EXPECT_FALSE(contents.IsOpaque(matrix)); - -// // Create stroked path that required alpha coverage. -// contents.SetGeometry(Geometry::MakeStrokePath( -// PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), -// /*stroke_width=*/0.05)); -// contents.SetColors({Color::CornflowerBlue()}); - -// EXPECT_FALSE(contents.IsOpaque(matrix)); -// } - -// TEST_P(EntityTest, SweepGradientContentsIsOpaque) { -// Matrix matrix; -// RadialGradientContents contents; -// contents.SetGeometry(Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10))); - -// contents.SetColors({Color::CornflowerBlue()}); -// EXPECT_TRUE(contents.IsOpaque(matrix)); -// contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)}); -// EXPECT_FALSE(contents.IsOpaque(matrix)); -// contents.SetColors({Color::CornflowerBlue()}); -// contents.SetTileMode(Entity::TileMode::kDecal); -// EXPECT_FALSE(contents.IsOpaque(matrix)); - -// // Create stroked path that required alpha coverage. -// contents.SetGeometry(Geometry::MakeStrokePath( -// PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), -// /*stroke_width=*/0.05)); -// contents.SetColors({Color::CornflowerBlue()}); - -// EXPECT_FALSE(contents.IsOpaque(matrix)); -// } - -// TEST_P(EntityTest, TiledTextureContentsIsOpaque) { -// Matrix matrix; -// auto bay_bridge = CreateTextureForFixture("bay_bridge.jpg"); -// TiledTextureContents contents; -// contents.SetTexture(bay_bridge); -// // This is a placeholder test. Images currently never decompress as opaque -// // (whether in Flutter or the playground), and so this should currently -// always -// // return false in practice. -// EXPECT_FALSE(contents.IsOpaque(matrix)); -// } - -// TEST_P(EntityTest, PointFieldGeometryCoverage) { -// std::vector points = {{10, 20}, {100, 200}}; -// auto geometry = Geometry::MakePointField(points, 5.0, false); -// ASSERT_EQ(*geometry->GetCoverage(Matrix()), Rect::MakeLTRB(5, 15, 105, -// 205)); ASSERT_EQ(*geometry->GetCoverage(Matrix::MakeTranslation({30, 0, -// 0})), -// Rect::MakeLTRB(35, 15, 135, 205)); -// } - -// TEST_P(EntityTest, ColorFilterContentsWithLargeGeometry) { -// Entity entity; -// entity.SetTransform(Matrix::MakeScale(GetContentScale())); -// auto src_contents = std::make_shared(); -// src_contents->SetGeometry( -// Geometry::MakeRect(Rect::MakeLTRB(-300, -500, 30000, 50000))); -// src_contents->SetColor(Color::Red()); - -// auto dst_contents = std::make_shared(); -// dst_contents->SetGeometry( -// Geometry::MakeRect(Rect::MakeLTRB(300, 500, 20000, 30000))); -// dst_contents->SetColor(Color::Blue()); - -// auto contents = ColorFilterContents::MakeBlend( -// BlendMode::kSourceOver, {FilterInput::Make(dst_contents, false), -// FilterInput::Make(src_contents, false)}); -// entity.SetContents(std::move(contents)); -// ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); -// } - -// TEST_P(EntityTest, TextContentsCeilsGlyphScaleToDecimal) { -// ASSERT_EQ(TextFrame::RoundScaledFontSize(0.4321111f, 12), 0.43f); -// ASSERT_EQ(TextFrame::RoundScaledFontSize(0.5321111f, 12), 0.53f); -// ASSERT_EQ(TextFrame::RoundScaledFontSize(2.1f, 12), 2.1f); -// ASSERT_EQ(TextFrame::RoundScaledFontSize(0.0f, 12), 0.0f); -// ASSERT_EQ(TextFrame::RoundScaledFontSize(100000000.0f, 12), 48.0f); -// } - -// TEST_P(EntityTest, SpecializationConstantsAreAppliedToVariants) { -// auto content_context = GetContentContext(); - -// auto default_gyph = content_context->GetGlyphAtlasPipeline({ -// .color_attachment_pixel_format = PixelFormat::kR8G8B8A8UNormInt, -// .has_depth_stencil_attachments = false, -// }); -// auto alt_gyph = content_context->GetGlyphAtlasPipeline( -// {.color_attachment_pixel_format = PixelFormat::kR8G8B8A8UNormInt, -// .has_depth_stencil_attachments = true}); - -// EXPECT_NE(default_gyph, alt_gyph); -// EXPECT_EQ(default_gyph->GetDescriptor().GetSpecializationConstants(), -// alt_gyph->GetDescriptor().GetSpecializationConstants()); - -// auto use_a8 = GetContext()->GetCapabilities()->GetDefaultGlyphAtlasFormat() -// == -// PixelFormat::kA8UNormInt; - -// std::vector expected_constants = {static_cast(use_a8)}; -// EXPECT_EQ(default_gyph->GetDescriptor().GetSpecializationConstants(), -// expected_constants); -// } - -// TEST_P(EntityTest, DecalSpecializationAppliedToMorphologyFilter) { -// auto content_context = GetContentContext(); -// auto default_color_burn = content_context->GetMorphologyFilterPipeline({ -// .color_attachment_pixel_format = PixelFormat::kR8G8B8A8UNormInt, -// }); - -// auto decal_supported = static_cast( -// GetContext()->GetCapabilities()->SupportsDecalSamplerAddressMode()); -// std::vector expected_constants = {decal_supported}; -// ASSERT_EQ(default_color_burn->GetDescriptor().GetSpecializationConstants(), -// expected_constants); -// } - -// // This doesn't really tell you if the hashes will have frequent -// // collisions, but since this type is only used to hash a bounded -// // set of options, we can just compare benchmarks. -// TEST_P(EntityTest, ContentContextOptionsHasReasonableHashFunctions) { -// ContentContextOptions opts; -// auto hash_a = ContentContextOptions::Hash{}(opts); - -// opts.blend_mode = BlendMode::kColorBurn; -// auto hash_b = ContentContextOptions::Hash{}(opts); - -// opts.has_depth_stencil_attachments = false; -// auto hash_c = ContentContextOptions::Hash{}(opts); - -// opts.primitive_type = PrimitiveType::kPoint; -// auto hash_d = ContentContextOptions::Hash{}(opts); - -// EXPECT_NE(hash_a, hash_b); -// EXPECT_NE(hash_b, hash_c); -// EXPECT_NE(hash_c, hash_d); -// } - -// #ifdef FML_OS_LINUX -// TEST_P(EntityTest, FramebufferFetchVulkanBindingOffsetIsTheSame) { -// // Using framebuffer fetch on Vulkan requires that we maintain a subpass -// input -// // binding that we don't have a good route for configuring with the current -// // metadata approach. This test verifies that the binding value doesn't -// change -// // from the expected constant. -// // See also: -// // * impeller/renderer/backend/vulkan/binding_helpers_vk.cc -// // * impeller/entity/shaders/blending/framebuffer_blend.frag -// // This test only works on Linux because macOS hosts incorrectly populate -// the -// // Vulkan descriptor sets based on the MSL compiler settings. - -// bool expected_layout = false; -// for (const DescriptorSetLayout& layout : -// FramebufferBlendColorBurnPipeline:: -// FragmentShader::kDescriptorSetLayouts) { -// if (layout.binding == 64 && -// layout.descriptor_type == DescriptorType::kInputAttachment) { -// expected_layout = true; -// } -// } -// EXPECT_TRUE(expected_layout); -// } -// #endif - -// TEST_P(EntityTest, FillPathGeometryGetPositionBufferReturnsExpectedMode) { -// RenderTarget target; -// testing::MockRenderPass mock_pass(GetContext(), target); - -// auto get_result = [this, &mock_pass](const Path& path) { -// auto geometry = Geometry::MakeFillPath( -// path, /* inner rect */ Rect::MakeLTRB(0, 0, 100, 100)); -// return geometry->GetPositionBuffer(*GetContentContext(), {}, mock_pass); -// }; - -// // Convex path -// { -// GeometryResult result = -// get_result(PathBuilder{} -// .AddRect(Rect::MakeLTRB(0, 0, 100, 100)) -// .SetConvexity(Convexity::kConvex) -// .TakePath()); -// EXPECT_EQ(result.mode, GeometryResult::Mode::kNormal); -// } - -// // Concave path -// { -// Path path = PathBuilder{} -// .MoveTo({0, 0}) -// .LineTo({100, 0}) -// .LineTo({100, 100}) -// .LineTo({50, 50}) -// .Close() -// .TakePath(); -// GeometryResult result = get_result(path); -// EXPECT_EQ(result.mode, GeometryResult::Mode::kNonZero); -// } -// } - -// TEST_P(EntityTest, FailOnValidationError) { -// if (GetParam() != PlaygroundBackend::kVulkan) { -// GTEST_SKIP() << "Validation is only fatal on Vulkan backend."; -// } -// EXPECT_DEATH( -// // The easiest way to trigger a validation error is to try to compile -// // a shader with an unsupported pixel format. -// GetContentContext()->GetBlendColorBurnPipeline({ -// .color_attachment_pixel_format = PixelFormat::kUnknown, -// .has_depth_stencil_attachments = false, -// }), -// ""); -// } - -// TEST_P(EntityTest, CanComputeGeometryForEmptyPathsWithoutCrashing) { -// PathBuilder builder = {}; -// builder.AddRect(Rect::MakeLTRB(0, 0, 0, 0)); -// Path path = builder.TakePath(); - -// EXPECT_TRUE(path.GetBoundingBox()->IsEmpty()); - -// auto geom = Geometry::MakeFillPath(path); - -// Entity entity; -// RenderTarget target = -// GetContentContext()->GetRenderTargetCache()->CreateOffscreen( -// *GetContext(), {1, 1}, 1u); -// testing::MockRenderPass render_pass(GetContext(), target); -// auto position_result = -// geom->GetPositionBuffer(*GetContentContext(), entity, render_pass); - -// EXPECT_EQ(position_result.vertex_buffer.vertex_count, 0u); - -// EXPECT_EQ(geom->GetResultMode(), GeometryResult::Mode::kNormal); -// } - -// TEST_P(EntityTest, CanRenderEmptyPathsWithoutCrashing) { -// PathBuilder builder = {}; -// builder.AddRect(Rect::MakeLTRB(0, 0, 0, 0)); -// Path path = builder.TakePath(); - -// EXPECT_TRUE(path.GetBoundingBox()->IsEmpty()); - -// auto contents = std::make_shared(); -// contents->SetGeometry(Geometry::MakeFillPath(path)); -// contents->SetColor(Color::Red()); - -// Entity entity; -// entity.SetTransform(Matrix::MakeScale(GetContentScale())); -// entity.SetContents(contents); - -// ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); -// } - -// TEST_P(EntityTest, DrawSuperEllipse) { -// auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { -// // UI state. -// static float alpha = 10; -// static float beta = 10; -// static float radius = 40; -// static int degree = 4; -// static Color color = Color::Red(); - -// ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); -// ImGui::SliderFloat("Alpha", &alpha, 0, 100); -// ImGui::SliderFloat("Beta", &beta, 0, 100); -// ImGui::SliderInt("Degreee", °ree, 1, 20); -// ImGui::SliderFloat("Radius", &radius, 0, 400); -// ImGui::ColorEdit4("Color", reinterpret_cast(&color)); -// ImGui::End(); - -// auto contents = std::make_shared(); -// contents->SetColor(color); -// contents->SetGeometry(std::make_shared( -// Point{400, 400}, radius, degree, alpha, beta)); - -// Entity entity; -// entity.SetContents(contents); - -// return entity.Render(context, pass); -// }; - -// ASSERT_TRUE(OpenPlaygroundHere(callback)); -// } - -// TEST_P(EntityTest, SolidColorApplyColorFilter) { -// auto contents = SolidColorContents(); -// contents.SetColor(Color::CornflowerBlue().WithAlpha(0.75)); -// auto result = contents.ApplyColorFilter([](const Color& color) { -// return color.Blend(Color::LimeGreen().WithAlpha(0.75), -// BlendMode::kScreen); -// }); -// ASSERT_TRUE(result); -// ASSERT_COLOR_NEAR(contents.GetColor(), -// Color(0.424452, 0.828743, 0.79105, 0.9375)); -// } - -// #define APPLY_COLOR_FILTER_GRADIENT_TEST(name) \ -// TEST_P(EntityTest, name##GradientApplyColorFilter) { \ -// auto contents = name##GradientContents(); \ -// contents.SetColors({Color::CornflowerBlue().WithAlpha(0.75)}); \ -// auto result = contents.ApplyColorFilter([](const Color& color) { \ -// return color.Blend(Color::LimeGreen().WithAlpha(0.75), \ -// BlendMode::kScreen); \ -// }); \ -// ASSERT_TRUE(result); \ -// \ -// std::vector expected = {Color(0.433247, 0.879523, 0.825324, 0.75)}; \ -// ASSERT_COLORS_NEAR(contents.GetColors(), expected); \ -// } - -// APPLY_COLOR_FILTER_GRADIENT_TEST(Linear); -// APPLY_COLOR_FILTER_GRADIENT_TEST(Radial); -// APPLY_COLOR_FILTER_GRADIENT_TEST(Conical); -// APPLY_COLOR_FILTER_GRADIENT_TEST(Sweep); +using EntityTest = EntityPlayground; +INSTANTIATE_PLAYGROUND_SUITE(EntityTest); + +TEST_P(EntityTest, CanCreateEntity) { + Entity entity; + ASSERT_TRUE(entity.GetTransform().IsIdentity()); +} + +TEST_P(EntityTest, FilterCoverageRespectsCropRect) { + auto image = CreateTextureForFixture("boston.jpg"); + auto filter = ColorFilterContents::MakeBlend(BlendMode::kSoftLight, + FilterInput::Make({image})); + + // Without the crop rect (default behavior). + { + auto actual = filter->GetCoverage({}); + auto expected = Rect::MakeSize(image->GetSize()); + + ASSERT_TRUE(actual.has_value()); + ASSERT_RECT_NEAR(actual.value(), expected); + } + + // With the crop rect. + { + auto expected = Rect::MakeLTRB(50, 50, 100, 100); + filter->SetCoverageHint(expected); + auto actual = filter->GetCoverage({}); + + ASSERT_TRUE(actual.has_value()); + ASSERT_RECT_NEAR(actual.value(), expected); + } +} + +TEST_P(EntityTest, GeometryBoundsAreTransformed) { + auto geometry = Geometry::MakeRect(Rect::MakeXYWH(100, 100, 100, 100)); + auto transform = Matrix::MakeScale({2.0, 2.0, 2.0}); + + ASSERT_RECT_NEAR(geometry->GetCoverage(transform).value(), + Rect::MakeXYWH(200, 200, 200, 200)); +} + +TEST_P(EntityTest, ThreeStrokesInOnePath) { + Path path = PathBuilder{} + .MoveTo({100, 100}) + .LineTo({100, 200}) + .MoveTo({100, 300}) + .LineTo({100, 400}) + .MoveTo({100, 500}) + .LineTo({100, 600}) + .TakePath(); + + Entity entity; + entity.SetTransform(Matrix::MakeScale(GetContentScale())); + auto contents = std::make_unique(); + + static std::unique_ptr geom = Geometry::MakeStrokePath(path, 5.0); + contents->SetGeometry(geom.get()); + contents->SetColor(Color::Red()); + entity.SetContents(std::move(contents)); + ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); +} + +TEST_P(EntityTest, StrokeWithTextureContents) { + auto bridge = CreateTextureForFixture("bay_bridge.jpg"); + Path path = PathBuilder{} + .MoveTo({100, 100}) + .LineTo({100, 200}) + .MoveTo({100, 300}) + .LineTo({100, 400}) + .MoveTo({100, 500}) + .LineTo({100, 600}) + .TakePath(); + + Entity entity; + entity.SetTransform(Matrix::MakeScale(GetContentScale())); + auto contents = std::make_unique(); + static std::unique_ptr geom = Geometry::MakeStrokePath(path, 100.0); + contents->SetGeometry(geom.get()); + contents->SetTexture(bridge); + contents->SetTileModes(Entity::TileMode::kClamp, Entity::TileMode::kClamp); + entity.SetContents(std::move(contents)); + ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); +} + +TEST_P(EntityTest, TriangleInsideASquare) { + auto callback = [&](ContentContext& context, RenderPass& pass) { + Point offset(100, 100); + + static PlaygroundPoint point_a(Point(10, 10) + offset, 20, Color::White()); + Point a = DrawPlaygroundPoint(point_a); + static PlaygroundPoint point_b(Point(210, 10) + offset, 20, Color::White()); + Point b = DrawPlaygroundPoint(point_b); + static PlaygroundPoint point_c(Point(210, 210) + offset, 20, + Color::White()); + Point c = DrawPlaygroundPoint(point_c); + static PlaygroundPoint point_d(Point(10, 210) + offset, 20, Color::White()); + Point d = DrawPlaygroundPoint(point_d); + static PlaygroundPoint point_e(Point(50, 50) + offset, 20, Color::White()); + Point e = DrawPlaygroundPoint(point_e); + static PlaygroundPoint point_f(Point(100, 50) + offset, 20, Color::White()); + Point f = DrawPlaygroundPoint(point_f); + static PlaygroundPoint point_g(Point(50, 150) + offset, 20, Color::White()); + Point g = DrawPlaygroundPoint(point_g); + Path path = PathBuilder{} + .MoveTo(a) + .LineTo(b) + .LineTo(c) + .LineTo(d) + .Close() + .MoveTo(e) + .LineTo(f) + .LineTo(g) + .Close() + .TakePath(); + + Entity entity; + entity.SetTransform(Matrix::MakeScale(GetContentScale())); + auto contents = std::make_unique(); + static std::unique_ptr geom = + Geometry::MakeStrokePath(path, 20.0); + contents->SetGeometry(geom.get()); + contents->SetColor(Color::Red()); + entity.SetContents(std::move(contents)); + + return entity.Render(context, pass); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, StrokeCapAndJoinTest) { + const Point padding(300, 250); + const Point margin(140, 180); + + auto callback = [&](ContentContext& context, RenderPass& pass) { + // Slightly above sqrt(2) by default, so that right angles are just below + // the limit and acute angles are over the limit (causing them to get + // beveled). + static Scalar miter_limit = 1.41421357; + static Scalar width = 30; + + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + { + ImGui::SliderFloat("Miter limit", &miter_limit, 0, 30); + ImGui::SliderFloat("Stroke width", &width, 0, 100); + if (ImGui::Button("Reset")) { + miter_limit = 1.41421357; + width = 30; + } + } + ImGui::End(); + + auto world_matrix = Matrix::MakeScale(GetContentScale()); + auto render_path = [width = width, &context, &pass, &world_matrix]( + const Path& path, Cap cap, Join join) { + auto contents = std::make_unique(); + static std::unique_ptr geom = + Geometry::MakeStrokePath(path, width, miter_limit, cap, join); + contents->SetGeometry(geom.get()); + contents->SetColor(Color::Red()); + + Entity entity; + entity.SetTransform(world_matrix); + entity.SetContents(std::move(contents)); + + auto coverage = entity.GetCoverage(); + if (coverage.has_value()) { + auto bounds_contents = std::make_unique(); + + static std::unique_ptr geom = Geometry::MakeFillPath( + PathBuilder{}.AddRect(entity.GetCoverage().value()).TakePath()); + + bounds_contents->SetGeometry(geom.get()); + bounds_contents->SetColor(Color::Green().WithAlpha(0.5)); + Entity bounds_entity; + bounds_entity.SetContents(std::move(bounds_contents)); + bounds_entity.Render(context, pass); + } + + entity.Render(context, pass); + }; + + const Point a_def(0, 0), b_def(0, 100), c_def(150, 0), d_def(150, -100), + e_def(75, 75); + const Scalar r = 30; + // Cap::kButt demo. + { + Point off = Point(0, 0) * padding + margin; + static PlaygroundPoint point_a(off + a_def, r, Color::Black()); + static PlaygroundPoint point_b(off + b_def, r, Color::White()); + auto [a, b] = DrawPlaygroundLine(point_a, point_b); + static PlaygroundPoint point_c(off + c_def, r, Color::Black()); + static PlaygroundPoint point_d(off + d_def, r, Color::White()); + auto [c, d] = DrawPlaygroundLine(point_c, point_d); + render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(), + Cap::kButt, Join::kBevel); + } + + // Cap::kSquare demo. + { + Point off = Point(1, 0) * padding + margin; + static PlaygroundPoint point_a(off + a_def, r, Color::Black()); + static PlaygroundPoint point_b(off + b_def, r, Color::White()); + auto [a, b] = DrawPlaygroundLine(point_a, point_b); + static PlaygroundPoint point_c(off + c_def, r, Color::Black()); + static PlaygroundPoint point_d(off + d_def, r, Color::White()); + auto [c, d] = DrawPlaygroundLine(point_c, point_d); + render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(), + Cap::kSquare, Join::kBevel); + } + + // Cap::kRound demo. + { + Point off = Point(2, 0) * padding + margin; + static PlaygroundPoint point_a(off + a_def, r, Color::Black()); + static PlaygroundPoint point_b(off + b_def, r, Color::White()); + auto [a, b] = DrawPlaygroundLine(point_a, point_b); + static PlaygroundPoint point_c(off + c_def, r, Color::Black()); + static PlaygroundPoint point_d(off + d_def, r, Color::White()); + auto [c, d] = DrawPlaygroundLine(point_c, point_d); + render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(), + Cap::kRound, Join::kBevel); + } + + // Join::kBevel demo. + { + Point off = Point(0, 1) * padding + margin; + static PlaygroundPoint point_a = + PlaygroundPoint(off + a_def, r, Color::White()); + static PlaygroundPoint point_b = + PlaygroundPoint(off + e_def, r, Color::White()); + static PlaygroundPoint point_c = + PlaygroundPoint(off + c_def, r, Color::White()); + Point a = DrawPlaygroundPoint(point_a); + Point b = DrawPlaygroundPoint(point_b); + Point c = DrawPlaygroundPoint(point_c); + render_path( + PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(), + Cap::kButt, Join::kBevel); + } + + // Join::kMiter demo. + { + Point off = Point(1, 1) * padding + margin; + static PlaygroundPoint point_a(off + a_def, r, Color::White()); + static PlaygroundPoint point_b(off + e_def, r, Color::White()); + static PlaygroundPoint point_c(off + c_def, r, Color::White()); + Point a = DrawPlaygroundPoint(point_a); + Point b = DrawPlaygroundPoint(point_b); + Point c = DrawPlaygroundPoint(point_c); + render_path( + PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(), + Cap::kButt, Join::kMiter); + } + + // Join::kRound demo. + { + Point off = Point(2, 1) * padding + margin; + static PlaygroundPoint point_a(off + a_def, r, Color::White()); + static PlaygroundPoint point_b(off + e_def, r, Color::White()); + static PlaygroundPoint point_c(off + c_def, r, Color::White()); + Point a = DrawPlaygroundPoint(point_a); + Point b = DrawPlaygroundPoint(point_b); + Point c = DrawPlaygroundPoint(point_c); + render_path( + PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(), + Cap::kButt, Join::kRound); + } + + return true; + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, CubicCurveTest) { + // Compare with https://fiddle.skia.org/c/b3625f26122c9de7afe7794fcf25ead3 + Path path = + PathBuilder{} + .MoveTo({237.164, 125.003}) + .CubicCurveTo({236.709, 125.184}, {236.262, 125.358}, + {235.81, 125.538}) + .CubicCurveTo({235.413, 125.68}, {234.994, 125.832}, + {234.592, 125.977}) + .CubicCurveTo({234.592, 125.977}, {234.591, 125.977}, + {234.59, 125.977}) + .CubicCurveTo({222.206, 130.435}, {207.708, 135.753}, + {192.381, 141.429}) + .CubicCurveTo({162.77, 151.336}, {122.17, 156.894}, {84.1123, 160}) + .Close() + .TakePath(); + Entity entity; + entity.SetTransform(Matrix::MakeScale(GetContentScale())); + + static std::unique_ptr geom = Geometry::MakeFillPath(path); + + auto contents = std::make_shared(); + contents->SetColor(Color::Red()); + contents->SetGeometry(geom.get()); + + entity.SetContents(contents); + ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); +} + +TEST_P(EntityTest, CanDrawCorrectlyWithRotatedTransform) { + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + const char* input_axis[] = {"X", "Y", "Z"}; + static int rotation_axis_index = 0; + static float rotation = 0; + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + ImGui::SliderFloat("Rotation", &rotation, -kPi, kPi); + ImGui::Combo("Rotation Axis", &rotation_axis_index, input_axis, + sizeof(input_axis) / sizeof(char*)); + Matrix rotation_matrix; + switch (rotation_axis_index) { + case 0: + rotation_matrix = Matrix::MakeRotationX(Radians(rotation)); + break; + case 1: + rotation_matrix = Matrix::MakeRotationY(Radians(rotation)); + break; + case 2: + rotation_matrix = Matrix::MakeRotationZ(Radians(rotation)); + break; + default: + rotation_matrix = Matrix{}; + break; + } + + if (ImGui::Button("Reset")) { + rotation = 0; + } + ImGui::End(); + Matrix current_transform = + Matrix::MakeScale(GetContentScale()) + .MakeTranslation( + Vector3(Point(pass.GetRenderTargetSize().width / 2.0, + pass.GetRenderTargetSize().height / 2.0))); + Matrix result_transform = current_transform * rotation_matrix; + Path path = + PathBuilder{}.AddRect(Rect::MakeXYWH(-300, -400, 600, 800)).TakePath(); + + Entity entity; + entity.SetTransform(result_transform); + + static std::unique_ptr geom = Geometry::MakeFillPath(path); + + auto contents = std::make_shared(); + contents->SetColor(Color::Red()); + contents->SetGeometry(geom.get()); + + entity.SetContents(contents); + return entity.Render(context, pass); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, CubicCurveAndOverlapTest) { + // Compare with https://fiddle.skia.org/c/7a05a3e186c65a8dfb732f68020aae06 + Path path = + PathBuilder{} + .MoveTo({359.934, 96.6335}) + .CubicCurveTo({358.189, 96.7055}, {356.436, 96.7908}, + {354.673, 96.8895}) + .CubicCurveTo({354.571, 96.8953}, {354.469, 96.9016}, + {354.367, 96.9075}) + .CubicCurveTo({352.672, 97.0038}, {350.969, 97.113}, + {349.259, 97.2355}) + .CubicCurveTo({349.048, 97.2506}, {348.836, 97.2678}, + {348.625, 97.2834}) + .CubicCurveTo({347.019, 97.4014}, {345.407, 97.5299}, + {343.789, 97.6722}) + .CubicCurveTo({343.428, 97.704}, {343.065, 97.7402}, + {342.703, 97.7734}) + .CubicCurveTo({341.221, 97.9086}, {339.736, 98.0505}, + {338.246, 98.207}) + .CubicCurveTo({337.702, 98.2642}, {337.156, 98.3292}, + {336.612, 98.3894}) + .CubicCurveTo({335.284, 98.5356}, {333.956, 98.6837}, + {332.623, 98.8476}) + .CubicCurveTo({332.495, 98.8635}, {332.366, 98.8818}, + {332.237, 98.8982}) + .LineTo({332.237, 102.601}) + .LineTo({321.778, 102.601}) + .LineTo({321.778, 100.382}) + .CubicCurveTo({321.572, 100.413}, {321.367, 100.442}, + {321.161, 100.476}) + .CubicCurveTo({319.22, 100.79}, {317.277, 101.123}, + {315.332, 101.479}) + .CubicCurveTo({315.322, 101.481}, {315.311, 101.482}, + {315.301, 101.484}) + .LineTo({310.017, 105.94}) + .LineTo({309.779, 105.427}) + .LineTo({314.403, 101.651}) + .CubicCurveTo({314.391, 101.653}, {314.379, 101.656}, + {314.368, 101.658}) + .CubicCurveTo({312.528, 102.001}, {310.687, 102.366}, + {308.846, 102.748}) + .CubicCurveTo({307.85, 102.955}, {306.855, 103.182}, {305.859, 103.4}) + .CubicCurveTo({305.048, 103.579}, {304.236, 103.75}, + {303.425, 103.936}) + .LineTo({299.105, 107.578}) + .LineTo({298.867, 107.065}) + .LineTo({302.394, 104.185}) + .LineTo({302.412, 104.171}) + .CubicCurveTo({301.388, 104.409}, {300.366, 104.67}, + {299.344, 104.921}) + .CubicCurveTo({298.618, 105.1}, {297.89, 105.269}, {297.165, 105.455}) + .CubicCurveTo({295.262, 105.94}, {293.36, 106.445}, + {291.462, 106.979}) + .CubicCurveTo({291.132, 107.072}, {290.802, 107.163}, + {290.471, 107.257}) + .CubicCurveTo({289.463, 107.544}, {288.455, 107.839}, + {287.449, 108.139}) + .CubicCurveTo({286.476, 108.431}, {285.506, 108.73}, + {284.536, 109.035}) + .CubicCurveTo({283.674, 109.304}, {282.812, 109.579}, + {281.952, 109.859}) + .CubicCurveTo({281.177, 110.112}, {280.406, 110.377}, + {279.633, 110.638}) + .CubicCurveTo({278.458, 111.037}, {277.256, 111.449}, + {276.803, 111.607}) + .CubicCurveTo({276.76, 111.622}, {276.716, 111.637}, + {276.672, 111.653}) + .CubicCurveTo({275.017, 112.239}, {273.365, 112.836}, + {271.721, 113.463}) + .LineTo({271.717, 113.449}) + .CubicCurveTo({271.496, 113.496}, {271.238, 113.559}, + {270.963, 113.628}) + .CubicCurveTo({270.893, 113.645}, {270.822, 113.663}, + {270.748, 113.682}) + .CubicCurveTo({270.468, 113.755}, {270.169, 113.834}, + {269.839, 113.926}) + .CubicCurveTo({269.789, 113.94}, {269.732, 113.957}, + {269.681, 113.972}) + .CubicCurveTo({269.391, 114.053}, {269.081, 114.143}, + {268.756, 114.239}) + .CubicCurveTo({268.628, 114.276}, {268.5, 114.314}, + {268.367, 114.354}) + .CubicCurveTo({268.172, 114.412}, {267.959, 114.478}, + {267.752, 114.54}) + .CubicCurveTo({263.349, 115.964}, {258.058, 117.695}, + {253.564, 119.252}) + .CubicCurveTo({253.556, 119.255}, {253.547, 119.258}, + {253.538, 119.261}) + .CubicCurveTo({251.844, 119.849}, {250.056, 120.474}, + {248.189, 121.131}) + .CubicCurveTo({248, 121.197}, {247.812, 121.264}, {247.621, 121.331}) + .CubicCurveTo({247.079, 121.522}, {246.531, 121.715}, + {245.975, 121.912}) + .CubicCurveTo({245.554, 122.06}, {245.126, 122.212}, + {244.698, 122.364}) + .CubicCurveTo({244.071, 122.586}, {243.437, 122.811}, + {242.794, 123.04}) + .CubicCurveTo({242.189, 123.255}, {241.58, 123.472}, + {240.961, 123.693}) + .CubicCurveTo({240.659, 123.801}, {240.357, 123.909}, + {240.052, 124.018}) + .CubicCurveTo({239.12, 124.351}, {238.18, 124.687}, {237.22, 125.032}) + .LineTo({237.164, 125.003}) + .CubicCurveTo({236.709, 125.184}, {236.262, 125.358}, + {235.81, 125.538}) + .CubicCurveTo({235.413, 125.68}, {234.994, 125.832}, + {234.592, 125.977}) + .CubicCurveTo({234.592, 125.977}, {234.591, 125.977}, + {234.59, 125.977}) + .CubicCurveTo({222.206, 130.435}, {207.708, 135.753}, + {192.381, 141.429}) + .CubicCurveTo({162.77, 151.336}, {122.17, 156.894}, {84.1123, 160}) + .LineTo({360, 160}) + .LineTo({360, 119.256}) + .LineTo({360, 106.332}) + .LineTo({360, 96.6307}) + .CubicCurveTo({359.978, 96.6317}, {359.956, 96.6326}, + {359.934, 96.6335}) + .Close() + .MoveTo({337.336, 124.143}) + .CubicCurveTo({337.274, 122.359}, {338.903, 121.511}, + {338.903, 121.511}) + .CubicCurveTo({338.903, 121.511}, {338.96, 123.303}, + {337.336, 124.143}) + .Close() + .MoveTo({340.082, 121.849}) + .CubicCurveTo({340.074, 121.917}, {340.062, 121.992}, + {340.046, 122.075}) + .CubicCurveTo({340.039, 122.109}, {340.031, 122.142}, + {340.023, 122.177}) + .CubicCurveTo({340.005, 122.26}, {339.98, 122.346}, + {339.952, 122.437}) + .CubicCurveTo({339.941, 122.473}, {339.931, 122.507}, + {339.918, 122.544}) + .CubicCurveTo({339.873, 122.672}, {339.819, 122.804}, + {339.75, 122.938}) + .CubicCurveTo({339.747, 122.944}, {339.743, 122.949}, + {339.74, 122.955}) + .CubicCurveTo({339.674, 123.08}, {339.593, 123.205}, + {339.501, 123.328}) + .CubicCurveTo({339.473, 123.366}, {339.441, 123.401}, + {339.41, 123.438}) + .CubicCurveTo({339.332, 123.534}, {339.243, 123.625}, + {339.145, 123.714}) + .CubicCurveTo({339.105, 123.75}, {339.068, 123.786}, + {339.025, 123.821}) + .CubicCurveTo({338.881, 123.937}, {338.724, 124.048}, + {338.539, 124.143}) + .CubicCurveTo({338.532, 123.959}, {338.554, 123.79}, + {338.58, 123.626}) + .CubicCurveTo({338.58, 123.625}, {338.58, 123.625}, {338.58, 123.625}) + .CubicCurveTo({338.607, 123.455}, {338.65, 123.299}, + {338.704, 123.151}) + .CubicCurveTo({338.708, 123.14}, {338.71, 123.127}, + {338.714, 123.117}) + .CubicCurveTo({338.769, 122.971}, {338.833, 122.838}, + {338.905, 122.712}) + .CubicCurveTo({338.911, 122.702}, {338.916, 122.69200000000001}, + {338.922, 122.682}) + .CubicCurveTo({338.996, 122.557}, {339.072, 122.444}, + {339.155, 122.34}) + .CubicCurveTo({339.161, 122.333}, {339.166, 122.326}, + {339.172, 122.319}) + .CubicCurveTo({339.256, 122.215}, {339.339, 122.12}, + {339.425, 122.037}) + .CubicCurveTo({339.428, 122.033}, {339.431, 122.03}, + {339.435, 122.027}) + .CubicCurveTo({339.785, 121.687}, {340.106, 121.511}, + {340.106, 121.511}) + .CubicCurveTo({340.106, 121.511}, {340.107, 121.645}, + {340.082, 121.849}) + .Close() + .MoveTo({340.678, 113.245}) + .CubicCurveTo({340.594, 113.488}, {340.356, 113.655}, + {340.135, 113.775}) + .CubicCurveTo({339.817, 113.948}, {339.465, 114.059}, + {339.115, 114.151}) + .CubicCurveTo({338.251, 114.379}, {337.34, 114.516}, + {336.448, 114.516}) + .CubicCurveTo({335.761, 114.516}, {335.072, 114.527}, + {334.384, 114.513}) + .CubicCurveTo({334.125, 114.508}, {333.862, 114.462}, + {333.605, 114.424}) + .CubicCurveTo({332.865, 114.318}, {332.096, 114.184}, + {331.41, 113.883}) + .CubicCurveTo({330.979, 113.695}, {330.442, 113.34}, + {330.672, 112.813}) + .CubicCurveTo({331.135, 111.755}, {333.219, 112.946}, + {334.526, 113.833}) + .CubicCurveTo({334.54, 113.816}, {334.554, 113.8}, {334.569, 113.784}) + .CubicCurveTo({333.38, 112.708}, {331.749, 110.985}, + {332.76, 110.402}) + .CubicCurveTo({333.769, 109.82}, {334.713, 111.93}, + {335.228, 113.395}) + .CubicCurveTo({334.915, 111.889}, {334.59, 109.636}, + {335.661, 109.592}) + .CubicCurveTo({336.733, 109.636}, {336.408, 111.889}, + {336.07, 113.389}) + .CubicCurveTo({336.609, 111.93}, {337.553, 109.82}, + {338.563, 110.402}) + .CubicCurveTo({339.574, 110.984}, {337.942, 112.708}, + {336.753, 113.784}) + .CubicCurveTo({336.768, 113.8}, {336.782, 113.816}, + {336.796, 113.833}) + .CubicCurveTo({338.104, 112.946}, {340.187, 111.755}, + {340.65, 112.813}) + .CubicCurveTo({340.71, 112.95}, {340.728, 113.102}, + {340.678, 113.245}) + .Close() + .MoveTo({346.357, 106.771}) + .CubicCurveTo({346.295, 104.987}, {347.924, 104.139}, + {347.924, 104.139}) + .CubicCurveTo({347.924, 104.139}, {347.982, 105.931}, + {346.357, 106.771}) + .Close() + .MoveTo({347.56, 106.771}) + .CubicCurveTo({347.498, 104.987}, {349.127, 104.139}, + {349.127, 104.139}) + .CubicCurveTo({349.127, 104.139}, {349.185, 105.931}, + {347.56, 106.771}) + .Close() + .TakePath(); + Entity entity; + entity.SetTransform(Matrix::MakeScale(GetContentScale())); + + static std::unique_ptr geom = Geometry::MakeFillPath(path); + + auto contents = std::make_shared(); + contents->SetColor(Color::Red()); + contents->SetGeometry(geom.get()); + + entity.SetContents(contents); + ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); +} + +TEST_P(EntityTest, SolidColorContentsStrokeSetStrokeCapsAndJoins) { + { + auto geometry = Geometry::MakeStrokePath(Path{}); + auto path_geometry = static_cast(geometry.get()); + // Defaults. + ASSERT_EQ(path_geometry->GetStrokeCap(), Cap::kButt); + ASSERT_EQ(path_geometry->GetStrokeJoin(), Join::kMiter); + } + + { + auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, 4.0, Cap::kSquare); + auto path_geometry = static_cast(geometry.get()); + ASSERT_EQ(path_geometry->GetStrokeCap(), Cap::kSquare); + } + + { + auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, 4.0, Cap::kRound); + auto path_geometry = static_cast(geometry.get()); + ASSERT_EQ(path_geometry->GetStrokeCap(), Cap::kRound); + } +} + +TEST_P(EntityTest, SolidColorContentsStrokeSetMiterLimit) { + { + auto geometry = Geometry::MakeStrokePath(Path{}); + auto path_geometry = static_cast(geometry.get()); + ASSERT_FLOAT_EQ(path_geometry->GetMiterLimit(), 4); + } + + { + auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, + /*miter_limit=*/8.0); + auto path_geometry = static_cast(geometry.get()); + ASSERT_FLOAT_EQ(path_geometry->GetMiterLimit(), 8); + } + + { + auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, + /*miter_limit=*/-1.0); + auto path_geometry = static_cast(geometry.get()); + ASSERT_FLOAT_EQ(path_geometry->GetMiterLimit(), 4); + } +} + +TEST_P(EntityTest, BlendingModeOptions) { + std::vector blend_mode_names; + std::vector blend_mode_values; + { + // Force an exhausiveness check with a switch. When adding blend modes, + // update this switch with a new name/value to make it selectable in the + // test GUI. + + const BlendMode b{}; + static_assert(b == BlendMode::kClear); // Ensure the first item in + // the switch is the first + // item in the enum. + static_assert(Entity::kLastPipelineBlendMode == BlendMode::kModulate); + switch (b) { + case BlendMode::kClear: + blend_mode_names.push_back("Clear"); + blend_mode_values.push_back(BlendMode::kClear); + case BlendMode::kSource: + blend_mode_names.push_back("Source"); + blend_mode_values.push_back(BlendMode::kSource); + case BlendMode::kDestination: + blend_mode_names.push_back("Destination"); + blend_mode_values.push_back(BlendMode::kDestination); + case BlendMode::kSourceOver: + blend_mode_names.push_back("SourceOver"); + blend_mode_values.push_back(BlendMode::kSourceOver); + case BlendMode::kDestinationOver: + blend_mode_names.push_back("DestinationOver"); + blend_mode_values.push_back(BlendMode::kDestinationOver); + case BlendMode::kSourceIn: + blend_mode_names.push_back("SourceIn"); + blend_mode_values.push_back(BlendMode::kSourceIn); + case BlendMode::kDestinationIn: + blend_mode_names.push_back("DestinationIn"); + blend_mode_values.push_back(BlendMode::kDestinationIn); + case BlendMode::kSourceOut: + blend_mode_names.push_back("SourceOut"); + blend_mode_values.push_back(BlendMode::kSourceOut); + case BlendMode::kDestinationOut: + blend_mode_names.push_back("DestinationOut"); + blend_mode_values.push_back(BlendMode::kDestinationOut); + case BlendMode::kSourceATop: + blend_mode_names.push_back("SourceATop"); + blend_mode_values.push_back(BlendMode::kSourceATop); + case BlendMode::kDestinationATop: + blend_mode_names.push_back("DestinationATop"); + blend_mode_values.push_back(BlendMode::kDestinationATop); + case BlendMode::kXor: + blend_mode_names.push_back("Xor"); + blend_mode_values.push_back(BlendMode::kXor); + case BlendMode::kPlus: + blend_mode_names.push_back("Plus"); + blend_mode_values.push_back(BlendMode::kPlus); + case BlendMode::kModulate: + blend_mode_names.push_back("Modulate"); + blend_mode_values.push_back(BlendMode::kModulate); + }; + } + + auto callback = [&](ContentContext& context, RenderPass& pass) { + auto world_matrix = Matrix::MakeScale(GetContentScale()); + auto draw_rect = [&context, &pass, &world_matrix]( + Rect rect, Color color, BlendMode blend_mode) -> bool { + using VS = SolidFillPipeline::VertexShader; + using FS = SolidFillPipeline::FragmentShader; + + VertexBufferBuilder vtx_builder; + { + auto r = rect.GetLTRB(); + vtx_builder.AddVertices({ + {Point(r[0], r[1])}, + {Point(r[2], r[1])}, + {Point(r[2], r[3])}, + {Point(r[0], r[1])}, + {Point(r[2], r[3])}, + {Point(r[0], r[3])}, + }); + } + + pass.SetCommandLabel("Blended Rectangle"); + auto options = OptionsFromPass(pass); + options.blend_mode = blend_mode; + options.primitive_type = PrimitiveType::kTriangle; + pass.SetPipeline(context.GetSolidFillPipeline(options)); + pass.SetVertexBuffer( + vtx_builder.CreateVertexBuffer(context.GetTransientsBuffer())); + + VS::FrameInfo frame_info; + frame_info.mvp = pass.GetOrthographicTransform() * world_matrix; + VS::BindFrameInfo( + pass, context.GetTransientsBuffer().EmplaceUniform(frame_info)); + FS::FragInfo frag_info; + frag_info.color = color.Premultiply(); + FS::BindFragInfo( + pass, context.GetTransientsBuffer().EmplaceUniform(frame_info)); + return pass.Draw().ok(); + }; + + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + static Color color1(1, 0, 0, 0.5), color2(0, 1, 0, 0.5); + ImGui::ColorEdit4("Color 1", reinterpret_cast(&color1)); + ImGui::ColorEdit4("Color 2", reinterpret_cast(&color2)); + static int current_blend_index = 3; + ImGui::ListBox("Blending mode", ¤t_blend_index, + blend_mode_names.data(), blend_mode_names.size()); + ImGui::End(); + + BlendMode selected_mode = blend_mode_values[current_blend_index]; + + Point a, b, c, d; + static PlaygroundPoint point_a(Point(400, 100), 20, Color::White()); + static PlaygroundPoint point_b(Point(200, 300), 20, Color::White()); + std::tie(a, b) = DrawPlaygroundLine(point_a, point_b); + static PlaygroundPoint point_c(Point(470, 190), 20, Color::White()); + static PlaygroundPoint point_d(Point(270, 390), 20, Color::White()); + std::tie(c, d) = DrawPlaygroundLine(point_c, point_d); + + bool result = true; + result = result && + draw_rect(Rect::MakeXYWH(0, 0, pass.GetRenderTargetSize().width, + pass.GetRenderTargetSize().height), + Color(), BlendMode::kClear); + result = result && draw_rect(Rect::MakeLTRB(a.x, a.y, b.x, b.y), color1, + BlendMode::kSourceOver); + result = result && draw_rect(Rect::MakeLTRB(c.x, c.y, d.x, d.y), color2, + selected_mode); + return result; + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, BezierCircleScaled) { + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + static float scale = 20; + + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + ImGui::SliderFloat("Scale", &scale, 1, 100); + ImGui::End(); + + Entity entity; + entity.SetTransform(Matrix::MakeScale(GetContentScale())); + auto path = PathBuilder{} + .MoveTo({97.325, 34.818}) + .CubicCurveTo({98.50862885295136, 34.81812293973836}, + {99.46822048142015, 33.85863261475589}, + {99.46822048142015, 32.67499810206613}) + .CubicCurveTo({99.46822048142015, 31.491363589376355}, + {98.50862885295136, 30.53187326439389}, + {97.32499434685802, 30.531998226542708}) + .CubicCurveTo({96.14153655073771, 30.532123170035373}, + {95.18222070648729, 31.491540299350355}, + {95.18222070648729, 32.67499810206613}) + .CubicCurveTo({95.18222070648729, 33.85845590478189}, + {96.14153655073771, 34.81787303409686}, + {97.32499434685802, 34.81799797758954}) + .Close() + .TakePath(); + entity.SetTransform( + Matrix::MakeScale({scale, scale, 1.0}).Translate({-90, -20, 0})); + + static std::unique_ptr geom = Geometry::MakeFillPath(path); + + auto contents = std::make_shared(); + contents->SetColor(Color::Red()); + contents->SetGeometry(geom.get()); + + entity.SetContents(contents); + return entity.Render(context, pass); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, Filters) { + auto bridge = CreateTextureForFixture("bay_bridge.jpg"); + auto boston = CreateTextureForFixture("boston.jpg"); + auto kalimba = CreateTextureForFixture("kalimba.jpg"); + ASSERT_TRUE(bridge && boston && kalimba); + + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + auto fi_bridge = FilterInput::Make(bridge); + auto fi_boston = FilterInput::Make(boston); + auto fi_kalimba = FilterInput::Make(kalimba); + + std::shared_ptr blend0 = ColorFilterContents::MakeBlend( + BlendMode::kModulate, {fi_kalimba, fi_boston}); + + auto blend1 = ColorFilterContents::MakeBlend( + BlendMode::kScreen, + {FilterInput::Make(blend0), fi_bridge, fi_bridge, fi_bridge}); + + Entity entity; + entity.SetTransform(Matrix::MakeScale(GetContentScale()) * + Matrix::MakeTranslation({500, 300}) * + Matrix::MakeScale(Vector2{0.5, 0.5})); + entity.SetContents(blend1); + return entity.Render(context, pass); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, GaussianBlurFilter) { + auto boston = + CreateTextureForFixture("boston.jpg", /*enable_mipmapping=*/true); + ASSERT_TRUE(boston); + + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + const char* input_type_names[] = {"Texture", "Solid Color"}; + const char* blur_type_names[] = {"Image blur", "Mask blur"}; + const char* pass_variation_names[] = {"New"}; + const char* blur_style_names[] = {"Normal", "Solid", "Outer", "Inner"}; + const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"}; + const FilterContents::BlurStyle blur_styles[] = { + FilterContents::BlurStyle::kNormal, FilterContents::BlurStyle::kSolid, + FilterContents::BlurStyle::kOuter, FilterContents::BlurStyle::kInner}; + const Entity::TileMode tile_modes[] = { + Entity::TileMode::kClamp, Entity::TileMode::kRepeat, + Entity::TileMode::kMirror, Entity::TileMode::kDecal}; + + // UI state. + static int selected_input_type = 0; + static Color input_color = Color::Black(); + static int selected_blur_type = 0; + static int selected_pass_variation = 0; + static bool combined_sigma = false; + static float blur_amount_coarse[2] = {0, 0}; + static float blur_amount_fine[2] = {10, 10}; + static int selected_blur_style = 0; + static int selected_tile_mode = 3; + static Color cover_color(1, 0, 0, 0.2); + static Color bounds_color(0, 1, 0, 0.1); + static float offset[2] = {500, 400}; + static float rotation = 0; + static float scale[2] = {0.65, 0.65}; + static float skew[2] = {0, 0}; + static float path_rect[4] = {0, 0, + static_cast(boston->GetSize().width), + static_cast(boston->GetSize().height)}; + + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + { + ImGui::Combo("Input type", &selected_input_type, input_type_names, + sizeof(input_type_names) / sizeof(char*)); + if (selected_input_type == 0) { + ImGui::SliderFloat("Input opacity", &input_color.alpha, 0, 1); + } else { + ImGui::ColorEdit4("Input color", + reinterpret_cast(&input_color)); + } + ImGui::Combo("Blur type", &selected_blur_type, blur_type_names, + sizeof(blur_type_names) / sizeof(char*)); + if (selected_blur_type == 0) { + ImGui::Combo("Pass variation", &selected_pass_variation, + pass_variation_names, + sizeof(pass_variation_names) / sizeof(char*)); + } + ImGui::Checkbox("Combined sigma", &combined_sigma); + if (combined_sigma) { + ImGui::SliderFloat("Sigma (coarse)", blur_amount_coarse, 0, 1000); + ImGui::SliderFloat("Sigma (fine)", blur_amount_fine, 0, 10); + blur_amount_coarse[1] = blur_amount_coarse[0]; + blur_amount_fine[1] = blur_amount_fine[0]; + } else { + ImGui::SliderFloat2("Sigma (coarse)", blur_amount_coarse, 0, 1000); + ImGui::SliderFloat2("Sigma (fine)", blur_amount_fine, 0, 10); + } + ImGui::Combo("Blur style", &selected_blur_style, blur_style_names, + sizeof(blur_style_names) / sizeof(char*)); + ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names, + sizeof(tile_mode_names) / sizeof(char*)); + ImGui::ColorEdit4("Cover color", reinterpret_cast(&cover_color)); + ImGui::ColorEdit4("Bounds color ", + reinterpret_cast(&bounds_color)); + ImGui::SliderFloat2("Translation", offset, 0, + pass.GetRenderTargetSize().width); + ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2); + ImGui::SliderFloat2("Scale", scale, 0, 3); + ImGui::SliderFloat2("Skew", skew, -3, 3); + ImGui::SliderFloat4("Path XYWH", path_rect, -1000, 1000); + } + ImGui::End(); + + auto blur_sigma_x = Sigma{blur_amount_coarse[0] + blur_amount_fine[0]}; + auto blur_sigma_y = Sigma{blur_amount_coarse[1] + blur_amount_fine[1]}; + + std::shared_ptr input; + Size input_size; + + auto input_rect = + Rect::MakeXYWH(path_rect[0], path_rect[1], path_rect[2], path_rect[3]); + if (selected_input_type == 0) { + auto texture = std::make_shared(); + texture->SetSourceRect(Rect::MakeSize(boston->GetSize())); + texture->SetDestinationRect(input_rect); + texture->SetTexture(boston); + texture->SetOpacity(input_color.alpha); + + input = texture; + input_size = input_rect.GetSize(); + } else { + auto fill = std::make_shared(); + fill->SetColor(input_color); + static std::unique_ptr geom = + Geometry::MakeFillPath(PathBuilder{}.AddRect(input_rect).TakePath()); + + fill->SetGeometry(geom.get()); + + input = fill; + input_size = input_rect.GetSize(); + } + + std::shared_ptr blur; + switch (selected_pass_variation) { + case 0: + blur = std::make_shared( + blur_sigma_x.sigma, blur_sigma_y.sigma, + tile_modes[selected_tile_mode], blur_styles[selected_blur_style], + /*geometry=*/nullptr); + blur->SetInputs({FilterInput::Make(input)}); + break; + case 1: + blur = FilterContents::MakeGaussianBlur( + FilterInput::Make(input), blur_sigma_x, blur_sigma_y, + tile_modes[selected_tile_mode], blur_styles[selected_blur_style]); + break; + }; + FML_CHECK(blur); + + auto mask_blur = FilterContents::MakeBorderMaskBlur( + FilterInput::Make(input), blur_sigma_x, blur_sigma_y, + blur_styles[selected_blur_style]); + + auto ctm = Matrix::MakeScale(GetContentScale()) * + Matrix::MakeTranslation(Vector3(offset[0], offset[1])) * + Matrix::MakeRotationZ(Radians(rotation)) * + Matrix::MakeScale(Vector2(scale[0], scale[1])) * + Matrix::MakeSkew(skew[0], skew[1]) * + Matrix::MakeTranslation(-Point(input_size) / 2); + + auto target_contents = selected_blur_type == 0 ? blur : mask_blur; + + Entity entity; + entity.SetContents(target_contents); + entity.SetTransform(ctm); + + entity.Render(context, pass); + + // Renders a red "cover" rectangle that shows the original position of the + // unfiltered input. + Entity cover_entity; + static std::unique_ptr geom = + Geometry::MakeFillPath(PathBuilder{}.AddRect(input_rect).TakePath()); + auto contents = std::make_shared(); + contents->SetColor(cover_color); + contents->SetGeometry(geom.get()); + cover_entity.SetContents(std::move(contents)); + cover_entity.SetTransform(ctm); + cover_entity.Render(context, pass); + + // Renders a green bounding rect of the target filter. + Entity bounds_entity; + std::optional target_contents_coverage = + target_contents->GetCoverage(entity); + if (target_contents_coverage.has_value()) { + static std::unique_ptr geom = Geometry::MakeFillPath( + PathBuilder{} + .AddRect(target_contents->GetCoverage(entity).value()) + .TakePath()); + auto contents = std::make_shared(); + contents->SetColor(bounds_color); + contents->SetGeometry(geom.get()); + + bounds_entity.SetContents(contents); + bounds_entity.SetTransform(Matrix()); + bounds_entity.Render(context, pass); + } + + return true; + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, MorphologyFilter) { + auto boston = CreateTextureForFixture("boston.jpg"); + ASSERT_TRUE(boston); + + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + const char* morphology_type_names[] = {"Dilate", "Erode"}; + const FilterContents::MorphType morphology_types[] = { + FilterContents::MorphType::kDilate, FilterContents::MorphType::kErode}; + static Color input_color = Color::Black(); + // UI state. + static int selected_morphology_type = 0; + static float radius[2] = {20, 20}; + static Color cover_color(1, 0, 0, 0.2); + static Color bounds_color(0, 1, 0, 0.1); + static float offset[2] = {500, 400}; + static float rotation = 0; + static float scale[2] = {0.65, 0.65}; + static float skew[2] = {0, 0}; + static float path_rect[4] = {0, 0, + static_cast(boston->GetSize().width), + static_cast(boston->GetSize().height)}; + static float effect_transform_scale = 1; + + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + { + ImGui::Combo("Morphology type", &selected_morphology_type, + morphology_type_names, + sizeof(morphology_type_names) / sizeof(char*)); + ImGui::SliderFloat2("Radius", radius, 0, 200); + ImGui::SliderFloat("Input opacity", &input_color.alpha, 0, 1); + ImGui::ColorEdit4("Cover color", reinterpret_cast(&cover_color)); + ImGui::ColorEdit4("Bounds color ", + reinterpret_cast(&bounds_color)); + ImGui::SliderFloat2("Translation", offset, 0, + pass.GetRenderTargetSize().width); + ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2); + ImGui::SliderFloat2("Scale", scale, 0, 3); + ImGui::SliderFloat2("Skew", skew, -3, 3); + ImGui::SliderFloat4("Path XYWH", path_rect, -1000, 1000); + ImGui::SliderFloat("Effect transform scale", &effect_transform_scale, 0, + 3); + } + ImGui::End(); + + std::shared_ptr input; + Size input_size; + + auto input_rect = + Rect::MakeXYWH(path_rect[0], path_rect[1], path_rect[2], path_rect[3]); + auto texture = std::make_shared(); + texture->SetSourceRect(Rect::MakeSize(boston->GetSize())); + texture->SetDestinationRect(input_rect); + texture->SetTexture(boston); + texture->SetOpacity(input_color.alpha); + + input = texture; + input_size = input_rect.GetSize(); + + auto contents = FilterContents::MakeMorphology( + FilterInput::Make(input), Radius{radius[0]}, Radius{radius[1]}, + morphology_types[selected_morphology_type]); + contents->SetEffectTransform(Matrix::MakeScale( + Vector2{effect_transform_scale, effect_transform_scale})); + + auto ctm = Matrix::MakeScale(GetContentScale()) * + Matrix::MakeTranslation(Vector3(offset[0], offset[1])) * + Matrix::MakeRotationZ(Radians(rotation)) * + Matrix::MakeScale(Vector2(scale[0], scale[1])) * + Matrix::MakeSkew(skew[0], skew[1]) * + Matrix::MakeTranslation(-Point(input_size) / 2); + + Entity entity; + entity.SetContents(contents); + entity.SetTransform(ctm); + + entity.Render(context, pass); + + // Renders a red "cover" rectangle that shows the original position of the + // unfiltered input. + Entity cover_entity; + static std::unique_ptr geom = + Geometry::MakeFillPath(PathBuilder{}.AddRect(input_rect).TakePath()); + auto cover_contents = std::make_shared(); + cover_contents->SetColor(cover_color); + cover_contents->SetGeometry(geom.get()); + cover_entity.SetContents(cover_contents); + cover_entity.SetTransform(ctm); + cover_entity.Render(context, pass); + + // Renders a green bounding rect of the target filter. + Entity bounds_entity; + static std::unique_ptr bounds_geom = Geometry::MakeFillPath( + PathBuilder{} + .AddRect(contents->GetCoverage(entity).value()) + .TakePath()); + auto bounds_contents = std::make_shared(); + bounds_contents->SetColor(bounds_color); + bounds_contents->SetGeometry(bounds_geom.get()); + bounds_entity.SetContents(std::move(bounds_contents)); + bounds_entity.SetTransform(Matrix()); + + bounds_entity.Render(context, pass); + + return true; + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, SetBlendMode) { + Entity entity; + ASSERT_EQ(entity.GetBlendMode(), BlendMode::kSourceOver); + entity.SetBlendMode(BlendMode::kClear); + ASSERT_EQ(entity.GetBlendMode(), BlendMode::kClear); +} + +TEST_P(EntityTest, ContentsGetBoundsForEmptyPathReturnsNullopt) { + Entity entity; + entity.SetContents(std::make_shared()); + ASSERT_FALSE(entity.GetCoverage().has_value()); +} + +TEST_P(EntityTest, SolidStrokeCoverageIsCorrect) { + { + auto geometry = Geometry::MakeStrokePath( + PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath(), 4.0, 4.0, + Cap::kButt, Join::kBevel); + + Entity entity; + auto contents = std::make_unique(); + contents->SetGeometry(geometry.get()); + contents->SetColor(Color::Black()); + entity.SetContents(std::move(contents)); + auto actual = entity.GetCoverage(); + auto expected = Rect::MakeLTRB(-2, -2, 12, 12); + + ASSERT_TRUE(actual.has_value()); + ASSERT_RECT_NEAR(actual.value(), expected); + } + + // Cover the Cap::kSquare case. + { + auto geometry = Geometry::MakeStrokePath( + PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath(), 4.0, 4.0, + Cap::kSquare, Join::kBevel); + + Entity entity; + auto contents = std::make_unique(); + contents->SetGeometry(geometry.get()); + contents->SetColor(Color::Black()); + entity.SetContents(std::move(contents)); + auto actual = entity.GetCoverage(); + auto expected = + Rect::MakeLTRB(-sqrt(8), -sqrt(8), 10 + sqrt(8), 10 + sqrt(8)); + + ASSERT_TRUE(actual.has_value()); + ASSERT_RECT_NEAR(actual.value(), expected); + } + + // Cover the Join::kMiter case. + { + auto geometry = Geometry::MakeStrokePath( + PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath(), 4.0, 2.0, + Cap::kSquare, Join::kMiter); + + Entity entity; + auto contents = std::make_unique(); + contents->SetGeometry(geometry.get()); + contents->SetColor(Color::Black()); + entity.SetContents(std::move(contents)); + auto actual = entity.GetCoverage(); + auto expected = Rect::MakeLTRB(-4, -4, 14, 14); + + ASSERT_TRUE(actual.has_value()); + ASSERT_RECT_NEAR(actual.value(), expected); + } +} + +TEST_P(EntityTest, BorderMaskBlurCoverageIsCorrect) { + auto fill = std::make_shared(); + auto geom = Geometry::MakeFillPath( + PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath()); + fill->SetGeometry(geom.get()); + fill->SetColor(Color::CornflowerBlue()); + auto border_mask_blur = FilterContents::MakeBorderMaskBlur( + FilterInput::Make(fill), Radius{3}, Radius{4}); + + { + Entity e; + e.SetTransform(Matrix()); + auto actual = border_mask_blur->GetCoverage(e); + auto expected = Rect::MakeXYWH(-3, -4, 306, 408); + ASSERT_TRUE(actual.has_value()); + ASSERT_RECT_NEAR(actual.value(), expected); + } + + { + Entity e; + e.SetTransform(Matrix::MakeRotationZ(Radians{kPi / 4})); + auto actual = border_mask_blur->GetCoverage(e); + auto expected = Rect::MakeXYWH(-287.792, -4.94975, 504.874, 504.874); + ASSERT_TRUE(actual.has_value()); + ASSERT_RECT_NEAR(actual.value(), expected); + } +} + +TEST_P(EntityTest, SolidFillCoverageIsCorrect) { + // No transform + { + auto fill = std::make_shared(); + fill->SetColor(Color::CornflowerBlue()); + auto expected = Rect::MakeLTRB(100, 110, 200, 220); + auto geom = + Geometry::MakeFillPath(PathBuilder{}.AddRect(expected).TakePath()); + fill->SetGeometry(geom.get()); + + auto coverage = fill->GetCoverage({}); + ASSERT_TRUE(coverage.has_value()); + ASSERT_RECT_NEAR(coverage.value(), expected); + } + + // Entity transform + { + auto fill = std::make_shared(); + auto geom = Geometry::MakeFillPath( + PathBuilder{}.AddRect(Rect::MakeLTRB(100, 110, 200, 220)).TakePath()); + fill->SetColor(Color::CornflowerBlue()); + fill->SetGeometry(geom.get()); + + Entity entity; + entity.SetTransform(Matrix::MakeTranslation(Vector2(4, 5))); + entity.SetContents(std::move(fill)); + + auto coverage = entity.GetCoverage(); + auto expected = Rect::MakeLTRB(104, 115, 204, 225); + ASSERT_TRUE(coverage.has_value()); + ASSERT_RECT_NEAR(coverage.value(), expected); + } + + // No coverage for fully transparent colors + { + auto fill = std::make_shared(); + auto geom = Geometry::MakeFillPath( + PathBuilder{}.AddRect(Rect::MakeLTRB(100, 110, 200, 220)).TakePath()); + fill->SetColor(Color::WhiteTransparent()); + fill->SetGeometry(geom.get()); + + auto coverage = fill->GetCoverage({}); + ASSERT_FALSE(coverage.has_value()); + } +} + +TEST_P(EntityTest, ClipContentsGetClipCoverageIsCorrect) { + // Intersection: No stencil coverage, no geometry. + { + auto clip = std::make_shared(); + clip->SetClipOperation(Entity::ClipOperation::kIntersect); + auto result = clip->GetClipCoverage(Entity{}, Rect{}); + + ASSERT_FALSE(result.coverage.has_value()); + } + + // Intersection: No stencil coverage, with geometry. + { + auto clip = std::make_shared(); + auto geom = Geometry::MakeFillPath( + PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 100, 100)).TakePath()); + clip->SetClipOperation(Entity::ClipOperation::kIntersect); + clip->SetGeometry(geom.get()); + auto result = clip->GetClipCoverage(Entity{}, Rect{}); + + ASSERT_FALSE(result.coverage.has_value()); + } + + // Intersection: With stencil coverage, no geometry. + { + auto clip = std::make_shared(); + clip->SetClipOperation(Entity::ClipOperation::kIntersect); + auto result = + clip->GetClipCoverage(Entity{}, Rect::MakeLTRB(0, 0, 100, 100)); + + ASSERT_FALSE(result.coverage.has_value()); + } + + // Intersection: With stencil coverage, with geometry. + { + auto clip = std::make_shared(); + auto geom = Geometry::MakeFillPath( + PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 50, 50)).TakePath()); + clip->SetClipOperation(Entity::ClipOperation::kIntersect); + clip->SetGeometry(geom.get()); + auto result = + clip->GetClipCoverage(Entity{}, Rect::MakeLTRB(0, 0, 100, 100)); + + ASSERT_TRUE(result.coverage.has_value()); + ASSERT_RECT_NEAR(result.coverage.value(), Rect::MakeLTRB(0, 0, 50, 50)); + ASSERT_EQ(result.type, Contents::ClipCoverage::Type::kAppend); + } + + // Difference: With stencil coverage, with geometry. + { + auto clip = std::make_shared(); + auto geom = Geometry::MakeFillPath( + PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 50, 50)).TakePath()); + clip->SetClipOperation(Entity::ClipOperation::kDifference); + clip->SetGeometry(geom.get()); + auto result = + clip->GetClipCoverage(Entity{}, Rect::MakeLTRB(0, 0, 100, 100)); + + ASSERT_TRUE(result.coverage.has_value()); + ASSERT_RECT_NEAR(result.coverage.value(), Rect::MakeLTRB(0, 0, 100, 100)); + ASSERT_EQ(result.type, Contents::ClipCoverage::Type::kAppend); + } +} + +TEST_P(EntityTest, RRectShadowTest) { + auto callback = [&](ContentContext& context, RenderPass& pass) { + static Color color = Color::Red(); + static float corner_radius = 100; + static float blur_radius = 100; + static bool show_coverage = false; + static Color coverage_color = Color::Green().WithAlpha(0.2); + + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + ImGui::SliderFloat("Corner radius", &corner_radius, 0, 300); + ImGui::SliderFloat("Blur radius", &blur_radius, 0, 300); + ImGui::ColorEdit4("Color", reinterpret_cast(&color)); + ImGui::Checkbox("Show coverage", &show_coverage); + if (show_coverage) { + ImGui::ColorEdit4("Coverage color", + reinterpret_cast(&coverage_color)); + } + ImGui::End(); + + static PlaygroundPoint top_left_point(Point(200, 200), 30, Color::White()); + static PlaygroundPoint bottom_right_point(Point(600, 400), 30, + Color::White()); + auto [top_left, bottom_right] = + DrawPlaygroundLine(top_left_point, bottom_right_point); + auto rect = + Rect::MakeLTRB(top_left.x, top_left.y, bottom_right.x, bottom_right.y); + + auto contents = std::make_unique(); + contents->SetRRect(rect, {corner_radius, corner_radius}); + contents->SetColor(color); + contents->SetSigma(Radius(blur_radius)); + + Entity entity; + entity.SetTransform(Matrix::MakeScale(GetContentScale())); + entity.SetContents(std::move(contents)); + entity.Render(context, pass); + + auto coverage = entity.GetCoverage(); + if (show_coverage && coverage.has_value()) { + auto bounds_contents = std::make_unique(); + auto geom = Geometry::MakeFillPath( + PathBuilder{}.AddRect(entity.GetCoverage().value()).TakePath()); + bounds_contents->SetGeometry(geom.get()); + bounds_contents->SetColor(coverage_color.Premultiply()); + Entity bounds_entity; + bounds_entity.SetContents(std::move(bounds_contents)); + bounds_entity.Render(context, pass); + } + + return true; + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, ColorMatrixFilterCoverageIsCorrect) { + // Set up a simple color background. + auto fill = std::make_shared(); + auto geom = Geometry::MakeFillPath( + PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath()); + fill->SetGeometry(geom.get()); + fill->SetColor(Color::Coral()); + + // Set the color matrix filter. + ColorMatrix matrix = { + 1, 1, 1, 1, 1, // + 1, 1, 1, 1, 1, // + 1, 1, 1, 1, 1, // + 1, 1, 1, 1, 1, // + }; + + auto filter = + ColorFilterContents::MakeColorMatrix(FilterInput::Make(fill), matrix); + + Entity e; + e.SetTransform(Matrix()); + + // Confirm that the actual filter coverage matches the expected coverage. + auto actual = filter->GetCoverage(e); + auto expected = Rect::MakeXYWH(0, 0, 300, 400); + + ASSERT_TRUE(actual.has_value()); + ASSERT_RECT_NEAR(actual.value(), expected); +} + +TEST_P(EntityTest, ColorMatrixFilterEditable) { + auto bay_bridge = CreateTextureForFixture("bay_bridge.jpg"); + ASSERT_TRUE(bay_bridge); + + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + // UI state. + static ColorMatrix color_matrix = { + 1, 0, 0, 0, 0, // + 0, 3, 0, 0, 0, // + 0, 0, 1, 0, 0, // + 0, 0, 0, 1, 0, // + }; + static float offset[2] = {500, 400}; + static float rotation = 0; + static float scale[2] = {0.65, 0.65}; + static float skew[2] = {0, 0}; + + // Define the ImGui + ImGui::Begin("Color Matrix", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + { + std::string label = "##1"; + for (int i = 0; i < 20; i += 5) { + ImGui::InputScalarN(label.c_str(), ImGuiDataType_Float, + &(color_matrix.array[i]), 5, nullptr, nullptr, + "%.2f", 0); + label[2]++; + } + + ImGui::SliderFloat2("Translation", &offset[0], 0, + pass.GetRenderTargetSize().width); + ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2); + ImGui::SliderFloat2("Scale", &scale[0], 0, 3); + ImGui::SliderFloat2("Skew", &skew[0], -3, 3); + } + ImGui::End(); + + // Set the color matrix filter. + auto filter = ColorFilterContents::MakeColorMatrix( + FilterInput::Make(bay_bridge), color_matrix); + + // Define the entity with the color matrix filter. + Entity entity; + entity.SetTransform( + Matrix::MakeScale(GetContentScale()) * + Matrix::MakeTranslation(Vector3(offset[0], offset[1])) * + Matrix::MakeRotationZ(Radians(rotation)) * + Matrix::MakeScale(Vector2(scale[0], scale[1])) * + Matrix::MakeSkew(skew[0], skew[1]) * + Matrix::MakeTranslation(-Point(bay_bridge->GetSize()) / 2)); + entity.SetContents(filter); + entity.Render(context, pass); + + return true; + }; + + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, LinearToSrgbFilterCoverageIsCorrect) { + // Set up a simple color background. + auto geom = Geometry::MakeFillPath( + PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath()); + auto fill = std::make_shared(); + fill->SetGeometry(geom.get()); + fill->SetColor(Color::MintCream()); + + auto filter = + ColorFilterContents::MakeLinearToSrgbFilter(FilterInput::Make(fill)); + + Entity e; + e.SetTransform(Matrix()); + + // Confirm that the actual filter coverage matches the expected coverage. + auto actual = filter->GetCoverage(e); + auto expected = Rect::MakeXYWH(0, 0, 300, 400); + + ASSERT_TRUE(actual.has_value()); + ASSERT_RECT_NEAR(actual.value(), expected); +} + +TEST_P(EntityTest, LinearToSrgbFilter) { + auto image = CreateTextureForFixture("kalimba.jpg"); + ASSERT_TRUE(image); + + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + auto filtered = + ColorFilterContents::MakeLinearToSrgbFilter(FilterInput::Make(image)); + + // Define the entity that will serve as the control image as a Gaussian blur + // filter with no filter at all. + Entity entity_left; + entity_left.SetTransform(Matrix::MakeScale(GetContentScale()) * + Matrix::MakeTranslation({100, 300}) * + Matrix::MakeScale(Vector2{0.5, 0.5})); + auto unfiltered = FilterContents::MakeGaussianBlur(FilterInput::Make(image), + Sigma{0}, Sigma{0}); + entity_left.SetContents(unfiltered); + + // Define the entity that will be filtered from linear to sRGB. + Entity entity_right; + entity_right.SetTransform(Matrix::MakeScale(GetContentScale()) * + Matrix::MakeTranslation({500, 300}) * + Matrix::MakeScale(Vector2{0.5, 0.5})); + entity_right.SetContents(filtered); + return entity_left.Render(context, pass) && + entity_right.Render(context, pass); + }; + + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, SrgbToLinearFilterCoverageIsCorrect) { + // Set up a simple color background. + auto fill = std::make_shared(); + auto geom = Geometry::MakeFillPath( + PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath()); + fill->SetGeometry(geom.get()); + fill->SetColor(Color::DeepPink()); + + auto filter = + ColorFilterContents::MakeSrgbToLinearFilter(FilterInput::Make(fill)); + + Entity e; + e.SetTransform(Matrix()); + + // Confirm that the actual filter coverage matches the expected coverage. + auto actual = filter->GetCoverage(e); + auto expected = Rect::MakeXYWH(0, 0, 300, 400); + + ASSERT_TRUE(actual.has_value()); + ASSERT_RECT_NEAR(actual.value(), expected); +} + +TEST_P(EntityTest, SrgbToLinearFilter) { + auto image = CreateTextureForFixture("embarcadero.jpg"); + ASSERT_TRUE(image); + + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + auto filtered = + ColorFilterContents::MakeSrgbToLinearFilter(FilterInput::Make(image)); + + // Define the entity that will serve as the control image as a Gaussian blur + // filter with no filter at all. + Entity entity_left; + entity_left.SetTransform(Matrix::MakeScale(GetContentScale()) * + Matrix::MakeTranslation({100, 300}) * + Matrix::MakeScale(Vector2{0.5, 0.5})); + auto unfiltered = FilterContents::MakeGaussianBlur(FilterInput::Make(image), + Sigma{0}, Sigma{0}); + entity_left.SetContents(unfiltered); + + // Define the entity that will be filtered from sRGB to linear. + Entity entity_right; + entity_right.SetTransform(Matrix::MakeScale(GetContentScale()) * + Matrix::MakeTranslation({500, 300}) * + Matrix::MakeScale(Vector2{0.5, 0.5})); + entity_right.SetContents(filtered); + return entity_left.Render(context, pass) && + entity_right.Render(context, pass); + }; + + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +static Vector3 RGBToYUV(Vector3 rgb, YUVColorSpace yuv_color_space) { + Vector3 yuv; + switch (yuv_color_space) { + case YUVColorSpace::kBT601FullRange: + yuv.x = rgb.x * 0.299 + rgb.y * 0.587 + rgb.z * 0.114; + yuv.y = rgb.x * -0.169 + rgb.y * -0.331 + rgb.z * 0.5 + 0.5; + yuv.z = rgb.x * 0.5 + rgb.y * -0.419 + rgb.z * -0.081 + 0.5; + break; + case YUVColorSpace::kBT601LimitedRange: + yuv.x = rgb.x * 0.257 + rgb.y * 0.516 + rgb.z * 0.100 + 0.063; + yuv.y = rgb.x * -0.145 + rgb.y * -0.291 + rgb.z * 0.439 + 0.5; + yuv.z = rgb.x * 0.429 + rgb.y * -0.368 + rgb.z * -0.071 + 0.5; + break; + } + return yuv; +} + +static std::vector> CreateTestYUVTextures( + Context* context, + YUVColorSpace yuv_color_space) { + Vector3 red = {244.0 / 255.0, 67.0 / 255.0, 54.0 / 255.0}; + Vector3 green = {76.0 / 255.0, 175.0 / 255.0, 80.0 / 255.0}; + Vector3 blue = {33.0 / 255.0, 150.0 / 255.0, 243.0 / 255.0}; + Vector3 white = {1.0, 1.0, 1.0}; + Vector3 red_yuv = RGBToYUV(red, yuv_color_space); + Vector3 green_yuv = RGBToYUV(green, yuv_color_space); + Vector3 blue_yuv = RGBToYUV(blue, yuv_color_space); + Vector3 white_yuv = RGBToYUV(white, yuv_color_space); + std::vector yuvs{red_yuv, green_yuv, blue_yuv, white_yuv}; + std::vector y_data; + std::vector uv_data; + for (int i = 0; i < 4; i++) { + auto yuv = yuvs[i]; + uint8_t y = std::round(yuv.x * 255.0); + uint8_t u = std::round(yuv.y * 255.0); + uint8_t v = std::round(yuv.z * 255.0); + for (int j = 0; j < 16; j++) { + y_data.push_back(y); + } + for (int j = 0; j < 8; j++) { + uv_data.push_back(j % 2 == 0 ? u : v); + } + } + auto cmd_buffer = context->CreateCommandBuffer(); + auto blit_pass = cmd_buffer->CreateBlitPass(); + + impeller::TextureDescriptor y_texture_descriptor; + y_texture_descriptor.storage_mode = impeller::StorageMode::kHostVisible; + y_texture_descriptor.format = PixelFormat::kR8UNormInt; + y_texture_descriptor.size = {8, 8}; + auto y_texture = + context->GetResourceAllocator()->CreateTexture(y_texture_descriptor); + auto y_mapping = std::make_shared(y_data); + auto y_mapping_buffer = + context->GetResourceAllocator()->CreateBufferWithCopy(*y_mapping); + + blit_pass->AddCopy(DeviceBuffer::AsBufferView(y_mapping_buffer), y_texture); + + impeller::TextureDescriptor uv_texture_descriptor; + uv_texture_descriptor.storage_mode = impeller::StorageMode::kHostVisible; + uv_texture_descriptor.format = PixelFormat::kR8G8UNormInt; + uv_texture_descriptor.size = {4, 4}; + auto uv_texture = + context->GetResourceAllocator()->CreateTexture(uv_texture_descriptor); + auto uv_mapping = std::make_shared(uv_data); + auto uv_mapping_buffer = + context->GetResourceAllocator()->CreateBufferWithCopy(*uv_mapping); + + blit_pass->AddCopy(DeviceBuffer::AsBufferView(uv_mapping_buffer), uv_texture); + + if (!blit_pass->EncodeCommands(context->GetResourceAllocator()) || + !context->GetCommandQueue()->Submit({cmd_buffer}).ok()) { + FML_DLOG(ERROR) << "Could not copy contents into Y/UV texture."; + } + + return {y_texture, uv_texture}; +} + +TEST_P(EntityTest, YUVToRGBFilter) { + if (GetParam() == PlaygroundBackend::kOpenGLES) { + // TODO(114588) : Support YUV to RGB filter on OpenGLES backend. + GTEST_SKIP() + << "YUV to RGB filter is not supported on OpenGLES backend yet."; + } + + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + YUVColorSpace yuv_color_space_array[2]{YUVColorSpace::kBT601FullRange, + YUVColorSpace::kBT601LimitedRange}; + for (int i = 0; i < 2; i++) { + auto yuv_color_space = yuv_color_space_array[i]; + auto textures = + CreateTestYUVTextures(GetContext().get(), yuv_color_space); + auto filter_contents = FilterContents::MakeYUVToRGBFilter( + textures[0], textures[1], yuv_color_space); + Entity filter_entity; + filter_entity.SetContents(filter_contents); + auto snapshot = filter_contents->RenderToSnapshot(context, filter_entity); + + Entity entity; + auto contents = TextureContents::MakeRect(Rect::MakeLTRB(0, 0, 256, 256)); + contents->SetTexture(snapshot->texture); + contents->SetSourceRect(Rect::MakeSize(snapshot->texture->GetSize())); + entity.SetContents(contents); + entity.SetTransform( + Matrix::MakeTranslation({static_cast(100 + 400 * i), 300})); + entity.Render(context, pass); + } + return true; + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, RuntimeEffect) { + auto runtime_stages = + OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); + auto runtime_stage = + runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; + ASSERT_TRUE(runtime_stage); + ASSERT_TRUE(runtime_stage->IsDirty()); + + bool expect_dirty = true; + Pipeline* first_pipeline; + static std::unique_ptr geom = Geometry::MakeCover(); + + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + EXPECT_EQ(runtime_stage->IsDirty(), expect_dirty); + + auto contents = std::make_shared(); + contents->SetGeometry(geom.get()); + contents->SetRuntimeStage(runtime_stage); + + struct FragUniforms { + Vector2 iResolution; + Scalar iTime; + } frag_uniforms = { + .iResolution = Vector2(GetWindowSize().width, GetWindowSize().height), + .iTime = static_cast(GetSecondsElapsed()), + }; + auto uniform_data = std::make_shared>(); + uniform_data->resize(sizeof(FragUniforms)); + memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms)); + contents->SetUniformData(uniform_data); + + Entity entity; + entity.SetContents(contents); + bool result = contents->Render(context, entity, pass); + + if (expect_dirty) { + EXPECT_NE(first_pipeline, pass.GetCommands().back().pipeline.get()); + first_pipeline = pass.GetCommands().back().pipeline.get(); + } else { + EXPECT_EQ(pass.GetCommands().back().pipeline.get(), first_pipeline); + } + + expect_dirty = false; + return result; + }; + + // Simulate some renders and hot reloading of the shader. + auto content_context = GetContentContext(); + { + RenderTarget target = + content_context->GetRenderTargetCache()->CreateOffscreen( + *content_context->GetContext(), {1, 1}, 1u); + + testing::MockRenderPass mock_pass(GetContext(), target); + callback(*content_context, mock_pass); + callback(*content_context, mock_pass); + + // Dirty the runtime stage. + runtime_stages = OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); + runtime_stage = + runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; + + ASSERT_TRUE(runtime_stage->IsDirty()); + expect_dirty = true; + + callback(*content_context, mock_pass); + } +} + +TEST_P(EntityTest, RuntimeEffectCanSuccessfullyRender) { + auto runtime_stages = + OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); + auto runtime_stage = + runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; + ASSERT_TRUE(runtime_stage); + ASSERT_TRUE(runtime_stage->IsDirty()); + + auto contents = std::make_shared(); + auto geom = Geometry::MakeCover(); + contents->SetGeometry(geom.get()); + contents->SetRuntimeStage(runtime_stage); + + struct FragUniforms { + Vector2 iResolution; + Scalar iTime; + } frag_uniforms = { + .iResolution = Vector2(GetWindowSize().width, GetWindowSize().height), + .iTime = static_cast(GetSecondsElapsed()), + }; + auto uniform_data = std::make_shared>(); + uniform_data->resize(sizeof(FragUniforms)); + memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms)); + contents->SetUniformData(uniform_data); + + Entity entity; + entity.SetContents(contents); + + // Create a render target with a depth-stencil, similar to how EntityPass + // does. + RenderTarget target = + GetContentContext()->GetRenderTargetCache()->CreateOffscreenMSAA( + *GetContext(), {GetWindowSize().width, GetWindowSize().height}, 1, + "RuntimeEffect Texture"); + testing::MockRenderPass pass(GetContext(), target); + + ASSERT_TRUE(contents->Render(*GetContentContext(), entity, pass)); + ASSERT_EQ(pass.GetCommands().size(), 1u); + const auto& command = pass.GetCommands()[0]; + ASSERT_TRUE(command.pipeline->GetDescriptor() + .GetDepthStencilAttachmentDescriptor() + .has_value()); + ASSERT_TRUE(command.pipeline->GetDescriptor() + .GetFrontStencilAttachmentDescriptor() + .has_value()); +} + +TEST_P(EntityTest, RuntimeEffectCanPrecache) { + auto runtime_stages = + OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); + auto runtime_stage = + runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; + ASSERT_TRUE(runtime_stage); + ASSERT_TRUE(runtime_stage->IsDirty()); + + auto contents = std::make_shared(); + contents->SetRuntimeStage(runtime_stage); + + EXPECT_TRUE(contents->BootstrapShader(*GetContentContext())); +} + +TEST_P(EntityTest, RuntimeEffectSetsRightSizeWhenUniformIsStruct) { + if (GetBackend() != PlaygroundBackend::kVulkan) { + GTEST_SKIP() << "Test only applies to Vulkan"; + } + + auto runtime_stages = + OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr"); + auto runtime_stage = + runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; + ASSERT_TRUE(runtime_stage); + ASSERT_TRUE(runtime_stage->IsDirty()); + + auto contents = std::make_shared(); + auto geom = Geometry::MakeCover(); + contents->SetGeometry(geom.get()); + contents->SetRuntimeStage(runtime_stage); + + struct FragUniforms { + Vector2 iResolution; + Scalar iTime; + } frag_uniforms = { + .iResolution = Vector2(GetWindowSize().width, GetWindowSize().height), + .iTime = static_cast(GetSecondsElapsed()), + }; + auto uniform_data = std::make_shared>(); + uniform_data->resize(sizeof(FragUniforms)); + memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms)); + contents->SetUniformData(uniform_data); + + Entity entity; + entity.SetContents(contents); + + auto context = GetContentContext(); + RenderTarget target = context->GetRenderTargetCache()->CreateOffscreen( + *context->GetContext(), {1, 1}, 1u); + + testing::MockRenderPass pass(GetContext(), target); + ASSERT_TRUE(contents->Render(*context, entity, pass)); + ASSERT_EQ(pass.GetCommands().size(), 1u); + const auto& command = pass.GetCommands()[0]; + ASSERT_EQ(command.fragment_bindings.buffers.size(), 1u); + // 16 bytes: + // 8 bytes for iResolution + // 4 bytes for iTime + // 4 bytes padding + EXPECT_EQ(command.fragment_bindings.buffers[0].view.resource.range.length, + 16u); +} + +TEST_P(EntityTest, ColorFilterWithForegroundColorAdvancedBlend) { + auto image = CreateTextureForFixture("boston.jpg"); + auto filter = ColorFilterContents::MakeBlend( + BlendMode::kColorBurn, FilterInput::Make({image}), Color::Red()); + + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + Entity entity; + entity.SetTransform(Matrix::MakeScale(GetContentScale()) * + Matrix::MakeTranslation({500, 300}) * + Matrix::MakeScale(Vector2{0.5, 0.5})); + entity.SetContents(filter); + return entity.Render(context, pass); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, ColorFilterWithForegroundColorClearBlend) { + auto image = CreateTextureForFixture("boston.jpg"); + auto filter = ColorFilterContents::MakeBlend( + BlendMode::kClear, FilterInput::Make({image}), Color::Red()); + + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + Entity entity; + entity.SetTransform(Matrix::MakeScale(GetContentScale()) * + Matrix::MakeTranslation({500, 300}) * + Matrix::MakeScale(Vector2{0.5, 0.5})); + entity.SetContents(filter); + return entity.Render(context, pass); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, ColorFilterWithForegroundColorSrcBlend) { + auto image = CreateTextureForFixture("boston.jpg"); + auto filter = ColorFilterContents::MakeBlend( + BlendMode::kSource, FilterInput::Make({image}), Color::Red()); + + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + Entity entity; + entity.SetTransform(Matrix::MakeScale(GetContentScale()) * + Matrix::MakeTranslation({500, 300}) * + Matrix::MakeScale(Vector2{0.5, 0.5})); + entity.SetContents(filter); + return entity.Render(context, pass); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, ColorFilterWithForegroundColorDstBlend) { + auto image = CreateTextureForFixture("boston.jpg"); + auto filter = ColorFilterContents::MakeBlend( + BlendMode::kDestination, FilterInput::Make({image}), Color::Red()); + + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + Entity entity; + entity.SetTransform(Matrix::MakeScale(GetContentScale()) * + Matrix::MakeTranslation({500, 300}) * + Matrix::MakeScale(Vector2{0.5, 0.5})); + entity.SetContents(filter); + return entity.Render(context, pass); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, ColorFilterWithForegroundColorSrcInBlend) { + auto image = CreateTextureForFixture("boston.jpg"); + auto filter = ColorFilterContents::MakeBlend( + BlendMode::kSourceIn, FilterInput::Make({image}), Color::Red()); + + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + Entity entity; + entity.SetTransform(Matrix::MakeScale(GetContentScale()) * + Matrix::MakeTranslation({500, 300}) * + Matrix::MakeScale(Vector2{0.5, 0.5})); + entity.SetContents(filter); + return entity.Render(context, pass); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, CoverageForStrokePathWithNegativeValuesInTransform) { + auto arrow_head = PathBuilder{} + .MoveTo({50, 120}) + .LineTo({120, 190}) + .LineTo({190, 120}) + .TakePath(); + auto geometry = Geometry::MakeStrokePath(arrow_head, 15.0, 4.0, Cap::kRound, + Join::kRound); + + auto transform = Matrix::MakeTranslation({300, 300}) * + Matrix::MakeRotationZ(Radians(kPiOver2)); + // Note that e[0][0] used to be tested here, but it was -epsilon solely + // due to floating point inaccuracy in the transcendental trig functions. + // e[1][0] is the intended negative value that we care about (-1.0) as it + // comes from the rotation of pi/2. + EXPECT_LT(transform.e[1][0], 0.0f); + auto coverage = geometry->GetCoverage(transform); + ASSERT_RECT_NEAR(coverage.value(), Rect::MakeXYWH(102.5, 342.5, 85, 155)); +} + +TEST_P(EntityTest, SolidColorContentsIsOpaque) { + Matrix matrix; + SolidColorContents contents; + auto geom = Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10)); + contents.SetGeometry(geom.get()); + + contents.SetColor(Color::CornflowerBlue()); + EXPECT_TRUE(contents.IsOpaque(matrix)); + contents.SetColor(Color::CornflowerBlue().WithAlpha(0.5)); + EXPECT_FALSE(contents.IsOpaque(matrix)); + + // Create stroked path that required alpha coverage. + geom = Geometry::MakeStrokePath( + PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), + /*stroke_width=*/0.05); + contents.SetGeometry(geom.get()); + contents.SetColor(Color::CornflowerBlue()); + + EXPECT_FALSE(contents.IsOpaque(matrix)); +} + +TEST_P(EntityTest, ConicalGradientContentsIsOpaque) { + Matrix matrix; + ConicalGradientContents contents; + auto geom = Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10)); + contents.SetGeometry(geom.get()); + + contents.SetColors({Color::CornflowerBlue()}); + EXPECT_FALSE(contents.IsOpaque(matrix)); + contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)}); + EXPECT_FALSE(contents.IsOpaque(matrix)); + + // Create stroked path that required alpha coverage. + geom = Geometry::MakeStrokePath( + PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), + /*stroke_width=*/0.05); + contents.SetGeometry(geom.get()); + contents.SetColors({Color::CornflowerBlue()}); + + EXPECT_FALSE(contents.IsOpaque(matrix)); +} + +TEST_P(EntityTest, LinearGradientContentsIsOpaque) { + Matrix matrix; + LinearGradientContents contents; + auto geom = Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10)); + contents.SetGeometry(geom.get()); + + contents.SetColors({Color::CornflowerBlue()}); + EXPECT_TRUE(contents.IsOpaque(matrix)); + contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)}); + EXPECT_FALSE(contents.IsOpaque(matrix)); + contents.SetColors({Color::CornflowerBlue()}); + contents.SetTileMode(Entity::TileMode::kDecal); + EXPECT_FALSE(contents.IsOpaque(matrix)); + + // Create stroked path that required alpha coverage. + geom = Geometry::MakeStrokePath( + PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), + /*stroke_width=*/0.05); + contents.SetGeometry(geom.get()); + contents.SetColors({Color::CornflowerBlue()}); + + EXPECT_FALSE(contents.IsOpaque(matrix)); +} + +TEST_P(EntityTest, RadialGradientContentsIsOpaque) { + Matrix matrix; + RadialGradientContents contents; + auto geom = Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10)); + contents.SetGeometry(geom.get()); + + contents.SetColors({Color::CornflowerBlue()}); + EXPECT_TRUE(contents.IsOpaque(matrix)); + contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)}); + EXPECT_FALSE(contents.IsOpaque(matrix)); + contents.SetColors({Color::CornflowerBlue()}); + contents.SetTileMode(Entity::TileMode::kDecal); + EXPECT_FALSE(contents.IsOpaque(matrix)); + + // Create stroked path that required alpha coverage. + geom = Geometry::MakeStrokePath( + PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), + /*stroke_width=*/0.05); + contents.SetGeometry(geom.get()); + contents.SetColors({Color::CornflowerBlue()}); + + EXPECT_FALSE(contents.IsOpaque(matrix)); +} + +TEST_P(EntityTest, SweepGradientContentsIsOpaque) { + Matrix matrix; + RadialGradientContents contents; + auto geom = Geometry::MakeRect(Rect::MakeLTRB(0, 0, 10, 10)); + contents.SetGeometry(geom.get()); + + contents.SetColors({Color::CornflowerBlue()}); + EXPECT_TRUE(contents.IsOpaque(matrix)); + contents.SetColors({Color::CornflowerBlue().WithAlpha(0.5)}); + EXPECT_FALSE(contents.IsOpaque(matrix)); + contents.SetColors({Color::CornflowerBlue()}); + contents.SetTileMode(Entity::TileMode::kDecal); + EXPECT_FALSE(contents.IsOpaque(matrix)); + + // Create stroked path that required alpha coverage. + geom = Geometry::MakeStrokePath( + PathBuilder{}.AddLine({0, 0}, {100, 100}).TakePath(), + /*stroke_width=*/0.05); + contents.SetGeometry(geom.get()); + contents.SetColors({Color::CornflowerBlue()}); + + EXPECT_FALSE(contents.IsOpaque(matrix)); +} + +TEST_P(EntityTest, TiledTextureContentsIsOpaque) { + Matrix matrix; + auto bay_bridge = CreateTextureForFixture("bay_bridge.jpg"); + TiledTextureContents contents; + contents.SetTexture(bay_bridge); + // This is a placeholder test. Images currently never decompress as opaque + // (whether in Flutter or the playground), and so this should currently always + // return false in practice. + EXPECT_FALSE(contents.IsOpaque(matrix)); +} + +TEST_P(EntityTest, PointFieldGeometryCoverage) { + std::vector points = {{10, 20}, {100, 200}}; + auto geometry = Geometry::MakePointField(points, 5.0, false); + ASSERT_EQ(*geometry->GetCoverage(Matrix()), Rect::MakeLTRB(5, 15, 105, 205)); + ASSERT_EQ(*geometry->GetCoverage(Matrix::MakeTranslation({30, 0, 0})), + Rect::MakeLTRB(35, 15, 135, 205)); +} + +TEST_P(EntityTest, ColorFilterContentsWithLargeGeometry) { + Entity entity; + entity.SetTransform(Matrix::MakeScale(GetContentScale())); + auto src_contents = std::make_shared(); + auto src_geom = Geometry::MakeRect(Rect::MakeLTRB(-300, -500, 30000, 50000)); + src_contents->SetGeometry(src_geom.get()); + src_contents->SetColor(Color::Red()); + + auto dst_contents = std::make_shared(); + auto dst_geom = Geometry::MakeRect(Rect::MakeLTRB(300, 500, 20000, 30000)); + dst_contents->SetGeometry(dst_geom.get()); + dst_contents->SetColor(Color::Blue()); + + auto contents = ColorFilterContents::MakeBlend( + BlendMode::kSourceOver, {FilterInput::Make(dst_contents, false), + FilterInput::Make(src_contents, false)}); + entity.SetContents(std::move(contents)); + ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); +} + +TEST_P(EntityTest, TextContentsCeilsGlyphScaleToDecimal) { + ASSERT_EQ(TextFrame::RoundScaledFontSize(0.4321111f, 12), 0.43f); + ASSERT_EQ(TextFrame::RoundScaledFontSize(0.5321111f, 12), 0.53f); + ASSERT_EQ(TextFrame::RoundScaledFontSize(2.1f, 12), 2.1f); + ASSERT_EQ(TextFrame::RoundScaledFontSize(0.0f, 12), 0.0f); + ASSERT_EQ(TextFrame::RoundScaledFontSize(100000000.0f, 12), 48.0f); +} + +TEST_P(EntityTest, SpecializationConstantsAreAppliedToVariants) { + auto content_context = GetContentContext(); + + auto default_gyph = content_context->GetGlyphAtlasPipeline({ + .color_attachment_pixel_format = PixelFormat::kR8G8B8A8UNormInt, + .has_depth_stencil_attachments = false, + }); + auto alt_gyph = content_context->GetGlyphAtlasPipeline( + {.color_attachment_pixel_format = PixelFormat::kR8G8B8A8UNormInt, + .has_depth_stencil_attachments = true}); + + EXPECT_NE(default_gyph, alt_gyph); + EXPECT_EQ(default_gyph->GetDescriptor().GetSpecializationConstants(), + alt_gyph->GetDescriptor().GetSpecializationConstants()); + + auto use_a8 = GetContext()->GetCapabilities()->GetDefaultGlyphAtlasFormat() == + PixelFormat::kA8UNormInt; + + std::vector expected_constants = {static_cast(use_a8)}; + EXPECT_EQ(default_gyph->GetDescriptor().GetSpecializationConstants(), + expected_constants); +} + +TEST_P(EntityTest, DecalSpecializationAppliedToMorphologyFilter) { + auto content_context = GetContentContext(); + auto default_color_burn = content_context->GetMorphologyFilterPipeline({ + .color_attachment_pixel_format = PixelFormat::kR8G8B8A8UNormInt, + }); + + auto decal_supported = static_cast( + GetContext()->GetCapabilities()->SupportsDecalSamplerAddressMode()); + std::vector expected_constants = {decal_supported}; + ASSERT_EQ(default_color_burn->GetDescriptor().GetSpecializationConstants(), + expected_constants); +} + +// This doesn't really tell you if the hashes will have frequent +// collisions, but since this type is only used to hash a bounded +// set of options, we can just compare benchmarks. +TEST_P(EntityTest, ContentContextOptionsHasReasonableHashFunctions) { + ContentContextOptions opts; + auto hash_a = ContentContextOptions::Hash{}(opts); + + opts.blend_mode = BlendMode::kColorBurn; + auto hash_b = ContentContextOptions::Hash{}(opts); + + opts.has_depth_stencil_attachments = false; + auto hash_c = ContentContextOptions::Hash{}(opts); + + opts.primitive_type = PrimitiveType::kPoint; + auto hash_d = ContentContextOptions::Hash{}(opts); + + EXPECT_NE(hash_a, hash_b); + EXPECT_NE(hash_b, hash_c); + EXPECT_NE(hash_c, hash_d); +} + +#ifdef FML_OS_LINUX +TEST_P(EntityTest, FramebufferFetchVulkanBindingOffsetIsTheSame) { + // Using framebuffer fetch on Vulkan requires that we maintain a subpass + input + // binding that we don't have a good route for configuring with the + // current metadata approach. This test verifies that the binding value + // doesn't + change + // from the expected constant. + // See also: + // * impeller/renderer/backend/vulkan/binding_helpers_vk.cc + // * impeller/entity/shaders/blending/framebuffer_blend.frag + // This test only works on Linux because macOS hosts incorrectly + // populate + the + // Vulkan descriptor sets based on the MSL compiler settings. + + bool expected_layout = false; + for (const DescriptorSetLayout& layout : FramebufferBlendColorBurnPipeline:: + FragmentShader::kDescriptorSetLayouts) { + if (layout.binding == 64 && + layout.descriptor_type == DescriptorType::kInputAttachment) { + expected_layout = true; + } + } + EXPECT_TRUE(expected_layout); +} +#endif + +TEST_P(EntityTest, FillPathGeometryGetPositionBufferReturnsExpectedMode) { + RenderTarget target; + testing::MockRenderPass mock_pass(GetContext(), target); + + auto get_result = [this, &mock_pass](const Path& path) { + auto geometry = Geometry::MakeFillPath( + path, /* inner rect */ Rect::MakeLTRB(0, 0, 100, 100)); + return geometry->GetPositionBuffer(*GetContentContext(), {}, mock_pass); + }; + + // Convex path + { + GeometryResult result = + get_result(PathBuilder{} + .AddRect(Rect::MakeLTRB(0, 0, 100, 100)) + .SetConvexity(Convexity::kConvex) + .TakePath()); + EXPECT_EQ(result.mode, GeometryResult::Mode::kNormal); + } + + // Concave path + { + Path path = PathBuilder{} + .MoveTo({0, 0}) + .LineTo({100, 0}) + .LineTo({100, 100}) + .LineTo({50, 50}) + .Close() + .TakePath(); + GeometryResult result = get_result(path); + EXPECT_EQ(result.mode, GeometryResult::Mode::kNonZero); + } +} + +TEST_P(EntityTest, FailOnValidationError) { + if (GetParam() != PlaygroundBackend::kVulkan) { + GTEST_SKIP() << "Validation is only fatal on Vulkan backend."; + } + EXPECT_DEATH( + // The easiest way to trigger a validation error is to try to compile + // a shader with an unsupported pixel format. + GetContentContext()->GetBlendColorBurnPipeline({ + .color_attachment_pixel_format = PixelFormat::kUnknown, + .has_depth_stencil_attachments = false, + }), + ""); +} + +TEST_P(EntityTest, CanComputeGeometryForEmptyPathsWithoutCrashing) { + PathBuilder builder = {}; + builder.AddRect(Rect::MakeLTRB(0, 0, 0, 0)); + Path path = builder.TakePath(); + + EXPECT_TRUE(path.GetBoundingBox()->IsEmpty()); + + auto geom = Geometry::MakeFillPath(path); + + Entity entity; + RenderTarget target = + GetContentContext()->GetRenderTargetCache()->CreateOffscreen( + *GetContext(), {1, 1}, 1u); + testing::MockRenderPass render_pass(GetContext(), target); + auto position_result = + geom->GetPositionBuffer(*GetContentContext(), entity, render_pass); + + EXPECT_EQ(position_result.vertex_buffer.vertex_count, 0u); + + EXPECT_EQ(geom->GetResultMode(), GeometryResult::Mode::kNormal); +} + +TEST_P(EntityTest, CanRenderEmptyPathsWithoutCrashing) { + PathBuilder builder = {}; + builder.AddRect(Rect::MakeLTRB(0, 0, 0, 0)); + Path path = builder.TakePath(); + + EXPECT_TRUE(path.GetBoundingBox()->IsEmpty()); + + auto contents = std::make_shared(); + static std::unique_ptr geom = Geometry::MakeFillPath(path); + contents->SetGeometry(geom.get()); + contents->SetColor(Color::Red()); + + Entity entity; + entity.SetTransform(Matrix::MakeScale(GetContentScale())); + entity.SetContents(contents); + + ASSERT_TRUE(OpenPlaygroundHere(std::move(entity))); +} + +TEST_P(EntityTest, DrawSuperEllipse) { + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + // UI state. + static float alpha = 10; + static float beta = 10; + static float radius = 40; + static int degree = 4; + static Color color = Color::Red(); + + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + ImGui::SliderFloat("Alpha", &alpha, 0, 100); + ImGui::SliderFloat("Beta", &beta, 0, 100); + ImGui::SliderInt("Degreee", °ree, 1, 20); + ImGui::SliderFloat("Radius", &radius, 0, 400); + ImGui::ColorEdit4("Color", reinterpret_cast(&color)); + ImGui::End(); + + auto contents = std::make_shared(); + static std::unique_ptr geom = + std::make_unique(Point{400, 400}, radius, degree, + alpha, beta); + contents->SetColor(color); + contents->SetGeometry(geom.get()); + + Entity entity; + entity.SetContents(contents); + + return entity.Render(context, pass); + }; + + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, SolidColorApplyColorFilter) { + auto contents = SolidColorContents(); + contents.SetColor(Color::CornflowerBlue().WithAlpha(0.75)); + auto result = contents.ApplyColorFilter([](const Color& color) { + return color.Blend(Color::LimeGreen().WithAlpha(0.75), BlendMode::kScreen); + }); + ASSERT_TRUE(result); + ASSERT_COLOR_NEAR(contents.GetColor(), + Color(0.424452, 0.828743, 0.79105, 0.9375)); +} + +#define APPLY_COLOR_FILTER_GRADIENT_TEST(name) \ + TEST_P(EntityTest, name##GradientApplyColorFilter) { \ + auto contents = name##GradientContents(); \ + contents.SetColors({Color::CornflowerBlue().WithAlpha(0.75)}); \ + auto result = contents.ApplyColorFilter([](const Color& color) { \ + return color.Blend(Color::LimeGreen().WithAlpha(0.75), \ + BlendMode::kScreen); \ + }); \ + ASSERT_TRUE(result); \ + \ + std::vector expected = {Color(0.433247, 0.879523, 0.825324, 0.75)}; \ + ASSERT_COLORS_NEAR(contents.GetColors(), expected); \ + } + +APPLY_COLOR_FILTER_GRADIENT_TEST(Linear); +APPLY_COLOR_FILTER_GRADIENT_TEST(Radial); +APPLY_COLOR_FILTER_GRADIENT_TEST(Conical); +APPLY_COLOR_FILTER_GRADIENT_TEST(Sweep); } // namespace testing } // namespace impeller From b104b6c0195ea42e4f59fd3764e4454ba0eb9582 Mon Sep 17 00:00:00 2001 From: jonahwilliams Date: Sat, 5 Oct 2024 14:28:33 -0700 Subject: [PATCH 3/4] ++ --- impeller/entity/entity_unittests.cc | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index 568ba93d3c07a..6804b1ed4804b 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -2250,22 +2250,19 @@ TEST_P(EntityTest, ContentContextOptionsHasReasonableHashFunctions) { #ifdef FML_OS_LINUX TEST_P(EntityTest, FramebufferFetchVulkanBindingOffsetIsTheSame) { - // Using framebuffer fetch on Vulkan requires that we maintain a subpass - input - // binding that we don't have a good route for configuring with the - // current metadata approach. This test verifies that the binding value - // doesn't - change - // from the expected constant. - // See also: - // * impeller/renderer/backend/vulkan/binding_helpers_vk.cc - // * impeller/entity/shaders/blending/framebuffer_blend.frag - // This test only works on Linux because macOS hosts incorrectly - // populate - the - // Vulkan descriptor sets based on the MSL compiler settings. - - bool expected_layout = false; + // Using framebuffer fetch on Vulkan requires that we maintain a subpass input + // binding that we don't have a good route for configuring with the + // current metadata approach. This test verifies that the binding value + // doesn't change + // from the expected constant. + // See also: + // * impeller/renderer/backend/vulkan/binding_helpers_vk.cc + // * impeller/entity/shaders/blending/framebuffer_blend.frag + // This test only works on Linux because macOS hosts incorrectly + // populate the + // Vulkan descriptor sets based on the MSL compiler settings. + + bool expected_layout = false; for (const DescriptorSetLayout& layout : FramebufferBlendColorBurnPipeline:: FragmentShader::kDescriptorSetLayouts) { if (layout.binding == 64 && From 4b9d22c6e1e718287adacdaf4611f1da0cccf177 Mon Sep 17 00:00:00 2001 From: jonahwilliams Date: Tue, 8 Oct 2024 12:03:32 -0700 Subject: [PATCH 4/4] chinmay review. --- impeller/display_list/canvas.cc | 3 +-- impeller/entity/geometry/circle_geometry.cc | 2 +- impeller/entity/geometry/line_geometry.cc | 2 +- impeller/entity/geometry/point_field_geometry.cc | 2 +- impeller/entity/geometry/rect_geometry.cc | 2 +- impeller/entity/geometry/round_rect_geometry.cc | 2 +- 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/impeller/display_list/canvas.cc b/impeller/display_list/canvas.cc index 4d19c7a212628..d14db69dc0d69 100644 --- a/impeller/display_list/canvas.cc +++ b/impeller/display_list/canvas.cc @@ -1402,7 +1402,7 @@ void Canvas::AddRenderEntityWithFiltersToCurrentPass(Entity& entity, } if (paint.invert_colors) { contents_copy = - WrapWithInvertColors(FilterInput::Make(contents_copy), + WrapWithInvertColors(FilterInput::Make(std::move(contents_copy)), ColorFilterContents::AbsorbOpacity::kYes); } } @@ -1418,7 +1418,6 @@ void Canvas::AddRenderEntityWithFiltersToCurrentPass(Entity& entity, entity.SetContents(std::move(contents_copy)); AddRenderEntityToCurrentPass(entity, reuse_depth); - return; } void Canvas::AddRenderEntityToCurrentPass(Entity& entity, bool reuse_depth) { diff --git a/impeller/entity/geometry/circle_geometry.cc b/impeller/entity/geometry/circle_geometry.cc index 8934d11d586cd..ab265a6c10462 100644 --- a/impeller/entity/geometry/circle_geometry.cc +++ b/impeller/entity/geometry/circle_geometry.cc @@ -17,7 +17,7 @@ CircleGeometry::CircleGeometry(const Point& center, Scalar radius) FML_DCHECK(radius >= 0); } -CircleGeometry::~CircleGeometry() {} +CircleGeometry::~CircleGeometry() = default; CircleGeometry::CircleGeometry(const Point& center, Scalar radius, diff --git a/impeller/entity/geometry/line_geometry.cc b/impeller/entity/geometry/line_geometry.cc index 281c32b4ada36..d5f5b640630b1 100644 --- a/impeller/entity/geometry/line_geometry.cc +++ b/impeller/entity/geometry/line_geometry.cc @@ -12,7 +12,7 @@ LineGeometry::LineGeometry(Point p0, Point p1, Scalar width, Cap cap) FML_DCHECK(width >= 0); } -LineGeometry::~LineGeometry() {} +LineGeometry::~LineGeometry() = default; Scalar LineGeometry::ComputePixelHalfWidth(const Matrix& transform, Scalar width, diff --git a/impeller/entity/geometry/point_field_geometry.cc b/impeller/entity/geometry/point_field_geometry.cc index 0b2a037bc9361..ff7cf8cf124f2 100644 --- a/impeller/entity/geometry/point_field_geometry.cc +++ b/impeller/entity/geometry/point_field_geometry.cc @@ -15,7 +15,7 @@ PointFieldGeometry::PointFieldGeometry(std::vector points, bool round) : points_(std::move(points)), radius_(radius), round_(round) {} -PointFieldGeometry::~PointFieldGeometry() {} +PointFieldGeometry::~PointFieldGeometry() = default; GeometryResult PointFieldGeometry::GetPositionBuffer( const ContentContext& renderer, diff --git a/impeller/entity/geometry/rect_geometry.cc b/impeller/entity/geometry/rect_geometry.cc index 58513cb45d18e..19de79d718610 100644 --- a/impeller/entity/geometry/rect_geometry.cc +++ b/impeller/entity/geometry/rect_geometry.cc @@ -8,7 +8,7 @@ namespace impeller { RectGeometry::RectGeometry(Rect rect) : rect_(rect) {} -RectGeometry::~RectGeometry() {} +RectGeometry::~RectGeometry() = default; GeometryResult RectGeometry::GetPositionBuffer(const ContentContext& renderer, const Entity& entity, diff --git a/impeller/entity/geometry/round_rect_geometry.cc b/impeller/entity/geometry/round_rect_geometry.cc index e9a5016d077bf..1d673f53add31 100644 --- a/impeller/entity/geometry/round_rect_geometry.cc +++ b/impeller/entity/geometry/round_rect_geometry.cc @@ -9,7 +9,7 @@ namespace impeller { RoundRectGeometry::RoundRectGeometry(const Rect& bounds, const Size& radii) : bounds_(bounds), radii_(radii) {} -RoundRectGeometry::~RoundRectGeometry() {} +RoundRectGeometry::~RoundRectGeometry() = default; GeometryResult RoundRectGeometry::GetPositionBuffer( const ContentContext& renderer,