[graphite] Flesh out Device/SDC a bit more

Renames SDCTask -> RenderPassTask and adds comments describing its
purpose. Has SDC use a DrawList and expose similar drawing functions.
Stubs out all Device methods that will eventually need to be supported,
but groups them into in-scope and out-of-scope sections. Implements the
drawing functions in terms of drawPath() and has drawPath() invoke the
SDC functions (with untested support for path effects, hairline, etc.).

Bug: skia:12466
Change-Id: Id661fc987aeb35f0dfa536c0cb0b6210742cf32c
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/452233
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/experimental/graphite/src/Device.cpp b/experimental/graphite/src/Device.cpp
index b6226ac..43b89f5 100644
--- a/experimental/graphite/src/Device.cpp
+++ b/experimental/graphite/src/Device.cpp
@@ -8,8 +8,17 @@
 #include "experimental/graphite/src/Device.h"
 
 #include "experimental/graphite/include/SkStuff.h"
+#include "experimental/graphite/src/DrawList.h"
 #include "experimental/graphite/src/SurfaceDrawContext.h"
 
+#include "include/core/SkPath.h"
+#include "include/core/SkPathBuilder.h"
+#include "include/core/SkPathEffect.h"
+#include "include/core/SkStrokeRec.h"
+
+#include "src/core/SkMatrixPriv.h"
+#include "src/core/SkPaintPriv.h"
+
 namespace skgpu {
 
 sk_sp<Device> Device::Make(const SkImageInfo& ii) {
@@ -22,8 +31,16 @@
 }
 
 Device::Device(sk_sp<SurfaceDrawContext> sdc)
-    : SkBaseDevice(sdc->imageInfo(), SkSurfaceProps())
-    , fSDC(std::move(sdc)) {
+        : SkBaseDevice(sdc->imageInfo(), SkSurfaceProps())
+        , fSDC(std::move(sdc)) {
+    SkASSERT(SkToBool(sdc));
+}
+
+SkBaseDevice* Device::onCreateDevice(const CreateInfo& info, const SkPaint*) {
+    // TODO: Inspect the paint and create info to determine if there's anything that has to be
+    // modified to support inline subpasses.
+    // TODO: onCreateDevice really should return sk_sp<SkBaseDevice>...
+    return Make(info.fInfo).release();
 }
 
 sk_sp<SkSurface> Device::makeSurface(const SkImageInfo& ii, const SkSurfaceProps& /* props */) {
@@ -31,9 +48,158 @@
 }
 
 bool Device::onReadPixels(const SkPixmap& pm, int x, int y) {
+    // TODO: If we're reading back pixels we need to push deferred clip draws and snap off the draw
+    // task so we can have a read back task added to the graph, the same as will be done if the
+    // Device has a snapped special image or is drawn into another device directly.
     // TODO: actually do a read back
     pm.erase(SK_ColorGREEN);
     return true;
 }
 
+void Device::drawPaint(const SkPaint& paint) {
+    SkRect deviceBounds = SkRect::Make(this->devClipBounds());
+    SkM44 devToLocal;
+    if (!this->localToDevice44().invert(&devToLocal)) {
+        // TBD: This matches legacy behavior for drawPaint() that requires local coords, although
+        // v1 handles arbitrary transforms when the paint is solid color because it just fills the
+        // device bounds directly. In the new world it might be nice to have non-invertible
+        // transforms formalized (i.e. no drawing ever, handled at SkCanvas level possibly?)
+        return;
+    }
+    SkRect localCoveringBounds = SkMatrixPriv::MapRect(devToLocal, deviceBounds);
+    this->drawRect(localCoveringBounds, paint);
+}
+
+void Device::drawRect(const SkRect& r, const SkPaint& paint) {
+    // TODO: If the SDC primitive is a rrect (and no simpler), this can be wasted effort since
+    // SkCanvas checks SkRRects for being a rect and reduces it, only for Device to rebuild it
+    // It would be less effort if we can skip the validation of SkRRect ctors here.
+    // TBD: For now rects are paths too, but they may become an SDC primitive
+    this->drawPath(SkPath::Rect(r), paint, /*pathIsMutable=*/true);
+}
+
+void Device::drawOval(const SkRect& oval, const SkPaint& paint) {
+    // TODO: This has wasted effort from the SkCanvas level since it instead converts rrects that
+    // happen to be ovals into this, only for us to go right back to rrect.
+
+    // Ovals are always a simplification of round rects
+    this->drawRRect(SkRRect::MakeOval(oval), paint);
+}
+
+void Device::drawRRect(const SkRRect& rr, const SkPaint& paint) {
+    // TBD: If the SDC has a rrect primitive, this won't need to be converted to a path
+    this->drawPath(SkPath::RRect(rr), paint, /*pathIsMutable=*/true);
+}
+
+void Device::drawPoints(SkCanvas::PointMode mode, size_t count,
+                        const SkPoint* points, const SkPaint& paint) {
+    // TODO: I'm [ml] not sure either CPU or GPU backend really has a fast path for this that
+    // isn't captured by drawOval and drawLine, so could easily be moved into SkCanvas.
+    if (mode == SkCanvas::kPoints_PointMode) {
+        SkPaint filled = paint;
+        filled.setStyle(SkPaint::kFill_Style);
+        float radius = 0.5f * paint.getStrokeWidth();
+        for (size_t i = 0; i < count; ++i) {
+            SkRect cap = SkRect::MakeLTRB(points[i].fX - radius, points[i].fY - radius,
+                                          points[i].fX + radius, points[i].fY + radius);
+            if (paint.getStrokeCap() == SkPaint::kRound_Cap) {
+                this->drawOval(cap, filled);
+            } else {
+                this->drawRect(cap, filled);
+            }
+        }
+    } else {
+        size_t inc = (mode == SkCanvas::kLines_PointMode) ? 2 : 1;
+        SkPathBuilder builder;
+        for (size_t i = 0; i < count; i += inc) {
+            builder.moveTo(points[i]);
+            builder.lineTo(points[i + 1]);
+            this->drawPath(builder.detach(), paint, /*pathIsMutable=*/true);
+        }
+    }
+}
+
+void Device::drawPath(const SkPath& path, const SkPaint& paint, bool pathIsMutable) {
+    // Heavy weight paint options like path effects, mask filters, and stroke-and-fill style are
+    // applied on the CPU by generating a new path and recursing on drawPath().
+    if (paint.getPathEffect()) {
+        // Apply the path effect before anything else
+        // TODO: If asADash() returns true and the base path matches the dashing fast path, then
+        // that should be detected now as well.
+        // TODO: This logic is also a candidate for moving to SkCanvas if SkDevice exposes a faster
+        // dash path.
+
+        // Strip off path effect
+        SkPaint noPE = paint;
+        noPE.setPathEffect(nullptr);
+
+        float scaleFactor = SkPaintPriv::ComputeResScaleForStroking(this->localToDevice());
+        SkStrokeRec stroke(paint, scaleFactor);
+        SkPath dst;
+        if (paint.getPathEffect()->filterPath(&dst, path, &stroke,
+                                              nullptr, this->localToDevice())) {
+            // Adjust paint style to reflect modifications to stroke rec
+            stroke.applyToPaint(&noPE);
+            this->drawPath(dst, noPE, /*pathIsMutable=*/true);
+            return;
+        } else {
+            // TBD: This warning should go through the general purpose graphite logging system
+            SkDebugf("[graphite] WARNING - Path effect failed to apply, drawing original path.\n");
+            this->drawPath(path, noPE, pathIsMutable);
+            return;
+        }
+    }
+
+    // TODO: Handle mask filters, ignored for now but would be applied at this point. My[ml]
+    // thinking is that if there's a mask filter we call a helper function with the path and the
+    // paint, which returns a coverage mask. Then we do a rectangular draw sampling the mask and
+    // handling the rest of the paint's shading. I don't think that's really any different from
+    // the way it is right now. (not 100% sure, but this may also be a reasonable approach for CPU
+    // so could make SkCanvas handle all path effects, image filters, and mask filters and Devices
+    // only need to handle shaders, color filters, and blenders).
+    if (paint.getMaskFilter()) {
+        return;
+    }
+
+    // Resolve stroke-and-fill -> fill, and hairline -> stroke since the SDC only supports stroke
+    // or fill for path rendering.
+    if (paint.getStyle() == SkPaint::kStrokeAndFill_Style) {
+        // TODO: Could const-cast path when pathIsMutable is true, might not be worth complexity...
+        SkPath simplified;
+        SkPaint styledPaint = paint;
+        if (paint.getFillPath(path, &simplified, nullptr, this->localToDevice())) {
+            styledPaint.setStyle(SkPaint::kFill_Style);
+        } else {
+            styledPaint.setStyle(SkPaint::kStroke_Style);
+            styledPaint.setStrokeWidth(0.f);
+        }
+        this->drawPath(simplified, styledPaint, /*pathIsMutable=*/true);
+        return;
+    }
+
+    // TODO: Implement clipping and z determination
+    SkIRect scissor = this->devClipBounds();
+
+    auto blendMode = paint.asBlendMode();
+    PaintParams shading{paint.getColor4f(),
+                        blendMode.has_value() ? *blendMode : SkBlendMode::kSrcOver,
+                        paint.refShader()};
+    if (paint.getStyle() == SkPaint::kStroke_Style) {
+        StrokeParams stroke{paint.getStrokeWidth(), paint.getStrokeMiter(),
+                            paint.getStrokeJoin(), paint.getStrokeCap()};
+        if (paint.getStrokeWidth() <= 0.f) {
+            // Handle hairlines by transforming the control points into device space and drawing
+            // that path with a stroke width of 1 and the identity matrix
+            // FIXME: This doesn't work if the shading requires local coords...
+            SkPath devicePath = path.makeTransform(this->localToDevice());
+            stroke.fWidth = 1.f;
+            fSDC->strokePath(SkM44(), devicePath, stroke, scissor, 0, 0, &shading);
+        } else {
+            fSDC->strokePath(this->localToDevice44(), path, stroke, scissor, 0, 0, &shading);
+        }
+    } else {
+        fSDC->fillPath(this->localToDevice44(), path, scissor, 0, 0, &shading);
+    }
+}
+
 } // namespace skgpu
diff --git a/experimental/graphite/src/Device.h b/experimental/graphite/src/Device.h
index cbe1ea1..d09535c 100644
--- a/experimental/graphite/src/Device.h
+++ b/experimental/graphite/src/Device.h
@@ -19,31 +19,85 @@
     static sk_sp<Device> Make(const SkImageInfo&);
 
 protected:
+    // Clipping
+    void onSave() override {}
+    void onRestore() override {}
+
     bool onClipIsAA() const override { return false; }
     bool onClipIsWideOpen() const override { return false; }
-    void onAsRgnClip(SkRegion*) const override { }
     ClipType onGetClipType() const override { return ClipType::kEmpty; }
     SkIRect onDevClipBounds() const override { return {}; }
 
-    void drawPaint(const SkPaint& paint) override {}
-    void drawPoints(SkCanvas::PointMode mode, size_t count,
-                    const SkPoint[], const SkPaint& paint) override {}
-    void drawRect(const SkRect& r, const SkPaint& paint) override {}
-    void drawOval(const SkRect& oval, const SkPaint& paint) override {}
-    void drawRRect(const SkRRect& rr, const SkPaint& paint) override {}
-    void drawPath(const SkPath& path,
-                  const SkPaint& paint,
-                  bool pathIsMutable = false) override {}
+    void onClipRect(const SkRect& rect, SkClipOp, bool aa) override {}
+    void onClipRRect(const SkRRect& rrect, SkClipOp, bool aa) override {}
+    void onClipPath(const SkPath& path, SkClipOp, bool aa) override {}
 
+    // Drawing
+    void drawPaint(const SkPaint& paint) override;
+    void drawRect(const SkRect& r, const SkPaint& paint) override;
+    void drawOval(const SkRect& oval, const SkPaint& paint) override;
+    void drawRRect(const SkRRect& rr, const SkPaint& paint) override;
+    void drawPoints(SkCanvas::PointMode mode, size_t count,
+                    const SkPoint[], const SkPaint& paint) override;
+    void drawPath(const SkPath& path, const SkPaint& paint, bool pathIsMutable = false) override;
+
+    // No need to specialize drawDRRect, drawArc, drawRegion, drawPatch as the default impls all
+    // route to drawPath, drawRect, or drawVertices as desired.
+
+    // Pixel management
+    sk_sp<SkSurface> makeSurface(const SkImageInfo&, const SkSurfaceProps&) override;
+    SkBaseDevice* onCreateDevice(const CreateInfo&, const SkPaint*) override;
+
+    bool onReadPixels(const SkPixmap&, int x, int y) override;
+
+    /*
+     * TODO: These functions are not in scope to be implemented yet, but will need to be. Call them
+     * out explicitly so it's easy to keep tabs on how close feature-complete actually is.
+     */
+
+    void onAsRgnClip(SkRegion*) const override {}
+    void onClipShader(sk_sp<SkShader>) override {}
+    void onClipRegion(const SkRegion& deviceRgn, SkClipOp) override {}
+    void onReplaceClip(const SkIRect& rect) override {}
+
+    bool onWritePixels(const SkPixmap&, int x, int y) override { return false; }
+
+    // TODO: This will likely be implemented with the same primitive building block that drawRect
+    // and drawRRect will rely on.
+    void drawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4],
+                        SkCanvas::QuadAAFlags aaFlags, const SkColor4f& color,
+                        SkBlendMode mode) override {}
+
+    void drawEdgeAAImageSet(const SkCanvas::ImageSetEntry[], int count,
+                            const SkPoint dstClips[], const SkMatrix preViewMatrices[],
+                            const SkSamplingOptions&, const SkPaint&,
+                            SkCanvas::SrcRectConstraint) override {}
+
+    // TODO: These image drawing APIs can likely be implemented with the same primitive building
+    // block that drawEdgeAAImageSet will use.
     void drawImageRect(const SkImage*, const SkRect* src, const SkRect& dst,
                        const SkSamplingOptions&, const SkPaint&,
                        SkCanvas::SrcRectConstraint) override {}
