Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(525)

Side by Side Diff: src/gpu/batches/GrBWFillRectBatch.cpp

Issue 1302033003: Revert of fill rect batch refactor into separate batches (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « src/gpu/batches/GrAAFillRectBatch.cpp ('k') | src/gpu/batches/GrTInstanceBatch.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 /* 1 /*
2 * Copyright 2015 Google Inc. 2 * Copyright 2015 Google Inc.
3 * 3 *
4 * Use of this source code is governed by a BSD-style license that can be 4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file. 5 * found in the LICENSE file.
6 */ 6 */
7 7
8 #include "GrBWFillRectBatch.h" 8 #include "GrBWFillRectBatch.h"
9 9
10 #include "GrBatchFlushState.h" 10 #include "GrBatchFlushState.h"
11 #include "GrColor.h" 11 #include "GrColor.h"
12 #include "GrDefaultGeoProcFactory.h" 12 #include "GrDefaultGeoProcFactory.h"
13 #include "GrPrimitiveProcessor.h" 13 #include "GrPrimitiveProcessor.h"
14 #include "GrResourceProvider.h"
15 #include "GrTInstanceBatch.h"
16 #include "GrVertexBatch.h" 14 #include "GrVertexBatch.h"
17 15
18 // Common functions 16 class GrBatchFlushState;
19 class BWFillRectBatchBase { 17 class SkMatrix;
20 public: 18 struct SkRect;
21 static const int kVertsPerInstance = 4; 19
22 static const int kIndicesPerInstance = 6; 20 class BWFillRectBatch : public GrVertexBatch {
23
24 static const GrIndexBuffer* GetIndexBuffer(GrResourceProvider* rp) {
25 return rp->refQuadIndexBuffer();
26 }
27
28 template <typename Geometry>
29 static void SetBounds(const Geometry& geo, SkRect* outBounds) {
30 geo.fViewMatrix.mapRect(outBounds, geo.fRect);
31 }
32 };
33
34 /** We always use per-vertex colors so that rects can be batched across color ch anges. Sometimes
35 we have explicit local coords and sometimes not. We *could* always provide explicit local
36 coords and just duplicate the positions when the caller hasn't provided a lo cal coord rect,
37 but we haven't seen a use case which frequently switches between local rect and no local
38 rect draws.
39
40 The color param is used to determine whether the opaque hint can be set on t he draw state.
41 The caller must populate the vertex colors itself.
42
43 The vertex attrib order is always pos, color, [local coords].
44 */
45 static const GrGeometryProcessor* create_gp(const SkMatrix& viewMatrix,
46 bool readsCoverage,
47 bool hasExplicitLocalCoords,
48 const SkMatrix* localMatrix) {
49 using namespace GrDefaultGeoProcFactory;
50 Color color(Color::kAttribute_Type);
51 Coverage coverage(readsCoverage ? Coverage::kSolid_Type : Coverage::kNone_Ty pe);
52
53 // if we have a local rect, then we apply the localMatrix directly to the lo calRect to
54 // generate vertex local coords
55 if (hasExplicitLocalCoords) {
56 LocalCoords localCoords(LocalCoords::kHasExplicit_Type);
57 return GrDefaultGeoProcFactory::Create(color, coverage, localCoords, SkM atrix::I());
58 } else {
59 LocalCoords localCoords(LocalCoords::kUsePosition_Type, localMatrix ? lo calMatrix : NULL);
60 return GrDefaultGeoProcFactory::CreateForDeviceSpace(color, coverage, lo calCoords,
61 viewMatrix);
62 }
63 }
64
65 static void tesselate(intptr_t vertices,
66 size_t vertexStride,
67 GrColor color,
68 const SkMatrix& viewMatrix,
69 const SkRect& rect,
70 const SkRect* localRect,
71 const SkMatrix* localMatrix) {
72 SkPoint* positions = reinterpret_cast<SkPoint*>(vertices);
73
74 positions->setRectFan(rect.fLeft, rect.fTop,
75 rect.fRight, rect.fBottom, vertexStride);
76 viewMatrix.mapPointsWithStride(positions, vertexStride, BWFillRectBatchBase: :kVertsPerInstance);
77
78 // TODO we should only do this if local coords are being read
79 if (localRect) {
80 static const int kLocalOffset = sizeof(SkPoint) + sizeof(GrColor);
81 SkPoint* coords = reinterpret_cast<SkPoint*>(vertices + kLocalOffset);
82 coords->setRectFan(localRect->fLeft, localRect->fTop,
83 localRect->fRight, localRect->fBottom,
84 vertexStride);
85 if (localMatrix) {
86 localMatrix->mapPointsWithStride(coords, vertexStride,
87 BWFillRectBatchBase::kVertsPerInsta nce);
88 }
89 }
90
91 static const int kColorOffset = sizeof(SkPoint);
92 GrColor* vertColor = reinterpret_cast<GrColor*>(vertices + kColorOffset);
93 for (int j = 0; j < 4; ++j) {
94 *vertColor = color;
95 vertColor = (GrColor*) ((intptr_t) vertColor + vertexStride);
96 }
97 }
98
99 class BWFillRectBatchNoLocalMatrixImp : public BWFillRectBatchBase {
100 public:
101 struct Geometry {
102 SkMatrix fViewMatrix;
103 SkRect fRect;
104 GrColor fColor;
105 };
106
107 static const char* Name() { return "BWFillRectBatchNoLocalMatrix"; }
108
109 static bool CanCombine(const Geometry& mine, const Geometry& theirs,
110 const GrPipelineOptimizations& opts) {
111 // We apply the viewmatrix to the rect points on the cpu. However, if t he pipeline uses
112 // local coords then we won't be able to batch. We could actually uploa d the viewmatrix
113 // using vertex attributes in these cases, but haven't investigated that
114 return !opts.readsLocalCoords() || mine.fViewMatrix.cheapEqualTo(theirs. fViewMatrix);
115 }
116
117 static const GrGeometryProcessor* CreateGP(const Geometry& geo,
118 const GrPipelineOptimizations& op ts) {
119 const GrGeometryProcessor* gp = create_gp(geo.fViewMatrix, opts.readsCov erage(), false,
120 NULL);
121
122 SkASSERT(gp->getVertexStride() == sizeof(GrDefaultGeoProcFactory::Positi onColorAttr));
123 return gp;
124 }
125
126 static void Tesselate(intptr_t vertices, size_t vertexStride, const Geometry & geo,
127 const GrPipelineOptimizations& opts) {
128 tesselate(vertices, vertexStride, geo.fColor, geo.fViewMatrix, geo.fRect , NULL, NULL);
129 }
130 };
131
132 class BWFillRectBatchLocalMatrixImp : public BWFillRectBatchBase {
133 public:
134 struct Geometry {
135 SkMatrix fViewMatrix;
136 SkMatrix fLocalMatrix;
137 SkRect fRect;
138 GrColor fColor;
139 };
140
141 static const char* Name() { return "BWFillRectBatchLocalMatrix"; }
142
143 static bool CanCombine(const Geometry& mine, const Geometry& theirs,
144 const GrPipelineOptimizations& opts) {
145 // We apply the viewmatrix to the rect points on the cpu. However, if t he pipeline uses
146 // local coords then we won't be able to batch. We could actually uploa d the viewmatrix
147 // using vertex attributes in these cases, but haven't investigated that
148 return !opts.readsLocalCoords() || mine.fViewMatrix.cheapEqualTo(theirs. fViewMatrix);
149 }
150
151 static const GrGeometryProcessor* CreateGP(const Geometry& geo,
152 const GrPipelineOptimizations& op ts) {
153 const GrGeometryProcessor* gp = create_gp(geo.fViewMatrix, opts.readsCov erage(), false,
154 &geo.fLocalMatrix);
155
156 SkASSERT(gp->getVertexStride() == sizeof(GrDefaultGeoProcFactory::Positi onColorAttr));
157 return gp;
158 }
159
160 static void Tesselate(intptr_t vertices, size_t vertexStride, const Geometry & geo,
161 const GrPipelineOptimizations& opts) {
162 tesselate(vertices, vertexStride, geo.fColor, geo.fViewMatrix, geo.fRect , NULL,
163 &geo.fLocalMatrix);
164 }
165 };
166
167 class BWFillRectBatchLocalRectImp : public BWFillRectBatchBase {
168 public: 21 public:
169 struct Geometry { 22 struct Geometry {
170 SkMatrix fViewMatrix; 23 SkMatrix fViewMatrix;
171 SkRect fRect; 24 SkRect fRect;
172 SkRect fLocalRect; 25 SkRect fLocalRect;
26 SkMatrix fLocalMatrix;
173 GrColor fColor; 27 GrColor fColor;
28 bool fHasLocalRect;
29 bool fHasLocalMatrix;
174 }; 30 };
175 31
176 static const char* Name() { return "BWFillRectBatchLocalRect"; } 32 static GrDrawBatch* Create(const Geometry& geometry) {
177 33 return SkNEW_ARGS(BWFillRectBatch, (geometry));
178 static bool CanCombine(const Geometry& mine, const Geometry& theirs, 34 }
179 const GrPipelineOptimizations& opts) { 35
36 const char* name() const override { return "RectBatch"; }
37
38 void getInvariantOutputColor(GrInitInvariantOutput* out) const override {
39 // When this is called on a batch, there is only one geometry bundle
40 out->setKnownFourComponents(fGeoData[0].fColor);
41 }
42
43 void getInvariantOutputCoverage(GrInitInvariantOutput* out) const override {
44 out->setKnownSingleComponent(0xff);
45 }
46
47 SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; }
48
49 private:
50 BWFillRectBatch(const Geometry& geometry) {
51 this->initClassID<BWFillRectBatch>();
52 fGeoData.push_back(geometry);
53
54 fBounds = geometry.fRect;
55 geometry.fViewMatrix.mapRect(&fBounds);
56 }
57
58 GrColor color() const { return fBatch.fColor; }
59 bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; }
60 bool colorIgnored() const { return fBatch.fColorIgnored; }
61 const SkMatrix& viewMatrix() const { return fGeoData[0].fViewMatrix; }
62 const SkMatrix& localMatrix() const { return fGeoData[0].fLocalMatrix; }
63 bool hasLocalRect() const { return fGeoData[0].fHasLocalRect; }
64 bool hasLocalMatrix() const { return fGeoData[0].fHasLocalMatrix; }
65 bool coverageIgnored() const { return fBatch.fCoverageIgnored; }
66
67 void initBatchTracker(const GrPipelineOptimizations& init) override {
68 // Handle any color overrides
69 if (!init.readsColor()) {
70 fGeoData[0].fColor = GrColor_ILLEGAL;
71 }
72 init.getOverrideColorIfSet(&fGeoData[0].fColor);
73
74 // setup batch properties
75 fBatch.fColorIgnored = !init.readsColor();
76 fBatch.fColor = fGeoData[0].fColor;
77 fBatch.fUsesLocalCoords = init.readsLocalCoords();
78 fBatch.fCoverageIgnored = !init.readsCoverage();
79 }
80
81 void onPrepareDraws(Target* target) override {
82 SkAutoTUnref<const GrGeometryProcessor> gp(this->createRectGP());
83 if (!gp) {
84 SkDebugf("Could not create GrGeometryProcessor\n");
85 return;
86 }
87
88 target->initDraw(gp, this->pipeline());
89
90 int instanceCount = fGeoData.count();
91 size_t vertexStride = gp->getVertexStride();
92 SkASSERT(this->hasLocalRect() ?
93 vertexStride == sizeof(GrDefaultGeoProcFactory::PositionColorLo calCoordAttr) :
94 vertexStride == sizeof(GrDefaultGeoProcFactory::PositionColorAt tr));
95 QuadHelper helper;
96 void* vertices = helper.init(target, vertexStride, instanceCount);
97
98 if (!vertices) {
99 return;
100 }
101
102 for (int i = 0; i < instanceCount; i++) {
103 const Geometry& geom = fGeoData[i];
104
105 intptr_t offset = reinterpret_cast<intptr_t>(vertices) +
106 kVerticesPerQuad * i * vertexStride;
107 SkPoint* positions = reinterpret_cast<SkPoint*>(offset);
108
109 positions->setRectFan(geom.fRect.fLeft, geom.fRect.fTop,
110 geom.fRect.fRight, geom.fRect.fBottom, vertexS tride);
111 geom.fViewMatrix.mapPointsWithStride(positions, vertexStride, kVerti cesPerQuad);
112
113 // TODO we should only do this if local coords are being read
114 if (geom.fHasLocalRect) {
115 static const int kLocalOffset = sizeof(SkPoint) + sizeof(GrColor );
116 SkPoint* coords = reinterpret_cast<SkPoint*>(offset + kLocalOffs et);
117 coords->setRectFan(geom.fLocalRect.fLeft, geom.fLocalRect.fTop,
118 geom.fLocalRect.fRight, geom.fLocalRect.fBott om,
119 vertexStride);
120 if (geom.fHasLocalMatrix) {
121 geom.fLocalMatrix.mapPointsWithStride(coords, vertexStride, kVerticesPerQuad);
122 }
123 }
124
125 static const int kColorOffset = sizeof(SkPoint);
126 GrColor* vertColor = reinterpret_cast<GrColor*>(offset + kColorOffse t);
127 for (int j = 0; j < 4; ++j) {
128 *vertColor = geom.fColor;
129 vertColor = (GrColor*) ((intptr_t) vertColor + vertexStride);
130 }
131 }
132
133 helper.recordDraw(target);
134 }
135
136 bool onCombineIfPossible(GrBatch* t, const GrCaps& caps) override {
137 BWFillRectBatch* that = t->cast<BWFillRectBatch>();
138 if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pi peline(),
139 that->bounds(), caps)) {
140 return false;
141 }
142
143 if (this->hasLocalRect() != that->hasLocalRect()) {
144 return false;
145 }
146
147 SkASSERT(this->usesLocalCoords() == that->usesLocalCoords());
148 if (!this->hasLocalRect() && this->usesLocalCoords()) {
149 if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
150 return false;
151 }
152
153 if (this->hasLocalMatrix() != that->hasLocalMatrix()) {
154 return false;
155 }
156
157 if (this->hasLocalMatrix() && !this->localMatrix().cheapEqualTo(that ->localMatrix())) {
158 return false;
159 }
160 }
161
162 if (this->color() != that->color()) {
163 fBatch.fColor = GrColor_ILLEGAL;
164 }
165 fGeoData.push_back_n(that->geoData()->count(), that->geoData()->begin()) ;
166 this->joinBounds(that->bounds());
180 return true; 167 return true;
181 } 168 }
182 169
183 static const GrGeometryProcessor* CreateGP(const Geometry& geo, 170
184 const GrPipelineOptimizations& op ts) { 171 /** We always use per-vertex colors so that rects can be batched across colo r changes. Sometimes
185 const GrGeometryProcessor* gp = create_gp(geo.fViewMatrix, opts.readsCov erage(), true, 172 we have explicit local coords and sometimes not. We *could* always prov ide explicit local
186 NULL); 173 coords and just duplicate the positions when the caller hasn't provided a local coord rect,
187 174 but we haven't seen a use case which frequently switches between local r ect and no local
188 SkASSERT(gp->getVertexStride() == 175 rect draws.
189 sizeof(GrDefaultGeoProcFactory::PositionColorLocalCoordAttr)); 176
190 return gp; 177 The color param is used to determine whether the opaque hint can be set on the draw state.
191 } 178 The caller must populate the vertex colors itself.
192 179
193 static void Tesselate(intptr_t vertices, size_t vertexStride, const Geometry & geo, 180 The vertex attrib order is always pos, color, [local coords].
194 const GrPipelineOptimizations& opts) { 181 */
195 tesselate(vertices, vertexStride, geo.fColor, geo.fViewMatrix, geo.fRect , &geo.fLocalRect, 182 const GrGeometryProcessor* createRectGP() const {
196 NULL); 183 using namespace GrDefaultGeoProcFactory;
197 } 184 Color color(Color::kAttribute_Type);
185 Coverage coverage(this->coverageIgnored() ? Coverage::kNone_Type : Cover age::kSolid_Type);
186
187 // if we have a local rect, then we apply the localMatrix directly to th e localRect to
188 // generate vertex local coords
189 if (this->hasLocalRect()) {
190 LocalCoords localCoords(LocalCoords::kHasExplicit_Type);
191 return GrDefaultGeoProcFactory::Create(color, coverage, localCoords, SkMatrix::I());
192 } else {
193 LocalCoords localCoords(LocalCoords::kUsePosition_Type,
194 this->hasLocalMatrix() ? &this->localMatrix( ) : NULL);
195 return GrDefaultGeoProcFactory::CreateForDeviceSpace(color, coverage , localCoords,
196 this->viewMatri x());
197 }
198 }
199
200 struct BatchTracker {
201 GrColor fColor;
202 bool fUsesLocalCoords;
203 bool fColorIgnored;
204 bool fCoverageIgnored;
205 };
206
207 BatchTracker fBatch;
208 SkSTArray<1, Geometry, true> fGeoData;
198 }; 209 };
199 210
200 class BWFillRectBatchLocalMatrixLocalRectImp : public BWFillRectBatchBase {
201 public:
202 struct Geometry {
203 SkMatrix fViewMatrix;
204 SkMatrix fLocalMatrix;
205 SkRect fRect;
206 SkRect fLocalRect;
207 GrColor fColor;
208 };
209
210 static const char* Name() { return "BWFillRectBatchLocalMatrixLocalRect"; }
211
212 static bool CanCombine(const Geometry& mine, const Geometry& theirs,
213 const GrPipelineOptimizations& opts) {
214 return true;
215 }
216
217 static const GrGeometryProcessor* CreateGP(const Geometry& geo,
218 const GrPipelineOptimizations& op ts) {
219 const GrGeometryProcessor* gp = create_gp(geo.fViewMatrix, opts.readsCov erage(), true,
220 NULL);
221
222 SkASSERT(gp->getVertexStride() ==
223 sizeof(GrDefaultGeoProcFactory::PositionColorLocalCoordAttr));
224 return gp;
225 }
226
227 static void Tesselate(intptr_t vertices, size_t vertexStride, const Geometry & geo,
228 const GrPipelineOptimizations& opts) {
229 tesselate(vertices, vertexStride, geo.fColor, geo.fViewMatrix, geo.fRect , &geo.fLocalRect,
230 &geo.fLocalMatrix);
231 }
232 };
233
234 typedef GrTInstanceBatch<BWFillRectBatchNoLocalMatrixImp> BWFillRectBatchSimple;
235 typedef GrTInstanceBatch<BWFillRectBatchLocalMatrixImp> BWFillRectBatchLocalMatr ix;
236 typedef GrTInstanceBatch<BWFillRectBatchLocalRectImp> BWFillRectBatchLocalRect;
237 typedef GrTInstanceBatch<BWFillRectBatchLocalMatrixLocalRectImp> BWFillRectBatch LocalMatrixLocalRect;
238
239 namespace GrBWFillRectBatch { 211 namespace GrBWFillRectBatch {
240 GrDrawBatch* Create(GrColor color, 212 GrDrawBatch* Create(GrColor color,
241 const SkMatrix& viewMatrix, 213 const SkMatrix& viewMatrix,
242 const SkRect& rect, 214 const SkRect& rect,
243 const SkRect* localRect, 215 const SkRect* localRect,
244 const SkMatrix* localMatrix) { 216 const SkMatrix* localMatrix) {
245 // TODO bubble these up as separate calls 217 BWFillRectBatch::Geometry geometry;
246 if (localRect && localMatrix) { 218 geometry.fColor = color;
247 BWFillRectBatchLocalMatrixLocalRect* batch = BWFillRectBatchLocalMatrixL ocalRect::Create(); 219 geometry.fViewMatrix = viewMatrix;
248 BWFillRectBatchLocalMatrixLocalRect::Geometry& geo = *batch->geometry(); 220 geometry.fRect = rect;
249 geo.fColor = color; 221
250 geo.fViewMatrix = viewMatrix; 222 if (localRect) {
251 geo.fLocalMatrix = *localMatrix; 223 geometry.fHasLocalRect = true;
252 geo.fRect = rect; 224 geometry.fLocalRect = *localRect;
253 geo.fLocalRect = *localRect;
254 batch->init();
255 return batch;
256 } else if (localRect) {
257 BWFillRectBatchLocalRect* batch = BWFillRectBatchLocalRect::Create();
258 BWFillRectBatchLocalRect::Geometry& geo = *batch->geometry();
259 geo.fColor = color;
260 geo.fViewMatrix = viewMatrix;
261 geo.fRect = rect;
262 geo.fLocalRect = *localRect;
263 batch->init();
264 return batch;
265 } else if (localMatrix) {
266 BWFillRectBatchLocalMatrix* batch = BWFillRectBatchLocalMatrix::Create() ;
267 BWFillRectBatchLocalMatrix::Geometry& geo = *batch->geometry();
268 geo.fColor = color;
269 geo.fViewMatrix = viewMatrix;
270 geo.fLocalMatrix = *localMatrix;
271 geo.fRect = rect;
272 batch->init();
273 return batch;
274 } else { 225 } else {
275 BWFillRectBatchSimple* batch = BWFillRectBatchSimple::Create(); 226 geometry.fHasLocalRect = false;
276 BWFillRectBatchSimple::Geometry& geo = *batch->geometry();
277 geo.fColor = color;
278 geo.fViewMatrix = viewMatrix;
279 geo.fRect = rect;
280 batch->init();
281 return batch;
282 } 227 }
228
229 if (localMatrix) {
230 geometry.fHasLocalMatrix = true;
231 geometry.fLocalMatrix = *localMatrix;
232 } else {
233 geometry.fHasLocalMatrix = false;
234 }
235
236 return BWFillRectBatch::Create(geometry);
283 } 237 }
284 }; 238 };
285 239
286 //////////////////////////////////////////////////////////////////////////////// /////////////////// 240 //////////////////////////////////////////////////////////////////////////////// ///////////////////
287 241
288 #ifdef GR_TEST_UTILS 242 #ifdef GR_TEST_UTILS
289 243
290 #include "GrBatchTest.h" 244 #include "GrBatchTest.h"
291 245
292 DRAW_BATCH_TEST_DEFINE(RectBatch) { 246 DRAW_BATCH_TEST_DEFINE(RectBatch) {
293 GrColor color = GrRandomColor(random); 247 BWFillRectBatch::Geometry geometry;
294 SkRect rect = GrTest::TestRect(random); 248 geometry.fColor = GrRandomColor(random);
295 SkRect localRect = GrTest::TestRect(random);
296 SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random);
297 SkMatrix localMatrix = GrTest::TestMatrix(random);
298 249
299 bool hasLocalRect = random->nextBool(); 250 geometry.fRect = GrTest::TestRect(random);
300 bool hasLocalMatrix = random->nextBool(); 251 geometry.fHasLocalRect = random->nextBool();
301 return GrBWFillRectBatch::Create(color, viewMatrix, rect, hasLocalRect ? &lo calRect : nullptr, 252
302 hasLocalMatrix ? &localMatrix : nullptr); 253 if (geometry.fHasLocalRect) {
254 geometry.fViewMatrix = GrTest::TestMatrixInvertible(random);
255 geometry.fLocalRect = GrTest::TestRect(random);
256 } else {
257 geometry.fViewMatrix = GrTest::TestMatrix(random);
258 }
259
260 geometry.fHasLocalMatrix = random->nextBool();
261 if (geometry.fHasLocalMatrix) {
262 geometry.fLocalMatrix = GrTest::TestMatrix(random);
263 }
264
265 return BWFillRectBatch::Create(geometry);
303 } 266 }
304 267
305 #endif 268 #endif
OLDNEW
« no previous file with comments | « src/gpu/batches/GrAAFillRectBatch.cpp ('k') | src/gpu/batches/GrTInstanceBatch.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698