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

Side by Side Diff: third_party/WebKit/Source/platform/graphics/Canvas2DLayerBridgeTest.cpp

Issue 1361043003: Revert of Make 2D canvas smarter about chosing whether or not to use GPU acceleration (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 2 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) 2011 Google Inc. All rights reserved. 2 * Copyright (C) 2011 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 5 * modification, are permitted provided that the following conditions
6 * are met: 6 * are met:
7 * 1. Redistributions of source code must retain the above copyright 7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer. 8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright 9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the 10 * notice, this list of conditions and the following disclaimer in the
(...skipping 15 matching lines...) Expand all
26 #include "platform/graphics/Canvas2DLayerBridge.h" 26 #include "platform/graphics/Canvas2DLayerBridge.h"
27 27
28 #include "SkSurface.h" 28 #include "SkSurface.h"
29 #include "platform/graphics/ImageBuffer.h" 29 #include "platform/graphics/ImageBuffer.h"
30 #include "platform/graphics/test/MockWebGraphicsContext3D.h" 30 #include "platform/graphics/test/MockWebGraphicsContext3D.h"
31 #include "public/platform/Platform.h" 31 #include "public/platform/Platform.h"
32 #include "public/platform/WebExternalBitmap.h" 32 #include "public/platform/WebExternalBitmap.h"
33 #include "public/platform/WebGraphicsContext3DProvider.h" 33 #include "public/platform/WebGraphicsContext3DProvider.h"
34 #include "public/platform/WebThread.h" 34 #include "public/platform/WebThread.h"
35 #include "third_party/skia/include/core/SkDevice.h" 35 #include "third_party/skia/include/core/SkDevice.h"
36 #include "third_party/skia/include/gpu/GrContext.h"
37 #include "third_party/skia/include/gpu/gl/SkNullGLContext.h"
38 #include "wtf/RefPtr.h" 36 #include "wtf/RefPtr.h"
39 #include <gmock/gmock.h> 37 #include <gmock/gmock.h>
40 #include <gtest/gtest.h> 38 #include <gtest/gtest.h>
41 39
42 using testing::InSequence; 40 using testing::InSequence;
43 using testing::Return; 41 using testing::Return;
44 using testing::Test; 42 using testing::Test;
45 43
46 namespace blink { 44 namespace blink {
47 45
48 namespace { 46 namespace {
49 47
50 class MockCanvasContext : public MockWebGraphicsContext3D { 48 class MockCanvasContext : public MockWebGraphicsContext3D {
51 public: 49 public:
52 MOCK_METHOD0(flush, void(void)); 50 MOCK_METHOD0(flush, void(void));
53 MOCK_METHOD0(createTexture, unsigned(void)); 51 MOCK_METHOD0(createTexture, unsigned(void));
54 MOCK_METHOD1(deleteTexture, void(unsigned)); 52 MOCK_METHOD1(deleteTexture, void(unsigned));
55 }; 53 };
56 54
57 class MockWebGraphicsContext3DProvider : public WebGraphicsContext3DProvider { 55 class MockWebGraphicsContext3DProvider : public WebGraphicsContext3DProvider {
58 public: 56 public:
59 MockWebGraphicsContext3DProvider(WebGraphicsContext3D* context3d) 57 MockWebGraphicsContext3DProvider(WebGraphicsContext3D* context3d)
60 : m_context3d(context3d) 58 : m_context3d(context3d) { }
61 {
62 RefPtr<SkGLContext> glContext = adoptRef(SkNullGLContext::Create(kNone_G rGLStandard));
63 glContext->makeCurrent();
64 m_grContext = adoptRef(GrContext::Create(kOpenGL_GrBackend, reinterpret_ cast<GrBackendContext>(glContext->gl())));
65 }
66 59
67 WebGraphicsContext3D* context3d() override 60 WebGraphicsContext3D* context3d() override
68 { 61 {
69 return m_context3d; 62 return m_context3d;
70 } 63 }
71 64
72 GrContext* grContext() override 65 GrContext* grContext() override
73 { 66 {
74 return m_grContext.get(); 67 return 0;
75 } 68 }
76 69
77 private: 70 private:
78 WebGraphicsContext3D* m_context3d; 71 WebGraphicsContext3D* m_context3d;
79 RefPtr<GrContext> m_grContext;
80 }; 72 };
81 73
82 class Canvas2DLayerBridgePtr { 74 class Canvas2DLayerBridgePtr {
83 public: 75 public:
84 Canvas2DLayerBridgePtr(PassRefPtr<Canvas2DLayerBridge> layerBridge) 76 Canvas2DLayerBridgePtr(PassRefPtr<Canvas2DLayerBridge> layerBridge)
85 : m_layerBridge(layerBridge) { } 77 : m_layerBridge(layerBridge) { }
86 78
87 ~Canvas2DLayerBridgePtr() 79 ~Canvas2DLayerBridgePtr()
88 { 80 {
89 m_layerBridge->beginDestruction(); 81 m_layerBridge->beginDestruction();
(...skipping 24 matching lines...) Expand all
114 }; 106 };
115 107
116 } // anonymous namespace 108 } // anonymous namespace
117 109
118 class Canvas2DLayerBridgeTest : public Test { 110 class Canvas2DLayerBridgeTest : public Test {
119 protected: 111 protected:
120 void fullLifecycleTest() 112 void fullLifecycleTest()
121 { 113 {
122 MockCanvasContext mainMock; 114 MockCanvasContext mainMock;
123 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr(new MockWebGraphicsContext3DProvider(&mainMock)); 115 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr(new MockWebGraphicsContext3DProvider(&mainMock));
116 RefPtr<SkSurface> surface = adoptRef(SkSurface::NewRasterN32Premul(300, 150));
124 117
125 ::testing::Mock::VerifyAndClearExpectations(&mainMock); 118 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
126 119
127 { 120 {
128 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainM ockProvider.release(), IntSize(300, 150), 0, NonOpaque, Canvas2DLayerBridge::Dis ableAcceleration))); 121 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainM ockProvider.release(), surface, 0, NonOpaque)));
129 122
130 ::testing::Mock::VerifyAndClearExpectations(&mainMock); 123 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
131 124
132 unsigned textureId = bridge->newImageSnapshot(PreferAcceleration)->g etTextureHandle(true); 125 unsigned textureId = bridge->newImageSnapshot()->getTextureHandle(tr ue);
133 EXPECT_EQ(textureId, 0u); 126 EXPECT_EQ(textureId, 0u);
134 127
135 ::testing::Mock::VerifyAndClearExpectations(&mainMock); 128 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
136 } // bridge goes out of scope here 129 } // bridge goes out of scope here
137 130
138 ::testing::Mock::VerifyAndClearExpectations(&mainMock); 131 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
139 } 132 }
140 133
141 void fallbackToSoftwareIfContextLost()
142 {
143 MockCanvasContext mainMock;
144 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr(new MockWebGraphicsContext3DProvider(&mainMock));
145
146 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
147
148 {
149 mainMock.fakeContextLost();
150 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainM ockProvider.release(), IntSize(300, 150), 0, NonOpaque, Canvas2DLayerBridge::Ena bleAcceleration)));
151 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
152 EXPECT_TRUE(bridge->checkSurfaceValid());
153 EXPECT_FALSE(bridge->isAccelerated());
154 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
155 }
156
157 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
158 }
159
160 void fallbackToSoftwareOnFailedTextureAlloc()
161 {
162 MockCanvasContext mainMock;
163
164 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
165
166 {
167 // No fallback case
168 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr (new MockWebGraphicsContext3DProvider(&mainMock));
169 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainM ockProvider.release(), IntSize(300, 150), 0, NonOpaque, Canvas2DLayerBridge::Ena bleAcceleration)));
170 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
171 EXPECT_TRUE(bridge->checkSurfaceValid());
172 EXPECT_TRUE(bridge->isAccelerated());
173 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
174 RefPtr<SkImage> snapshot = bridge->newImageSnapshot(PreferAccelerati on);
175 EXPECT_TRUE(bridge->isAccelerated());
176 EXPECT_TRUE(snapshot->isTextureBacked());
177 }
178
179 {
180 // Fallback case
181 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr (new MockWebGraphicsContext3DProvider(&mainMock));
182 GrContext* gr = mainMockProvider->grContext();
183 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainM ockProvider.release(), IntSize(300, 150), 0, NonOpaque, Canvas2DLayerBridge::Ena bleAcceleration)));
184 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
185 EXPECT_TRUE(bridge->checkSurfaceValid());
186 EXPECT_TRUE(bridge->isAccelerated()); // We don't yet know that allo cation will fail
187 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
188 gr->abandonContext(); // This will cause SkSurface_Gpu creation to f ail without Canvas2DLayerBridge otherwise detecting that anything was disabled.
189 RefPtr<SkImage> snapshot = bridge->newImageSnapshot(PreferAccelerati on);
190 EXPECT_FALSE(bridge->isAccelerated());
191 EXPECT_FALSE(snapshot->isTextureBacked());
192 }
193
194 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
195 }
196
197 void noDrawOnContextLostTest() 134 void noDrawOnContextLostTest()
198 { 135 {
199 MockCanvasContext mainMock; 136 MockCanvasContext mainMock;
200 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr(new MockWebGraphicsContext3DProvider(&mainMock)); 137 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr(new MockWebGraphicsContext3DProvider(&mainMock));
138 RefPtr<SkSurface> surface = adoptRef(SkSurface::NewRasterN32Premul(300, 150));
201 139
202 ::testing::Mock::VerifyAndClearExpectations(&mainMock); 140 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
203 141
204 { 142 {
205 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainM ockProvider.release(), IntSize(300, 150), 0, NonOpaque, Canvas2DLayerBridge::For ceAccelerationForTesting))); 143 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainM ockProvider.release(), surface, 0, NonOpaque)));
206 ::testing::Mock::VerifyAndClearExpectations(&mainMock); 144 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
207 EXPECT_TRUE(bridge->checkSurfaceValid()); 145 EXPECT_TRUE(bridge->checkSurfaceValid());
208 SkPaint paint; 146 SkPaint paint;
209 uint32_t genID = bridge->getOrCreateSurface()->generationID(); 147 uint32_t genID = surface->generationID();
210 bridge->canvas()->drawRect(SkRect::MakeXYWH(0, 0, 1, 1), paint); 148 bridge->canvas()->drawRect(SkRect::MakeXYWH(0, 0, 1, 1), paint);
211 EXPECT_EQ(genID, bridge->getOrCreateSurface()->generationID()); 149 EXPECT_EQ(genID, surface->generationID());
212 mainMock.fakeContextLost(); 150 mainMock.fakeContextLost();
213 EXPECT_EQ(genID, bridge->getOrCreateSurface()->generationID()); 151 EXPECT_EQ(genID, surface->generationID());
214 bridge->canvas()->drawRect(SkRect::MakeXYWH(0, 0, 1, 1), paint); 152 bridge->canvas()->drawRect(SkRect::MakeXYWH(0, 0, 1, 1), paint);
215 EXPECT_EQ(genID, bridge->getOrCreateSurface()->generationID()); 153 EXPECT_EQ(genID, surface->generationID());
216 EXPECT_FALSE(bridge->checkSurfaceValid()); // This results in the in ternal surface being torn down in response to the context loss 154 EXPECT_FALSE(bridge->checkSurfaceValid());
217 EXPECT_EQ(nullptr, bridge->getOrCreateSurface()); 155 EXPECT_EQ(genID, surface->generationID());
218 // The following passes by not crashing
219 bridge->canvas()->drawRect(SkRect::MakeXYWH(0, 0, 1, 1), paint); 156 bridge->canvas()->drawRect(SkRect::MakeXYWH(0, 0, 1, 1), paint);
157 EXPECT_EQ(genID, surface->generationID());
220 bridge->flush(); 158 bridge->flush();
159 EXPECT_EQ(genID, surface->generationID());
221 ::testing::Mock::VerifyAndClearExpectations(&mainMock); 160 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
222 } 161 }
223 162
224 ::testing::Mock::VerifyAndClearExpectations(&mainMock); 163 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
225 } 164 }
226 165
227 void prepareMailboxWithBitmapTest() 166 void prepareMailboxWithBitmapTest()
228 { 167 {
229 MockCanvasContext mainMock; 168 MockCanvasContext mainMock;
169 RefPtr<SkSurface> surface = adoptRef(SkSurface::NewRasterN32Premul(300, 150));
230 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr(new MockWebGraphicsContext3DProvider(&mainMock)); 170 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr(new MockWebGraphicsContext3DProvider(&mainMock));
231 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainMockP rovider.release(), IntSize(300, 150), 0, NonOpaque, Canvas2DLayerBridge::ForceAc celerationForTesting))); 171 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainMockP rovider.release(), surface, 0, NonOpaque)));
232 bridge->m_lastImageId = 1; 172 bridge->m_lastImageId = 1;
233 173
234 NullWebExternalBitmap bitmap; 174 NullWebExternalBitmap bitmap;
235 bridge->prepareMailbox(0, &bitmap); 175 bridge->prepareMailbox(0, &bitmap);
236 EXPECT_EQ(0u, bridge->m_lastImageId); 176 EXPECT_EQ(0u, bridge->m_lastImageId);
237 } 177 }
238 178
239 void prepareMailboxAndLoseResourceTest() 179 void prepareMailboxAndLoseResourceTest()
240 { 180 {
241 MockCanvasContext mainMock; 181 MockCanvasContext mainMock;
182 RefPtr<SkSurface> surface = adoptRef(SkSurface::NewRasterN32Premul(300, 150));
242 bool lostResource = true; 183 bool lostResource = true;
243 184
244 // Prepare a mailbox, then report the resource as lost. 185 // Prepare a mailbox, then report the resource as lost.
245 // This test passes by not crashing and not triggering assertions. 186 // This test passes by not crashing and not triggering assertions.
246 { 187 {
247 WebExternalTextureMailbox mailbox; 188 WebExternalTextureMailbox mailbox;
248 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr (new MockWebGraphicsContext3DProvider(&mainMock)); 189 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr (new MockWebGraphicsContext3DProvider(&mainMock));
249 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainM ockProvider.release(), IntSize(300, 150), 0, NonOpaque, Canvas2DLayerBridge::For ceAccelerationForTesting))); 190 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainM ockProvider.release(), surface, 0, NonOpaque)));
250 bridge->prepareMailbox(&mailbox, 0); 191 bridge->prepareMailbox(&mailbox, 0);
251 bridge->mailboxReleased(mailbox, lostResource); 192 bridge->mailboxReleased(mailbox, lostResource);
252 } 193 }
253 194
254 // Retry with mailbox released while bridge destruction is in progress 195 // Retry with mailbox released while bridge destruction is in progress
255 { 196 {
256 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr (new MockWebGraphicsContext3DProvider(&mainMock)); 197 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr (new MockWebGraphicsContext3DProvider(&mainMock));
257 WebExternalTextureMailbox mailbox; 198 WebExternalTextureMailbox mailbox;
258 Canvas2DLayerBridge* rawBridge; 199 Canvas2DLayerBridge* rawBridge;
259 { 200 {
260 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(m ainMockProvider.release(), IntSize(300, 150), 0, NonOpaque, Canvas2DLayerBridge: :ForceAccelerationForTesting))); 201 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(m ainMockProvider.release(), surface, 0, NonOpaque)));
261 bridge->prepareMailbox(&mailbox, 0); 202 bridge->prepareMailbox(&mailbox, 0);
262 rawBridge = bridge.get(); 203 rawBridge = bridge.get();
263 } // bridge goes out of scope, but object is kept alive by self refe rences 204 } // bridge goes out of scope, but object is kept alive by self refe rences
264 // before fixing crbug.com/411864, the following line you cause a me mory use after free 205 // before fixing crbug.com/411864, the following line you cause a me mory use after free
265 // that sometimes causes a crash in normal builds and crashes consis tently with ASAN. 206 // that sometimes causes a crash in normal builds and crashes consis tently with ASAN.
266 rawBridge->mailboxReleased(mailbox, lostResource); // This should se lf-destruct the bridge. 207 rawBridge->mailboxReleased(mailbox, lostResource); // This should se lf-destruct the bridge.
267 } 208 }
268 } 209 }
269
270 void accelerationHintTest()
271 {
272 MockCanvasContext mainMock;
273 {
274
275 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr (new MockWebGraphicsContext3DProvider(&mainMock));
276 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
277 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainM ockProvider.release(), IntSize(300, 300), 0, NonOpaque, Canvas2DLayerBridge::Ena bleAcceleration)));
278 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
279 SkPaint paint;
280 bridge->canvas()->drawRect(SkRect::MakeXYWH(0, 0, 1, 1), paint);
281 RefPtr<SkImage> image = bridge->newImageSnapshot(PreferAcceleration) ;
282 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
283 EXPECT_TRUE(bridge->checkSurfaceValid());
284 EXPECT_TRUE(bridge->isAccelerated());
285 }
286 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
287
288 {
289 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr (new MockWebGraphicsContext3DProvider(&mainMock));
290 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
291 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainM ockProvider.release(), IntSize(300, 300), 0, NonOpaque, Canvas2DLayerBridge::Ena bleAcceleration)));
292 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
293 SkPaint paint;
294 bridge->canvas()->drawRect(SkRect::MakeXYWH(0, 0, 1, 1), paint);
295 RefPtr<SkImage> image = bridge->newImageSnapshot(PreferNoAcceleratio n);
296 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
297 EXPECT_TRUE(bridge->checkSurfaceValid());
298 EXPECT_FALSE(bridge->isAccelerated());
299 }
300 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
301 }
302 }; 210 };
303 211
304 TEST_F(Canvas2DLayerBridgeTest, FullLifecycleSingleThreaded) 212 TEST_F(Canvas2DLayerBridgeTest, testFullLifecycleSingleThreaded)
305 { 213 {
306 fullLifecycleTest(); 214 fullLifecycleTest();
307 } 215 }
308 216
309 TEST_F(Canvas2DLayerBridgeTest, NoDrawOnContextLost) 217 TEST_F(Canvas2DLayerBridgeTest, testNoDrawOnContextLost)
310 { 218 {
311 noDrawOnContextLostTest(); 219 noDrawOnContextLostTest();
312 } 220 }
313 221
314 TEST_F(Canvas2DLayerBridgeTest, PrepareMailboxWithBitmap) 222 TEST_F(Canvas2DLayerBridgeTest, testPrepareMailboxWithBitmap)
315 { 223 {
316 prepareMailboxWithBitmapTest(); 224 prepareMailboxWithBitmapTest();
317 } 225 }
318 226
319 TEST_F(Canvas2DLayerBridgeTest, PrepareMailboxAndLoseResource) 227 TEST_F(Canvas2DLayerBridgeTest, testPrepareMailboxAndLoseResource)
320 { 228 {
321 prepareMailboxAndLoseResourceTest(); 229 prepareMailboxAndLoseResourceTest();
322 } 230 }
323 231
324 TEST_F(Canvas2DLayerBridgeTest, AccelerationHint)
325 {
326 accelerationHintTest();
327 }
328
329 TEST_F(Canvas2DLayerBridgeTest, FallbackToSoftwareIfContextLost)
330 {
331 fallbackToSoftwareIfContextLost();
332 }
333
334 TEST_F(Canvas2DLayerBridgeTest, FallbackToSoftwareOnFailedTextureAlloc)
335 {
336 fallbackToSoftwareOnFailedTextureAlloc();
337 }
338
339 } // namespace blink 232 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698