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

Side by Side Diff: src/gpu/GrStencilSettings.cpp

Issue 1969693003: Revert of Separate user and raw stencil settings (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: Created 4 years, 7 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/GrStencilSettings.h ('k') | src/gpu/GrUserStencilSettings.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 /*
2 * Copyright 2011 Google Inc.
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
9 #include "GrStencilSettings.h"
10
11 #include "GrProcessor.h"
12
13 constexpr const GrUserStencilSettings gUnused(
14 GrUserStencilSettings::StaticInit<
15 0x0000,
16 GrUserStencilTest::kAlwaysIfInClip,
17 0xffff,
18 GrUserStencilOp::kKeep,
19 GrUserStencilOp::kKeep,
20 0x0000>()
21 );
22
23 GR_STATIC_ASSERT(kAll_StencilFlags == (gUnused.fFrontFlags[0] & gUnused.fBackFla gs[0]));
24
25 const GrUserStencilSettings& GrUserStencilSettings::kUnused = gUnused;
26
27 void GrStencilSettings::reset(const GrUserStencilSettings& user, bool hasStencil Clip,
28 int numStencilBits) {
29 uint16_t frontFlags = user.fFrontFlags[hasStencilClip];
30 if (frontFlags & kSingleSided_StencilFlag) {
31 fFlags = frontFlags;
32 if (!this->isDisabled()) {
33 fFront.reset(user.fFront, hasStencilClip, numStencilBits);
34 }
35 return;
36 }
37
38 uint16_t backFlags = user.fBackFlags[hasStencilClip];
39 fFlags = frontFlags & backFlags;
40 if (this->isDisabled()) {
41 return;
42 }
43 if (!(frontFlags & kDisabled_StencilFlag)) {
44 fFront.reset(user.fFront, hasStencilClip, numStencilBits);
45 } else {
46 fFront.setDisabled();
47 }
48 if (!(backFlags & kDisabled_StencilFlag)) {
49 fBack.reset(user.fBack, hasStencilClip, numStencilBits);
50 } else {
51 fBack.setDisabled();
52 }
53 }
54
55 void GrStencilSettings::reset(const GrStencilSettings& that) {
56 fFlags = that.fFlags;
57 if ((kInvalid_PrivateFlag | kDisabled_StencilFlag) & fFlags) {
58 return;
59 }
60 if (!this->isTwoSided()) {
61 memcpy(&fFront, &that.fFront, sizeof(Face));
62 } else {
63 memcpy(&fFront, &that.fFront, 2 * sizeof(Face));
64 GR_STATIC_ASSERT(sizeof(Face) ==
65 offsetof(GrStencilSettings, fBack) - offsetof(GrStencil Settings, fFront));
66 }
67 }
68
69 bool GrStencilSettings::operator==(const GrStencilSettings& that) const {
70 if ((kInvalid_PrivateFlag | kDisabled_StencilFlag) & (fFlags | that.fFlags)) {
71 // At least one is invalid and/or disabled.
72 if (kInvalid_PrivateFlag & (fFlags | that.fFlags)) {
73 return false; // We never allow invalid stencils to be equal.
74 }
75 // They're only equal if both are disabled.
76 return kDisabled_StencilFlag & (fFlags & that.fFlags);
77 }
78 if (kSingleSided_StencilFlag & (fFlags & that.fFlags)) {
79 return 0 == memcmp(&fFront, &that.fFront, sizeof(Face)); // Both are sin gle sided.
80 } else {
81 return 0 == memcmp(&fFront, &that.fFront, 2 * sizeof(Face));
82 GR_STATIC_ASSERT(sizeof(Face) ==
83 offsetof(GrStencilSettings, fBack) - offsetof(GrStencil Settings, fFront));
84 }
85 // memcmp relies on GrStencilSettings::Face being tightly packed.
86 GR_STATIC_ASSERT(0 == offsetof(Face, fRef));
87 GR_STATIC_ASSERT(2 == sizeof(Face::fRef));
88 GR_STATIC_ASSERT(2 == offsetof(Face, fTest));
89 GR_STATIC_ASSERT(2 == sizeof(Face::fTest));
90 GR_STATIC_ASSERT(4 == offsetof(Face, fTestMask));
91 GR_STATIC_ASSERT(2 == sizeof(Face::fTestMask));
92 GR_STATIC_ASSERT(6 == offsetof(Face, fPassOp));
93 GR_STATIC_ASSERT(1 == sizeof(Face::fPassOp));
94 GR_STATIC_ASSERT(7 == offsetof(Face, fFailOp));
95 GR_STATIC_ASSERT(1 == sizeof(Face::fFailOp));
96 GR_STATIC_ASSERT(8 == offsetof(Face, fWriteMask));
97 GR_STATIC_ASSERT(2 == sizeof(Face::fWriteMask));
98 GR_STATIC_ASSERT(10 == sizeof(Face));
99 }
100
101 static constexpr GrStencilTest gUserStencilTestToRaw[kGrUserStencilTestCount] = {
102 // Tests that respect the clip.
103 GrStencilTest::kAlways, // kAlwaysIfInClip (This is only for when there is not a stencil clip).
104 GrStencilTest::kEqual, // kEqualIfInClip.
105 GrStencilTest::kLess, // kLessIfInClip.
106 GrStencilTest::kLEqual, // kLEqualIfInClip.
107
108 // Tests that ignore the clip.
109 GrStencilTest::kAlways,
110 GrStencilTest::kNever,
111 GrStencilTest::kGreater,
112 GrStencilTest::kGEqual,
113 GrStencilTest::kLess,
114 GrStencilTest::kLEqual,
115 GrStencilTest::kEqual,
116 GrStencilTest::kNotEqual
117 };
118
119 GR_STATIC_ASSERT(0 == (int)GrUserStencilTest::kAlwaysIfInClip);
120 GR_STATIC_ASSERT(1 == (int)GrUserStencilTest::kEqualIfInClip);
121 GR_STATIC_ASSERT(2 == (int)GrUserStencilTest::kLessIfInClip);
122 GR_STATIC_ASSERT(3 == (int)GrUserStencilTest::kLEqualIfInClip);
123 GR_STATIC_ASSERT(4 == (int)GrUserStencilTest::kAlways);
124 GR_STATIC_ASSERT(5 == (int)GrUserStencilTest::kNever);
125 GR_STATIC_ASSERT(6 == (int)GrUserStencilTest::kGreater);
126 GR_STATIC_ASSERT(7 == (int)GrUserStencilTest::kGEqual);
127 GR_STATIC_ASSERT(8 == (int)GrUserStencilTest::kLess);
128 GR_STATIC_ASSERT(9 == (int)GrUserStencilTest::kLEqual);
129 GR_STATIC_ASSERT(10 == (int)GrUserStencilTest::kEqual);
130 GR_STATIC_ASSERT(11 == (int)GrUserStencilTest::kNotEqual);
131
132 static constexpr GrStencilOp gUserStencilOpToRaw[kGrUserStencilOpCount] = {
133 GrStencilOp::kKeep,
134
135 // Ops that only modify user bits.
136 GrStencilOp::kZero,
137 GrStencilOp::kReplace,
138 GrStencilOp::kInvert,
139 GrStencilOp::kIncWrap,
140 GrStencilOp::kDecWrap,
141 GrStencilOp::kIncClamp, // kIncMaybeClamp.
142 GrStencilOp::kDecClamp, // kDecMaybeClamp.
143
144 // Ops that only modify the clip bit.
145 GrStencilOp::kZero, // kZeroClipBit.
146 GrStencilOp::kReplace, // kSetClipBit.
147 GrStencilOp::kInvert, // kInvertClipBit.
148
149 // Ops that modify clip and user bits.
150 GrStencilOp::kReplace, // kSetClipAndReplaceUserBits.
151 GrStencilOp::kZero // kZeroClipAndUserBits.
152 };
153
154 GR_STATIC_ASSERT(0 == (int)GrUserStencilOp::kKeep);
155 GR_STATIC_ASSERT(1 == (int)GrUserStencilOp::kZero);
156 GR_STATIC_ASSERT(2 == (int)GrUserStencilOp::kReplace);
157 GR_STATIC_ASSERT(3 == (int)GrUserStencilOp::kInvert);
158 GR_STATIC_ASSERT(4 == (int)GrUserStencilOp::kIncWrap);
159 GR_STATIC_ASSERT(5 == (int)GrUserStencilOp::kDecWrap);
160 GR_STATIC_ASSERT(6 == (int)GrUserStencilOp::kIncMaybeClamp);
161 GR_STATIC_ASSERT(7 == (int)GrUserStencilOp::kDecMaybeClamp);
162 GR_STATIC_ASSERT(8 == (int)GrUserStencilOp::kZeroClipBit);
163 GR_STATIC_ASSERT(9 == (int)GrUserStencilOp::kSetClipBit);
164 GR_STATIC_ASSERT(10 == (int)GrUserStencilOp::kInvertClipBit);
165 GR_STATIC_ASSERT(11 == (int)GrUserStencilOp::kSetClipAndReplaceUserBits);
166 GR_STATIC_ASSERT(12 == (int)GrUserStencilOp::kZeroClipAndUserBits);
167
168 void GrStencilSettings::Face::reset(const GrUserStencilSettings::Face& user, boo l hasStencilClip,
169 int numStencilBits) {
170 SkASSERT(user.fTest < (GrUserStencilTest)kGrUserStencilTestCount);
171 SkASSERT(user.fPassOp < (GrUserStencilOp)kGrUserStencilOpCount);
172 SkASSERT(user.fFailOp < (GrUserStencilOp)kGrUserStencilOpCount);
173 SkASSERT(numStencilBits <= 16);
174 int clipBit = 1 << (numStencilBits - 1);
175 int userMask = clipBit - 1;
176
177 GrUserStencilOp maxOp = SkTMax(user.fPassOp, user.fFailOp);
178 SkDEBUGCODE(GrUserStencilOp otherOp = SkTMin(user.fPassOp, user.fFailOp);)
179 if (maxOp <= kLastUserOnlyStencilOp) {
180 // Ops that only modify user bits.
181 fWriteMask = user.fWriteMask & userMask;
182 SkASSERT(otherOp <= kLastUserOnlyStencilOp);
183 } else if (maxOp <= kLastClipOnlyStencilOp) {
184 // Ops that only modify the clip bit.
185 fWriteMask = clipBit;
186 SkASSERT(GrUserStencilOp::kKeep == otherOp ||
187 (otherOp > kLastUserOnlyStencilOp && otherOp <= kLastClipOnlySt encilOp));
188 } else {
189 // Ops that modify both clip and user bits.
190 fWriteMask = clipBit | (user.fWriteMask & userMask);
191 SkASSERT(GrUserStencilOp::kKeep == otherOp || otherOp > kLastClipOnlySte ncilOp);
192 }
193
194 fFailOp = gUserStencilOpToRaw[(int)user.fFailOp];
195 fPassOp = gUserStencilOpToRaw[(int)user.fPassOp];
196
197 if (!hasStencilClip || user.fTest > kLastClippedStencilTest) {
198 // Ignore the clip.
199 fTestMask = user.fTestMask & userMask;
200 fTest = gUserStencilTestToRaw[(int)user.fTest];
201 } else if (GrUserStencilTest::kAlwaysIfInClip != user.fTest) {
202 // Respect the clip.
203 fTestMask = clipBit | (user.fTestMask & userMask);
204 fTest = gUserStencilTestToRaw[(int)user.fTest];
205 } else {
206 // Test only for clip.
207 fTestMask = clipBit;
208 fTest = GrStencilTest::kEqual;
209 }
210
211 fRef = (clipBit | user.fRef) & (fTestMask | fWriteMask);
212 }
213
214 void GrStencilSettings::Face::setDisabled() {
215 memset(this, 0, sizeof(*this));
216 GR_STATIC_ASSERT(0 == (int)GrStencilTest::kAlways);
217 GR_STATIC_ASSERT(0 == (int)GrStencilOp::kKeep);
218 }
219
220 ////////////////////////////////////////////////////////////////////////////////
221 // Stencil Rules for Merging user stencil space into clip
222 //
223
224 ///////
225 // Replace
226 static constexpr GrUserStencilSettings gUserToClipReplace(
227 GrUserStencilSettings::StaticInit<
228 0x0000,
229 GrUserStencilTest::kNotEqual,
230 0xffff,
231 GrUserStencilOp::kSetClipAndReplaceUserBits,
232 GrUserStencilOp::kZeroClipAndUserBits,
233 0xffff>()
234 );
235
236 static constexpr GrUserStencilSettings gInvUserToClipReplace(
237 GrUserStencilSettings::StaticInit<
238 0x0000,
239 GrUserStencilTest::kEqual,
240 0xffff,
241 GrUserStencilOp::kSetClipAndReplaceUserBits,
242 GrUserStencilOp::kZeroClipAndUserBits,
243 0xffff>()
244 );
245
246 ///////
247 // Intersect
248 static constexpr GrUserStencilSettings gUserToClipIsect(
249 GrUserStencilSettings::StaticInit<
250 0x0000,
251 GrUserStencilTest::kLessIfInClip, // "0 < userBits" is equivalent to "0 != userBits".
252 0xffff,
253 GrUserStencilOp::kSetClipAndReplaceUserBits,
254 GrUserStencilOp::kZeroClipAndUserBits,
255 0xffff>()
256 );
257
258 ///////
259 // Difference
260 static constexpr GrUserStencilSettings gUserToClipDiff(
261 GrUserStencilSettings::StaticInit<
262 0x0000,
263 GrUserStencilTest::kEqualIfInClip,
264 0xffff,
265 GrUserStencilOp::kSetClipAndReplaceUserBits,
266 GrUserStencilOp::kZeroClipAndUserBits,
267 0xffff>()
268 );
269
270 ///////
271 // Union
272 static constexpr GrUserStencilSettings gUserToClipUnion(
273 GrUserStencilSettings::StaticInit<
274 0x0000,
275 GrUserStencilTest::kNotEqual,
276 0xffff,
277 GrUserStencilOp::kSetClipAndReplaceUserBits,
278 GrUserStencilOp::kKeep,
279 0xffff>()
280 );
281
282 static constexpr GrUserStencilSettings gInvUserToClipUnionPass0( // Does not zer o user bits.
283 GrUserStencilSettings::StaticInit<
284 0x0000,
285 GrUserStencilTest::kEqual,
286 0xffff,
287 GrUserStencilOp::kSetClipBit,
288 GrUserStencilOp::kKeep,
289 0x0000>()
290 );
291
292 ///////
293 // Xor
294 static constexpr GrUserStencilSettings gUserToClipXorPass0( // Does not zero use r bits.
295 GrUserStencilSettings::StaticInit<
296 0x0000,
297 GrUserStencilTest::kNotEqual,
298 0xffff,
299 GrUserStencilOp::kInvertClipBit,
300 GrUserStencilOp::kKeep,
301 0x0000>()
302 );
303
304 static constexpr GrUserStencilSettings gInvUserToClipXorPass0( // Does not zero user bits.
305 GrUserStencilSettings::StaticInit<
306 0x0000,
307 GrUserStencilTest::kEqual,
308 0xffff,
309 GrUserStencilOp::kInvertClipBit,
310 GrUserStencilOp::kKeep,
311 0x0000>()
312 );
313
314 ///////
315 // Reverse Diff
316 static constexpr GrUserStencilSettings gUserToClipRDiffPass0( // Does not zero u ser bits.
317 GrUserStencilSettings::StaticInit<
318 0x0000,
319 GrUserStencilTest::kNotEqual,
320 0xffff,
321 GrUserStencilOp::kInvertClipBit,
322 GrUserStencilOp::kZeroClipBit,
323 0x0000>()
324 );
325
326 static constexpr GrUserStencilSettings gInvUserToClipRDiffPass0( // Does not zer o user bits.
327 GrUserStencilSettings::StaticInit<
328 0x0000,
329 GrUserStencilTest::kEqual,
330 0xffff,
331 GrUserStencilOp::kInvertClipBit,
332 GrUserStencilOp::kZeroClipBit,
333 0x0000>()
334 );
335
336 ///////
337 // Second pass to clear user bits (only needed sometimes)
338 static constexpr GrUserStencilSettings gZeroUserBits(
339 GrUserStencilSettings::StaticInit<
340 0x0000,
341 GrUserStencilTest::kNotEqual,
342 0xffff,
343 GrUserStencilOp::kZero,
344 GrUserStencilOp::kKeep,
345 0xffff>()
346 );
347
348 static constexpr const GrUserStencilSettings* gUserToClipTable[2][1 + SkRegion:: kLastOp][3] = {
349 { /* Normal fill. */
350 {&gUserToClipDiff, nullptr, nullptr}, // kDifference_ Op.
351 {&gUserToClipIsect, nullptr, nullptr}, // kIntersect_O p.
352 {&gUserToClipUnion, nullptr, nullptr}, // kUnion_Op.
353 {&gUserToClipXorPass0, &gZeroUserBits, nullptr}, // kXOR_Op.
354 {&gUserToClipRDiffPass0, &gZeroUserBits, nullptr}, // kReverseDiff erence_Op.
355 {&gUserToClipReplace, nullptr, nullptr} // kReplace_Op.
356
357 }, /* Inverse fill. */ {
358 {&gUserToClipIsect, nullptr, nullptr}, // ~diff (aka i sect).
359 {&gUserToClipDiff, nullptr, nullptr}, // ~isect (aka diff).
360 {&gInvUserToClipUnionPass0, &gZeroUserBits, nullptr}, // ~union.
361 {&gInvUserToClipXorPass0, &gZeroUserBits, nullptr}, // ~xor.
362 {&gInvUserToClipRDiffPass0, &gZeroUserBits, nullptr}, // ~reverse dif f.
363 {&gInvUserToClipReplace, nullptr, nullptr} // ~replace.
364 }
365 };
366
367 GR_STATIC_ASSERT(0 == SkRegion::kDifference_Op);
368 GR_STATIC_ASSERT(1 == SkRegion::kIntersect_Op);
369 GR_STATIC_ASSERT(2 == SkRegion::kUnion_Op);
370 GR_STATIC_ASSERT(3 == SkRegion::kXOR_Op);
371 GR_STATIC_ASSERT(4 == SkRegion::kReverseDifference_Op);
372 GR_STATIC_ASSERT(5 == SkRegion::kReplace_Op);
373
374 ///////
375 // Direct to Stencil
376
377 // We can render a clip element directly without first writing to the client
378 // portion of the clip when the fill is not inverse and the set operation will
379 // only modify the in/out status of samples covered by the clip element.
380
381 // this one only works if used right after stencil clip was cleared.
382 // Our clip mask creation code doesn't allow midstream replace ops.
383 static constexpr GrUserStencilSettings gReplaceClip(
384 GrUserStencilSettings::StaticInit<
385 0x0000,
386 GrUserStencilTest::kAlways,
387 0xffff,
388 GrUserStencilOp::kSetClipBit,
389 GrUserStencilOp::kSetClipBit,
390 0x0000>()
391 );
392
393 static constexpr GrUserStencilSettings gUnionClip(
394 GrUserStencilSettings::StaticInit<
395 0x0000,
396 GrUserStencilTest::kAlwaysIfInClip,
397 0xffff,
398 GrUserStencilOp::kKeep,
399 GrUserStencilOp::kSetClipBit,
400 0x0000>()
401 );
402
403 static constexpr GrUserStencilSettings gXorClip(
404 GrUserStencilSettings::StaticInit<
405 0x0000,
406 GrUserStencilTest::kAlways,
407 0xffff,
408 GrUserStencilOp::kInvertClipBit,
409 GrUserStencilOp::kInvertClipBit,
410 0x0000>()
411 );
412
413 static constexpr GrUserStencilSettings gDiffClip(
414 GrUserStencilSettings::StaticInit<
415 0x0000,
416 GrUserStencilTest::kAlwaysIfInClip,
417 0xffff,
418 GrUserStencilOp::kZeroClipBit,
419 GrUserStencilOp::kKeep,
420 0x0000>()
421 );
422
423 static constexpr const GrUserStencilSettings* gDirectDrawTable[1 + SkRegion::kLa stOp][2] = {
424 {&gDiffClip, nullptr}, // kDifference_Op.
425 {nullptr, nullptr}, // kIntersect_Op.
426 {&gUnionClip, nullptr}, // kUnion_Op.
427 {&gXorClip, nullptr}, // kXOR_Op.
428 {nullptr, nullptr}, // kReverseDifference_Op.
429 {&gReplaceClip, nullptr} // kReplace_Op.
430 };
431
432 GR_STATIC_ASSERT(0 == SkRegion::kDifference_Op);
433 GR_STATIC_ASSERT(1 == SkRegion::kIntersect_Op);
434 GR_STATIC_ASSERT(2 == SkRegion::kUnion_Op);
435 GR_STATIC_ASSERT(3 == SkRegion::kXOR_Op);
436 GR_STATIC_ASSERT(4 == SkRegion::kReverseDifference_Op);
437 GR_STATIC_ASSERT(5 == SkRegion::kReplace_Op);
438
439 GrUserStencilSettings const* const* GrStencilSettings::GetClipPasses(SkRegion::O p op,
440 bool canBeD irect,
441 bool invert edFill,
442 bool* drawD irectToClip) {
443 SkASSERT((unsigned)op <= SkRegion::kLastOp);
444 if (canBeDirect && !invertedFill) { // TODO: inverse fill + intersect op can be direct.
445 GrUserStencilSettings const* const* directPass = gDirectDrawTable[op];
446 if (directPass[0]) {
447 *drawDirectToClip = true;
448 return directPass;
449 }
450 }
451 *drawDirectToClip = false;
452 return gUserToClipTable[invertedFill][op];
453 }
454
455 void GrStencilSettings::genKey(GrProcessorKeyBuilder* b) const {
456 b->add32(fFlags);
457 if (this->isDisabled()) {
458 return;
459 }
460 if (!this->isTwoSided()) {
461 constexpr int kCount16 = sizeof(Face) / sizeof(uint16_t);
462 GR_STATIC_ASSERT(0 == sizeof(Face) % sizeof(uint16_t));
463 uint16_t* key = reinterpret_cast<uint16_t*>(b->add32n((kCount16 + 1) / 2 ));
464 memcpy(key, &fFront, sizeof(Face));
465 key[kCount16] = 0;
466 GR_STATIC_ASSERT(1 == kCount16 % 2);
467 } else {
468 constexpr int kCount32 = (2 * sizeof(Face)) / sizeof(uint32_t);
469 GR_STATIC_ASSERT(0 == (2 * sizeof(Face)) % sizeof(uint32_t));
470 uint32_t* key = b->add32n(kCount32);
471 memcpy(key, &fFront, 2 * sizeof(Face));
472 GR_STATIC_ASSERT(sizeof(Face) ==
473 offsetof(GrStencilSettings, fBack) - offsetof(GrStencil Settings, fFront));
474 }
475 // We rely on GrStencilSettings::Face being tightly packed for the key to be reliable.
476 GR_STATIC_ASSERT(0 == offsetof(Face, fRef));
477 GR_STATIC_ASSERT(2 == sizeof(Face::fRef));
478 GR_STATIC_ASSERT(2 == offsetof(Face, fTest));
479 GR_STATIC_ASSERT(2 == sizeof(Face::fTest));
480 GR_STATIC_ASSERT(4 == offsetof(Face, fTestMask));
481 GR_STATIC_ASSERT(2 == sizeof(Face::fTestMask));
482 GR_STATIC_ASSERT(6 == offsetof(Face, fPassOp));
483 GR_STATIC_ASSERT(1 == sizeof(Face::fPassOp));
484 GR_STATIC_ASSERT(7 == offsetof(Face, fFailOp));
485 GR_STATIC_ASSERT(1 == sizeof(Face::fFailOp));
486 GR_STATIC_ASSERT(8 == offsetof(Face, fWriteMask));
487 GR_STATIC_ASSERT(2 == sizeof(Face::fWriteMask));
488 GR_STATIC_ASSERT(10 == sizeof(Face));
489 }
OLDNEW
« no previous file with comments | « src/gpu/GrStencilSettings.h ('k') | src/gpu/GrUserStencilSettings.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698