[Impeller] texture coordinates implementation (#39781)

[Impeller] texture coordinates implementation
diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc
index 6ba4d87..59d4c0d 100644
--- a/impeller/aiks/canvas.cc
+++ b/impeller/aiks/canvas.cc
@@ -21,6 +21,23 @@
 
 namespace impeller {
 
+static bool UseColorSourceContents(
+    const std::shared_ptr<VerticesGeometry>& vertices,
+    const Paint::ColorSourceType& type) {
+  // If there are no vertex color or texture coordinates. Or if there
+  // are vertex coordinates then only if the contents are an image or
+  // a solid color.
+  if (vertices->HasVertexColors()) {
+    return false;
+  }
+  if (vertices->HasTextureCoordinates() &&
+      (type == Paint::ColorSourceType::kImage ||
+       type == Paint::ColorSourceType::kColor)) {
+    return true;
+  }
+  return !vertices->HasTextureCoordinates();
+}
+
 Canvas::Canvas() {
   Initialize();
 }
@@ -386,7 +403,9 @@
   entity.SetStencilDepth(GetStencilDepth());
   entity.SetBlendMode(paint.blend_mode);
 
-  if (!vertices->HasVertexColors()) {
+  // If there are no vertex color or texture coordinates. Or if there
+  // are vertex coordinates then only if the contents are an image.
+  if (UseColorSourceContents(vertices, paint.color_source_type)) {
     auto contents = paint.CreateContentsForGeometry(vertices);
     entity.SetContents(paint.WithFilters(std::move(contents)));
     GetCurrentPass().AddEntity(entity);
@@ -395,7 +414,27 @@
 
   auto src_paint = paint;
   src_paint.color = paint.color.WithAlpha(1.0);
-  auto src_contents = src_paint.CreateContentsForGeometry(vertices);
+
+  std::shared_ptr<Contents> src_contents =
+      src_paint.CreateContentsForGeometry(vertices);
+  if (vertices->HasTextureCoordinates() &&
+      paint.color_source_type != Paint::ColorSourceType::kImage) {
+    // If the color source has an intrinsic size, then we use that to
+    // create the src contents as a simplification. Otherwise we use
+    // the extent of the texture coordinates to determine how large
+    // the src contents should be. If neither has a value we fall back
+    // to using the geometry coverage data.
+    Rect src_coverage;
+    auto size = src_contents->ColorSourceSize();
+    if (size.has_value()) {
+      src_coverage = Rect::MakeXYWH(0, 0, size->width, size->height);
+    } else {
+      src_coverage = vertices->GetTextureCoordinateCoverge().value_or(
+          vertices->GetCoverage(Matrix{}).value());
+    }
+    src_contents =
+        src_paint.CreateContentsForGeometry(Geometry::MakeRect(src_coverage));
+  }
 
   auto contents = std::make_shared<VerticesContents>();
   contents->SetAlpha(paint.color.alpha);
diff --git a/impeller/display_list/display_list_dispatcher.cc b/impeller/display_list/display_list_dispatcher.cc
index 02e8516..1d5dba8 100644
--- a/impeller/display_list/display_list_dispatcher.cc
+++ b/impeller/display_list/display_list_dispatcher.cc
@@ -396,6 +396,13 @@
         contents->SetEndPoints(start_point, end_point);
         contents->SetTileMode(tile_mode);
         contents->SetEffectTransform(matrix);
+
+        std::vector<Point> bounds{start_point, end_point};
+        auto intrinsic_size =
+            Rect::MakePointBounds(bounds.begin(), bounds.end());
+        if (intrinsic_size.has_value()) {
+          contents->SetColorSourceSize(intrinsic_size->size);
+        }
         return contents;
       };
       return;
@@ -420,6 +427,14 @@
         contents->SetCenterAndRadius(center, radius);
         contents->SetTileMode(tile_mode);
         contents->SetEffectTransform(matrix);
+
+        auto radius_pt = Point(radius, radius);
+        std::vector<Point> bounds{center + radius_pt, center - radius_pt};
+        auto intrinsic_size =
+            Rect::MakePointBounds(bounds.begin(), bounds.end());
+        if (intrinsic_size.has_value()) {
+          contents->SetColorSourceSize(intrinsic_size->size);
+        }
         return contents;
       };
       return;
@@ -447,6 +462,7 @@
         contents->SetStops(stops);
         contents->SetTileMode(tile_mode);
         contents->SetEffectTransform(matrix);
+
         return contents;
       };
       return;
@@ -468,6 +484,7 @@
         contents->SetSamplerDescriptor(desc);
         contents->SetEffectTransform(matrix);
         contents->SetColorFilter(paint.color_filter);
+        contents->SetColorSourceSize(Size::Ceil(texture->GetSize()));
         return contents;
       };
       return;
