Flutter Engine
The Flutter Engine
Classes | Public Types | Public Member Functions | List of all members
skgpu::graphite::ClipStack Class Reference

#include <ClipStack_graphite.h>

Classes

struct  Element
 
class  ElementIter
 
struct  TransformedShape
 

Public Types

enum class  ClipState : uint8_t {
  kEmpty , kWideOpen , kDeviceRect , kDeviceRRect ,
  kComplex
}
 
using ElementList = skia_private::STArray< 4, const Element * >
 

Public Member Functions

 ClipStack (Device *owningDevice)
 
 ~ClipStack ()
 
 ClipStack (const ClipStack &)=delete
 
ClipStackoperator= (const ClipStack &)=delete
 
ClipState clipState () const
 
int maxDeferredClipDraws () const
 
Rect conservativeBounds () const
 
ElementIter begin () const
 
ElementIter end () const
 
void save ()
 
void restore ()
 
void clipShape (const Transform &localToDevice, const Shape &shape, SkClipOp op)
 
void clipShader (sk_sp< SkShader > shader)
 
Clip visitClipStackForDraw (const Transform &, const Geometry &, const SkStrokeRec &, bool outsetBoundsForAA, ElementList *outEffectiveElements) const
 
CompressedPaintersOrder updateClipStateForDraw (const Clip &clip, const ElementList &effectiveElements, const BoundsManager *, PaintersDepth z)
 
void recordDeferredClipDraws ()
 

Detailed Description

Definition at line 30 of file ClipStack_graphite.h.

Member Typedef Documentation

◆ ElementList

Definition at line 87 of file ClipStack_graphite.h.

Member Enumeration Documentation

◆ ClipState

enum class skgpu::graphite::ClipStack::ClipState : uint8_t
strong
Enumerator
kEmpty 
kWideOpen 
kDeviceRect 
kDeviceRRect 
kComplex 

Definition at line 34 of file ClipStack_graphite.h.

34 : uint8_t {
35 kEmpty, kWideOpen, kDeviceRect, kDeviceRRect, kComplex
36 };

Constructor & Destructor Documentation

◆ ClipStack() [1/2]

skgpu::graphite::ClipStack::ClipStack ( Device owningDevice)

Definition at line 966 of file ClipStack_graphite.cpp.

967 : fElements(kElementStackIncrement)
968 , fSaves(kSaveStackIncrement)
969 , fDevice(owningDevice) {
970 // Start with a save record that is wide open
971 fSaves.emplace_back(this->deviceBounds());
972}
T & emplace_back(Args &&... args)
Definition: SkTBlockList.h:101
static constexpr int kElementStackIncrement
static constexpr int kSaveStackIncrement

◆ ~ClipStack()

skgpu::graphite::ClipStack::~ClipStack ( )
default

◆ ClipStack() [2/2]

skgpu::graphite::ClipStack::ClipStack ( const ClipStack )
delete

Member Function Documentation

◆ begin()

ClipStack::ElementIter skgpu::graphite::ClipStack::begin ( ) const
inline

Definition at line 369 of file ClipStack_graphite.h.

369 {
370 if (this->currentSaveRecord().state() == ClipState::kEmpty ||
371 this->currentSaveRecord().state() == ClipState::kWideOpen) {
372 // No visible clip elements when empty or wide open
373 return this->end();
374 }
375 int count = fElements.count() - this->currentSaveRecord().oldestElementIndex();
376 return ElementIter(fElements.ritems().begin(), count);
377}
int count
Definition: FontMgrTest.cpp:50
Item begin() const
Definition: SkTBlockList.h:450
RIter ritems()
Definition: SkTBlockList.h:301
int count() const
Definition: SkTBlockList.h:167
AtkStateType state

◆ clipShader()

void skgpu::graphite::ClipStack::clipShader ( sk_sp< SkShader shader)

Definition at line 1034 of file ClipStack_graphite.cpp.

1034 {
1035 // Shaders can't bring additional coverage
1036 if (this->currentSaveRecord().state() == ClipState::kEmpty) {
1037 return;
1038 }
1039
1040 bool wasDeferred;
1041 this->writableSaveRecord(&wasDeferred).addShader(std::move(shader));
1042 // Geometry elements are not invalidated by updating the clip shader
1043 // TODO(b/238763003): Integrating clipShader into graphite needs more thought, particularly how
1044 // to handle the shader explosion and where to put the effects in the GraphicsPipelineDesc.
1045 // One idea is to use sample locations and draw the clipShader into the depth buffer.
1046 // Another is resolve the clip shader into an alpha mask image that is sampled by the draw.
1047}