+    void drawImageLattice(const SkImage*, const SkCanvas::Lattice&,
+                          const SkRect& dst, SkFilterMode, const SkPaint&) override {}
+    void drawAtlas(const SkImage* atlas, const SkRSXform[], const SkRect[],
+                   const SkColor[], int count, SkBlendMode, const SkSamplingOptions&,
+                   const SkPaint&) override {}
 
+    void drawDrawable(SkDrawable*, const SkMatrix*, SkCanvas*) override {}
     void drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) override {}
+    void drawShadow(const SkPath&, const SkDrawShadowRec&) override {}
     void onDrawGlyphRunList(const SkGlyphRunList& glyphRunList, const SkPaint& paint) override {}
 
-    sk_sp<SkSurface> makeSurface(const SkImageInfo&, const SkSurfaceProps&) override;
-    bool onReadPixels(const SkPixmap&, int x, int y) override;
+    void drawDevice(SkBaseDevice*, const SkSamplingOptions&, const SkPaint&) override {}
+    void drawSpecial(SkSpecialImage*, const SkMatrix& localToDevice,
+                     const SkSamplingOptions&, const SkPaint&) override {}
+
+    sk_sp<SkSpecialImage> makeSpecial(const SkBitmap&) override { return nullptr; }
+    sk_sp<SkSpecialImage> makeSpecial(const SkImage*) override { return nullptr; }
+    sk_sp<SkSpecialImage> snapSpecial(const SkIRect& subset, bool forceCopy = false) override {
+        return nullptr;
+    }
 
 private:
     Device(sk_sp<SurfaceDrawContext>);