diff --git a/impeller/display_list/display_list_unittests.cc b/impeller/display_list/display_list_unittests.cc
index 20e5e0c..a9e23ef 100644
--- a/impeller/display_list/display_list_unittests.cc
+++ b/impeller/display_list/display_list_unittests.cc
@@ -1151,6 +1151,60 @@
   ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
 }
 
+TEST_P(DisplayListTest, DrawVerticesLinearGradientWithTextureCoordinates) {
+  std::vector<SkPoint> positions = {SkPoint::Make(100, 300),
+                                    SkPoint::Make(200, 100),
+                                    SkPoint::Make(300, 300)};
+  std::vector<SkPoint> texture_coordinates = {SkPoint::Make(300, 100),
+                                              SkPoint::Make(100, 200),
+                                              SkPoint::Make(300, 300)};
+
+  auto vertices = flutter::DlVertices::Make(
+      flutter::DlVertexMode::kTriangles, 3, positions.data(),
+      texture_coordinates.data(), /*colors=*/nullptr);
+
+  std::vector<flutter::DlColor> colors = {flutter::DlColor::kBlue(),
+                                          flutter::DlColor::kRed()};
+  const float stops[2] = {0.0, 1.0};
+
+  auto linear = flutter::DlColorSource::MakeLinear(
+      {100.0, 100.0}, {300.0, 300.0}, 2, colors.data(), stops,
+      flutter::DlTileMode::kRepeat);
+
+  flutter::DisplayListBuilder builder;
+  flutter::DlPaint paint;
+
+  paint.setColorSource(linear);
+  builder.DrawVertices(vertices, flutter::DlBlendMode::kSrcOver, paint);
+
+  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
+}
+
+TEST_P(DisplayListTest, DrawVerticesImageSourceWithTextureCoordinates) {
+  auto texture = CreateTextureForFixture("embarcadero.jpg");
+  auto dl_image = DlImageImpeller::Make(texture);
+  std::vector<SkPoint> positions = {SkPoint::Make(100, 300),
+                                    SkPoint::Make(200, 100),
+                                    SkPoint::Make(300, 300)};
+  std::vector<SkPoint> texture_coordinates = {
+      SkPoint::Make(0, 0), SkPoint::Make(100, 200), SkPoint::Make(200, 100)};
+
+  auto vertices = flutter::DlVertices::Make(
+      flutter::DlVertexMode::kTriangles, 3, positions.data(),
+      texture_coordinates.data(), /*colors=*/nullptr);
+
+  flutter::DisplayListBuilder builder;
+  flutter::DlPaint paint;
+
+  auto image_source = flutter::DlImageColorSource(
+      dl_image, flutter::DlTileMode::kRepeat, flutter::DlTileMode::kRepeat);
+
+  paint.setColorSource(&image_source);
+  builder.DrawVertices(vertices, flutter::DlBlendMode::kSrcOver, paint);
+
+  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
+}
+
 TEST_P(DisplayListTest, DrawVerticesSolidColorTrianglesWithIndices) {
   std::vector<SkPoint> positions = {
       SkPoint::Make(100, 300), SkPoint::Make(200, 100), SkPoint::Make(300, 300),
diff --git a/impeller/display_list/display_list_vertices_geometry.cc b/impeller/display_list/display_list_vertices_geometry.cc
index 52caeea..1966229 100644
--- a/impeller/display_list/display_list_vertices_geometry.cc
+++ b/impeller/display_list/display_list_vertices_geometry.cc
@@ -7,6 +7,7 @@
 #include "impeller/entity/contents/content_context.h"
 #include "impeller/entity/entity.h"
 #include "impeller/entity/position_color.vert.h"
+#include "impeller/entity/texture_fill.vert.h"
 #include "impeller/geometry/matrix.h"
 #include "impeller/geometry/path_builder.h"
 #include "impeller/geometry/point.h"
@@ -100,10 +101,38 @@
   }
 }
 
