/* * 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 "include/core/SkSpan.h" #include "include/core/SkString.h" #include "include/core/SkTypes.h" #include "include/gpu/graphite/GraphiteTypes.h" #include "src/base/SkEnumBitMask.h" #include "src/base/SkVx.h" #include "src/gpu/graphite/Attribute.h" #include "src/gpu/graphite/DrawTypes.h" #include "src/gpu/graphite/ResourceTypes.h" #include "src/gpu/graphite/Uniform.h" #include #include #include #include #include enum class SkPathFillType; namespace skgpu { enum class MaskFormat; } namespace skgpu::graphite { class DrawWriter; class DrawParams; class PipelineDataGatherer; class Rect; class ResourceProvider; class TextureDataBlock; class Transform; struct ResourceBindingRequirements; enum class Coverage { kNone, kSingleChannel, kLCD }; /** * 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. * * All Renderers are accessed through the SharedContext's RendererProvider. */ class RenderStep { public: 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&, skvx::uint2 ssboIndices) 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. virtual void writeUniformsAndTextures(const DrawParams&, PipelineDataGatherer*) const = 0; // Returns the body of a vertex function, which must define a float4 devPosition variable and // must write to an already-defined float2 stepLocalCoords variable. This will be automatically // set to a varying for the fragment shader if the paint requires local coords. This SkSL 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 in any manner. // // 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 std::string vertexSkSL() const = 0; // Emits code to set up textures and samplers. Should only be defined if hasTextures is true. virtual std::string texturesAndSamplersSkSL(const ResourceBindingRequirements&, int* nextBindingIndex) 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"()"; } // Emits code to set up a primitive color value. Should only be defined if emitsPrimitiveColor // is true. When implemented, the returned SkSL fragment should write its color into a // 'half4 primitiveColor' variable (defined in the calling code). virtual const char* fragmentColorSkSL() const { return R"()"; } uint32_t uniqueID() const { return fUniqueID; } // 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 SkToBool(fFlags & Flags::kRequiresMSAA); } bool performsShading() const { return SkToBool(fFlags & Flags::kPerformsShading); } bool hasTextures() const { return SkToBool(fFlags & Flags::kHasTextures); } bool emitsPrimitiveColor() const { return SkToBool(fFlags & Flags::kEmitsPrimitiveColor); } bool outsetBoundsForAA() const { return SkToBool(fFlags & Flags::kOutsetBoundsForAA); } bool useNonAAInnerFill() const { return SkToBool(fFlags & Flags::kUseNonAAInnerFill); } Coverage coverage() const { return RenderStep::GetCoverage(fFlags); } 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(); } // Name of an attribute containing both render step and shading SSBO indices, if used. static const char* ssboIndicesAttribute() { return "ssboIndices"; } // Name of a varying to pass SSBO indices to fragment shader. Both render step and shading // indices are passed, because render step uniforms are sometimes used for coverage. static const char* ssboIndicesVarying() { return "ssboIndicesVar"; } // 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 uniforms() const { return SkSpan(fUniforms); } SkSpan vertexAttributes() const { return SkSpan(fVertexAttrs); } SkSpan instanceAttributes() const { return SkSpan(fInstanceAttrs); } SkSpan varyings() const { return SkSpan(fVaryings); } const DepthStencilSettings& depthStencilSettings() const { return fDepthStencilSettings; } SkEnumBitMask 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? protected: enum class Flags : unsigned { kNone = 0b00000000, kRequiresMSAA = 0b00000001, kPerformsShading = 0b00000010, kHasTextures = 0b00000100, kEmitsCoverage = 0b00001000, kLCDCoverage = 0b00010000, kEmitsPrimitiveColor = 0b00100000, kOutsetBoundsForAA = 0b01000000, kUseNonAAInnerFill = 0b10000000, }; SK_DECL_BITMASK_OPS_FRIENDS(Flags) // 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, std::initializer_list uniforms, PrimitiveType primitiveType, DepthStencilSettings depthStencilSettings, SkSpan vertexAttrs, SkSpan instanceAttrs, SkSpan varyings = {}); private: friend class Renderer; // for Flags // Cannot copy or move RenderStep(const RenderStep&) = delete; RenderStep(RenderStep&&) = delete; static Coverage GetCoverage(SkEnumBitMask); uint32_t fUniqueID; SkEnumBitMask 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 fUniforms; std::vector fVertexAttrs; std::vector fInstanceAttrs; std::vector fVaryings; size_t fVertexStride; // derived from vertex attribute set size_t fInstanceStride; // derived from instance attribute set std::string fName; }; SK_MAKE_BITMASK_OPS(RenderStep::Flags) class Renderer { using StepFlags = RenderStep::Flags; public: // The maximum number of render steps that any Renderer is allowed to have. static constexpr int kMaxRenderSteps = 4; const RenderStep& step(int i) const { SkASSERT(i >= 0 && i < fStepCount); return *fSteps[i]; } SkSpan steps() const { SkASSERT(fStepCount > 0); // steps() should only be called on valid Renderers. return {fSteps.data(), static_cast(fStepCount) }; } const char* name() const { return fName.c_str(); } DrawTypeFlags drawTypes() const { return fDrawTypes; } int numRenderSteps() const { return fStepCount; } bool requiresMSAA() const { return SkToBool(fStepFlags & StepFlags::kRequiresMSAA); } bool emitsPrimitiveColor() const { return SkToBool(fStepFlags & StepFlags::kEmitsPrimitiveColor); } bool outsetBoundsForAA() const { return SkToBool(fStepFlags & StepFlags::kOutsetBoundsForAA); } bool useNonAAInnerFill() const { return SkToBool(fStepFlags & StepFlags::kUseNonAAInnerFill); } SkEnumBitMask depthStencilFlags() const { return fDepthStencilFlags; } Coverage coverage() const { return RenderStep::GetCoverage(fStepFlags); } private: friend class RendererProvider; // for ctors // Max render steps is 4, so just spell the options out for now... Renderer(std::string_view name, DrawTypeFlags drawTypes, const RenderStep* s1) : Renderer(name, drawTypes, std::array{s1}) {} Renderer(std::string_view name, DrawTypeFlags drawTypes, const RenderStep* s1, const RenderStep* s2) : Renderer(name, drawTypes, std::array{s1, s2}) {} Renderer(std::string_view name, DrawTypeFlags drawTypes, const RenderStep* s1, const RenderStep* s2, const RenderStep* s3) : Renderer(name, drawTypes, std::array{s1, s2, s3}) {} Renderer(std::string_view name, DrawTypeFlags drawTypes, const RenderStep* s1, const RenderStep* s2, const RenderStep* s3, const RenderStep* s4) : Renderer(name, drawTypes, std::array{s1, s2, s3, s4}) {} template Renderer(std::string_view name, DrawTypeFlags drawTypes, std::array steps) : fName(name) , fDrawTypes(drawTypes) , fStepCount(SkTo(N)) { static_assert(N <= kMaxRenderSteps); for (int i = 0 ; i < fStepCount; ++i) { fSteps[i] = steps[i]; fStepFlags |= fSteps[i]->fFlags; fDepthStencilFlags |= fSteps[i]->depthStencilFlags(); } // At least one step needs to actually shade. SkASSERT(fStepFlags & RenderStep::Flags::kPerformsShading); // A render step using non-AA inner fills with a second draw should not also be part of a // multi-step renderer (to keep reasoning simple) and must use the GREATER depth test. SkASSERT(!this->useNonAAInnerFill() || (fStepCount == 1 && fSteps[0]->depthStencilSettings().fDepthTestEnabled && fSteps[0]->depthStencilSettings().fDepthCompareOp == CompareOp::kGreater)); } // For RendererProvider to manage initialization; it will never expose a Renderer that is only // default-initialized and not replaced because it's algorithm is disabled by caps/options. Renderer() : fSteps(), fName(""), fStepCount(0) {} Renderer& operator=(Renderer&&) = default; std::array fSteps; std::string fName; DrawTypeFlags fDrawTypes = DrawTypeFlags::kNone; int fStepCount; SkEnumBitMask fStepFlags = StepFlags::kNone; SkEnumBitMask fDepthStencilFlags = DepthStencilFlags::kNone; }; } // namespace skgpu::graphite #endif // skgpu_graphite_Renderer_DEFINED