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

Side by Side Diff: conformance/more/demos/opengl_web.html

Issue 41503006: Add ToT WebGL conformance tests : part 8 (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/deps/third_party/webgl/sdk/tests/
Patch Set: Created 7 years, 1 month 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 | Annotate | Revision Log
« no previous file with comments | « conformance/more/conformance/webGLArrays.html ('k') | conformance/more/demos/video.html » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Property Changes:
Added: svn:eol-style
## -0,0 +1 ##
+LF
\ No newline at end of property
OLDNEW
(Empty)
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta charset="utf-8">
5 <!--
6
7 /*
8 ** Copyright (c) 2012 The Khronos Group Inc.
9 **
10 ** Permission is hereby granted, free of charge, to any person obtaining a
11 ** copy of this software and/or associated documentation files (the
12 ** "Materials"), to deal in the Materials without restriction, including
13 ** without limitation the rights to use, copy, modify, merge, publish,
14 ** distribute, sublicense, and/or sell copies of the Materials, and to
15 ** permit persons to whom the Materials are furnished to do so, subject to
16 ** the following conditions:
17 **
18 ** The above copyright notice and this permission notice shall be included
19 ** in all copies or substantial portions of the Materials.
20 **
21 ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24 ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
25 ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
26 ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27 ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
28 */
29
30 -->
31
32 <title>OpenGL for the web</title>
33
34 <script type="application/x-javascript" src="../util.js"></script>
35
36 <script type="application/x-javascript">
37
38 function log(msg) {
39 document.getElementById('note').textContent += "\n"+msg;
40 }
41
42
43
44
45 function init(ev) {
46 var canvas = document.getElementById('canvas');
47 var gl = getGLContext(canvas);
48
49 var shader = new Shader(gl, "ppix-vert", "ppix-frag");
50 shader.compile();
51 var fbo = new FBO(gl, canvas.width, canvas.height);
52 var fbo2 = new FBO(gl, canvas.width, canvas.height);
53 var fbo3 = new FBO(gl, canvas.width, canvas.height);
54 var depth = new Shader(gl, "depth-vert", "depth-frag");
55 var identity = new Filter(gl, "identity-vert", "identity-frag");
56 var unpremult = new Filter(gl, "identity-vert", "unpremult-frag");
57 var hblur = new Filter(gl, "identity-vert", "hblur-frag");
58 var vblur = new Filter(gl, "identity-vert", "vblur-frag");
59 var hdof = new Filter(gl, "identity-vert", "hdof-frag");
60 var vdof = new Filter(gl, "identity-vert", "vdof-frag");
61
62 redraw(canvas, gl, shader, fbo, fbo2, fbo3, depth, identity, unpremult, hblu r, vblur, hdof, vdof);
63
64 setInterval(function(){
65 redraw(canvas, gl, shader, fbo, fbo2, fbo3, depth, identity, unpremult, hblur, vblur, hdof, vdof);
66 }, 33);
67 }
68
69 function drawCube (gl, shader, angle, axis, x,y,z, s, va, na, ta) {
70 Matrix.copyMatrix(look, vmat);
71 Matrix.translate3InPlace(x,y,z,vmat);
72 Matrix.scale1InPlace(s,vmat);
73 Matrix.rotateInPlace(angle, axis, vmat);
74
75 // Note: we could just use mat3(MVMatrix) as the normal matrix
76 // as MVMatrix has only rotations, translations and uniform scaling
77 // <=> MVMatrix is a scaled orthonormal matrix
78 // hence normalize(mat3(MVMatrix)*v) == normalize(mat3(transpose(inverse(MVM atrix))*v)
79 //
80 // But let's do it the hard way to see if Matrix.inverse3x3 works...
81 Matrix.inverseTo3x3InPlace(vmat, nmat);
82 Matrix.transpose3x3InPlace(nmat);
83
84 shader.uniformMatrix4fv("MVMatrix", vmat);
85 shader.uniformMatrix3fv("NMatrix", nmat);
86
87 var cube = Cube.getCachedVBO(gl);
88 cube.draw(va, na, ta);
89 }
90
91 var carr = [];
92 for (var i=0; i<25; i++) {
93 carr.push([Math.random(), Math.random(), Math.random()]);
94 }
95
96 function drawScene (gl, shader, va, na, ta) {
97 var ot = new Date().getTime();
98 var t = ot;
99
100 shader.uniformMatrix4fv("PMatrix", pmat);
101 for (var i=0; i<carr.length; i++){
102 var c = carr[i];
103 var f = c[1] < 0.5 ? 1 : -1;
104 var t = ot;
105 drawCube(gl, shader,
106 (t/(f*400*(c[0]+0.5))) % (2*Math.PI), c,
107
108 0.45+0.8*c[2],
109 -0.4+Math.cos((i/carr.length*Math.PI*2)+t/1000),
110 0.8+Math.sin((i/carr.length*Math.PI*2)+t/1000)*3.2,
111
112 0.05 + Math.pow((c[0]+c[1]+c[2])*0.33, 2)*0.3,
113 va, na, ta);
114 }
115 }
116
117 var nmat = Matrix.newIdentity3x3();
118 var vmat = Matrix.newIdentity();
119 var vmat2 = Matrix.newIdentity();
120 var pmat = null;
121 var look = Matrix.lookAt([4,-1,8], [-0.2,0,0], [0,1,0]);
122 var useDoF = false;
123
124 var firstFrame = true;
125
126 function redraw(canvas, gl, shader, fbo, fbo2, fbo3, depth, identity, unpremult, hblur, vblur, hdof, vdof) {
127
128 var doDoF = useDoF;
129 gl.viewport(0, 0, canvas.width, canvas.height);
130 gl.clearColor(0.0, 0.0, 0.0, 0.0);
131 gl.enable(gl.DEPTH_TEST);
132
133 gl.bindFramebuffer(gl.FRAMEBUFFER, null);
134 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
135
136 fbo.use();
137 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
138
139 shader.use();
140
141 var va = shader.attrib("Vertex");
142 var na = shader.attrib("Normal");
143 var ta = shader.attrib("Tex");
144
145 if (pmat == null)
146 pmat = Matrix.perspective(30, canvas.width/canvas.height, 1, 100);
147
148 shader.uniform4f("MaterialSpecular", 0.95, 0.9, 0.6, 1);
149 shader.uniform4f("MaterialDiffuse", 0.50, 0.35, 0.35, 1);
150 shader.uniform4f("MaterialAmbient", 0.0, 0.1, 0.2, 1);
151 shader.uniform1f("MaterialShininess", 1.5);
152
153 shader.uniform4f("GlobalAmbient", 0.1, 0.1, 0.1, 1);
154
155 shader.uniform4f("LightPos", 1, 5, 3, 1.0);
156
157 shader.uniform4f("LightSpecular", 0.9, 0.9, 0.9, 1);
158 shader.uniform4f("LightDiffuse", 0.8, 0.8, 0.8, 1);
159 shader.uniform4f("LightAmbient", 0.0, 0.06, 0.2, 1);
160 shader.uniform1f("LightConstantAtt", 0.0);
161 shader.uniform1f("LightLinearAtt", 0.1);
162 shader.uniform1f("LightQuadraticAtt", 0.0);
163
164 drawScene(gl, shader, va, na);
165
166 if (doDoF || firstFrame) {
167
168 fbo3.use();
169 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
170 depth.use();
171 var dva = depth.attrib("Vertex");
172
173 drawScene(gl, depth, dva);
174
175 gl.disable(gl.DEPTH_TEST);
176 gl.activeTexture(gl.TEXTURE1);
177 gl.bindTexture(gl.TEXTURE_2D, fbo3.texture);
178 gl.activeTexture(gl.TEXTURE0);
179
180
181 for (var i=0; i<3; i++) {
182 fbo2.use();
183 gl.bindTexture(gl.TEXTURE_2D, fbo.texture);
184
185 hdof.apply(function(f){
186 f.uniform1i("Texture", 0);
187 f.uniform1i("Depth", 1);
188 f.uniform1f("iter", i);
189 f.uniform1f("step", 1.0/canvas.width);
190 });
191
192 fbo.use();
193 gl.bindTexture(gl.TEXTURE_2D, fbo2.texture);
194
195 vdof.apply(function(f){
196 f.uniform1i("Texture", 0);
197 f.uniform1i("Depth", 1);
198 f.uniform1f("iter", i);
199 f.uniform1f("step", 1.0/canvas.width);
200 });
201 }
202
203 }
204 firstFrame = false;
205
206 gl.bindFramebuffer(gl.FRAMEBUFFER, null);
207
208 gl.activeTexture(gl.TEXTURE1);
209 gl.bindTexture(gl.TEXTURE_2D, null);
210 gl.activeTexture(gl.TEXTURE0);
211 gl.bindTexture(gl.TEXTURE_2D, fbo.texture);
212
213 // The DoF blur blurs the color from the transparent black background with
214 // the cubes. To get rid of the border, we can treat it as premultiplied alp ha.
215 // To see the problem, try replacing unpremult with identity.
216 unpremult.apply(function(f){
217 f.uniform1i("Texture", 0);
218 });
219
220 }
221
222 window.addEventListener("load", init, false);
223 </script>
224
225 <script id="ppix-vert" type="x-shader/x-vertex">
226 attribute vec3 Vertex;
227 attribute vec3 Normal;
228 attribute vec2 Tex;
229
230 uniform mat4 PMatrix;
231 uniform mat4 MVMatrix;
232 uniform mat3 NMatrix;
233
234 uniform vec4 MaterialAmbient;
235 uniform vec4 MaterialDiffuse;
236
237 uniform vec4 LightAmbient;
238 uniform vec4 LightDiffuse;
239
240 uniform vec4 GlobalAmbient;
241
242 uniform vec4 LightPos;
243
244 varying vec4 diffuse, ambientGlobal, ambient;
245 varying vec3 normal, lightDir, halfVector;
246 varying float dist;
247
248 void main()
249 {
250 vec4 worldPos;
251 vec3 lightVector;
252 vec4 v = vec4(Vertex, 1.0);
253
254 /* transform vertex normal into world space and normalize */
255 normal = normalize(NMatrix * Normal);
256
257 /* transform vertex into world space and compute the vector
258 from it to the light */
259 worldPos = MVMatrix * v;
260 lightVector = vec3(LightPos - worldPos);
261 lightDir = normalize(lightVector);
262 dist = length(lightVector);
263
264 /* Half-vector used in Blinn-Phong shading due to computational efficien cy */
265 halfVector = normalize(lightVector - vec3(worldPos));
266
267 diffuse = MaterialDiffuse * LightDiffuse;
268
269 /* The ambient terms have been separated since one of them */
270 /* suffers attenuation */
271 ambient = MaterialAmbient * LightAmbient;
272 ambientGlobal = GlobalAmbient * MaterialAmbient;
273
274 gl_Position = PMatrix * worldPos;
275 }
276 </script>
277
278 <script id="ppix-frag" type="x-shader/x-fragment">
279 precision mediump float;
280
281 uniform vec4 LightSpecular;
282 uniform vec4 MaterialSpecular;
283 uniform float MaterialShininess;
284
285 uniform float LightConstantAtt;
286 uniform float LightLinearAtt;
287 uniform float LightQuadraticAtt;
288
289 varying vec4 diffuse,ambientGlobal, ambient;
290 varying vec3 normal, lightDir, halfVector;
291 varying float dist;
292
293 void main()
294 {
295 vec3 n, halfV, viewV, ldir;
296 float NdotL, NdotHV;
297 vec4 color = ambientGlobal;
298 float att;
299
300 n = normalize(normal);
301
302 NdotL = max(dot(n, normalize(lightDir)), 0.0);
303
304 if (NdotL > 0.0) {
305
306 att = 1.0 / (LightConstantAtt + LightLinearAtt * dist + LightQuadratic Att * dist * dist);
307
308 color += att * (diffuse * NdotL + ambient);
309
310 halfV = normalize(halfVector);
311 NdotHV = max( dot(normal, halfV), 0.0 );
312
313 color += att * MaterialSpecular * LightSpecular * pow(NdotHV, Material Shininess);
314 }
315
316 gl_FragColor = color;
317 }
318 </script>
319 <script id="depth-vert" type="x-shader/x-vertex">
320 attribute vec3 Vertex;
321 uniform mat4 PMatrix;
322 uniform mat4 MVMatrix;
323 varying float depth;
324 void main()
325 {
326 gl_Position = PMatrix * (MVMatrix * vec4(Vertex, 1.0));
327 depth = 1.0-(gl_Position.z / gl_Position.w);
328 }
329 </script>
330 <script id="depth-frag" type="x-shader/x-fragment">
331 precision mediump float;
332
333 varying float depth;
334 void main()
335 {
336 vec4 c = vec4(depth, 0.0, 0.0, 1.0);
337 gl_FragColor = c;
338 }
339 </script>
340
341 <script id="identity-vert" type="x-shader/x-vertex">
342 attribute vec3 Vertex;
343 attribute vec2 Tex;
344
345 varying vec4 texCoord0;
346 void main()
347 {
348 texCoord0 = vec4(Tex,0.0,0.0);
349 gl_Position = vec4(Vertex, 1.0);
350 }
351 </script>
352 <script id="identity-frag" type="x-shader/x-fragment">
353 precision mediump float;
354
355 uniform sampler2D Texture;
356
357 varying vec4 texCoord0;
358 void main()
359 {
360 vec4 c = texture2D(Texture, texCoord0.st);
361 gl_FragColor = c;
362 }
363 </script>
364 <script id="premult-frag" type="x-shader/x-fragment">
365 precision mediump float;
366
367 uniform sampler2D Texture;
368
369 varying vec4 texCoord0;
370 void main()
371 {
372 vec4 c = texture2D(Texture, texCoord0.st);
373 float a = c.a;
374 c *= a;
375 c.a = a;
376 gl_FragColor = c;
377 }
378 </script>
379 <script id="unpremult-frag" type="x-shader/x-fragment">
380 precision mediump float;
381
382 uniform sampler2D Texture;
383
384 varying vec4 texCoord0;
385 void main()
386 {
387 vec4 c = texture2D(Texture, texCoord0.st);
388 float a = c.a;
389 c /= a;
390 c.a = a;
391 gl_FragColor = c;
392 }
393 </script>
394
395 <script id="hblur-frag" type="x-shader/x-fragment">
396 precision mediump float;
397
398 uniform sampler2D Texture;
399 uniform float step;
400 float kernel[7] = float[](0.046, 0.111, 0.202, 0.283, 0.202, 0.111, 0.046) ;
401
402 varying vec4 texCoord0;
403 void main()
404 {
405 int i=0;
406 if (texture2D(Texture, texCoord0.st).a > 0.0) {
407 vec4 sum = vec4(0.0);
408 for (i=0; i<7; i++) {
409 vec4 tmp = texture2D(Texture, texCoord0.st + vec2(i*step,0));
410 sum += tmp * kernel[i];
411 }
412 gl_FragColor = sum;
413 } else {
414 gl_FragColor = texture2D(Texture, texCoord0.st);
415 }
416 }
417 </script>
418 <script id="vblur-frag" type="x-shader/x-fragment">
419 precision mediump float;
420
421 uniform sampler2D Texture;
422 uniform float step;
423 float kernel[7] = float[](0.046, 0.111, 0.202, 0.283, 0.202, 0.111, 0.046) ;
424
425 varying vec4 texCoord0;
426 void main()
427 {
428 int i=0;
429 if (texture2D(Texture, texCoord0.st).a > 0.0) {
430 vec4 sum = vec4(0.0);
431 for (i=0; i<7; i++) {
432 vec4 tmp = texture2D(Texture, texCoord0.st + vec2(0,i*step));
433 sum += tmp * kernel[i];
434 }
435 gl_FragColor = sum;
436 } else {
437 gl_FragColor = texture2D(Texture, texCoord0.st);
438 }
439 }
440 </script>
441 <script id="hdof-frag" type="x-shader/x-fragment">
442 precision mediump float;
443
444 uniform sampler2D Texture;
445 uniform sampler2D Depth;
446 uniform float step;
447 uniform float iter;
448 float kernel[7] = { 0.046, 0.111, 0.202, 0.283, 0.202, 0.111, 0.046 };
449
450 varying vec4 texCoord0;
451 void main()
452 {
453 vec4 tmp;
454 vec4 sum = vec4(0.0);
455 bool b = (iter < -26.0+36.0*(1.0-texture2D(Depth, texCoord0.st).r) && te xture2D(Texture, texCoord0.st).a > 0.0);
456 tmp = texture2D(Texture, texCoord0.st + vec2(float(0-3)*step,0));
457 sum += tmp * kernel[0];
458 tmp = texture2D(Texture, texCoord0.st + vec2(float(1-3)*step,0));
459 sum += tmp * kernel[1];
460 tmp = texture2D(Texture, texCoord0.st + vec2(float(2-3)*step,0));
461 sum += tmp * kernel[2];
462 tmp = texture2D(Texture, texCoord0.st + vec2(float(3-3)*step,0));
463 sum += tmp * kernel[3];
464 tmp = texture2D(Texture, texCoord0.st + vec2(float(4-3)*step,0));
465 sum += tmp * kernel[4];
466 tmp = texture2D(Texture, texCoord0.st + vec2(float(5-3)*step,0));
467 sum += tmp * kernel[5];
468 tmp = texture2D(Texture, texCoord0.st + vec2(float(6-3)*step,0));
469 sum += tmp * kernel[6];
470 gl_FragColor = mix(texture2D(Texture, texCoord0.st), sum, b ? 1.0 : 0.0) ;
471 }
472 </script>
473 <script id="vdof-frag" type="x-shader/x-fragment">
474 precision mediump float;
475
476 uniform sampler2D Texture;
477 uniform sampler2D Depth;
478 uniform float step;
479 uniform float iter;
480 float kernel[7] = float[7](0.046, 0.111, 0.202, 0.283, 0.202, 0.111, 0.046 );
481
482 varying vec4 texCoord0;
483 void main()
484 {
485 vec4 tmp;
486 vec4 sum = vec4(0.0);
487 bool b = (iter < -26.0+36.0*(1.0-texture2D(Depth, texCoord0.st).r) && te xture2D(Texture, texCoord0.st).a > 0.0);
488 tmp = texture2D(Texture, texCoord0.st + vec2(0,float(0-3)*step));
489 sum += tmp * kernel[0];
490 tmp = texture2D(Texture, texCoord0.st + vec2(0,float(1-3)*step));
491 sum += tmp * kernel[1];
492 tmp = texture2D(Texture, texCoord0.st + vec2(0,float(2-3)*step));
493 sum += tmp * kernel[2];
494 tmp = texture2D(Texture, texCoord0.st + vec2(0,float(3-3)*step));
495 sum += tmp * kernel[3];
496 tmp = texture2D(Texture, texCoord0.st + vec2(0,float(4-3)*step));
497 sum += tmp * kernel[4];
498 tmp = texture2D(Texture, texCoord0.st + vec2(0,float(5-3)*step));
499 sum += tmp * kernel[5];
500 tmp = texture2D(Texture, texCoord0.st + vec2(0,float(6-3)*step));
501 sum += tmp * kernel[6];
502 gl_FragColor = mix(texture2D(Texture, texCoord0.st), sum, b ? 1.0 : 0.0) ;
503 }
504 </script>
505
506 <style>
507 * { margin: 0px; padding: 0px; }
508 html {
509 background-color: #707888;
510 color: #222222;
511 }
512 #canvas {
513 position: absolute;
514 cursor: pointer;
515 top: 115px; left: 550px;
516 }
517 #note {
518 position: absolute;
519 top: 4px;
520 left: 4px;
521 }
522 #content {
523 margin-left: 99px;
524 padding-left: 8px;
525 padding-right: 8px;
526 padding-bottom: 16px;
527 width: 600px;
528 background-color: rgba(255,255,255,1.0);
529 text-align: center;
530 border-left: 1px solid rgba(0,0,0,0.5);
531 border-right: 2px solid rgba(0,0,0,0.75);
532 }
533 h1 {
534 padding-top: 24px;
535 padding-bottom: 16px;
536 margin-bottom: 24px;
537 border-bottom: 1px solid black;
538 font-family: Times New Roman, Serif;
539 font-weight: bold;
540 font-size: 40px;
541 }
542 #content p {
543 text-indent: 24px;
544 margin-left: 24px;
545 margin-right: 32px;
546 text-align: justify;
547 font-family: Serif;
548 padding-bottom: 16px;
549 }
550 #above {
551 position: absolute;
552 top: 300px;
553 left: 716px;
554 padding: 10px 20px;
555 background-color: rgba(0,225,0,0.5);
556 border-left: 2px solid rgba(0,64,0,0.75);
557 color: white;
558 font-size: small;
559 font-family: sans-serif;
560 }
561 #above p {
562 text-align: center;
563 }
564 </style>
565
566 </head><body>
567 <canvas id="canvas" width="400" height="400" title="Click to toggle DOF shad er" onclick="useDoF = !useDoF"></canvas>
568 <pre id="note"></pre>
569
570 <div id="content">
571 <h1>OpenGL for the web</h1>
572 <p>
573 The WebGL specification gives web developers access to an
574 OpenGL ES 2.0 drawing context for the canvas tag. What that means is
575 that you can finally harness the power of the GPU for awesome visuals
576 and heavy-duty number crunching in your web apps. </p><p> OpenGL ES 2.0 is a sub set of OpenGL 2.0 aimed at embedded
577 devices and game consoles. As such, it's a very minimalistic low-level
578 API, even more so than desktop OpenGL. In fact, if you took desktop
579 OpenGL and stripped out everything but shaders, vertex arrays and
580 textures, you'd get something quite like OpenGL ES 2.0. </p>
581 <p>
582 As there is no fixed-function pipeline, you need to write GLSL shaders to draw <i>anything</i>.
583 And you need to do your own transformation math, including keeping
584 track of the transformation matrix stack. So the raw API is really not
585 for the faint of heart; you do need to know your 3D math and shading
586 equations. </p>
587 <p> For example, to draw the spinning cubes on the
588 right - around 200 lines of application code, 250 lines of shaders and
589 800 lines of library code - I had to scrounge the internet for <a href="http://w ww.lighthouse3d.com/opengl/glsl/index.php?pointlight">GLSL shaders</a>
590 to do the transformation and lighting, write a small matrix math
591 library in JavaScript and a DOF blur shader. While highly educational,
592 it was also a rather steep hill to climb. </p>
593 <p> So, the intended audience of the raw context
594 interface are not really end-users, but library developers who can
595 write easy-to-use interfaces to the functionality, and 3D developers
596 who require a high level of control over the rendering pipeline. </p>
597 <p> The really cool thing about the OpenGL Canvas is
598 that it doesn't make policy decisions. There's no single set-in-stone
599 use case for it: In addition to 3D graphics, you can also use it for
600 filtering images, visualizing fluid dynamics, doing real-time video
601 effects, or just crunching a whole lot of FP math. If you can do it on
602 the GPU, you're in luck! </p>
603 </div>
604 <div id="above">
605 <p>You can also place content above the canvas</p>
606 </div>
607 </body></html>
OLDNEW
« no previous file with comments | « conformance/more/conformance/webGLArrays.html ('k') | conformance/more/demos/video.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698