+std::optional<Rect> DLVerticesGeometry::GetTextureCoordinateCoverge() const {
+  if (!HasTextureCoordinates()) {
+    return std::nullopt;
+  }
+  auto vertex_count = vertices_->vertex_count();
+  auto* dl_texture_coordinates = vertices_->texture_coordinates();
+  if (vertex_count == 0) {
+    return std::nullopt;
+  }
+
+  auto left = dl_texture_coordinates[0].x();
+  auto top = dl_texture_coordinates[0].y();
+  auto right = dl_texture_coordinates[0].x();
+  auto bottom = dl_texture_coordinates[0].y();
+
+  for (auto i = 0; i < vertex_count; i++) {
+    left = std::min(left, dl_texture_coordinates[i].x());
+    top = std::min(top, dl_texture_coordinates[i].y());
+    right = std::max(right, dl_texture_coordinates[i].x());
+    bottom = std::max(bottom, dl_texture_coordinates[i].y());
+  }
+  return Rect::MakeLTRB(left, top, right, bottom);
+}
+
 bool DLVerticesGeometry::HasVertexColors() const {
   return vertices_->colors() != nullptr;
 }
 
+bool DLVerticesGeometry::HasTextureCoordinates() const {
+  return vertices_->texture_coordinates() != nullptr;
+}
+
 GeometryResult DLVerticesGeometry::GetPositionBuffer(
     const ContentContext& renderer,
     const Entity& entity,
@@ -225,12 +254,80 @@
 }
 
 GeometryResult DLVerticesGeometry::GetPositionUVBuffer(
+    Rect texture_coverage,
+    Matrix effect_transform,
     const ContentContext& renderer,
     const Entity& entity,
     RenderPass& pass) {
-  // TODO(jonahwilliams): support texture coordinates in vertices
-  // https://github.com/flutter/flutter/issues/109956
-  return {};
+  using VS = TexturePipeline::VertexShader;
+
+  auto index_count = normalized_indices_.size() == 0
+                         ? vertices_->index_count()
+                         : normalized_indices_.size();
+  auto vertex_count = vertices_->vertex_count();
+  auto* dl_indices = normalized_indices_.size() == 0
+                         ? vertices_->indices()
+                         : normalized_indices_.data();
+  auto* dl_vertices = vertices_->vertices();
+  auto* dl_texture_coordinates = vertices_->texture_coordinates();
+
+  auto size = texture_coverage.size;
+  auto origin = texture_coverage.origin;
+  std::vector<VS::PerVertexData> vertex_data(vertex_count);
+  {
+    for (auto i = 0; i < vertex_count; i++) {
+      auto sk_point = dl_vertices[i];
+      auto texture_coord = dl_texture_coordinates[i];
+      auto uv = effect_transform *
+                Point((texture_coord.x() - origin.x) / size.width,
+                      (texture_coord.y() - origin.y) / size.height);
+      // From experimentation we need to clamp these values to < 1.0 or else
+      // there can be flickering.
+      vertex_data[i] = {
+          .position = Point(sk_point.x(), sk_point.y()),
+          .texture_coords =
+              Point(std::clamp(uv.x, 0.0f, 1.0f - kEhCloseEnough),
+                    std::clamp(uv.y, 0.0f, 1.0f - kEhCloseEnough)),
+      };
+    }
+  }
+
+  size_t total_vtx_bytes = vertex_data.size() * sizeof(VS::PerVertexData);
+  size_t total_idx_bytes = index_count * sizeof(uint16_t);
+
+  DeviceBufferDescriptor buffer_desc;
+  buffer_desc.size = total_vtx_bytes + total_idx_bytes;
+  buffer_desc.storage_mode = StorageMode::kHostVisible;
+
+  auto buffer =
+      renderer.GetContext()->GetResourceAllocator()->CreateBuffer(buffer_desc);
+
+  if (!buffer->CopyHostBuffer(reinterpret_cast<uint8_t*>(vertex_data.data()),
+                              Range{0, total_vtx_bytes}, 0)) {
+    return {};
+  }
+  if (!buffer->CopyHostBuffer(
+          reinterpret_cast<uint8_t*>(const_cast<uint16_t*>(dl_indices)),
+          Range{0, total_idx_bytes}, total_vtx_bytes)) {
+    return {};
+  }
+
+  return GeometryResult{
+      .type = GetPrimitiveType(vertices_),
+      .vertex_buffer =
+          {
+              .vertex_buffer = {.buffer = buffer,
+                                .range = Range{0, total_vtx_bytes}},
+              .index_buffer = {.buffer = buffer,
+                               .range =
+                                   Range{total_vtx_bytes, total_idx_bytes}},
+              .index_count = index_count,
+              .index_type = IndexType::k16bit,
+          },
+      .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
+                   entity.GetTransformation(),
+      .prevent_overdraw = false,
+  };
 }
 
 GeometryVertexType DLVerticesGeometry::GetVertexType() const {
@@ -238,6 +335,11 @@
   if (dl_colors != nullptr) {
     return GeometryVertexType::kColor;
   }
+  auto* dl_texture_coordinates = vertices_->texture_coordinates();
+  if (dl_texture_coordinates != nullptr) {
+    return GeometryVertexType::kUV;
+  }
+
   return GeometryVertexType::kPosition;
 }
 