◆ clipShape()

void skgpu::graphite::ClipStack::clipShape ( const Transform localToDevice,
const Shape shape,
SkClipOp  op 
)

Definition at line 1049 of file ClipStack_graphite.cpp.

1051 {
1052 if (this->currentSaveRecord().state() == ClipState::kEmpty) {
1053 return;
1054 }
1055
1056 // This will apply the transform if it's shape-type preserving, and clip the element's bounds
1057 // to the device bounds (NOT the conservative clip bounds, since those are based on the net
1058 // effect of all elements while device bounds clipping happens implicitly. During addElement,
1059 // we may still be able to invalidate some older elements).
1060 // NOTE: Does not try to simplify the shape type by inspecting the SkPath.
1061 RawElement element{this->deviceBounds(), localToDevice, shape, op};
1062
1063 // An empty op means do nothing (for difference), or close the save record, so we try and detect
1064 // that early before doing additional unnecessary save record allocation.
1065 if (element.shape().isEmpty()) {
1066 if (element.op() == SkClipOp::kDifference) {
1067 // If the shape is empty and we're subtracting, this has no effect on the clip
1068 return;
1069 }
1070 // else we will make the clip empty, but we need a new save record to record that change
1071 // in the clip state; fall through to below and updateForElement() will handle it.
1072 }
1073
1074 bool wasDeferred;
1075 SaveRecord& save = this->writableSaveRecord(&wasDeferred);
1076 SkDEBUGCODE(int elementCount = fElements.count();)
1077 if (!save.addElement(std::move(element), &fElements, fDevice)) {
1078 if (wasDeferred) {
1079 // We made a new save record, but ended up not adding an element to the stack.
1080 // So instead of keeping an empty save record around, pop it off and restore the counter
1081 SkASSERT(elementCount == fElements.count());
1082 fSaves.pop_back();
1083 fSaves.back().pushSave();
1084 }
1085 }
1086}
#define SkASSERT(cond)
Definition: SkAssert.h:116
SkDEBUGCODE(SK_SPI) SkThreadID SkGetThreadID()
void pop_back()
Definition: SkTBlockList.h:130
if(end==-1)
Definition: ref_ptr.h:256

◆ clipState()

ClipState skgpu::graphite::ClipStack::clipState ( ) const
inline

Definition at line 53 of file ClipStack_graphite.h.

53{ return this->currentSaveRecord().state(); }

◆ conservativeBounds()

Rect skgpu::graphite::ClipStack::conservativeBounds ( ) const

Definition at line 1002 of file ClipStack_graphite.cpp.

1002 {
1003 const SaveRecord& current = this->currentSaveRecord();
1004 if (current.state() == ClipState::kEmpty) {
1005 return Rect::InfiniteInverted();
1006 } else if (current.state() == ClipState::kWideOpen) {
1007 return this->deviceBounds();
1008 } else {
1009 if (current.op() == SkClipOp::kDifference) {
1010 // The outer/inner bounds represent what's cut out, so full bounds remains the device
1011 // bounds, minus any fully clipped content that spans the device edge.
1012 return subtract(this->deviceBounds(), current.innerBounds(), /* exact */ true);
1013 } else {
1014 SkASSERT(this->deviceBounds().contains(current.outerBounds()));
1015 return current.outerBounds();
1016 }
1017 }
1018}
static bool subtract(const R &a, const R &b, R *out)
Definition: SkRect.cpp:177
static AI Rect InfiniteInverted()
Definition: Rect.h:64
constexpr bool contains(std::string_view str, std::string_view needle)
Definition: SkStringView.h:41

◆ end()

ClipStack::ElementIter skgpu::graphite::ClipStack::end ( ) const
inline

Definition at line 379 of file ClipStack_graphite.h.

379 {
380 return ElementIter(fElements.ritems().end(), 0);
381}
Item end() const
Definition: SkTBlockList.h:451

◆ maxDeferredClipDraws()

