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

Side by Side Diff: cc/output/gl_renderer.cc

Issue 14273026: cc: Make async readback path use async glRreadPixels. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: There, I fixed it Created 7 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 | Annotate | Revision Log
OLDNEW
1 // Copyright 2010 The Chromium Authors. All rights reserved. 1 // Copyright 2010 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "cc/output/gl_renderer.h" 5 #include "cc/output/gl_renderer.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <set> 8 #include <set>
9 #include <string> 9 #include <string>
10 #include <vector> 10 #include <vector>
(...skipping 12 matching lines...) Expand all
23 #include "cc/output/gl_frame_data.h" 23 #include "cc/output/gl_frame_data.h"
24 #include "cc/output/output_surface.h" 24 #include "cc/output/output_surface.h"
25 #include "cc/output/render_surface_filters.h" 25 #include "cc/output/render_surface_filters.h"
26 #include "cc/quads/picture_draw_quad.h" 26 #include "cc/quads/picture_draw_quad.h"
27 #include "cc/quads/render_pass.h" 27 #include "cc/quads/render_pass.h"
28 #include "cc/quads/stream_video_draw_quad.h" 28 #include "cc/quads/stream_video_draw_quad.h"
29 #include "cc/quads/texture_draw_quad.h" 29 #include "cc/quads/texture_draw_quad.h"
30 #include "cc/resources/layer_quad.h" 30 #include "cc/resources/layer_quad.h"
31 #include "cc/resources/priority_calculator.h" 31 #include "cc/resources/priority_calculator.h"
32 #include "cc/resources/scoped_resource.h" 32 #include "cc/resources/scoped_resource.h"
33 #include "cc/resources/sync_point_helper.h"
33 #include "cc/trees/damage_tracker.h" 34 #include "cc/trees/damage_tracker.h"
34 #include "cc/trees/proxy.h" 35 #include "cc/trees/proxy.h"
35 #include "cc/trees/single_thread_proxy.h" 36 #include "cc/trees/single_thread_proxy.h"
36 #include "gpu/GLES2/gl2extchromium.h" 37 #include "gpu/GLES2/gl2extchromium.h"
37 #include "third_party/WebKit/Source/Platform/chromium/public/WebGraphicsContext3 D.h" 38 #include "third_party/WebKit/Source/Platform/chromium/public/WebGraphicsContext3 D.h"
38 #include "third_party/khronos/GLES2/gl2.h" 39 #include "third_party/khronos/GLES2/gl2.h"
39 #include "third_party/khronos/GLES2/gl2ext.h" 40 #include "third_party/khronos/GLES2/gl2ext.h"
40 #include "third_party/skia/include/core/SkBitmap.h" 41 #include "third_party/skia/include/core/SkBitmap.h"
41 #include "third_party/skia/include/core/SkColor.h" 42 #include "third_party/skia/include/core/SkColor.h"
42 #include "third_party/skia/include/core/SkColorFilter.h" 43 #include "third_party/skia/include/core/SkColorFilter.h"
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
80 return false; 81 return false;
81 #endif 82 #endif
82 } 83 }
83 84
84 // Smallest unit that impact anti-aliasing output. We use this to 85 // Smallest unit that impact anti-aliasing output. We use this to
85 // determine when anti-aliasing is unnecessary. 86 // determine when anti-aliasing is unnecessary.
86 const float kAntiAliasingEpsilon = 1.0f / 1024.0f; 87 const float kAntiAliasingEpsilon = 1.0f / 1024.0f;
87 88
88 } // anonymous namespace 89 } // anonymous namespace
89 90
91 struct GLRenderer::PendingAsyncReadPixels {
92 PendingAsyncReadPixels() : sync_point(0) {}
93
94 CopyRenderPassCallback copy_callback;
95 base::CancelableClosure finished_read_pixels_callback;
96 unsigned sync_point;
97
98 private:
99 DISALLOW_COPY_AND_ASSIGN(PendingAsyncReadPixels);
100 };
101
90 scoped_ptr<GLRenderer> GLRenderer::Create(RendererClient* client, 102 scoped_ptr<GLRenderer> GLRenderer::Create(RendererClient* client,
91 OutputSurface* output_surface, 103 OutputSurface* output_surface,
92 ResourceProvider* resource_provider, 104 ResourceProvider* resource_provider,
93 int highp_threshold_min) { 105 int highp_threshold_min) {
94 scoped_ptr<GLRenderer> renderer(new GLRenderer( 106 scoped_ptr<GLRenderer> renderer(new GLRenderer(
95 client, output_surface, resource_provider, highp_threshold_min)); 107 client, output_surface, resource_provider, highp_threshold_min));
96 if (!renderer->Initialize()) 108 if (!renderer->Initialize())
97 return scoped_ptr<GLRenderer>(); 109 return scoped_ptr<GLRenderer>();
98 110
99 return renderer.Pass(); 111 return renderer.Pass();
(...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after
185 197
186 if (!InitializeSharedObjects()) 198 if (!InitializeSharedObjects())
187 return false; 199 return false;
188 200
189 // Make sure the viewport and context gets initialized, even if it is to zero. 201 // Make sure the viewport and context gets initialized, even if it is to zero.
190 ViewportChanged(); 202 ViewportChanged();
191 return true; 203 return true;
192 } 204 }
193 205
194 GLRenderer::~GLRenderer() { 206 GLRenderer::~GLRenderer() {
207 while (!pending_async_read_pixels_.empty()) {
208 pending_async_read_pixels_.back()->finished_read_pixels_callback.Cancel();
209 pending_async_read_pixels_.back()->copy_callback.Run(
210 scoped_ptr<SkBitmap>());
211 pending_async_read_pixels_.pop_back();
212 }
213
195 context_->setMemoryAllocationChangedCallbackCHROMIUM(NULL); 214 context_->setMemoryAllocationChangedCallbackCHROMIUM(NULL);
196 CleanupSharedObjects(); 215 CleanupSharedObjects();
197 } 216 }
198 217
199 const RendererCapabilities& GLRenderer::Capabilities() const { 218 const RendererCapabilities& GLRenderer::Capabilities() const {
200 return capabilities_; 219 return capabilities_;
201 } 220 }
202 221
203 WebGraphicsContext3D* GLRenderer::Context() { return context_; } 222 WebGraphicsContext3D* GLRenderer::Context() { return context_; }
204 223
(...skipping 1564 matching lines...) Expand 10 before | Expand all | Expand 10 after
1769 1788
1770 void GLRenderer::EnsureScissorTestDisabled() { 1789 void GLRenderer::EnsureScissorTestDisabled() {
1771 if (!is_scissor_enabled_) 1790 if (!is_scissor_enabled_)
1772 return; 1791 return;
1773 1792
1774 FlushTextureQuadCache(); 1793 FlushTextureQuadCache();
1775 GLC(context_, context_->disable(GL_SCISSOR_TEST)); 1794 GLC(context_, context_->disable(GL_SCISSOR_TEST));
1776 is_scissor_enabled_ = false; 1795 is_scissor_enabled_ = false;
1777 } 1796 }
1778 1797
1779 void GLRenderer::CopyCurrentRenderPassToBitmap(DrawingFrame* frame, 1798 void GLRenderer::CopyCurrentRenderPassToBitmap(
1780 SkBitmap* bitmap) { 1799 DrawingFrame* frame,
1781 gfx::Size render_pass_size = frame->current_render_pass->output_rect.size(); 1800 const CopyRenderPassCallback& callback) {
1782 bitmap->setConfig(SkBitmap::kARGB_8888_Config, 1801 GetFramebufferPixels(frame->current_render_pass->output_rect, callback);
1783 render_pass_size.width(),
1784 render_pass_size.height());
1785 if (bitmap->allocPixels()) {
1786 bitmap->lockPixels();
1787 GetFramebufferPixels(bitmap->getPixels(), gfx::Rect(render_pass_size));
1788 bitmap->unlockPixels();
1789 }
1790 } 1802 }
1791 1803
1792 void GLRenderer::ToGLMatrix(float* gl_matrix, const gfx::Transform& transform) { 1804 void GLRenderer::ToGLMatrix(float* gl_matrix, const gfx::Transform& transform) {
1793 transform.matrix().asColMajorf(gl_matrix); 1805 transform.matrix().asColMajorf(gl_matrix);
1794 } 1806 }
1795 1807
1796 void GLRenderer::SetShaderQuadF(const gfx::QuadF& quad, int quad_location) { 1808 void GLRenderer::SetShaderQuadF(const gfx::QuadF& quad, int quad_location) {
1797 if (quad_location == -1) 1809 if (quad_location == -1)
1798 return; 1810 return;
1799 1811
(...skipping 181 matching lines...) Expand 10 before | Expand all | Expand 10 after
1981 1993
1982 void GLRenderer::EnsureBackbuffer() { 1994 void GLRenderer::EnsureBackbuffer() {
1983 if (!is_backbuffer_discarded_) 1995 if (!is_backbuffer_discarded_)
1984 return; 1996 return;
1985 1997
1986 output_surface_->EnsureBackbuffer(); 1998 output_surface_->EnsureBackbuffer();
1987 is_backbuffer_discarded_ = false; 1999 is_backbuffer_discarded_ = false;
1988 } 2000 }
1989 2001
1990 void GLRenderer::GetFramebufferPixels(void* pixels, gfx::Rect rect) { 2002 void GLRenderer::GetFramebufferPixels(void* pixels, gfx::Rect rect) {
2003 if (!pixels || rect.IsEmpty())
2004 return;
2005
2006 scoped_ptr<PendingAsyncReadPixels> pending_read(new PendingAsyncReadPixels);
2007 pending_async_read_pixels_.insert(pending_async_read_pixels_.begin(),
2008 pending_read.Pass());
2009
2010 // This is a syncronous call since the callback is null.
2011 DoGetFramebufferPixels(static_cast<uint8*>(pixels),
2012 rect,
2013 AsyncGetFramebufferPixelsCleanupCallback());
2014 }
2015
2016 void GLRenderer::GetFramebufferPixels(gfx::Rect rect,
2017 CopyRenderPassCallback callback) {
2018 if (callback.is_null())
2019 return;
2020 if (rect.IsEmpty()) {
2021 callback.Run(scoped_ptr<SkBitmap>());
2022 return;
2023 }
2024
2025 scoped_ptr<SkBitmap> bitmap(new SkBitmap);
2026 bitmap->setConfig(SkBitmap::kARGB_8888_Config, rect.width(), rect.height());
2027 bitmap->allocPixels();
2028
2029 scoped_ptr<SkAutoLockPixels> lock(new SkAutoLockPixels(*bitmap));
2030
2031 // Save a pointer to the pixels, the bitmap is owned by the cleanup_callback.
2032 uint8* pixels = static_cast<uint8*>(bitmap->getPixels());
2033
2034 AsyncGetFramebufferPixelsCleanupCallback cleanup_callback = base::Bind(
2035 &GLRenderer::PassOnSkBitmap,
2036 base::Unretained(this),
2037 base::Passed(&bitmap),
2038 base::Passed(&lock),
2039 callback);
2040
2041 scoped_ptr<PendingAsyncReadPixels> pending_read(new PendingAsyncReadPixels);
2042 pending_read->copy_callback = callback;
2043 pending_async_read_pixels_.insert(pending_async_read_pixels_.begin(),
2044 pending_read.Pass());
2045
2046 // This is an asyncronous call since the callback is not null.
2047 DoGetFramebufferPixels(pixels, rect, cleanup_callback);
2048 }
2049
2050 void GLRenderer::DoGetFramebufferPixels(
2051 uint8* dest_pixels,
2052 gfx::Rect rect,
2053 const AsyncGetFramebufferPixelsCleanupCallback& cleanup_callback) {
1991 DCHECK(rect.right() <= ViewportWidth()); 2054 DCHECK(rect.right() <= ViewportWidth());
1992 DCHECK(rect.bottom() <= ViewportHeight()); 2055 DCHECK(rect.bottom() <= ViewportHeight());
1993 2056
1994 if (!pixels) 2057 bool is_async = !cleanup_callback.is_null();
1995 return;
1996 2058
1997 MakeContextCurrent(); 2059 MakeContextCurrent();
1998 2060
1999 bool do_workaround = NeedsIOSurfaceReadbackWorkaround(); 2061 bool do_workaround = NeedsIOSurfaceReadbackWorkaround();
2000 2062
2001 GLuint temporary_texture = 0; 2063 unsigned temporary_texture = 0;
2002 GLuint temporary_fbo = 0; 2064 unsigned temporary_fbo = 0;
2003 2065
2004 if (do_workaround) { 2066 if (do_workaround) {
2005 // On Mac OS X, calling glReadPixels() against an FBO whose color attachment 2067 // On Mac OS X, calling glReadPixels() against an FBO whose color attachment
2006 // is an IOSurface-backed texture causes corruption of future glReadPixels() 2068 // is an IOSurface-backed texture causes corruption of future glReadPixels()
2007 // calls, even those on different OpenGL contexts. It is believed that this 2069 // calls, even those on different OpenGL contexts. It is believed that this
2008 // is the root cause of top crasher 2070 // is the root cause of top crasher
2009 // http://crbug.com/99393. <rdar://problem/10949687> 2071 // http://crbug.com/99393. <rdar://problem/10949687>
2010 2072
2011 temporary_texture = context_->createTexture(); 2073 temporary_texture = context_->createTexture();
2012 GLC(context_, context_->bindTexture(GL_TEXTURE_2D, temporary_texture)); 2074 GLC(context_, context_->bindTexture(GL_TEXTURE_2D, temporary_texture));
(...skipping 27 matching lines...) Expand all
2040 context_->framebufferTexture2D(GL_FRAMEBUFFER, 2102 context_->framebufferTexture2D(GL_FRAMEBUFFER,
2041 GL_COLOR_ATTACHMENT0, 2103 GL_COLOR_ATTACHMENT0,
2042 GL_TEXTURE_2D, 2104 GL_TEXTURE_2D,
2043 temporary_texture, 2105 temporary_texture,
2044 0)); 2106 0));
2045 2107
2046 DCHECK(context_->checkFramebufferStatus(GL_FRAMEBUFFER) == 2108 DCHECK(context_->checkFramebufferStatus(GL_FRAMEBUFFER) ==
2047 GL_FRAMEBUFFER_COMPLETE); 2109 GL_FRAMEBUFFER_COMPLETE);
2048 } 2110 }
2049 2111
2050 scoped_ptr<uint8_t[]> src_pixels( 2112 unsigned buffer = 0;
2051 new uint8_t[rect.width() * rect.height() * 4]); 2113 scoped_ptr<uint8[]> sync_pixels;
2114
2115 if (is_async) {
piman 2013/04/27 00:42:25 I think you can make your life a bit easier if: -
danakj 2013/04/29 14:59:05 Thanks! This is what I was trying to do at first,
2116 buffer = context_->createBuffer();
2117 GLC(context_, context_->bindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
2118 buffer));
2119 GLC(context_, context_->bufferData(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
2120 4 * rect.size().GetArea(),
2121 NULL,
2122 GL_STREAM_READ));
2123 } else {
2124 sync_pixels.reset(new uint8[4 * rect.size().GetArea()]);
2125 }
2126
2052 GLC(context_, 2127 GLC(context_,
2053 context_->readPixels(rect.x(), 2128 context_->readPixels(rect.x(),
2054 ViewportSize().height() - rect.bottom(), 2129 ViewportSize().height() - rect.bottom(),
2055 rect.width(), 2130 rect.width(),
2056 rect.height(), 2131 rect.height(),
2057 GL_RGBA, 2132 GL_RGBA,
2058 GL_UNSIGNED_BYTE, 2133 GL_UNSIGNED_BYTE,
2059 src_pixels.get())); 2134 sync_pixels.get()));
2060 2135
2061 uint8_t* dest_pixels = static_cast<uint8_t*>(pixels); 2136 if (is_async) {
2062 size_t row_bytes = rect.width() * 4; 2137 GLC(context_, context_->bindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
2063 int num_rows = rect.height(); 2138 0));
2064 size_t total_bytes = num_rows * row_bytes;
2065 for (size_t dest_y = 0; dest_y < total_bytes; dest_y += row_bytes) {
2066 // Flip Y axis.
2067 size_t src_y = total_bytes - dest_y - row_bytes;
2068 // Swizzle BGRA -> RGBA.
2069 for (size_t x = 0; x < row_bytes; x += 4) {
2070 dest_pixels[dest_y + (x + 0)] = src_pixels.get()[src_y + (x + 2)];
2071 dest_pixels[dest_y + (x + 1)] = src_pixels.get()[src_y + (x + 1)];
2072 dest_pixels[dest_y + (x + 2)] = src_pixels.get()[src_y + (x + 0)];
2073 dest_pixels[dest_y + (x + 3)] = src_pixels.get()[src_y + (x + 3)];
2074 }
2075 } 2139 }
2076 2140
2077 if (do_workaround) { 2141 if (do_workaround) {
2078 // Clean up. 2142 // Clean up.
2079 GLC(context_, context_->bindFramebuffer(GL_FRAMEBUFFER, 0)); 2143 GLC(context_, context_->bindFramebuffer(GL_FRAMEBUFFER, 0));
2080 GLC(context_, context_->bindTexture(GL_TEXTURE_2D, 0)); 2144 GLC(context_, context_->bindTexture(GL_TEXTURE_2D, 0));
2081 GLC(context_, context_->deleteFramebuffer(temporary_fbo)); 2145 GLC(context_, context_->deleteFramebuffer(temporary_fbo));
2082 GLC(context_, context_->deleteTexture(temporary_texture)); 2146 GLC(context_, context_->deleteTexture(temporary_texture));
2083 } 2147 }
2084 2148
2149 unsigned sync_point = 0;
2150 if (is_async)
2151 sync_point = context_->insertSyncPoint();
2152 pending_async_read_pixels_.back()->sync_point = sync_point;
2153
2154 base::Closure finished_callback =
2155 base::Bind(&GLRenderer::FinishedReadback,
2156 base::Unretained(this),
2157 cleanup_callback,
2158 buffer,
2159 base::Passed(&sync_pixels),
2160 dest_pixels,
2161 rect.size(),
2162 sync_point);
piman 2013/04/27 00:42:25 nit: Given the above, and given that you only pass
danakj 2013/04/29 14:59:05 Oh, good idea.
2163 // Save the finished_callback so it can be cancelled.
2164 pending_async_read_pixels_.back()->finished_read_pixels_callback.Reset(
2165 finished_callback);
2166
2167 if (is_async) {
2168 SyncPointHelper::SignalSyncPoint(
2169 context_,
2170 sync_point,
2171 finished_callback);
2172 } else {
2173 finished_callback.Run();
2174 }
2175
2085 EnforceMemoryPolicy(); 2176 EnforceMemoryPolicy();
2086 } 2177 }
2087 2178
2179 void GLRenderer::FinishedReadback(
2180 const AsyncGetFramebufferPixelsCleanupCallback& cleanup_callback,
2181 unsigned source_buffer,
2182 scoped_ptr<uint8[]> sync_pixels,
2183 uint8* dest_pixels,
2184 gfx::Size size,
2185 unsigned sync_point) {
2186 DCHECK(!pending_async_read_pixels_.empty());
2187 DCHECK_EQ(sync_point, pending_async_read_pixels_.back()->sync_point);
2188
2189 uint8* src_pixels = NULL;
2190 bool success = false;
2191
2192 if (sync_pixels) {
2193 src_pixels = sync_pixels.get();
2194 success = true;
2195 } else if (source_buffer != 0) {
2196 GLC(context_, context_->bindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
2197 source_buffer));
2198 src_pixels = static_cast<uint8*>(
2199 context_->mapBufferCHROMIUM(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
2200 GL_READ_ONLY));
2201
2202 if (src_pixels) {
2203 memcpy(dest_pixels, src_pixels, 4 * size.GetArea());
2204 GLC(context_, context_->unmapBufferCHROMIUM(
2205 GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM));
2206 success = true;
2207 }
2208 GLC(context_, context_->bindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
2209 0));
2210 GLC(context_, context_->deleteBuffer(source_buffer));
2211 }
2212
2213 if (src_pixels) {
2214 size_t row_bytes = size.width() * 4;
2215 int num_rows = size.height();
2216 size_t total_bytes = num_rows * row_bytes;
2217 for (size_t dest_y = 0; dest_y < total_bytes; dest_y += row_bytes) {
2218 // Flip Y axis.
2219 size_t src_y = total_bytes - dest_y - row_bytes;
2220 // Swizzle BGRA -> RGBA.
2221 for (size_t x = 0; x < row_bytes; x += 4) {
2222 dest_pixels[dest_y + (x + 0)] = src_pixels[src_y + (x + 2)];
2223 dest_pixels[dest_y + (x + 1)] = src_pixels[src_y + (x + 1)];
2224 dest_pixels[dest_y + (x + 2)] = src_pixels[src_y + (x + 0)];
2225 dest_pixels[dest_y + (x + 3)] = src_pixels[src_y + (x + 3)];
2226 }
2227 }
2228 }
2229
2230 // TODO(danakj): This can go away when synchronous readback is no more and its
2231 // contents can just move here.
2232 if (!cleanup_callback.is_null())
2233 cleanup_callback.Run(success);
2234
2235 pending_async_read_pixels_.pop_back();
2236 }
2237
2238 void GLRenderer::PassOnSkBitmap(
2239 scoped_ptr<SkBitmap> bitmap,
2240 scoped_ptr<SkAutoLockPixels> lock,
2241 const CopyRenderPassCallback& callback,
2242 bool success) {
2243 DCHECK(callback.Equals(pending_async_read_pixels_.back()->copy_callback));
2244
2245 lock.reset();
2246 if (success)
2247 callback.Run(bitmap.Pass());
2248 else
2249 callback.Run(scoped_ptr<SkBitmap>());
2250 }
2251
2088 bool GLRenderer::GetFramebufferTexture(ScopedResource* texture, 2252 bool GLRenderer::GetFramebufferTexture(ScopedResource* texture,
2089 gfx::Rect device_rect) { 2253 gfx::Rect device_rect) {
2090 DCHECK(!texture->id() || (texture->size() == device_rect.size() && 2254 DCHECK(!texture->id() || (texture->size() == device_rect.size() &&
2091 texture->format() == GL_RGB)); 2255 texture->format() == GL_RGB));
2092 2256
2093 if (!texture->id() && !texture->Allocate(device_rect.size(), 2257 if (!texture->id() && !texture->Allocate(device_rect.size(),
2094 GL_RGB, 2258 GL_RGB,
2095 ResourceProvider::TextureUsageAny)) 2259 ResourceProvider::TextureUsageAny))
2096 return false; 2260 return false;
2097 2261
(...skipping 536 matching lines...) Expand 10 before | Expand all | Expand 10 after
2634 resource_provider_->DeleteResource(on_demand_tile_raster_resource_id_); 2798 resource_provider_->DeleteResource(on_demand_tile_raster_resource_id_);
2635 2799
2636 ReleaseRenderPassTextures(); 2800 ReleaseRenderPassTextures();
2637 } 2801 }
2638 2802
2639 bool GLRenderer::IsContextLost() { 2803 bool GLRenderer::IsContextLost() {
2640 return (context_->getGraphicsResetStatusARB() != GL_NO_ERROR); 2804 return (context_->getGraphicsResetStatusARB() != GL_NO_ERROR);
2641 } 2805 }
2642 2806
2643 } // namespace cc 2807 } // namespace cc
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698