blob: 116a0ae6a4f338f807c2dbdeff5681bb2775202f [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.
#ifndef skgpu_graphite_Renderer_DEFINED
#define skgpu_graphite_Renderer_DEFINED
#include "src/core/SkEnumBitMask.h"
#include "src/gpu/graphite/Attribute.h"
#include "src/gpu/graphite/DrawTypes.h"
#include "src/gpu/graphite/ResourceTypes.h"
#include "include/core/SkSpan.h"
#include "include/core/SkString.h"
#include "include/core/SkTypes.h"
#include "include/core/SkVertices.h"
#include "src/core/SkUniform.h"
#include <array>
#include <initializer_list>
#include <string>
#include <string_view>
#include <vector>
enum class SkPathFillType;
class SkPipelineDataGatherer;
class SkTextureDataBlock;
namespace skgpu { enum class MaskFormat; }
namespace skgpu::graphite {
class DrawWriter;
class DrawParams;
class ResourceProvider;
struct Varying {
const char* fName;
SkSLType fType;
// TODO: add modifier (e.g., flat and noperspective) support
class RenderStep {
virtual ~RenderStep() = default;
// The DrawWriter is configured with the vertex and instance strides of the RenderStep, and its
// primitive type. The recorded draws will be executed with a graphics pipeline compatible with
// this RenderStep.
virtual void writeVertices(DrawWriter*, const DrawParams&) const = 0;
// Write out the uniform values (aligned for the layout), textures, and samplers. The uniform
// values will be de-duplicated across all draws using the RenderStep before uploading to the
// GPU, but it can be assumed the uniforms will be bound before the draws recorded in
// 'writeVertices' are executed.
// TODO: We definitely want this to return CPU memory since it's better for the caller to handle
// the de-duplication and GPU upload/binding (DrawPass tracks all this). However, a RenderStep's
// uniforms aren't going to change, and the Layout won't change during a process, so it would be
// nice if we could remember the offsets for the layout/gpu and reuse them across draws.
// Similarly, it would be nice if this could write into reusable storage and then DrawPass or
// UniformCache handles making an sk_sp if we need to assign a new unique ID to the uniform data
virtual void writeUniformsAndTextures(const DrawParams&, SkPipelineDataGatherer*) const = 0;
// TODO: This is only temporary. Eventually the RenderStep will define its logic in SkSL and
// be able to have code operate in both the vertex and fragment shaders. Ideally the RenderStep
// will provide two functions that fit some ABI for integrating with the common and paint SkSL,
// although we could go as far as allowing RenderStep to handle composing the final SkSL if
// given the paint combination's SkSL.
// Returns the body of a vertex function, which must define a float4 devPosition variable.
// It has access to the variables declared by vertexAttributes(), instanceAttributes(),
// and uniforms(). The 'devPosition' variable's z must store the PaintDepth normalized to a
// float from [0, 1], for each processed draw although the RenderStep can choose to upload it
// as attributes or uniforms.
// NOTE: The above contract is mainly so that the entire SkSL program can be created by just str
// concatenating struct definitions generated from the RenderStep and paint Combination
// and then including the function bodies returned here.
virtual const char* vertexSkSL() const = 0;
// Emits code to set up textures and samplers. Should only be defined if hasTextures is true.
virtual std::string texturesAndSamplersSkSL(int startBinding) const { return R"()"; }
// Emits code to set up coverage value. Should only be defined if overridesCoverage is true.
// When implemented the returned SkSL fragment should write its coverage into a
// 'half4 outputCoverage' variable (defined in the calling code) with the actual
// coverage splatted out into all four channels.
virtual const char* fragmentCoverageSkSL() const { return R"()"; }
// Returns a name formatted as "Subclass[variant]", where "Subclass" matches the C++ class name
// and variant is a unique term describing instance's specific configuration.
const char* name() const { return fName.c_str(); }
bool requiresMSAA() const { return fFlags & Flags::kRequiresMSAA; }
bool performsShading() const { return fFlags & Flags::kPerformsShading; }
bool hasTextures() const { return fFlags & Flags::kHasTextures; }
bool emitsCoverage() const { return fFlags & Flags::kEmitsCoverage; }
PrimitiveType primitiveType() const { return fPrimitiveType; }
size_t vertexStride() const { return fVertexStride; }
size_t instanceStride() const { return fInstanceStride; }
size_t numUniforms() const { return fUniforms.size(); }
size_t numVertexAttributes() const { return fVertexAttrs.size(); }
size_t numInstanceAttributes() const { return fInstanceAttrs.size(); }
size_t numVaryings() const { return fVaryings.size(); }
// The uniforms of a RenderStep are bound to the kRenderStep slot, the rest of the pipeline
// may still use uniforms bound to other slots.
SkSpan<const SkUniform> uniforms() const { return SkSpan(fUniforms); }
SkSpan<const Attribute> vertexAttributes() const { return SkSpan(fVertexAttrs); }
SkSpan<const Attribute> instanceAttributes() const { return SkSpan(fInstanceAttrs); }
SkSpan<const Varying> varyings() const { return SkSpan(fVaryings); }
const DepthStencilSettings& depthStencilSettings() const { return fDepthStencilSettings; }
SkEnumBitMask<DepthStencilFlags> depthStencilFlags() const {
return (fDepthStencilSettings.fStencilTestEnabled
? DepthStencilFlags::kStencil : DepthStencilFlags::kNone) |
(fDepthStencilSettings.fDepthTestEnabled || fDepthStencilSettings.fDepthWriteEnabled
? DepthStencilFlags::kDepth : DepthStencilFlags::kNone);
// TODO: Actual API to do things
// 6. Some Renderers benefit from being able to share vertices between RenderSteps. Must find a
// way to support that. It may mean that RenderSteps get state per draw.
// - Does Renderer make RenderStepFactories that create steps for each DrawList::Draw?
// - Does DrawList->DrawPass conversion build a separate array of blind data that the
// stateless Renderstep can refer to for {draw,step} pairs?
// - Does each DrawList::Draw have extra space (e.g. 8 bytes) that steps can cache data in?
enum class Flags : unsigned {
kNone = 0b0000,
kRequiresMSAA = 0b0001,
kPerformsShading = 0b0010,
kHasTextures = 0b0100,
kEmitsCoverage = 0b1000,
// While RenderStep does not define the full program that's run for a draw, it defines the
// entire vertex layout of the pipeline. This is not allowed to change, so can be provided to
// the RenderStep constructor by subclasses.
RenderStep(std::string_view className,
std::string_view variantName,
SkEnumBitMask<Flags> flags,
std::initializer_list<SkUniform> uniforms,
PrimitiveType primitiveType,
DepthStencilSettings depthStencilSettings,
std::initializer_list<Attribute> vertexAttrs,
std::initializer_list<Attribute> instanceAttrs,
std::initializer_list<Varying> varyings = {})
: fFlags(flags)
, fPrimitiveType(primitiveType)
, fDepthStencilSettings(depthStencilSettings)
, fUniforms(uniforms)
, fVertexAttrs(vertexAttrs)
, fInstanceAttrs(instanceAttrs)
, fVaryings(varyings)
, fVertexStride(0)
, fInstanceStride(0)
, fName(className) {
for (auto v : this->vertexAttributes()) {
fVertexStride += v.sizeAlign4();
for (auto i : this->instanceAttributes()) {
fInstanceStride += i.sizeAlign4();
if (variantName.size() > 0) {
fName += "[";
fName += variantName;
fName += "]";
// Cannot copy or move
RenderStep(const RenderStep&) = delete;
RenderStep(RenderStep&&) = delete;
SkEnumBitMask<Flags> fFlags;
PrimitiveType fPrimitiveType;
DepthStencilSettings fDepthStencilSettings;
// TODO: When we always use C++17 for builds, we should be able to just let subclasses declare
// constexpr arrays and point to those, but we need explicit storage for C++14.
// Alternatively, if we imposed a max attr count, similar to Renderer's num render steps, we
// could just have this be std::array and keep all attributes inline with the RenderStep memory.
// On the other hand, the attributes are only needed when creating a new pipeline so it's not
// that performance sensitive.
std::vector<SkUniform> fUniforms;
std::vector<Attribute> fVertexAttrs;
std::vector<Attribute> fInstanceAttrs;
std::vector<Varying> fVaryings;
size_t fVertexStride; // derived from vertex attribute set
size_t fInstanceStride; // derived from instance attribute set
std::string fName;
* The actual technique for rasterizing a high-level draw recorded in a DrawList is handled by a
* specific Renderer. Each technique has an associated singleton Renderer that decomposes the
* technique into a series of RenderSteps that must be executed in the specified order for the draw.
* However, the RenderStep executions for multiple draws can be re-arranged so batches of each
* step can be performed in a larger GPU operation. This re-arranging relies on accurate
* determination of the DisjointStencilIndex for each draw so that stencil steps are not corrupted
* by another draw before its cover step is executed. It also relies on the CompressedPaintersOrder
* for each draw to ensure steps are not re-arranged in a way that violates the original draw order.
* Renderer itself is non-virtual since it simply has to point to a list of RenderSteps. RenderSteps
* on the other hand are virtual implement the technique specific functionality. It is entirely
* possible for certain types of steps, e.g. a bounding box cover, to be re-used across different
* Renderers even if the preceeding steps were different.
class Renderer {
// Graphite defines a limited set of renderers in order to increase likelihood of batching
// across draw calls, and reduce the number of shader permutations required. These Renderers
// are stateless singletons and remain alive for the entire program. Each Renderer corresponds
// to a specific recording function on DrawList and fill type.
static const Renderer& StencilTessellatedCurvesAndTris(SkPathFillType);
static const Renderer& StencilTessellatedWedges(SkPathFillType);
static const Renderer& ConvexTessellatedWedges();
static const Renderer& TessellatedStrokes();
static const Renderer& TextDirect(bool isA8);
static const Renderer& TextSDF(bool useLCDText);
static const Renderer& Vertices(SkVertices::VertexMode, bool hasColors, bool hasTexCoords);
// TODO: Add renderers for primitives (rect, rrect, etc.) and atlas draws; add support for
// inverse filled strokes.
// The maximum number of render steps that any Renderer is allowed to have.
static constexpr int kMaxRenderSteps = 4;
SkSpan<const RenderStep* const> steps() const {
return {&fSteps.front(), static_cast<size_t>(fStepCount) };
const char* name() const { return fName.c_str(); }
int numRenderSteps() const { return fStepCount; }
bool requiresMSAA() const { return fRequiresMSAA; }
bool emitsCoverage() const { return fEmitsCoverage; }
SkEnumBitMask<DepthStencilFlags> depthStencilFlags() const { return fDepthStencilFlags; }
// max render steps is 4, so just spell the options out for now...
Renderer(const char* name, const RenderStep* s1)
: Renderer(name, std::array<const RenderStep*, 1>{s1}) {}
Renderer(const char* name, const RenderStep* s1, const RenderStep* s2)
: Renderer(name, std::array<const RenderStep*, 2>{s1, s2}) {}
Renderer(const char* name, const RenderStep* s1, const RenderStep* s2, const RenderStep* s3)
: Renderer(name, std::array<const RenderStep*, 3>{s1, s2, s3}) {}
Renderer(const char* name, const RenderStep* s1, const RenderStep* s2,
const RenderStep* s3, const RenderStep* s4)
: Renderer(name, std::array<const RenderStep*, 4>{s1, s2, s3, s4}) {}
template<size_t N>
Renderer(const char* name, std::array<const RenderStep*, N> steps)
: fName(name)
, fStepCount(SkTo<int>(N)) {
static_assert(N <= kMaxRenderSteps);
SkDEBUGCODE(bool performsShading = false;)
for (int i = 0 ; i < fStepCount; ++i) {
fSteps[i] = steps[i];
fRequiresMSAA |= fSteps[i]->requiresMSAA();
fEmitsCoverage |= fSteps[i]->emitsCoverage();
fDepthStencilFlags |= fSteps[i]->depthStencilFlags();
SkDEBUGCODE(performsShading |= fSteps[i]->performsShading());
SkASSERT(performsShading); // at least one step needs to actually shade
// Cannot move or copy
Renderer(const Renderer&) = delete;
Renderer(Renderer&&) = delete;
std::array<const RenderStep*, kMaxRenderSteps> fSteps;
SkString fName;
int fStepCount;
bool fRequiresMSAA = false;
bool fEmitsCoverage = false;
SkEnumBitMask<DepthStencilFlags> fDepthStencilFlags = DepthStencilFlags::kNone;
} // skgpu namespace::graphite
#endif // skgpu_graphite_Renderer_DEFINED