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

Side by Side Diff: Source/platform/graphics/gpu/DrawingBuffer.cpp

Issue 274833002: Enable WebGL rendering into ImageCHROMIUM, backed by a scanout buffer to enable overlay suppor… (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Created 6 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
OLDNEW
1 /* 1 /*
2 * Copyright (c) 2010, Google Inc. All rights reserved. 2 * Copyright (c) 2010, Google Inc. All rights reserved.
3 * 3 *
4 * Redistribution and use in source and binary forms, with or without 4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are 5 * modification, are permitted provided that the following conditions are
6 * met: 6 * met:
7 * 7 *
8 * * Redistributions of source code must retain the above copyright 8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer. 9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above 10 * * Redistributions in binary form must reproduce the above
(...skipping 14 matching lines...) Expand all
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */ 29 */
30 30
31 #include "config.h" 31 #include "config.h"
32 32
33 #include "platform/graphics/gpu/DrawingBuffer.h" 33 #include "platform/graphics/gpu/DrawingBuffer.h"
34 34
35 #include "RuntimeEnabledFeatures.h"
35 #include <algorithm> 36 #include <algorithm>
36 #include "platform/TraceEvent.h" 37 #include "platform/TraceEvent.h"
37 #include "platform/graphics/GraphicsLayer.h" 38 #include "platform/graphics/GraphicsLayer.h"
38 #include "platform/graphics/gpu/Extensions3DUtil.h" 39 #include "platform/graphics/gpu/Extensions3DUtil.h"
39 #include "public/platform/Platform.h" 40 #include "public/platform/Platform.h"
40 #include "public/platform/WebCompositorSupport.h" 41 #include "public/platform/WebCompositorSupport.h"
41 #include "public/platform/WebExternalBitmap.h" 42 #include "public/platform/WebExternalBitmap.h"
42 #include "public/platform/WebExternalTextureLayer.h" 43 #include "public/platform/WebExternalTextureLayer.h"
43 #include "public/platform/WebGraphicsContext3D.h" 44 #include "public/platform/WebGraphicsContext3D.h"
44 #include "public/platform/WebGraphicsContext3DProvider.h" 45 #include "public/platform/WebGraphicsContext3DProvider.h"
(...skipping 176 matching lines...) Expand 10 before | Expand all | Expand 10 after
221 222
222 // We must restore the texture binding since creating new textures, 223 // We must restore the texture binding since creating new textures,
223 // consuming and producing mailboxes changes it. 224 // consuming and producing mailboxes changes it.
224 ScopedTextureUnit0BindingRestorer restorer(m_context.get(), m_activeTextureU nit, m_texture2DBinding); 225 ScopedTextureUnit0BindingRestorer restorer(m_context.get(), m_activeTextureU nit, m_texture2DBinding);
225 226
226 // First try to recycle an old buffer. 227 // First try to recycle an old buffer.
227 RefPtr<MailboxInfo> frontColorBufferMailbox = recycledMailbox(); 228 RefPtr<MailboxInfo> frontColorBufferMailbox = recycledMailbox();
228 229
229 // No buffer available to recycle, create a new one. 230 // No buffer available to recycle, create a new one.
230 if (!frontColorBufferMailbox) { 231 if (!frontColorBufferMailbox) {
231 unsigned newColorBuffer = createColorTexture(m_size); 232 unsigned newColorBuffer = createColorTexture();
233 allocateTextureMemory(newColorBuffer, m_size);
232 // Bad things happened, abandon ship. 234 // Bad things happened, abandon ship.
233 if (!newColorBuffer) 235 if (!newColorBuffer)
234 return false; 236 return false;
235 237
236 frontColorBufferMailbox = createNewMailbox(newColorBuffer); 238 frontColorBufferMailbox = createNewMailbox(newColorBuffer);
237 } 239 }
238 240
239 if (m_preserveDrawingBuffer == Discard) { 241 if (m_preserveDrawingBuffer == Discard) {
240 swap(frontColorBufferMailbox->textureId, m_colorBuffer); 242 swap(frontColorBufferMailbox->textureId, m_colorBuffer);
241 // It appears safe to overwrite the context's framebuffer binding in the Discard case since there will always be a 243 // It appears safe to overwrite the context's framebuffer binding in the Discard case since there will always be a
(...skipping 13 matching lines...) Expand all
255 bind(); 257 bind();
256 else 258 else
257 restoreFramebufferBinding(); 259 restoreFramebufferBinding();
258 260
259 m_contentsChanged = false; 261 m_contentsChanged = false;
260 262
261 m_context->bindTexture(GL_TEXTURE_2D, frontColorBufferMailbox->textureId); 263 m_context->bindTexture(GL_TEXTURE_2D, frontColorBufferMailbox->textureId);
262 m_context->produceTextureCHROMIUM(GL_TEXTURE_2D, frontColorBufferMailbox->ma ilbox.name); 264 m_context->produceTextureCHROMIUM(GL_TEXTURE_2D, frontColorBufferMailbox->ma ilbox.name);
263 m_context->flush(); 265 m_context->flush();
264 frontColorBufferMailbox->mailbox.syncPoint = m_context->insertSyncPoint(); 266 frontColorBufferMailbox->mailbox.syncPoint = m_context->insertSyncPoint();
267 frontColorBufferMailbox->mailbox.allowOverlay = m_textureToImageChromiumMap. find(frontColorBufferMailbox->textureId) != m_textureToImageChromiumMap.end();
265 markLayerComposited(); 268 markLayerComposited();
266 269
267 // set m_parentDrawingBuffer to make sure 'this' stays alive as long as it h as live mailboxes 270 // set m_parentDrawingBuffer to make sure 'this' stays alive as long as it h as live mailboxes
268 ASSERT(!frontColorBufferMailbox->m_parentDrawingBuffer); 271 ASSERT(!frontColorBufferMailbox->m_parentDrawingBuffer);
269 frontColorBufferMailbox->m_parentDrawingBuffer = this; 272 frontColorBufferMailbox->m_parentDrawingBuffer = this;
270 *outMailbox = frontColorBufferMailbox->mailbox; 273 *outMailbox = frontColorBufferMailbox->mailbox;
271 m_frontColorBuffer = frontColorBufferMailbox->textureId; 274 m_frontColorBuffer = frontColorBufferMailbox->textureId;
272 return true; 275 return true;
273 } 276 }
274 277
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
316 } 319 }
317 ASSERT(mailboxInfo); 320 ASSERT(mailboxInfo);
318 321
319 if (mailboxInfo->mailbox.syncPoint) { 322 if (mailboxInfo->mailbox.syncPoint) {
320 m_context->waitSyncPoint(mailboxInfo->mailbox.syncPoint); 323 m_context->waitSyncPoint(mailboxInfo->mailbox.syncPoint);
321 mailboxInfo->mailbox.syncPoint = 0; 324 mailboxInfo->mailbox.syncPoint = 0;
322 } 325 }
323 326
324 if (mailboxInfo->size != m_size) { 327 if (mailboxInfo->size != m_size) {
325 m_context->bindTexture(GL_TEXTURE_2D, mailboxInfo->textureId); 328 m_context->bindTexture(GL_TEXTURE_2D, mailboxInfo->textureId);
326 texImage2DResourceSafe(GL_TEXTURE_2D, 0, m_internalColorFormat, m_size.w idth(), m_size.height(), 0, m_colorFormat, GL_UNSIGNED_BYTE); 329 allocateTextureMemory(mailboxInfo->textureId, m_size);
327 mailboxInfo->size = m_size; 330 mailboxInfo->size = m_size;
328 } 331 }
329 332
330 return mailboxInfo.release(); 333 return mailboxInfo.release();
331 } 334 }
332 335
333 PassRefPtr<DrawingBuffer::MailboxInfo> DrawingBuffer::createNewMailbox(unsigned textureId) 336 PassRefPtr<DrawingBuffer::MailboxInfo> DrawingBuffer::createNewMailbox(unsigned textureId)
334 { 337 {
335 RefPtr<MailboxInfo> returnMailbox = adoptRef(new MailboxInfo()); 338 RefPtr<MailboxInfo> returnMailbox = adoptRef(new MailboxInfo());
336 m_context->genMailboxCHROMIUM(returnMailbox->mailbox.name); 339 m_context->genMailboxCHROMIUM(returnMailbox->mailbox.name);
337 returnMailbox->textureId = textureId; 340 returnMailbox->textureId = textureId;
338 returnMailbox->size = m_size; 341 returnMailbox->size = m_size;
339 m_textureMailboxes.append(returnMailbox); 342 m_textureMailboxes.append(returnMailbox);
340 return returnMailbox.release(); 343 return returnMailbox.release();
341 } 344 }
342 345
343 void DrawingBuffer::deleteMailbox(const blink::WebExternalTextureMailbox& mailbo x) 346 void DrawingBuffer::deleteMailbox(const blink::WebExternalTextureMailbox& mailbo x)
344 { 347 {
345 for (size_t i = 0; i < m_textureMailboxes.size(); i++) { 348 for (size_t i = 0; i < m_textureMailboxes.size(); i++) {
346 if (nameEquals(m_textureMailboxes[i]->mailbox, mailbox)) { 349 if (nameEquals(m_textureMailboxes[i]->mailbox, mailbox)) {
347 if (mailbox.syncPoint) 350 if (mailbox.syncPoint)
348 m_context->waitSyncPoint(mailbox.syncPoint); 351 m_context->waitSyncPoint(mailbox.syncPoint);
352 if (RuntimeEnabledFeatures::webGLImageChromiumEnabled())
353 deleteChromiumImageForTexture(m_textureMailboxes[i]->textureId);
354
349 m_context->deleteTexture(m_textureMailboxes[i]->textureId); 355 m_context->deleteTexture(m_textureMailboxes[i]->textureId);
350 m_textureMailboxes.remove(i); 356 m_textureMailboxes.remove(i);
351 return; 357 return;
352 } 358 }
353 } 359 }
354 ASSERT_NOT_REACHED(); 360 ASSERT_NOT_REACHED();
355 } 361 }
356 362
357 bool DrawingBuffer::initialize(const IntSize& size) 363 bool DrawingBuffer::initialize(const IntSize& size)
358 { 364 {
(...skipping 164 matching lines...) Expand 10 before | Expand all | Expand 10 after
523 context->deleteTexture(sourceTexture); 529 context->deleteTexture(sourceTexture);
524 context->flush(); 530 context->flush();
525 m_context->waitSyncPoint(context->insertSyncPoint()); 531 m_context->waitSyncPoint(context->insertSyncPoint());
526 return; 532 return;
527 } 533 }
528 534
529 // Since the m_frontColorBuffer was produced and sent to the compositor, it cannot be bound to an fbo. 535 // Since the m_frontColorBuffer was produced and sent to the compositor, it cannot be bound to an fbo.
530 // We have to make a copy of it here and bind that copy instead. 536 // We have to make a copy of it here and bind that copy instead.
531 // FIXME: That's not true any more, provided we don't change texture 537 // FIXME: That's not true any more, provided we don't change texture
532 // parameters. 538 // parameters.
533 unsigned sourceTexture = createColorTexture(m_size); 539 unsigned sourceTexture = createColorTexture();
540 texImage2DResourceSafe(GL_TEXTURE_2D, 0, m_internalColorFormat, m_size.width (), m_size.height(), 0, m_colorFormat, GL_UNSIGNED_BYTE);
534 m_context->copyTextureCHROMIUM(GL_TEXTURE_2D, m_frontColorBuffer, sourceText ure, 0, GL_RGBA, GL_UNSIGNED_BYTE); 541 m_context->copyTextureCHROMIUM(GL_TEXTURE_2D, m_frontColorBuffer, sourceText ure, 0, GL_RGBA, GL_UNSIGNED_BYTE);
535 542
536 // Since we're using the same context as WebGL, we have to restore any state we change (in this case, just the framebuffer binding). 543 // Since we're using the same context as WebGL, we have to restore any state we change (in this case, just the framebuffer binding).
537 // FIXME: The WebGLRenderingContext tracks the current framebuffer binding, it would be slightly more efficient to use this value 544 // FIXME: The WebGLRenderingContext tracks the current framebuffer binding, it would be slightly more efficient to use this value
538 // rather than querying it off of the context. 545 // rather than querying it off of the context.
539 GLint previousFramebuffer = 0; 546 GLint previousFramebuffer = 0;
540 m_context->getIntegerv(GL_FRAMEBUFFER_BINDING, &previousFramebuffer); 547 m_context->getIntegerv(GL_FRAMEBUFFER_BINDING, &previousFramebuffer);
541 548
542 Platform3DObject framebuffer = m_context->createFramebuffer(); 549 Platform3DObject framebuffer = m_context->createFramebuffer();
543 m_context->bindFramebuffer(GL_FRAMEBUFFER, framebuffer); 550 m_context->bindFramebuffer(GL_FRAMEBUFFER, framebuffer);
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
581 588
582 if (m_depthStencilBuffer) 589 if (m_depthStencilBuffer)
583 m_context->deleteRenderbuffer(m_depthStencilBuffer); 590 m_context->deleteRenderbuffer(m_depthStencilBuffer);
584 591
585 if (m_depthBuffer) 592 if (m_depthBuffer)
586 m_context->deleteRenderbuffer(m_depthBuffer); 593 m_context->deleteRenderbuffer(m_depthBuffer);
587 594
588 if (m_stencilBuffer) 595 if (m_stencilBuffer)
589 m_context->deleteRenderbuffer(m_stencilBuffer); 596 m_context->deleteRenderbuffer(m_stencilBuffer);
590 597
591 if (m_colorBuffer) 598 if (m_colorBuffer) {
599 if (RuntimeEnabledFeatures::webGLImageChromiumEnabled())
600 deleteChromiumImageForTexture(m_colorBuffer);
601
592 m_context->deleteTexture(m_colorBuffer); 602 m_context->deleteTexture(m_colorBuffer);
603 }
593 604
594 setSize(IntSize()); 605 setSize(IntSize());
595 606
596 m_colorBuffer = 0; 607 m_colorBuffer = 0;
597 m_frontColorBuffer = 0; 608 m_frontColorBuffer = 0;
598 m_multisampleColorBuffer = 0; 609 m_multisampleColorBuffer = 0;
599 m_depthStencilBuffer = 0; 610 m_depthStencilBuffer = 0;
600 m_depthBuffer = 0; 611 m_depthBuffer = 0;
601 m_stencilBuffer = 0; 612 m_stencilBuffer = 0;
602 m_multisampleFBO = 0; 613 m_multisampleFBO = 0;
603 m_fbo = 0; 614 m_fbo = 0;
604 m_contextEvictionManager.clear(); 615 m_contextEvictionManager.clear();
605 616
606 if (m_layer) 617 if (m_layer)
607 GraphicsLayer::unregisterContentsLayer(m_layer->layer()); 618 GraphicsLayer::unregisterContentsLayer(m_layer->layer());
608 } 619 }
609 620
610 unsigned DrawingBuffer::createColorTexture(const IntSize& size) 621 unsigned DrawingBuffer::createColorTexture()
611 { 622 {
612 unsigned offscreenColorTexture = m_context->createTexture(); 623 unsigned offscreenColorTexture = m_context->createTexture();
613 if (!offscreenColorTexture) 624 if (!offscreenColorTexture)
614 return 0; 625 return 0;
615 626
616 m_context->bindTexture(GL_TEXTURE_2D, offscreenColorTexture); 627 m_context->bindTexture(GL_TEXTURE_2D, offscreenColorTexture);
617 m_context->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 628 m_context->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
618 m_context->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 629 m_context->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
619 m_context->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) ; 630 m_context->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) ;
620 m_context->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) ; 631 m_context->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) ;
621 if (!size.isEmpty())
622 texImage2DResourceSafe(GL_TEXTURE_2D, 0, m_internalColorFormat, size.wid th(), size.height(), 0, m_colorFormat, GL_UNSIGNED_BYTE);
623 632
624 return offscreenColorTexture; 633 return offscreenColorTexture;
625 } 634 }
626 635
627 void DrawingBuffer::createSecondaryBuffers() 636 void DrawingBuffer::createSecondaryBuffers()
628 { 637 {
629 // create a multisample FBO 638 // create a multisample FBO
630 if (m_multisampleMode == ExplicitResolve) { 639 if (m_multisampleMode == ExplicitResolve) {
631 m_multisampleFBO = m_context->createFramebuffer(); 640 m_multisampleFBO = m_context->createFramebuffer();
632 m_context->bindFramebuffer(GL_FRAMEBUFFER, m_multisampleFBO); 641 m_context->bindFramebuffer(GL_FRAMEBUFFER, m_multisampleFBO);
633 m_multisampleColorBuffer = m_context->createRenderbuffer(); 642 m_multisampleColorBuffer = m_context->createRenderbuffer();
634 } 643 }
635 } 644 }
636 645
637 bool DrawingBuffer::resizeFramebuffer(const IntSize& size) 646 bool DrawingBuffer::resizeFramebuffer(const IntSize& size)
638 { 647 {
639 // resize regular FBO 648 // resize regular FBO
640 m_context->bindFramebuffer(GL_FRAMEBUFFER, m_fbo); 649 m_context->bindFramebuffer(GL_FRAMEBUFFER, m_fbo);
641 650
642 m_context->bindTexture(GL_TEXTURE_2D, m_colorBuffer); 651 m_context->bindTexture(GL_TEXTURE_2D, m_colorBuffer);
643 652
644 texImage2DResourceSafe(GL_TEXTURE_2D, 0, m_internalColorFormat, size.width() , size.height(), 0, m_colorFormat, GL_UNSIGNED_BYTE); 653 allocateTextureMemory(m_colorBuffer, size);
645 654
646 if (m_multisampleMode == ImplicitResolve) 655 if (m_multisampleMode == ImplicitResolve)
647 m_context->framebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_A TTACHMENT0, GL_TEXTURE_2D, m_colorBuffer, 0, m_sampleCount); 656 m_context->framebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_A TTACHMENT0, GL_TEXTURE_2D, m_colorBuffer, 0, m_sampleCount);
648 else 657 else
649 m_context->framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL _TEXTURE_2D, m_colorBuffer, 0); 658 m_context->framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL _TEXTURE_2D, m_colorBuffer, 0);
650 659
651 m_context->bindTexture(GL_TEXTURE_2D, 0); 660 m_context->bindTexture(GL_TEXTURE_2D, 0);
652 661
653 if (m_multisampleMode != ExplicitResolve) 662 if (m_multisampleMode != ExplicitResolve)
654 resizeDepthStencil(size); 663 resizeDepthStencil(size);
(...skipping 358 matching lines...) Expand 10 before | Expand all | Expand 10 after
1013 memcpy(rowA, scanline, rowBytes); 1022 memcpy(rowA, scanline, rowBytes);
1014 } 1023 }
1015 } 1024 }
1016 1025
1017 void DrawingBuffer::texImage2DResourceSafe(GLenum target, GLint level, GLenum in ternalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, GLint unpackAlignment) 1026 void DrawingBuffer::texImage2DResourceSafe(GLenum target, GLint level, GLenum in ternalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, GLint unpackAlignment)
1018 { 1027 {
1019 ASSERT(unpackAlignment == 1 || unpackAlignment == 2 || unpackAlignment == 4 || unpackAlignment == 8); 1028 ASSERT(unpackAlignment == 1 || unpackAlignment == 2 || unpackAlignment == 4 || unpackAlignment == 8);
1020 m_context->texImage2D(target, level, internalformat, width, height, border, format, type, 0); 1029 m_context->texImage2D(target, level, internalformat, width, height, border, format, type, 0);
1021 } 1030 }
1022 1031
1032 void DrawingBuffer::allocateTextureMemory(Platform3DObject textureId, const IntS ize& size)
1033 {
1034 if (RuntimeEnabledFeatures::webGLImageChromiumEnabled()) {
1035 deleteChromiumImageForTexture(textureId);
1036
1037 blink::WGC3Duint newImage = m_context->createImageCHROMIUM(size.width(), size.height(), GL_RGBA8_OES, GC3D_IMAGE_SCANOUT_CHROMIUM);
1038 if (newImage) {
Ken Russell (switch to Gerrit) 2014/05/14 22:11:33 Under what conditions will createImageCHROMIUM fai
alexst (slow to review) 2014/05/14 22:29:55 There may be a limit of memory available to scanou
1039 m_context->bindTexImage2DCHROMIUM(GL_TEXTURE_2D, newImage);
1040 m_textureToImageChromiumMap.add(textureId, newImage);
Ken Russell (switch to Gerrit) 2014/05/14 22:11:33 The bookkeeping associated with these ImageChromiu
alexst (slow to review) 2014/05/14 22:29:55 Let me have a look.
1041 return;
1042 }
1043 }
1044
1045 texImage2DResourceSafe(GL_TEXTURE_2D, 0, m_internalColorFormat, size.width() , size.height(), 0, m_colorFormat, GL_UNSIGNED_BYTE);
1046 }
1047
1048 void DrawingBuffer::deleteChromiumImageForTexture(Platform3DObject textureId)
1049 {
1050 HashMap<Platform3DObject, blink::WGC3Duint>::iterator imageIter = m_textureT oImageChromiumMap.find(textureId);
1051 // Release the old buffer if we have any.
1052 if (imageIter != m_textureToImageChromiumMap.end()) {
1053 m_context->releaseTexImage2DCHROMIUM(GL_TEXTURE_2D, imageIter->value);
1054 m_context->destroyImageCHROMIUM(imageIter->value);
1055 m_textureToImageChromiumMap.remove(imageIter);
1056 }
1057 }
1058
1023 } // namespace WebCore 1059 } // namespace WebCore
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698