/src/skia/src/gpu/geometry/GrQuadUtils.h
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2019 Google LLC |
3 | | * |
4 | | * Use of this source code is governed by a BSD-style license that can be |
5 | | * found in the LICENSE file. |
6 | | */ |
7 | | |
8 | | #ifndef GrQuadUtils_DEFINED |
9 | | #define GrQuadUtils_DEFINED |
10 | | |
11 | | #include "include/private/SkVx.h" |
12 | | #include "src/gpu/geometry/GrQuad.h" |
13 | | |
14 | | enum class GrQuadAAFlags; |
15 | | enum class GrAA : bool; |
16 | | enum class GrAAType : unsigned; |
17 | | struct SkRect; |
18 | | |
19 | | namespace GrQuadUtils { |
20 | | |
21 | | // Resolve disagreements between the overall requested AA type and the per-edge quad AA flags. |
22 | | // Both outAAType and outEdgeFlags will be updated. |
23 | | void ResolveAAType(GrAAType requestedAAType, GrQuadAAFlags requestedEdgeFlags, |
24 | | const GrQuad& quad, GrAAType* outAAtype, GrQuadAAFlags* outEdgeFlags); |
25 | | |
26 | | /** |
27 | | * Clip the device vertices of 'quad' to be in front of the W = 0 plane (w/in epsilon). The |
28 | | * local coordinates will be updated to match the new clipped vertices. This returns the number |
29 | | * of clipped quads that need to be drawn: 0 if 'quad' was entirely behind the plane, 1 if |
30 | | * 'quad' did not need to be clipped or if 2 or 3 vertices were clipped, or 2 if 'quad' had one |
31 | | * vertex clipped (producing a pentagonal shape spanned by 'quad' and 'extraVertices'). |
32 | | */ |
33 | | int ClipToW0(DrawQuad* quad, DrawQuad* extraVertices); |
34 | | |
35 | | /** |
36 | | * Crops quad to the provided device-space axis-aligned rectangle. If the intersection of this |
37 | | * quad (projected) and cropRect results in a quadrilateral, this returns true. If not, this |
38 | | * quad may be updated to be a smaller quad of the same type such that its intersection with |
39 | | * cropRect is visually the same. This function assumes that the 'quad' coordinates are finite. |
40 | | * |
41 | | * The provided edge flags are updated to reflect edges clipped by cropRect (toggling on or off |
42 | | * based on cropAA policy). If provided, the local coordinates will be updated to reflect the |
43 | | * updated device coordinates of this quad. |
44 | | * |
45 | | * If 'computeLocal' is false, the local coordinates in 'quad' will not be modified. |
46 | | */ |
47 | | bool CropToRect(const SkRect& cropRect, GrAA cropAA, DrawQuad* quad, bool computeLocal=true); |
48 | | |
49 | | inline void Outset(const skvx::Vec<4, float>& edgeDistances, GrQuad* quad); |
50 | | |
51 | | bool WillUseHairline(const GrQuad& quad, GrAAType aaType, GrQuadAAFlags edgeFlags); |
52 | | |
53 | | class TessellationHelper { |
54 | | public: |
55 | | // Set the original device and (optional) local coordinates that are inset or outset |
56 | | // by the requested edge distances. Use nullptr if there are no local coordinates to update. |
57 | | // This assumes all device coordinates have been clipped to W > 0. |
58 | | void reset(const GrQuad& deviceQuad, const GrQuad* localQuad); |
59 | | |
60 | | // Calculates a new quadrilateral with edges parallel to the original except that they |
61 | | // have been moved inwards by edgeDistances (which should be positive). Distances are |
62 | | // ordered L, B, T, R to match CCW tristrip ordering of GrQuad vertices. Edges that are |
63 | | // not moved (i.e. distance == 0) will not be used in calculations and the corners will |
64 | | // remain on that edge. |
65 | | // |
66 | | // The per-vertex coverage will be returned. When the inset geometry does not collapse to |
67 | | // a point or line, this will be 1.0 for every vertex. When it does collapse, the per-vertex |
68 | | // coverages represent estimated pixel coverage to simulate drawing the subpixel-sized |
69 | | // original quad. |
70 | | // |
71 | | // Note: the edge distances are in device pixel units, so after rendering the new quad |
72 | | // edge's shortest distance to the original quad's edge would be equal to provided edge dist |
73 | | skvx::Vec<4, float> inset(const skvx::Vec<4, float>& edgeDistances, |
74 | | GrQuad* deviceInset, GrQuad* localInset); |
75 | | |
76 | | // Calculates a new quadrilateral that outsets the original edges by the given distances. |
77 | | // Other than moving edges outwards, this function is equivalent to inset(). If the exact |
78 | | // same edge distances are provided, certain internal computations can be reused across |
79 | | // consecutive calls to inset() and outset() (in any order). |
80 | | void outset(const skvx::Vec<4, float>& edgeDistances, |
81 | | GrQuad* deviceOutset, GrQuad* localOutset); |
82 | | |
83 | | // Compute the edge equations of the original device space quad passed to 'reset()'. The |
84 | | // coefficients are stored per-edge in 'a', 'b', and 'c', such that ax + by + c = 0, and |
85 | | // a positive distance indicates the interior of the quad. Edges are ordered L, B, T, R, |
86 | | // matching edge distances passed to inset() and outset(). |
87 | | void getEdgeEquations(skvx::Vec<4, float>* a, |
88 | | skvx::Vec<4, float>* b, |
89 | | skvx::Vec<4, float>* c); |
90 | | |
91 | | // Compute the edge lengths of the original device space quad passed to 'reset()'. The |
92 | | // edge lengths are ordered LBTR to match distances passed to inset() and outset(). |
93 | | skvx::Vec<4, float> getEdgeLengths(); |
94 | | |
95 | | // Determine if the original device space quad has vertices closer than 1px to its opposing |
96 | | // edges, without going through the full work of computing the insets (assuming that the |
97 | | // inset distances would be 0.5px). |
98 | | bool isSubpixel(); |
99 | | |
100 | | private: |
101 | | // NOTE: This struct is named 'EdgeVectors' because it holds a lot of cached calculations |
102 | | // pertaining to the edge vectors of the input quad, projected into 2D device coordinates. |
103 | | // While they are not direction vectors, this struct represents a convenient storage space |
104 | | // for the projected corners of the quad. |
105 | | struct EdgeVectors { |
106 | | // Projected corners (x/w and y/w); these are the 2D coordinates that determine the |
107 | | // actual edge direction vectors, dx, dy, and invLengths |
108 | | skvx::Vec<4, float> fX2D, fY2D; |
109 | | // Normalized edge vectors of the device space quad, ordered L, B, T, R |
110 | | // (i.e. next_ccw(x) - x). |
111 | | skvx::Vec<4, float> fDX, fDY; |
112 | | // Reciprocal of edge length of the device space quad, i.e. 1 / sqrt(dx*dx + dy*dy) |
113 | | skvx::Vec<4, float> fInvLengths; |
114 | | // Theta represents the angle formed by the two edges connected at each corner. |
115 | | skvx::Vec<4, float> fCosTheta; |
116 | | skvx::Vec<4, float> fInvSinTheta; // 1 / sin(theta) |
117 | | |
118 | | void reset(const skvx::Vec<4, float>& xs, const skvx::Vec<4, float>& ys, |
119 | | const skvx::Vec<4, float>& ws, GrQuad::Type quadType); |
120 | | }; |
121 | | |
122 | | struct EdgeEquations { |
123 | | // a * x + b * y + c = 0; positive distance is inside the quad; ordered LBTR. |
124 | | skvx::Vec<4, float> fA, fB, fC; |
125 | | |
126 | | void reset(const EdgeVectors& edgeVectors); |
127 | | |
128 | | skvx::Vec<4, float> estimateCoverage(const skvx::Vec<4, float>& x2d, |
129 | | const skvx::Vec<4, float>& y2d) const; |
130 | | |
131 | | bool isSubpixel(const skvx::Vec<4, float>& x2d, const skvx::Vec<4, float>& y2d) const; |
132 | | |
133 | | // Outsets or insets 'x2d' and 'y2d' in place. To be used when the interior is very |
134 | | // small, edges are near parallel, or edges are very short/zero-length. Returns number |
135 | | // of effective vertices in the degenerate quad. |
136 | | int computeDegenerateQuad(const skvx::Vec<4, float>& signedEdgeDistances, |
137 | | skvx::Vec<4, float>* x2d, skvx::Vec<4, float>* y2d, |
138 | | skvx::Vec<4, int32_t>* aaMask) const; |
139 | | }; |
140 | | |
141 | | struct OutsetRequest { |
142 | | // Positive edge distances to move each edge of the quad. These distances represent the |
143 | | // shortest (perpendicular) distance between the original edge and the inset or outset |
144 | | // edge. If the distance is 0, then the edge will not move. |
145 | | skvx::Vec<4, float> fEdgeDistances; |
146 | | // True if the new corners cannot be calculated by simply adding scaled edge vectors. |
147 | | // The quad may be degenerate because of the original geometry (near colinear edges), or |
148 | | // be because of the requested edge distances (collapse of inset, etc.) |
149 | | bool fInsetDegenerate; |
150 | | bool fOutsetDegenerate; |
151 | | |
152 | | void reset(const EdgeVectors& edgeVectors, GrQuad::Type quadType, |
153 | | const skvx::Vec<4, float>& edgeDistances); |
154 | | }; |
155 | | |
156 | | struct Vertices { |
157 | | // X, Y, and W coordinates in device space. If not perspective, w should be set to 1.f |
158 | | skvx::Vec<4, float> fX, fY, fW; |
159 | | // U, V, and R coordinates representing local quad. |
160 | | // Ignored depending on uvrCount (0, 1, 2). |
161 | | skvx::Vec<4, float> fU, fV, fR; |
162 | | int fUVRCount; |
163 | | |
164 | | void reset(const GrQuad& deviceQuad, const GrQuad* localQuad); |
165 | | |
166 | | void asGrQuads(GrQuad* deviceOut, GrQuad::Type deviceType, |
167 | | GrQuad* localOut, GrQuad::Type localType) const; |
168 | | |
169 | | // Update the device and optional local coordinates by moving the corners along their |
170 | | // edge vectors such that the new edges have moved 'signedEdgeDistances' from their |
171 | | // original lines. This should only be called if the 'edgeVectors' fInvSinTheta data is |
172 | | // numerically sound. |
173 | | void moveAlong(const EdgeVectors& edgeVectors, |
174 | | const skvx::Vec<4, float>& signedEdgeDistances); |
175 | | |
176 | | // Update the device coordinates by deriving (x,y,w) that project to (x2d, y2d), with |
177 | | // optional local coordinates updated to match the new vertices. It is assumed that |
178 | | // 'mask' was respected when determining (x2d, y2d), but it is used to ensure that only |
179 | | // unmasked unprojected edge vectors are used when computing device and local coords. |
180 | | void moveTo(const skvx::Vec<4, float>& x2d, |
181 | | const skvx::Vec<4, float>& y2d, |
182 | | const skvx::Vec<4, int32_t>& mask); |
183 | | }; |
184 | | |
185 | | Vertices fOriginal; |
186 | | EdgeVectors fEdgeVectors; |
187 | | GrQuad::Type fDeviceType; |
188 | | GrQuad::Type fLocalType; |
189 | | |
190 | | // Lazily computed as needed; use accessor functions instead of direct access. |
191 | | OutsetRequest fOutsetRequest; |
192 | | EdgeEquations fEdgeEquations; |
193 | | |
194 | | // Validity of Vertices/EdgeVectors (always true after first call to set()). |
195 | | bool fVerticesValid = false; |
196 | | // Validity of outset request (true after calling getOutsetRequest() until next set() call |
197 | | // or next inset/outset() with different edge distances). |
198 | | bool fOutsetRequestValid = false; |
199 | | // Validity of edge equations (true after calling getEdgeEquations() until next set() call). |
200 | | bool fEdgeEquationsValid = false; |
201 | | |
202 | | // The requested edge distances must be positive so that they can be reused between inset |
203 | | // and outset calls. |
204 | | const OutsetRequest& getOutsetRequest(const skvx::Vec<4, float>& edgeDistances); |
205 | | const EdgeEquations& getEdgeEquations(); |
206 | | |
207 | | // Outsets or insets 'vertices' by the given perpendicular 'signedEdgeDistances' (inset or |
208 | | // outset is determined implicitly by the sign of the distances). |
209 | | void adjustVertices(const skvx::Vec<4, float>& signedEdgeDistances, Vertices* vertices); |
210 | | // Like adjustVertices() but handles empty edges, collapsed quads, numerical issues, and |
211 | | // returns the number of effective vertices in the adjusted shape. |
212 | | int adjustDegenerateVertices(const skvx::Vec<4, float>& signedEdgeDistances, |
213 | | Vertices* vertices); |
214 | | |
215 | | friend int ClipToW0(DrawQuad*, DrawQuad*); // To reuse Vertices struct |
216 | | }; |
217 | | |
218 | | }; // namespace GrQuadUtils |
219 | | |
220 | 96 | void GrQuadUtils::Outset(const skvx::Vec<4, float>& edgeDistances, GrQuad* quad) { |
221 | 96 | TessellationHelper outsetter; |
222 | 96 | outsetter.reset(*quad, nullptr); |
223 | 96 | outsetter.outset(edgeDistances, quad, nullptr); |
224 | 96 | } |
225 | | |
226 | | #endif |