diff --git a/experimental/graphite/src/DrawCommandList.h b/experimental/graphite/src/DrawList.h
similarity index 99%
rename from experimental/graphite/src/DrawCommandList.h
rename to experimental/graphite/src/DrawList.h
index d09edeb..16cbd03 100644
--- a/experimental/graphite/src/DrawCommandList.h
+++ b/experimental/graphite/src/DrawList.h
@@ -54,7 +54,7 @@
  * it to. Draw-specific simplification, style application, and advanced clipping should be handled
  * at a higher layer.
  */
-class DrawCommandList {
+class DrawList {
 public:
     // TBD: Do we always need the inverse deviceToLocal matrix? If not the entire matrix, do we need
     // some other matrix-dependent value (e.g. scale factor) frequently? Since the localToDevice
diff --git a/experimental/graphite/src/RenderPassTask.cpp b/experimental/graphite/src/RenderPassTask.cpp
new file mode 100644
index 0000000..ca906b4
--- /dev/null
+++ b/experimental/graphite/src/RenderPassTask.cpp
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "experimental/graphite/src/RenderPassTask.h"
+
+#include "experimental/graphite/src/DrawList.h"
+
+namespace skgpu {
+
+sk_sp<RenderPassTask> RenderPassTask::Make(sk_sp<Task> prior, std::unique_ptr<DrawList> cmds) {
+    (void) prior; // unused for now, might be newTask.addDependency(prior)?
+    return sk_sp<RenderPassTask>(new RenderPassTask(std::move(cmds)));
+}
+
+RenderPassTask::RenderPassTask(std::unique_ptr<DrawList> cmds) : fCmds(std::move(cmds)) {}
+RenderPassTask::~RenderPassTask() = default;
+
+} // namespace skgpu
diff --git a/experimental/graphite/src/RenderPassTask.h b/experimental/graphite/src/RenderPassTask.h
new file mode 100644
index 0000000..7904138
--- /dev/null
+++ b/experimental/graphite/src/RenderPassTask.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef skgpu_RenderPassTask_DEFINED
+#define skgpu_RenderPassTask_DEFINED
+
+#include "experimental/graphite/src/Task.h"
+
+namespace skgpu {
+
+class DrawList;
+
+/**
+ * RenderPassTask handles preparing and recording DrawLists into a single render pass within a
+ * command buffer. If the backend supports subpasses, and the DrawLists/surfaces are compatible, a
+ * RenderPassTask can execute multiple DrawLists across different surfaces as subpasses nested
+ * within a single render pass. If there is no such support, a RenderPassTask is one-to-one with a
+ * "render pass" to specific surface.
+ */
+class RenderPassTask final : public Task {
+public:
+    // TODO: 'prior' isn't actually used yet but is here to represent the dependency between a
+    // series of RenderPassTasks.
+    // TODO: RenderPassTask also needs to know the surface the commads are sent to (might not need
+    // to be explicit if there's a render pass list that collects {draw lists + surface}).
+    static sk_sp<RenderPassTask> Make(sk_sp<Task> prior, std::unique_ptr<DrawList> cmds);
+
+    ~RenderPassTask() override;
+
+private:
+    RenderPassTask(std::unique_ptr<DrawList> cmds);
+
+    // TODO: Seems very likely that the RenderPassTask will store an optimized/immutable
+    // representation derived from a DrawList and not directly a DrawList.
+    std::unique_ptr<DrawList> fCmds;
+};
+
+} // namespace skgpu
+
+#endif // skgpu_RenderPassTask_DEFINED
diff --git a/experimental/graphite/src/SDCTask.cpp b/experimental/graphite/src/SDCTask.cpp
deleted file mode 100644
index 95a19a0..0000000
--- a/experimental/graphite/src/SDCTask.cpp
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright 2021 Google LLC
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "experimental/graphite/src/SDCTask.h"
-
-namespace skgpu {
-
-sk_sp<SDCTask> SDCTask::Make() {
-    return sk_sp<SDCTask>(new SDCTask());
-}
-
-SDCTask::SDCTask() {}
-SDCTask::~SDCTask() {}
-
-} // namespace skgpu
diff --git a/experimental/graphite/src/SDCTask.h b/experimental/graphite/src/SDCTask.h
deleted file mode 100644
index 35a0429..0000000
--- a/experimental/graphite/src/SDCTask.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2021 Google LLC
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef skgpu_SDCTask_DEFINED
-#define skgpu_SDCTask_DEFINED
-
-#include "experimental/graphite/src/Task.h"
-
-namespace skgpu {
-
-class SDCTask final : public Task {
-public:
-    static sk_sp<SDCTask> Make();
-
-    ~SDCTask() override;
-
-private:
-    SDCTask();
-};
-
-} // namespace skgpu
-
-#endif // skgpu_SDCTask_DEFINED
diff --git a/experimental/graphite/src/SurfaceDrawContext.cpp b/experimental/graphite/src/SurfaceDrawContext.cpp
index bfb7a93..e10e517 100644
--- a/experimental/graphite/src/SurfaceDrawContext.cpp
+++ b/experimental/graphite/src/SurfaceDrawContext.cpp
@@ -7,24 +7,56 @@
 
 #include "experimental/graphite/src/SurfaceDrawContext.h"
 
-#include "experimental/graphite/src/SDCTask.h"
+#include "experimental/graphite/src/DrawList.h"
+#include "experimental/graphite/src/RenderPassTask.h"
 
 namespace skgpu {
 
 sk_sp<SurfaceDrawContext> SurfaceDrawContext::Make(const SkImageInfo& ii) {
-    sk_sp<SDCTask> task = SDCTask::Make();
-    if (!task) {
-        return nullptr;
-    }
-
-    return sk_sp<SurfaceDrawContext>(new SurfaceDrawContext(ii, std::move(task)));
+    return sk_sp<SurfaceDrawContext>(new SurfaceDrawContext(ii));
 }
 
-SurfaceDrawContext::SurfaceDrawContext(const SkImageInfo& ii, sk_sp<SDCTask> task)
-    : fImageInfo(ii)
-    , fTask(std::move(task)) {
+SurfaceDrawContext::SurfaceDrawContext(const SkImageInfo& ii)
+        : fImageInfo(ii)
+        , fPendingDraws(std::make_unique<DrawList>())
+        , fTail(nullptr) {
+    // TBD - Will probably want DrawLists (and its internal commands) to come from an arena
+    // that the SDC manages.
 }
 
-SurfaceDrawContext::~SurfaceDrawContext() {}
+SurfaceDrawContext::~SurfaceDrawContext() {
+    // If the SDC is destroyed and there are pending commands, they won't be drawn. Maybe that's ok
+    // but for now consider it a bug for not calling snapDrawTask().
+    SkASSERT(fPendingDraws->count() == 0);
+}
+
+void SurfaceDrawContext::fillPath(const SkM44& localToDevice,
+                                  const SkPath& path,
+                                  const SkIRect& scissor,
+                                  uint16_t sortZ,
+                                  uint16_t testZ,
+                                  const PaintParams* paint) {
+    fPendingDraws->fillPath(localToDevice, path, scissor, sortZ, testZ, paint);
+}
+
+void SurfaceDrawContext::strokePath(const SkM44& localToDevice,
+                                    const SkPath& path,
+                                    const StrokeParams& stroke,
+                                    const SkIRect& scissor,
+                                    uint16_t sortZ,
+                                    uint16_t testZ,
+                                    const PaintParams* paint) {
+    fPendingDraws->strokePath(localToDevice, path, stroke, scissor, sortZ, testZ, paint);
+}
+
+void SurfaceDrawContext::snapDrawPass(const BoundsManager* occlusionCuller) {
+    // TODO: actually sort, cull, and merge the DL for the DrawPass
+    (void) occlusionCuller;
+    // TODO: eventually the RenderPassTask will be handled later since it might take over
+    // multiple DrawPasses from the SDC.
+    auto task = RenderPassTask::Make(std::move(fTail), std::move(fPendingDraws));
+    fTail = task;
+    fPendingDraws = std::make_unique<DrawList>();
+}
 
 } // namespace skgpu
diff --git a/experimental/graphite/src/SurfaceDrawContext.h b/experimental/graphite/src/SurfaceDrawContext.h
index f1160af..c0f7a7c 100644
--- a/experimental/graphite/src/SurfaceDrawContext.h
+++ b/experimental/graphite/src/SurfaceDrawContext.h
@@ -11,10 +11,22 @@
 #include "include/core/SkImageInfo.h"
 #include "include/core/SkRefCnt.h"
 
+class SkPath;
+class SkM44;
+
 namespace skgpu {
 
-class SDCTask;
+class BoundsManager;
+class DrawList;
+class Task;
 
+struct PaintParams;
+struct StrokeParams;
+
+/**
+ * SurfaceDrawContext records draw commands into a specific Surface, via a general task graph
+ * representing GPU work and their inter-dependencies.
+ */
 class SurfaceDrawContext final : public SkRefCnt {
 public:
     static sk_sp<SurfaceDrawContext> Make(const SkImageInfo&);
@@ -23,14 +35,56 @@
 
     const SkImageInfo& imageInfo() { return fImageInfo; }
 
-private:
-    SurfaceDrawContext(const SkImageInfo&, sk_sp<SDCTask>);
+    // TODO: need color/depth clearing functions (so DCL will probably need those too)
 
-    SkImageInfo    fImageInfo;
-    sk_sp<SDCTask> fTask;
+    void fillPath(const SkM44& localToDevice,
+                  const SkPath& path,
+                  const SkIRect& scissor,
+                  uint16_t sortZ,
+                  uint16_t testZ,
+                  const PaintParams* paint);
+
+    void strokePath(const SkM44& localToDevice,
+                    const SkPath& path,
+                    const StrokeParams& stroke,
+                    const SkIRect& scissor,
+                    uint16_t sortZ,
+                    uint16_t testZ,
+                    const PaintParams* paint);
+
+    // Ends the current DrawList being accumulated by the SDC, converting it into an optimized and
+    // immutable DrawPass. The DrawPass will be ordered after any other snapped DrawPasses or
+    // appended DrawPasses from a child SDC. A new DrawList is started to record subsequent drawing
+    // operations.
+
+    // If 'occlusionCuller' is null, then culling is skipped when converting the DrawList into a
+    // DrawPass.
+    // TBD - should this also return the task so the caller can point to it with its own
+    // dependencies? Or will that be mostly automatic based on draws and proxy refs?
+    void snapDrawPass(const BoundsManager* occlusionCuller);
+
+private:
+    SurfaceDrawContext(const SkImageInfo&);
+
+    SkImageInfo fImageInfo;
+
+    // TODO: After discussing the possibility of sub renderpasses and memoryless saved layers,
+    // an SDC ought to hold on to a list of {DrawList, Proxy} that represent the pending subpasses.
+    // A saveLayer can be then be drawn by (1) a regular render pass, followed by a regular texture
+    // sample in the parent's SDC, (2) a sub pass where the layer's draw list targets a
+    // "memory-less" 2nd surface w/in a regular render pass for the parent, or (3) fully eliding the
+    // layer by appending it's draw list directly to the parent's draw list. This will need to be
+    // represented at this high API level because it requires decisions to be made by Device when
+    // creating the SkDevice for the new layer, and during the restore.
+    std::unique_ptr<DrawList> fPendingDraws;
+
+    // TBD - Does the SDC even need to hold on to its tail task? Or when it finalizes its current
+    // DCL into a DrawTask it can send that back to the Recorder as part of a larger task graph?
+    // The only question then would be how to track the dependencies of that DrawTask since it would
+    // depend on the prior DrawTask and the SDC's surface view.
+    sk_sp<Task> fTail;
 };
 
 } // namespace skgpu
 
 #endif // skgpu_SurfaceDrawContext_DEFINED
-
diff --git a/gn/graphite.gni b/gn/graphite.gni
index a88c529..305a7cd 100644
--- a/gn/graphite.gni
+++ b/gn/graphite.gni
@@ -20,15 +20,15 @@
   "$_src/Context.cpp",
   "$_src/Device.cpp",
   "$_src/Device.h",
-  "$_src/DrawCommandList.h",
+  "$_src/DrawList.h",
   "$_src/Gpu.cpp",
   "$_src/Gpu.h",
   "$_src/Image_Graphite.cpp",
   "$_src/Image_Graphite.h",
+  "$_src/RenderPassTask.cpp",
+  "$_src/RenderPassTask.h",
   "$_src/ResourceProvider.cpp",
   "$_src/ResourceProvider.h",
-  "$_src/SDCTask.cpp",
-  "$_src/SDCTask.h",
   "$_src/SkStuff.cpp",
   "$_src/SurfaceDrawContext.cpp",
   "$_src/SurfaceDrawContext.h",