blob: e90da47b74ec883c9354644aeca2da492659c895 [file] [log] [blame]
/*
* 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/DrawPass.h"
#include "experimental/graphite/include/GraphiteTypes.h"
#include "experimental/graphite/src/DrawContext.h"
#include "experimental/graphite/src/DrawList.h"
#include "src/core/SkUtils.h"
namespace skgpu {
namespace {
// Any given draw command in a DrawList might require more than one actual operation on the GPU
// (e.g. stencil then cover passes). While this does get encoded in the pipeline description and
// thus pipeline index of SortKeys, correctly rendering the original command requires a guaranteed
// order so the specific steps are ordered explicitly with two reserved bits higher than the
// pipeline index.
enum class DrawStage : unsigned {
kStencilCurves = 0b00, // Primary stencil pass for large paths, or only stencil pass
kStencilTris = 0b01, // Optional secondary pass for large paths, just inner triangles
kFillCurves = 0b10, // Primary color (and/or depth) pass for paths and other primitives
kFillTris = 0b11, // Secondary pass for color/depth for large convex paths' interiors
};
// Each command in a DrawList can produce up to several actual "draw" operations that are
// dependent on the original command but can also be sorted independently. The goal of sorting
// the operations for the DrawPass is to minimize pipeline transitions and dynamic binds within
// a pipeline, while still respecting the overall painter's order. This reduces to a vertex
// coloring problem on the intersection graph formed by the commands and how their bounds
// overlap, followed by ordering by pipeline description and uniform data. General vertex
// coloring is NP-complete so DrawPass uses a greedy algorithm where the order it "colors" the
// vertices is based on the ordering constraints for the color+depth buffer and optionally the
// stencil buffer (stored in fColorDepthIndex and fStencilIndex respectively). skgpu::Device
// determines the ordering on-the-fly by using BoundsManager to approximate intersections as
// draw commands are recorded. It is possible to issue draws to Skia that produce pathologic
// orderings using this method, but it strikes a reasonable balance between finding a near
// optimal ordering that respects painter's order and is very efficient to compute.
//
// The color-depth index and stencil index represent the most significant bits of the key, and
// are shared by all SortKeys produced by the same command. Next, the pipeline description is
// encoded in two steps:
// 1. The logical type of draw (i.e. stencil, stencil inner triangles, depth-only, regular) is
// packed in the high bits to ensure dependent draws are ordered correctly.
// 2. An index into a cache of pipeline descriptions is used to encode the identity of the
// pipeline (sort keys that differ in the high bits from #1 necessarily would have different
// description indices, but then ordering isn't enforced).
// Last, the command-specific uniform/sampling data is hashed to increase the probability that
// draws with the same pipeline and same data are adjacent. This data hash is split into data
// for the geometry (e.g. transform matrix and scissor) and for shading (e.g. color) so that
// a hierarchical uniform binding approach can be more easily implemented.
//
// The SortKey also stores an index pointing back to the command in the DrawList. To minimize
// the size of the struct, this index (and the pipeline description index) are not pointers,
// which means using SortKeys is only possible when the originating DrawList and DrawPass are
// available.
struct SortKey {
SortKey(int commandIndex,
CompressedPaintersOrder colorDepthOrder,
CompressedPaintersOrder stencilOrder,
DrawStage drawStage,
int pipelineIndex,
uint16_t geomDataHash,
uint32_t shadingDataHash)
: fPipelineKey{colorDepthOrder,
stencilOrder,
static_cast<uint16_t>(drawStage),
static_cast<uint32_t>(pipelineIndex)}
, fDataHash{geomDataHash,
static_cast<uint16_t>(commandIndex),
shadingDataHash} {}
bool operator<(const SortKey& k) const {
uint64_t k1 = this->pipelineKey();
uint64_t k2 = k.pipelineKey();
return k1 < k2 || (k1 == k2 && this->dataHash() < k.dataHash());
}
int commandIndex() const { return static_cast<int>(fDataHash.fCommandIndex); }
int pipelineIndex() const { return static_cast<int>(fPipelineKey.fPipelineIndex); }
DrawStage stage() const { return static_cast<DrawStage>(fPipelineKey.fDrawStage); }
// Exposed for inspection, but generally the painters ordering isn't needed after sorting
// since draws can be merged with different values as long as they have the same pipeline and
// their sorted ordering is preserved within the pipeline.
CompressedPaintersOrder colorDepthOrder() const {
return static_cast<CompressedPaintersOrder>(fPipelineKey.fColorDepthOrder);
}
CompressedPaintersOrder stencilOrder() const {
return static_cast<CompressedPaintersOrder>(fPipelineKey.fStencilOrder);
}
// These are exposed to help optimize detecting when new uniforms need to be bound.
// Differing hashes definitely represent different uniform bindings, but identical hashes
// require a complete comparison.
uint16_t geomDataHash() const { return static_cast<uint16_t>(fDataHash.fGeomDataHash); }
uint32_t shadingDataHash() const {
return static_cast<uint32_t>(fDataHash.fShadingDataHash);
}
private:
// Fields are ordered from most-significant to lowest when sorting by 128-bit value.
struct {
// The compressed painters orders are limited by the number of unique values we can store
// in the depth attachment, which at minimum supports 16-bits, so this packing is sufficient
uint64_t fColorDepthOrder : 16;
uint64_t fStencilOrder : 16;
uint64_t fDrawStage : 2;
// The 16-bit limitation on command index (and buffer indices), combined with the max of
// 4 dependent draws per command means that 30 bits for the pipeline index is more than
// sufficient to represent the unique pipeline descriptions referenced in a DrawPass.
uint64_t fPipelineIndex : 30;
} fPipelineKey; // NOTE: named for bit-punning, can't take address of a bit-field
uint64_t pipelineKey() const { return sk_bit_cast<uint64_t>(fPipelineKey); }
struct {
// Presumably there is less variance in geometric uniform data, so a 16-bit hash is
// hopefully sufficient (also why it has higher sort precedence than shading).
uint64_t fGeomDataHash : 16;
// The command index does not impact comparison of SortKeys but is stored in the data
// hash for better packing (must be masked off to compare).
uint64_t fCommandIndex : 16;
// 32-bit hash could have collisions, but hopefully it's low enough given that
// collisions only produce sub-optimal ordering when they have the same pipeline desc.
uint64_t fShadingDataHash : 32;
} fDataHash;
uint64_t dataHash() const {
static constexpr uint64_t kCommandIndexMask = 0xffff0000ffffffff;
return sk_bit_cast<uint64_t>(fDataHash) & kCommandIndexMask;
}
};
// NOTE: This assert is here to ensure SortKey is as tightly packed as possible. Any change to its
// size should be done with care and good reason.
static_assert(sizeof(SortKey) == 16);
} // namespace
std::unique_ptr<DrawPass> DrawPass::Make(std::unique_ptr<DrawList> cmds, DrawContext* dc) {
// TODO: DrawList processing will likely go here and then move the results into the DrawPass
return std::unique_ptr<DrawPass>(new DrawPass());
}
} // namespace skgpu