int skgpu::graphite::ClipStack::maxDeferredClipDraws ( ) const
inline

Definition at line 54 of file ClipStack_graphite.h.

54{ return fElements.count(); }

◆ operator=()

ClipStack & skgpu::graphite::ClipStack::operator= ( const ClipStack )
delete

◆ recordDeferredClipDraws()

void skgpu::graphite::ClipStack::recordDeferredClipDraws ( )

Definition at line 1275 of file ClipStack_graphite.cpp.

1275 {
1276 for (auto& e : fElements.items()) {
1277 // When a Device requires all clip elements to be recorded, we have to iterate all elements,
1278 // and will draw clip shapes for elements that are still marked as invalid from the clip
1279 // stack, including those that are older than the current save record's oldest valid index,
1280 // because they could have accumulated draw usage prior to being invalidated, but weren't
1281 // flushed when they were invalidated because of an intervening save.
1282 e.drawClip(fDevice);
1283 }
1284}

◆ restore()

void skgpu::graphite::ClipStack::restore ( )

Definition at line 981 of file ClipStack_graphite.cpp.

981 {
982 SkASSERT(!fSaves.empty());
983 SaveRecord& current = fSaves.back();
984 if (current.popSave()) {
985 // This was just a deferred save being undone, so the record doesn't need to be removed yet
986 return;
987 }
988
989 // When we remove a save record, we delete all elements >= its starting index and any masks
990 // that were rasterized for it.
991 current.removeElements(&fElements, fDevice);
992
993 fSaves.pop_back();
994 // Restore any remaining elements that were only invalidated by the now-removed save record.
995 fSaves.back().restoreElements(&fElements);
996}
bool empty() const
Definition: SkTBlockList.h:185

◆ save()

void skgpu::graphite::ClipStack::save ( )

Definition at line 976 of file ClipStack_graphite.cpp.

976 {
977 SkASSERT(!fSaves.empty());
978 fSaves.back().pushSave();
979}

◆ updateClipStateForDraw()

CompressedPaintersOrder skgpu::graphite::ClipStack::updateClipStateForDraw ( const Clip clip,
const ElementList effectiveElements,
const BoundsManager boundsManager,
PaintersDepth  z 
)

Definition at line 1249 of file ClipStack_graphite.cpp.

1252 {
1253 if (clip.isClippedOut()) {
1255 }
1256
1257 SkDEBUGCODE(const SaveRecord& cs = this->currentSaveRecord();)
1258 SkASSERT(cs.state() != ClipState::kEmpty);
1259
1261 for (int i = 0; i < effectiveElements.size(); ++i) {
1262 // ClipStack owns the elements in the `clipState` so it's OK to downcast and cast away
1263 // const.
1264 // TODO: Enforce the ownership? In debug builds we could invalidate a `ClipStateForDraw` if
1265 // its element pointers become dangling and assert validity here.
1266 const RawElement* e = static_cast<const RawElement*>(effectiveElements[i]);
1268 const_cast<RawElement*>(e)->updateForDraw(boundsManager, clip.drawBounds(), z);
1269 maxClipOrder = std::max(order, maxClipOrder);
1270 }
1271
1272 return maxClipOrder;
1273}
static SkPath clip(const SkPath &path, const SkHalfPlane &plane)
Definition: SkPath.cpp:3892
static constexpr CompressedPaintersOrder kNoIntersection
Definition: DrawOrder.h:113
static float max(float r, float g, float b)
Definition: hsl.cpp:49
MonotonicValue< CompressedPaintersOrderSequence > CompressedPaintersOrder
Definition: DrawOrder.h:58

◆ visitClipStackForDraw()

Clip skgpu::graphite::ClipStack::visitClipStackForDraw ( const Transform localToDevice,
const Geometry geometry,
const SkStrokeRec style,
bool  outsetBoundsForAA,
ClipStack::ElementList outEffectiveElements 
) const

Definition at line 1088 of file ClipStack_graphite.cpp.

