Graphite: Rough out DrawCommandList and BoundsManager

Device will own and manage a BoundsManager, which it will use to decide
the sort and test Z's that it passes to the DrawCommandList in its
draw() implementations.

DrawCommandList might end up being owned by the SDC, with the SDC
exposing a similar drawing API. There will need to be some mechanism to
end a DrawCommandList and start a new one (the list moves into an
SDCTask). This would either happen from an external flush call, or in
the rare case where the representable Z values are exhausted and we
have to insert a depth buffer clear and start a new task that depends
on the prior one.

Bug: skia:12466
Change-Id: I892b631037dc801eb94da2462683c9701afa281b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/451599
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
This commit is contained in:
Michael Ludwig 2021-09-22 14:44:20 -04:00 committed by SkCQ
parent f8a6faf94b
commit 01754ecf12
3 changed files with 206 additions and 0 deletions

View File

@ -0,0 +1,73 @@
/*
* 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_BoundsManager_DEFINED
#define skgpu_BoundsManager_DEFINED
#include <cstdint>
struct SkIRect;
namespace skgpu {
/**
* BoundsManager is an acceleration structure for device-space related pixel bounds queries.
* It supports querying the maximum previously written Z value within a given bounds, recording a
* new Z value within a bounding rect, and querying if a {bounds, Z} tuple would be fully occluded
* by a later operation.
*/
class BoundsManager {
public:
virtual ~BoundsManager() {}
virtual uint16_t getMaxZ(const SkIRect& bounds) const = 0;
virtual bool isOccluded(const SkIRect& bounds, uint16_t z) const = 0;
virtual void setZ(const SkIRect& bounds, uint16_t z, bool fullyOpaque=false) = 0;
};
// TODO: Select one most-effective BoundsManager implementation, make it the only option, and remove
// virtual-ness. For now, this seems useful for correctness testing by comparing against trivial
// implementations and for identifying how much "smarts" are actually worthwhile.
// A BoundsManager that produces exact painter's order and assumes nothing is occluded.
class NaiveBoundsManager final : public BoundsManager {
public:
~NaiveBoundsManager() override {}
uint16_t getMaxZ(const SkIRect& bounds) const override { return fMaxZ; }
bool isOccluded(const SkIRect& bounds, uint16_t z) const override { return false; }
void setZ(const SkIRect& bounds, uint16_t z, bool fullyOpaque=false) override {
if (z > fMaxZ) {
fMaxZ = z;
}
}
private:
uint16_t fMaxZ = 0;
};
// A BoundsManager that tracks every {bounds, Z} tuple and can exactly determine all queries
// using a brute force search.
class BruteForceBoundsManager : public BoundsManager {
public:
~BruteForceBoundsManager() override {}
// TODO: implement this class
uint16_t getMaxZ(const SkIRect& bounds) const override { return 0; }
bool isOccluded(const SkIRect& bounds, uint16_t z) const override { return false; }
void setZ(const SkIRect& bounds, uint16_t z, bool fullyOpaque=false) override {}
};
} // namespace skgpu
#endif // skgpu_BoundsManager_DEFINED

View File