diff --git a/impeller/display_list/display_list_vertices_geometry.h b/impeller/display_list/display_list_vertices_geometry.h
index 3ebfa89..9a668f9 100644
--- a/impeller/display_list/display_list_vertices_geometry.h
+++ b/impeller/display_list/display_list_vertices_geometry.h
@@ -32,7 +32,9 @@
                                         RenderPass& pass) override;
 
   // |VerticesGeometry|
-  GeometryResult GetPositionUVBuffer(const ContentContext& renderer,
+  GeometryResult GetPositionUVBuffer(Rect texture_coverage,
+                                     Matrix effect_transform,
+                                     const ContentContext& renderer,
                                      const Entity& entity,
                                      RenderPass& pass) override;
 
@@ -50,6 +52,12 @@
   // |VerticesGeometry|
   bool HasVertexColors() const override;
 
+  // |VerticesGeometry|
+  bool HasTextureCoordinates() const override;
+
+  // |VerticesGeometry|
+  std::optional<Rect> GetTextureCoordinateCoverge() const override;
+
  private:
   void NormalizeIndices();
 
diff --git a/impeller/entity/contents/content_context.cc b/impeller/entity/contents/content_context.cc
index 2799a91..fda3198 100644
--- a/impeller/entity/contents/content_context.cc
+++ b/impeller/entity/contents/content_context.cc
@@ -250,6 +250,8 @@
   texture_blend_pipelines_[{}] =
       CreateDefaultPipeline<BlendPipeline>(*context_);
   texture_pipelines_[{}] = CreateDefaultPipeline<TexturePipeline>(*context_);
+  position_uv_pipelines_[{}] =
+      CreateDefaultPipeline<PositionUVPipeline>(*context_);
   tiled_texture_pipelines_[{}] =
       CreateDefaultPipeline<TiledTexturePipeline>(*context_);
   gaussian_blur_pipelines_[{}] =
diff --git a/impeller/entity/contents/content_context.h b/impeller/entity/contents/content_context.h
index 65bd2a0..c5a5788 100644
--- a/impeller/entity/contents/content_context.h
+++ b/impeller/entity/contents/content_context.h
@@ -54,7 +54,6 @@
 
 #include "impeller/entity/position.vert.h"
 #include "impeller/entity/position_color.vert.h"
-#include "impeller/entity/position_uv.vert.h"
 
 #include "impeller/scene/scene_context.h"
 #include "impeller/typographer/glyph_atlas.h"
@@ -123,6 +122,8 @@
 using BlendPipeline = RenderPipelineT<BlendVertexShader, BlendFragmentShader>;
 using TexturePipeline =
     RenderPipelineT<TextureFillVertexShader, TextureFillFragmentShader>;
+using PositionUVPipeline =
+    RenderPipelineT<TextureFillVertexShader, TiledTextureFillFragmentShader>;
 using TiledTexturePipeline = RenderPipelineT<TiledTextureFillVertexShader,
                                              TiledTextureFillFragmentShader>;
 using GaussianBlurPipeline =
@@ -362,6 +363,11 @@
     return GetPipeline(texture_pipelines_, opts);
   }
 