1092 {
1093 static const Clip kClippedOut = {
1095
1096 const SaveRecord& cs = this->currentSaveRecord();
1097 if (cs.state() == ClipState::kEmpty) {
1098 // We know the draw is clipped out so don't bother computing the base draw bounds.
1099 return kClippedOut;
1100 }
1101 // Compute draw bounds, clipped only to our device bounds since we need to return that even if
1102 // the clip stack is known to be wide-open.
1103 const Rect deviceBounds = this->deviceBounds();
1104
1105 // When 'style' isn't fill, 'shape' describes the pre-stroke shape so we can't use it to check
1106 // against clip elements and so 'styledShape' will be set to the bounds post-stroking.
1107 SkTCopyOnFirstWrite<Shape> styledShape;
1108 if (geometry.isShape()) {
1109 styledShape.init(geometry.shape());
1110 } else {
1111 // The geometry is something special like text or vertices, in which case it's definitely
1112 // not a shape that could simplify cleanly with the clip stack.
1113 styledShape.initIfNeeded(geometry.bounds());
1114 }
1115
1116 auto origSize = geometry.bounds().size();
1117 if (!SkIsFinite(origSize.x(), origSize.y())) {
1118 // Discard all non-finite geometry as if it were clipped out
1119 return kClippedOut;
1120 }
1121
1122 // Inverse-filled shapes always fill the entire device (restricted to the clip).
1123 // Query the invertedness of the shape before any of the `setRect` calls below, which can
1124 // modify it.
1125 bool infiniteBounds = styledShape->inverted();
1126
1127 // Discard fills and strokes that cannot produce any coverage: an empty fill, or a
1128 // zero-length stroke that has butt caps. Otherwise the stroke style applies to a vertical
1129 // or horizontal line (making it non-empty), or it's a zero-length path segment that
1130 // must produce round or square caps (making it non-empty):
1131 // https://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes
1132 if (!infiniteBounds && (styledShape->isLine() || any(origSize == 0.f))) {
1133 if (style.isFillStyle() || (style.getCap() == SkPaint::kButt_Cap && all(origSize == 0.f))) {
1134 return kClippedOut;
1135 }
1136 }
1137
1138 Rect transformedShapeBounds;
1139 bool shapeInDeviceSpace = false;
1140
1141 // Some renderers make the drawn area larger than the geometry for anti-aliasing
1142 float rendererOutset = outsetBoundsForAA ? localToDevice.localAARadius(styledShape->bounds())
1143 : 0.f;
1144 if (!SkIsFinite(rendererOutset)) {
1145 transformedShapeBounds = deviceBounds;
1146 infiniteBounds = true;
1147 } else {
1148 // Will be in device space once style/AA outsets and the localToDevice transform are
1149 // applied.
1150 transformedShapeBounds = styledShape->bounds();
1151
1152 // Regular filled shapes and strokes get larger based on style and transform
1153 if (!style.isHairlineStyle() || rendererOutset != 0.0f) {
1154 float localStyleOutset = style.getInflationRadius() + rendererOutset;
1155 transformedShapeBounds.outset(localStyleOutset);
1156
1157 if (!style.isFillStyle() || rendererOutset != 0.0f) {
1158 // While this loses any shape type, the bounds remain local so hopefully tests are
1159 // fairly accurate.
1160 styledShape.writable()->setRect(transformedShapeBounds);
1161 }
1162 }
1163
1164 transformedShapeBounds = localToDevice.mapRect(transformedShapeBounds);
1165
1166 // Hairlines get an extra pixel *after* transforming to device space, unless the renderer
1167 // has already defined an outset
1168 if (style.isHairlineStyle() && rendererOutset == 0.0f) {
1169 transformedShapeBounds.outset(0.5f);
1170 // and the associated transform must be kIdentity since the bounds have been mapped by
1171 // localToDevice already.
1172 styledShape.writable()->setRect(transformedShapeBounds);
1173 shapeInDeviceSpace = true;
1174 }
1175
1176 // Restrict bounds to the device limits.
1177 transformedShapeBounds.intersect(deviceBounds);
1178 }
1179
1180 Rect drawBounds; // defined in device space
1181 if (infiniteBounds) {
1182 drawBounds = deviceBounds;
1183 styledShape.writable()->setRect(drawBounds);
1184 shapeInDeviceSpace = true;
1185 } else {
1186 drawBounds = transformedShapeBounds;
1187 }
1188
1189 if (drawBounds.isEmptyNegativeOrNaN() || cs.state() == ClipState::kWideOpen) {
1190 // Either the draw is off screen, so it's clipped out regardless of the state of the
1191 // SaveRecord, or there are no elements to apply to the draw. In both cases, 'drawBounds'
1192 // has the correct value, the scissor is the device bounds (ignored if clipped-out).
1193 return Clip(drawBounds, transformedShapeBounds, deviceBounds.asSkIRect(), cs.shader());
1194 }
1195
1196 // We don't evaluate Simplify() on the SaveRecord and the draw because a reduced version of
1197 // Simplify is effectively performed in computing the scissor rect.
1198 // Given that, we can skip iterating over the clip elements when:
1199 // - the draw's *scissored* bounds are empty, which happens when the draw was clipped out.
1200 // - the scissored bounds are contained in our inner bounds, which happens if all we need to
1201 // apply to the draw is the computed scissor rect.
1202 // TODO: The Clip's scissor is defined in terms of integer pixel coords, but if we move to
1203 // clip plane distances in the vertex shader, it can be defined in terms of the original float
1204 // coordinates.
1205 Rect scissor = cs.scissor(deviceBounds, drawBounds).makeRoundOut();
1206 drawBounds.intersect(scissor);
1207 transformedShapeBounds.intersect(scissor);
1208 if (drawBounds.isEmptyNegativeOrNaN() || cs.innerBounds().contains(drawBounds)) {
1209 // Like above, in both cases drawBounds holds the right value.
1210 return Clip(drawBounds, transformedShapeBounds, scissor.asSkIRect(), cs.shader());
1211 }
1212
1213 // If we made it here, the clip stack affects the draw in a complex way so iterate each element.
1214 // A draw is a transformed shape that "intersects" the clip. We use empty inner bounds because
1215 // there's currently no way to re-write the draw as the clip's geometry, so there's no need to
1216 // check if the draw contains the clip (vice versa is still checked and represents an unclipped
1217 // draw so is very useful to identify).
1218 TransformedShape draw{shapeInDeviceSpace ? kIdentity : localToDevice,
1219 *styledShape,
1220 /*outerBounds=*/drawBounds,
1221 /*innerBounds=*/Rect::InfiniteInverted(),
1222 /*op=*/SkClipOp::kIntersect,
1223 /*containsChecksOnlyBounds=*/true};
1224
1225 SkASSERT(outEffectiveElements);
1226 SkASSERT(outEffectiveElements->empty());
1227 int i = fElements.count();
1228 for (const RawElement& e : fElements.ritems()) {
1229 --i;
1230 if (i < cs.oldestElementIndex()) {
1231 // All earlier elements have been invalidated by elements already processed so the draw
1232 // can't be affected by them and cannot contribute to their usage bounds.
1233 break;
1234 }
1235
1236 auto influence = e.testForDraw(draw);
1237 if (influence == RawElement::DrawInfluence::kClipOut) {
1238 outEffectiveElements->clear();
1239 return kClippedOut;
1240 }
1241 if (influence == RawElement::DrawInfluence::kIntersect) {
1242 outEffectiveElements->push_back(&e);
1243 }
1244 }
1245
1246 return Clip(drawBounds, transformedShapeBounds, scissor.asSkIRect(), cs.shader());
1247}
static bool SkIsFinite(T x, Pack... values)
static void draw(SkCanvas *canvas, SkRect &target, int x, int y)
Definition: aaclip.cpp:27
@ kButt_Cap
no stroke extension
Definition: SkPaint.h:334
SkScalar getInflationRadius() const
bool isHairlineStyle() const
Definition: SkStrokeRec.h:47
SkPaint::Cap getCap() const
Definition: SkStrokeRec.h:44
bool isFillStyle() const
Definition: SkStrokeRec.h:51
void initIfNeeded(Args &&... args)
Definition: SkTLazy.h:172
void init(const T &initial)
Definition: SkTLazy.h:164
constexpr std::array< float, 9 > kIdentity
Clip
Definition: layer.h:53
TRect< Scalar > Rect
Definition: rect.h:769
SIT bool all(const Vec< 1, T > &x)
Definition: SkVx.h:582
SIT bool any(const Vec< 1, T > &x)
Definition: SkVx.h:530
static constexpr SkIRect MakeEmpty()
Definition: SkRect.h:45

The documentation for this class was generated from the following files: