/* * Copyright 2015 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #ifndef GrOp_DEFINED #define GrOp_DEFINED #include "../private/SkAtomics.h" #include "GrGpuResource.h" #include "GrNonAtomicRef.h" #include "GrXferProcessor.h" #include "SkMatrix.h" #include "SkRect.h" #include "SkString.h" #include <new> class GrCaps; class GrGpuCommandBuffer; class GrOpFlushState; class GrRenderTargetOpList; /** * GrOp is the base class for all Ganesh deferred GPU operations. To facilitate reordering and to * minimize draw calls, Ganesh does not generate geometry inline with draw calls. Instead, it * captures the arguments to the draw and then generates the geometry when flushing. This gives GrOp * subclasses complete freedom to decide how/when to combine in order to produce fewer draw calls * and minimize state changes. * * Ops of the same subclass may be merged using combineIfPossible. When two ops merge, one * takes on the union of the data and the other is left empty. The merged op becomes responsible * for drawing the data from both the original ops. * * If there are any possible optimizations which might require knowing more about the full state of * the draw, e.g. whether or not the GrOp is allowed to tweak alpha for coverage, then this * information will be communicated to the GrOp prior to geometry generation. * * The bounds of the op must contain all the vertices in device space *irrespective* of the clip. * The bounds are used in determining which clip elements must be applied and thus the bounds cannot * in turn depend upon the clip. */ #define GR_OP_SPEW 0 #if GR_OP_SPEW #define GrOP_SPEW(code) code #define GrOP_INFO(...) SkDebugf(__VA_ARGS__) #else #define GrOP_SPEW(code) #define GrOP_INFO(...) #endif // A helper macro to generate a class static id #define DEFINE_OP_CLASS_ID \ static uint32_t ClassID() { \ static uint32_t kClassID = GenOpClassID(); \ return kClassID; \ } class GrOp : private SkNoncopyable { public: GrOp(uint32_t classID); virtual ~GrOp(); virtual const char* name() const = 0; typedef std::function<void(GrSurfaceProxy*)> VisitProxyFunc; virtual void visitProxies(const VisitProxyFunc&) const { // This default implementation assumes the op has no proxies } bool combineIfPossible(GrOp* that, const GrCaps& caps) { if (this->classID() != that->classID()) { return false; } return this->onCombineIfPossible(that, caps); } const SkRect& bounds() const { SkASSERT(kUninitialized_BoundsFlag != fBoundsFlags); return fBounds; } void setClippedBounds(const SkRect& clippedBounds) { fBounds = clippedBounds; // The clipped bounds already incorporate any effect of the bounds flags. fBoundsFlags = 0; } bool hasAABloat() const { SkASSERT(fBoundsFlags != kUninitialized_BoundsFlag); return SkToBool(fBoundsFlags & kAABloat_BoundsFlag); } bool hasZeroArea() const { SkASSERT(fBoundsFlags != kUninitialized_BoundsFlag); return SkToBool(fBoundsFlags & kZeroArea_BoundsFlag); } void* operator new(size_t size); void operator delete(void* target); void* operator new(size_t size, void* placement) { return ::operator new(size, placement); } void operator delete(void* target, void* placement) { ::operator delete(target, placement); } /** * Helper for safely down-casting to a GrOp subclass */ template <typename T> const T& cast() const { SkASSERT(T::ClassID() == this->classID()); return *static_cast<const T*>(this); } template <typename T> T* cast() { SkASSERT(T::ClassID() == this->classID()); return static_cast<T*>(this); } uint32_t classID() const { SkASSERT(kIllegalOpID != fClassID); return fClassID; } // We lazily initialize the uniqueID because currently the only user is GrAuditTrail uint32_t uniqueID() const { if (kIllegalOpID == fUniqueID) { fUniqueID = GenOpID(); } return fUniqueID; } /** * This is called to notify the op that it has been recorded into a GrOpList. Ops can use this * to begin preparations for the flush of the op list. Note that the op still may either be * combined into another op or have another op combined into it via combineIfPossible() after * this call is made. */ virtual void wasRecorded(GrRenderTargetOpList*) {} /** * Called prior to executing. The op should perform any resource creation or data transfers * necessary before execute() is called. */ void prepare(GrOpFlushState* state) { this->onPrepare(state); } /** Issues the op's commands to GrGpu. */ void execute(GrOpFlushState* state) { this->onExecute(state); } /** Used for spewing information about ops when debugging. */ virtual SkString dumpInfo() const { SkString string; string.appendf("OpBounds: [L: %.2f, T: %.2f, R: %.2f, B: %.2f]\n", fBounds.fLeft, fBounds.fTop, fBounds.fRight, fBounds.fBottom); return string; } protected: /** * Indicates that the op will produce geometry that extends beyond its bounds for the * purpose of ensuring that the fragment shader runs on partially covered pixels for * non-MSAA antialiasing. */ enum class HasAABloat : bool { kNo = false, kYes = true }; /** * Indicates that the geometry represented by the op has zero area (e.g. it is hairline or * points). */ enum class IsZeroArea : bool { kNo = false, kYes = true }; void setBounds(const SkRect& newBounds, HasAABloat aabloat, IsZeroArea zeroArea) { fBounds = newBounds; this->setBoundsFlags(aabloat, zeroArea); } void setTransformedBounds(const SkRect& srcBounds, const SkMatrix& m, HasAABloat aabloat, IsZeroArea zeroArea) { m.mapRect(&fBounds, srcBounds); this->setBoundsFlags(aabloat, zeroArea); } void makeFullScreen(GrSurfaceProxy* proxy) { this->setBounds(SkRect::MakeIWH(proxy->width(), proxy->height()), HasAABloat::kNo, IsZeroArea::kNo); } void joinBounds(const GrOp& that) { if (that.hasAABloat()) { fBoundsFlags |= kAABloat_BoundsFlag; } if (that.hasZeroArea()) { fBoundsFlags |= kZeroArea_BoundsFlag; } return fBounds.joinPossiblyEmptyRect(that.fBounds); } void replaceBounds(const GrOp& that) { fBounds = that.fBounds; fBoundsFlags = that.fBoundsFlags; } static uint32_t GenOpClassID() { return GenID(&gCurrOpClassID); } private: virtual bool onCombineIfPossible(GrOp*, const GrCaps& caps) = 0; virtual void onPrepare(GrOpFlushState*) = 0; virtual void onExecute(GrOpFlushState*) = 0; static uint32_t GenID(int32_t* idCounter) { // The atomic inc returns the old value not the incremented value. So we add // 1 to the returned value. uint32_t id = static_cast<uint32_t>(sk_atomic_inc(idCounter)) + 1; if (!id) { SK_ABORT("This should never wrap as it should only be called once for each GrOp " "subclass."); } return id; } void setBoundsFlags(HasAABloat aabloat, IsZeroArea zeroArea) { fBoundsFlags = 0; fBoundsFlags |= (HasAABloat::kYes == aabloat) ? kAABloat_BoundsFlag : 0; fBoundsFlags |= (IsZeroArea ::kYes == zeroArea) ? kZeroArea_BoundsFlag : 0; } enum { kIllegalOpID = 0, }; enum BoundsFlags { kAABloat_BoundsFlag = 0x1, kZeroArea_BoundsFlag = 0x2, SkDEBUGCODE(kUninitialized_BoundsFlag = 0x4) }; const uint16_t fClassID; uint16_t fBoundsFlags; static uint32_t GenOpID() { return GenID(&gCurrOpUniqueID); } mutable uint32_t fUniqueID; SkRect fBounds; static int32_t gCurrOpUniqueID; static int32_t gCurrOpClassID; }; #endif