+  std::shared_ptr<Pipeline<PipelineDescriptor>> GetPositionUVPipeline(
+      ContentContextOptions opts) const {
+    return GetPipeline(position_uv_pipelines_, opts);
+  }
+
   std::shared_ptr<Pipeline<PipelineDescriptor>> GetTiledTexturePipeline(
       ContentContextOptions opts) const {
     return GetPipeline(tiled_texture_pipelines_, opts);
@@ -626,6 +632,7 @@
   mutable Variants<RRectBlurPipeline> rrect_blur_pipelines_;
   mutable Variants<BlendPipeline> texture_blend_pipelines_;
   mutable Variants<TexturePipeline> texture_pipelines_;
+  mutable Variants<PositionUVPipeline> position_uv_pipelines_;
   mutable Variants<TiledTexturePipeline> tiled_texture_pipelines_;
   mutable Variants<GaussianBlurPipeline> gaussian_blur_pipelines_;
   mutable Variants<GaussianBlurDecalPipeline> gaussian_blur_decal_pipelines_;
diff --git a/impeller/entity/contents/contents.h b/impeller/entity/contents/contents.h
index 2c5f3ab..a4ff06b 100644
--- a/impeller/entity/contents/contents.h
+++ b/impeller/entity/contents/contents.h
@@ -75,9 +75,19 @@
   virtual bool ShouldRender(const Entity& entity,
                             const std::optional<Rect>& stencil_coverage) const;
 
+  /// @brief  Return the color source's intrinsic size, if available.
+  ///
+  /// For example, a gradient has a size based on its end and beginning points,
+  /// ignoring any tiling. Solid colors and runtime effects have no size.
+  std::optional<Size> ColorSourceSize() const { return color_source_size_; }
+
+  void SetColorSourceSize(Size size) { color_source_size_ = size; }
+
  protected:
 
  private:
+  std::optional<Size> color_source_size_;
+
   FML_DISALLOW_COPY_AND_ASSIGN(Contents);
 };
 
diff --git a/impeller/entity/contents/tiled_texture_contents.cc b/impeller/entity/contents/tiled_texture_contents.cc
index d50af65..d9e8a80 100644
--- a/impeller/entity/contents/tiled_texture_contents.cc
+++ b/impeller/entity/contents/tiled_texture_contents.cc
@@ -56,6 +56,13 @@
   if (texture_ == nullptr) {
     return true;
   }
+  // TODO(jonahwilliams): this is a special case for VerticesGeometry which
+  // implements GetPositionUVBuffer. The general geometry case does not use
+  // this method (see note below).
+  auto geometry = GetGeometry();
+  if (geometry->GetVertexType() == GeometryVertexType::kUV) {
+    return RenderVertices(renderer, entity, pass);
+  }
 
   using VS = TiledTextureFillVertexShader;
   using FS = TiledTextureFillFragmentShader;
@@ -67,7 +74,6 @@
 
   auto& host_buffer = pass.GetTransientsBuffer();
 
-  auto geometry = GetGeometry();
   auto geometry_result =
       GetGeometry()->GetPositionBuffer(renderer, entity, pass);
 
@@ -132,4 +138,72 @@
   return true;
 }
 
+bool TiledTextureContents::RenderVertices(const ContentContext& renderer,
+                                          const Entity& entity,
+                                          RenderPass& pass) const {
+  using VS = PositionUVPipeline::VertexShader;
+  using FS = PositionUVPipeline::FragmentShader;
+
+  const auto texture_size = texture_->GetSize();
+  if (texture_size.IsEmpty()) {
+    return true;
+  }
+
+  auto& host_buffer = pass.GetTransientsBuffer();
+
+  auto geometry_result = GetGeometry()->GetPositionUVBuffer(
+      Rect::MakeSize(texture_size), GetInverseMatrix(), renderer, entity, pass);
+
+  VS::FrameInfo frame_info;
+  frame_info.mvp = geometry_result.transform;
+
+  FS::FragInfo frag_info;
+  frag_info.texture_sampler_y_coord_scale = texture_->GetYCoordScale();
+  frag_info.x_tile_mode = static_cast<Scalar>(x_tile_mode_);
+  frag_info.y_tile_mode = static_cast<Scalar>(y_tile_mode_);
+  frag_info.alpha = GetAlpha();
+
+  Command cmd;
+  cmd.label = "PositionUV";
+  cmd.stencil_reference = entity.GetStencilDepth();
+
+  auto options = OptionsFromPassAndEntity(pass, entity);
+  if (geometry_result.prevent_overdraw) {
+    options.stencil_compare = CompareFunction::kEqual;
+    options.stencil_operation = StencilOperation::kIncrementClamp;
+  }
+  options.primitive_type = geometry_result.type;
+  cmd.pipeline = renderer.GetPositionUVPipeline(options);
+
+  cmd.BindVertices(geometry_result.vertex_buffer);
+  VS::BindFrameInfo(cmd, host_buffer.EmplaceUniform(frame_info));
+  FS::BindFragInfo(cmd, host_buffer.EmplaceUniform(frag_info));
+  if (color_filter_.has_value()) {
+    auto filtered_texture = CreateFilterTexture(renderer);
+    if (!filtered_texture.has_value()) {
+      return false;
+    }
+    FS::BindTextureSampler(
+        cmd, filtered_texture.value(),
+        renderer.GetContext()->GetSamplerLibrary()->GetSampler(
+            sampler_descriptor_));
+  } else {
+    FS::BindTextureSampler(
+        cmd, texture_,
+        renderer.GetContext()->GetSamplerLibrary()->GetSampler(
+            sampler_descriptor_));
+  }
+
+  if (!pass.AddCommand(std::move(cmd))) {
+    return false;
+  }
+
+  if (geometry_result.prevent_overdraw) {
+    auto restore = ClipRestoreContents();
+    restore.SetRestoreCoverage(GetCoverage(entity));
+    return restore.Render(renderer, entity, pass);
+  }
+  return true;
+}
+
 }  // namespace impeller
diff --git a/impeller/entity/contents/tiled_texture_contents.h b/impeller/entity/contents/tiled_texture_contents.h
index f1db94f..0f0c6b3 100644
--- a/impeller/entity/contents/tiled_texture_contents.h
+++ b/impeller/entity/contents/tiled_texture_contents.h
@@ -53,6 +53,10 @@
   std::optional<std::shared_ptr<Texture>> CreateFilterTexture(
       const ContentContext& renderer) const;
 
+  bool RenderVertices(const ContentContext& renderer,
+                      const Entity& entity,
+                      RenderPass& pass) const;
+
   std::shared_ptr<Texture> texture_;
   SamplerDescriptor sampler_descriptor_ = {};
   Entity::TileMode x_tile_mode_ = Entity::TileMode::kClamp;
diff --git a/impeller/entity/contents/vertices_contents.cc b/impeller/entity/contents/vertices_contents.cc
index a7b6c0d..de2954b 100644
--- a/impeller/entity/contents/vertices_contents.cc
+++ b/impeller/entity/contents/vertices_contents.cc
@@ -10,7 +10,6 @@
 #include "impeller/entity/contents/texture_contents.h"
 #include "impeller/entity/position.vert.h"
 #include "impeller/entity/position_color.vert.h"
-#include "impeller/entity/position_uv.vert.h"
 #include "impeller/entity/vertices.frag.h"
 #include "impeller/geometry/color.h"
 #include "impeller/renderer/formats.h"