@ -0,0 +1,131 @@
/*
* 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_DrawCommandList_DEFINED
#define skgpu_DrawCommandList_DEFINED
#include "include/core/SkColor.h"
#include "include/core/SkPaint.h"
#include "include/core/SkShader.h"
#include <cstdint>
class SkM44;
class SkPath;
struct SkIRect;
namespace skgpu {
// Forward declarations that capture the intermediate state lying between public Skia types and
// the direct GPU representation.
struct PaintParams;
struct StrokeParams;
/**
* DrawCommandList represents a collection of drawing commands (and related clip/shading state) in
* a form that closely mirrors what can be rendered efficiently and directly by the GPU backend
* (while balancing how much pre-processing to do for draws that might get eliminated later due to
* occlusion culling).
*
* A draw command combines:
* - a shape
* - a transform
* - a primitive clip (not affected by the transform)
* - optional shading description (shader, color filter, blend mode, etc)
* - a sorting z
* - a write/test z
*
* Commands are accumulated in an arbitrary order and then sorted by increasing sort z when the list
* is prepared into an actual command buffer. The result of a draw command is the rasterization of
* the transformed shape, restricted by its primitive clip (e.g. a scissor rect) and a GEQUAL
* depth test vs. its write/test z. If the command has a shading description, the color buffer will
* be modified; if not, it will be a depth-only draw.
*
* In addition to sorting the collected commands, the command list can be optimized during
* preparation. Commands that are fully occluded by later operations can be skipped entirely without
* affecting the final results. Adjacent commands (post sort) that would use equivalent GPU
* pipelines are merged to produce fewer (but larger) operations on the GPU.
*
* Other than flush-time optimizations (sort, cull, and merge), the command list does what you tell
* it to. Draw-specific simplification, style application, and advanced clipping should be handled
* at a higher layer.
*/
class DrawCommandList {
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
// transform from the Device changes at the same or slower rate as draw commands, it may make
// sense for it to compute these dependent values and provide them here. Storing the scale
// factor per draw command is low overhead, but unsure about storing 2 matrices per command.
void fillPath(const SkM44& localToDevice,
const SkPath& path,
const SkIRect& scissor, // TBD: expand this to one xformed rrect analytic clip?
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) {}
// TODO: fill[R]Rect, stroke[R]Rect (will need to support per-edge aa and arbitrary quads)
// fillImage (per-edge aa and arbitrary quad, only if this fast path is required)
// dashPath(feasible for general paths?)
// dash[R]Rect(only if general dashPath isn't viable)
// dashLine(only if general or rrect version aren't viable)
int count() const { return 0; }
// TBD: Figure out preparation/flush APIs and/or how to iterate the draw commands. These will
// be responsible for sorting by sort z and shading state, doing occlusion culling, and possibly
// merging compatible, consecutive remaining commands. It can also easily track if there are
// remaining depth-only draws or complex path draws that would trigger DMSAA. I[ml] can see this
// all being internal to DrawCommandList, or being supported by accessors and iterators with the
// rest of the logic stored in SDC. It is also unknown at this time how much conversion the
// PaintParams will need to go through (vs. just building a key) in order to do state sorting.
// TBD: Any value in de-duplicating paint params/programs during accumulation or being able
// to query the set of required programs for a given command list? Any time query or flush time?
private:
// TODO: actually implement this, probably stl for now but will likely need something that
// is allocation efficient. Should also explore having 1 vector of commands, vs. parallel
// vectors of various fields.
};
// TBD: If occlusion culling is eliminated as a phase, we can easily move the paint conversion
// back to Device when the command is recorded (similar to SkPaint -> GrPaint), and then
// PaintParams is not required as an intermediate representation.
// NOTE: Only represents the shading state of an SkPaint. Style and complex effects (mask filters,
// image filters, path effects) must be handled higher up. AA is not tracked since everything is
// assumed to be anti-aliased.
struct PaintParams {
SkColor4f fColor;
SkBlendMode fBlendMode;
sk_sp<SkShader> fShader; // For now only use SkShader::asAGradient() when converting to GPU
// TODO: Will also store ColorFilter, custom Blender, dither, and any extra shader from an
// active clipShader().
};
// NOTE: Only represents the stroke style; stroke-and-fill and hairline must be handled higher up.
struct StrokeParams {
float fWidth; // > 0 and relative to shape's transform
float fMiterLimit;
SkPaint::Join fJoin;
SkPaint::Cap fCap;
};
// TBD: Separate DashParams extracted from an SkDashPathEffect? Or folded into StrokeParams?
} // namespace skgpu
#endif // skgpu_DrawCommandList_DEFINED

View File

@ -12,9 +12,11 @@ skia_graphite_public = [ "$_include/Context.h" ]
skia_graphite_sources = [ skia_graphite_sources = [
"$_include/Context.h", "$_include/Context.h",
"$_include/SkStuff.h", "$_include/SkStuff.h",
"$_src/BoundsManager.h",
"$_src/Context.cpp", "$_src/Context.cpp",
"$_src/Device.cpp", "$_src/Device.cpp",
"$_src/Device.h", "$_src/Device.h",
"$_src/DrawCommandList.h",
"$_src/SDCTask.cpp", "$_src/SDCTask.cpp",
"$_src/SDCTask.h", "$_src/SDCTask.h",
"$_src/SkStuff.cpp", "$_src/SkStuff.cpp",