@@ -48,23 +47,101 @@
   blend_mode_ = blend_mode;
 }
 
+const std::shared_ptr<Contents>& VerticesContents::GetSourceContents() const {
+  return src_contents_;
+}
+
 bool VerticesContents::Render(const ContentContext& renderer,
                               const Entity& entity,
                               RenderPass& pass) const {
   if (blend_mode_ == BlendMode::kClear) {
     return true;
   }
+  std::shared_ptr<Contents> src_contents = src_contents_;
+  if (geometry_->HasTextureCoordinates()) {
+    auto contents = std::make_shared<VerticesUVContents>(*this);
+    if (!geometry_->HasVertexColors()) {
+      contents->SetAlpha(alpha_);
+      return contents->Render(renderer, entity, pass);
+    }
+    src_contents = contents;
+  }
+
   auto dst_contents = std::make_shared<VerticesColorContents>(*this);
 
   auto contents = ColorFilterContents::MakeBlend(
       blend_mode_, {FilterInput::Make(dst_contents, false),
-                    FilterInput::Make(src_contents_, false)});
+                    FilterInput::Make(src_contents, false)});
   contents->SetAlpha(alpha_);
 
   return contents->Render(renderer, entity, pass);
 }
 
 //------------------------------------------------------
+// VerticesUVContents
+
+VerticesUVContents::VerticesUVContents(const VerticesContents& parent)
+    : parent_(parent) {}
+
+VerticesUVContents::~VerticesUVContents() {}
+
+std::optional<Rect> VerticesUVContents::GetCoverage(
+    const Entity& entity) const {
+  return parent_.GetCoverage(entity);
+}
+
+void VerticesUVContents::SetAlpha(Scalar alpha) {
+  alpha_ = alpha;
+}
+
+bool VerticesUVContents::Render(const ContentContext& renderer,
+                                const Entity& entity,
+                                RenderPass& pass) const {
+  using VS = TexturePipeline::VertexShader;
+  using FS = TexturePipeline::FragmentShader;
+
+  auto src_contents = parent_.GetSourceContents();
+
+  auto snapshot = src_contents->RenderToSnapshot(renderer, entity);
+  if (!snapshot.has_value()) {
+    return false;
+  }
+
+  Command cmd;
+  cmd.label = "VerticesUV";
+  auto& host_buffer = pass.GetTransientsBuffer();
+  auto geometry = parent_.GetGeometry();
+
+  auto coverage = src_contents->GetCoverage(Entity{});
+  if (!coverage.has_value()) {
+    return false;
+  }
+  auto geometry_result = geometry->GetPositionUVBuffer(
+      coverage.value(), Matrix(), renderer, entity, pass);
+  auto opts = OptionsFromPassAndEntity(pass, entity);
+  opts.primitive_type = geometry_result.type;
+  cmd.pipeline = renderer.GetTexturePipeline(opts);
+  cmd.stencil_reference = entity.GetStencilDepth();
+  cmd.BindVertices(geometry_result.vertex_buffer);
+
+  VS::FrameInfo frame_info;
+  frame_info.mvp = geometry_result.transform;
+  frame_info.texture_sampler_y_coord_scale =
+      snapshot->texture->GetYCoordScale();
+  VS::BindFrameInfo(cmd, host_buffer.EmplaceUniform(frame_info));
+
+  FS::FragInfo frag_info;
+  frag_info.alpha = alpha_ * snapshot->opacity;
+  FS::BindFragInfo(cmd, host_buffer.EmplaceUniform(frag_info));
+
+  FS::BindTextureSampler(cmd, snapshot->texture,
+                         renderer.GetContext()->GetSamplerLibrary()->GetSampler(
+                             snapshot->sampler_descriptor));
+
+  return pass.AddCommand(std::move(cmd));
+}
+
+//------------------------------------------------------
 // VerticesColorContents
 
 VerticesColorContents::VerticesColorContents(const VerticesContents& parent)
@@ -72,7 +149,6 @@
 
 VerticesColorContents::~VerticesColorContents() {}
 
-// |Contents|
 std::optional<Rect> VerticesColorContents::GetCoverage(
     const Entity& entity) const {
   return parent_.GetCoverage(entity);
@@ -82,7 +158,6 @@
   alpha_ = alpha;
 }
 
-// |Contents|
 bool VerticesColorContents::Render(const ContentContext& renderer,
                                    const Entity& entity,
                                    RenderPass& pass) const {
@@ -99,6 +174,7 @@
   auto opts = OptionsFromPassAndEntity(pass, entity);
   opts.primitive_type = geometry_result.type;
   cmd.pipeline = renderer.GetGeometryColorPipeline(opts);
+  cmd.stencil_reference = entity.GetStencilDepth();
   cmd.BindVertices(geometry_result.vertex_buffer);
 
   VS::FrameInfo frame_info;
diff --git a/impeller/entity/contents/vertices_contents.h b/impeller/entity/contents/vertices_contents.h
index b2459d5..88facd3 100644
--- a/impeller/entity/contents/vertices_contents.h
+++ b/impeller/entity/contents/vertices_contents.h
@@ -35,6 +35,8 @@
 
   std::shared_ptr<VerticesGeometry> GetGeometry() const;
 
+  const std::shared_ptr<Contents>& GetSourceContents() const;
+
   // |Contents|
   std::optional<Rect> GetCoverage(const Entity& entity) const override;
 
@@ -75,4 +77,27 @@
   FML_DISALLOW_COPY_AND_ASSIGN(VerticesColorContents);
 };
 
+class VerticesUVContents final : public Contents {
+ public:
+  explicit VerticesUVContents(const VerticesContents& parent);
+
+  ~VerticesUVContents() override;
+
+  // |Contents|
+  std::optional<Rect> GetCoverage(const Entity& entity) const override;
+
+  // |Contents|
+  bool Render(const ContentContext& renderer,
+              const Entity& entity,
+              RenderPass& pass) const override;
+
+  void SetAlpha(Scalar alpha);
+
+ private:
+  const VerticesContents& parent_;
+  Scalar alpha_ = 1.0;
+
+  FML_DISALLOW_COPY_AND_ASSIGN(VerticesUVContents);
+};
+
 }  // namespace impeller
diff --git a/impeller/entity/geometry.cc b/impeller/entity/geometry.cc
index c86eaa6..b610e03 100644
--- a/impeller/entity/geometry.cc
+++ b/impeller/entity/geometry.cc
@@ -17,6 +17,14 @@
 
 Geometry::~Geometry() = default;
 
+GeometryResult Geometry::GetPositionUVBuffer(Rect texture_coverage,
+                                             Matrix effect_transform,
+                                             const ContentContext& renderer,
+                                             const Entity& entity,
+                                             RenderPass& pass) {
+  return {};
+}
+
 // static
 std::unique_ptr<Geometry> Geometry::MakeFillPath(const Path& path) {
   return std::make_unique<FillPathGeometry>(path);
diff --git a/impeller/entity/geometry.h b/impeller/entity/geometry.h
index f595eee..edc59cd 100644
--- a/impeller/entity/geometry.h
+++ b/impeller/entity/geometry.h
@@ -65,6 +65,12 @@
                                            const Entity& entity,
                                            RenderPass& pass) = 0;
 
+  virtual GeometryResult GetPositionUVBuffer(Rect texture_coverage,
+                                             Matrix effect_transform,
+                                             const ContentContext& renderer,
+                                             const Entity& entity,
+                                             RenderPass& pass);
+
   virtual GeometryVertexType GetVertexType() const = 0;
 
   virtual std::optional<Rect> GetCoverage(const Matrix& transform) const = 0;
@@ -77,11 +83,11 @@
                                                 const Entity& entity,
                                                 RenderPass& pass) = 0;
 
-  virtual GeometryResult GetPositionUVBuffer(const ContentContext& renderer,
-                                             const Entity& entity,
-                                             RenderPass& pass) = 0;
-
   virtual bool HasVertexColors() const = 0;
+
+  virtual bool HasTextureCoordinates() const = 0;
+
+  virtual std::optional<Rect> GetTextureCoordinateCoverge() const = 0;
 };
 
 /// @brief A geometry that is created from a filled path object.