summaryrefslogtreecommitdiff
path: root/Source/WebKit/chromium/tests/CCLayerTreeHostImplTest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebKit/chromium/tests/CCLayerTreeHostImplTest.cpp')
-rw-r--r--Source/WebKit/chromium/tests/CCLayerTreeHostImplTest.cpp299
1 files changed, 191 insertions, 108 deletions
diff --git a/Source/WebKit/chromium/tests/CCLayerTreeHostImplTest.cpp b/Source/WebKit/chromium/tests/CCLayerTreeHostImplTest.cpp
index a715f5b9a..d7e955783 100644
--- a/Source/WebKit/chromium/tests/CCLayerTreeHostImplTest.cpp
+++ b/Source/WebKit/chromium/tests/CCLayerTreeHostImplTest.cpp
@@ -27,15 +27,16 @@
#include "CCLayerTreeHostImpl.h"
#include "CCAnimationTestCommon.h"
+#include "CCGeometryTestUtils.h"
#include "CCHeadsUpDisplayLayerImpl.h"
#include "CCIOSurfaceLayerImpl.h"
#include "CCLayerImpl.h"
#include "CCLayerTestCommon.h"
#include "CCLayerTilingData.h"
-#include "CCLayerTreeTestCommon.h"
-#include "CCQuadCuller.h"
+#include "CCQuadSink.h"
#include "CCRenderPassDrawQuad.h"
#include "CCRendererGL.h"
+#include "CCScrollbarGeometryFixedThumb.h"
#include "CCScrollbarLayerImpl.h"
#include "CCSettings.h"
#include "CCSingleThreadProxy.h"
@@ -69,7 +70,8 @@ namespace {
class CCLayerTreeHostImplTest : public testing::Test, public CCLayerTreeHostImplClient {
public:
CCLayerTreeHostImplTest()
- : m_didRequestCommit(false)
+ : m_onCanDrawStateChangedCalled(false)
+ , m_didRequestCommit(false)
, m_didRequestRedraw(false)
{
CCLayerTreeSettings settings;
@@ -83,6 +85,7 @@ public:
virtual void didLoseContextOnImplThread() OVERRIDE { }
virtual void onSwapBuffersCompleteOnImplThread() OVERRIDE { }
virtual void onVSyncParametersChanged(double, double) OVERRIDE { }
+ virtual void onCanDrawStateChanged(bool canDraw) OVERRIDE { m_onCanDrawStateChangedCalled = true; }
virtual void setNeedsRedrawOnImplThread() OVERRIDE { m_didRequestRedraw = true; }
virtual void setNeedsCommitOnImplThread() OVERRIDE { m_didRequestCommit = true; }
virtual void postAnimationEventsToMainThreadOnImplThread(PassOwnPtr<CCAnimationEventsVector>, double wallClockTime) OVERRIDE { }
@@ -184,6 +187,7 @@ protected:
DebugScopedSetMainThreadBlocked m_alwaysMainThreadBlocked;
OwnPtr<CCLayerTreeHostImpl> m_hostImpl;
+ bool m_onCanDrawStateChangedCalled;
bool m_didRequestCommit;
bool m_didRequestRedraw;
CCScopedSettings m_scopedSettings;
@@ -194,6 +198,53 @@ public:
virtual bool makeContextCurrent() { return false; }
};
+TEST_F(CCLayerTreeHostImplTest, notifyIfCanDrawChanged)
+{
+ // Note: It is not possible to disable the renderer once it has been set,
+ // so we do not need to test that disabling the renderer notifies us
+ // that canDraw changed.
+ EXPECT_FALSE(m_hostImpl->canDraw());
+ m_onCanDrawStateChangedCalled = false;
+
+ setupScrollAndContentsLayers(IntSize(100, 100));
+ EXPECT_TRUE(m_hostImpl->canDraw());
+ EXPECT_TRUE(m_onCanDrawStateChangedCalled);
+ m_onCanDrawStateChangedCalled = false;
+
+ // Toggle the root layer to make sure it toggles canDraw
+ m_hostImpl->setRootLayer(adoptPtr<CCLayerImpl>(0));
+ EXPECT_FALSE(m_hostImpl->canDraw());
+ EXPECT_TRUE(m_onCanDrawStateChangedCalled);
+ m_onCanDrawStateChangedCalled = false;
+
+ setupScrollAndContentsLayers(IntSize(100, 100));
+ EXPECT_TRUE(m_hostImpl->canDraw());
+ EXPECT_TRUE(m_onCanDrawStateChangedCalled);
+ m_onCanDrawStateChangedCalled = false;
+
+ // Toggle the device viewport size to make sure it toggles canDraw.
+ m_hostImpl->setViewportSize(IntSize(100, 100), IntSize(0, 0));
+ EXPECT_FALSE(m_hostImpl->canDraw());
+ EXPECT_TRUE(m_onCanDrawStateChangedCalled);
+ m_onCanDrawStateChangedCalled = false;
+
+ m_hostImpl->setViewportSize(IntSize(100, 100), IntSize(100, 100));
+ EXPECT_TRUE(m_hostImpl->canDraw());
+ EXPECT_TRUE(m_onCanDrawStateChangedCalled);
+ m_onCanDrawStateChangedCalled = false;
+
+ // Toggle contents textures purged to make sure it toggles canDraw
+ m_hostImpl->releaseContentsTextures();
+ EXPECT_FALSE(m_hostImpl->canDraw());
+ EXPECT_TRUE(m_onCanDrawStateChangedCalled);
+ m_onCanDrawStateChangedCalled = false;
+
+ m_hostImpl->resetContentsTexturesPurged();
+ EXPECT_TRUE(m_hostImpl->canDraw());
+ EXPECT_TRUE(m_onCanDrawStateChangedCalled);
+ m_onCanDrawStateChangedCalled = false;
+}
+
TEST_F(CCLayerTreeHostImplTest, scrollDeltaNoLayers)
{
ASSERT_FALSE(m_hostImpl->rootLayer());
@@ -399,6 +450,24 @@ TEST_F(CCLayerTreeHostImplTest, nonFastScrollableRegionWithOffset)
EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(10, 10), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollOnMainThread);
}
+TEST_F(CCLayerTreeHostImplTest, maxScrollPositionChangedByDeviceScaleFactor)
+{
+ setupScrollAndContentsLayers(IntSize(100, 100));
+
+ float deviceScaleFactor = 2;
+ IntSize layoutViewport(25, 25);
+ IntSize deviceViewport(layoutViewport);
+ deviceViewport.scale(deviceScaleFactor);
+ m_hostImpl->setViewportSize(layoutViewport, deviceViewport);
+ m_hostImpl->setDeviceScaleFactor(deviceScaleFactor);
+ EXPECT_EQ(m_hostImpl->rootLayer()->maxScrollPosition(), IntSize(25, 25));
+
+ deviceScaleFactor = 1;
+ m_hostImpl->setViewportSize(layoutViewport, layoutViewport);
+ m_hostImpl->setDeviceScaleFactor(deviceScaleFactor);
+ EXPECT_EQ(m_hostImpl->rootLayer()->maxScrollPosition(), IntSize(75, 75));
+}
+
TEST_F(CCLayerTreeHostImplTest, pinchGesture)
{
setupScrollAndContentsLayers(IntSize(100, 100));
@@ -1345,7 +1414,7 @@ class BlendStateCheckLayer : public CCLayerImpl {
public:
static PassOwnPtr<BlendStateCheckLayer> create(int id, CCResourceProvider* resourceProvider) { return adoptPtr(new BlendStateCheckLayer(id, resourceProvider)); }
- virtual void appendQuads(CCQuadSink& quadSink, bool&) OVERRIDE
+ virtual void appendQuads(CCQuadSink& quadSink, CCAppendQuadsData& appendQuadsData) OVERRIDE
{
m_quadsAppended = true;
@@ -1360,7 +1429,7 @@ public:
testBlendingDrawQuad->setQuadVisibleRect(m_quadVisibleRect);
EXPECT_EQ(m_blend, testBlendingDrawQuad->needsBlending());
EXPECT_EQ(m_hasRenderSurface, !!renderSurface());
- quadSink.append(testBlendingDrawQuad.release());
+ quadSink.append(testBlendingDrawQuad.release(), appendQuadsData);
}
void setExpectation(bool blend, bool hasRenderSurface)
@@ -1879,14 +1948,14 @@ class FakeLayerWithQuads : public CCLayerImpl {
public:
static PassOwnPtr<FakeLayerWithQuads> create(int id) { return adoptPtr(new FakeLayerWithQuads(id)); }
- virtual void appendQuads(CCQuadSink& quadSink, bool&) OVERRIDE
+ virtual void appendQuads(CCQuadSink& quadSink, CCAppendQuadsData& appendQuadsData) OVERRIDE
{
CCSharedQuadState* sharedQuadState = quadSink.useSharedQuadState(createSharedQuadState());
SkColor gray = SkColorSetRGB(100, 100, 100);
IntRect quadRect(IntPoint(0, 0), contentBounds());
OwnPtr<CCDrawQuad> myQuad = CCSolidColorDrawQuad::create(sharedQuadState, quadRect, gray);
- quadSink.append(myQuad.release());
+ quadSink.append(myQuad.release(), appendQuadsData);
}
private:
@@ -2416,7 +2485,7 @@ public:
IntSize size(10, 10);
GC3Denum format = GraphicsContext3D::RGBA;
CCResourceProvider::TextureUsageHint hint = CCResourceProvider::TextureUsageAny;
- setScrollbarGeometry(FakeWebScrollbarThemeGeometryNonEmpty::create());
+ setScrollbarGeometry(CCScrollbarGeometryFixedThumb::create(FakeWebScrollbarThemeGeometryNonEmpty::create()));
setBackTrackResourceId(provider->createResource(pool, size, format, hint));
setForeTrackResourceId(provider->createResource(pool, size, format, hint));
@@ -2801,7 +2870,7 @@ TEST_F(CCLayerTreeHostImplTest, textureCachingWithClipping)
EXPECT_LT(quadVisibleRect.width(), 100);
// Verify that the render surface texture is *not* clipped.
- EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 100), frame.renderPasses[0]->outputRect());
+ EXPECT_RECT_EQ(IntRect(0, 0, 100, 100), frame.renderPasses[0]->outputRect());
EXPECT_EQ(CCDrawQuad::RenderPass, frame.renderPasses[1]->quadList()[0]->material());
CCRenderPassDrawQuad* quad = static_cast<CCRenderPassDrawQuad*>(frame.renderPasses[1]->quadList()[0].get());
@@ -3406,13 +3475,14 @@ TEST_F(CCLayerTreeHostImplTest, textureCachingWithScissor)
grandChild->setDrawsContent(true);
CCTiledLayerImpl* childPtr = child.get();
+ CCRenderPass::Id childPassId(childPtr->id(), 0);
child->addChild(grandChild.release());
root->addChild(child.release());
myHostImpl->setRootLayer(root.release());
myHostImpl->setViewportSize(rootRect.size(), rootRect.size());
- EXPECT_FALSE(myHostImpl->renderer()->haveCachedResourcesForRenderPassId(childPtr->id()));
+ EXPECT_FALSE(myHostImpl->renderer()->haveCachedResourcesForRenderPassId(childPassId));
{
CCLayerTreeHostImpl::FrameData frame;
@@ -3422,7 +3492,7 @@ TEST_F(CCLayerTreeHostImplTest, textureCachingWithScissor)
}
// We should have cached textures for surface 2.
- EXPECT_TRUE(myHostImpl->renderer()->haveCachedResourcesForRenderPassId(childPtr->id()));
+ EXPECT_TRUE(myHostImpl->renderer()->haveCachedResourcesForRenderPassId(childPassId));
{
CCLayerTreeHostImpl::FrameData frame;
@@ -3432,7 +3502,7 @@ TEST_F(CCLayerTreeHostImplTest, textureCachingWithScissor)
}
// We should still have cached textures for surface 2 after drawing with no damage.
- EXPECT_TRUE(myHostImpl->renderer()->haveCachedResourcesForRenderPassId(childPtr->id()));
+ EXPECT_TRUE(myHostImpl->renderer()->haveCachedResourcesForRenderPassId(childPassId));
// Damage a single tile of surface 2.
childPtr->setUpdateRect(IntRect(10, 10, 10, 10));
@@ -3445,7 +3515,7 @@ TEST_F(CCLayerTreeHostImplTest, textureCachingWithScissor)
}
// We should have a cached texture for surface 2 again even though it was damaged.
- EXPECT_TRUE(myHostImpl->renderer()->haveCachedResourcesForRenderPassId(childPtr->id()));
+ EXPECT_TRUE(myHostImpl->renderer()->haveCachedResourcesForRenderPassId(childPassId));
}
TEST_F(CCLayerTreeHostImplTest, surfaceTextureCaching)
@@ -3807,18 +3877,18 @@ struct RenderPassCacheEntry {
};
struct RenderPassRemovalTestData : public CCLayerTreeHostImpl::FrameData {
- std::map<char, RenderPassCacheEntry> renderPassCache;
+ std::map<CCRenderPass::Id, RenderPassCacheEntry> renderPassCache;
OwnPtr<CCSharedQuadState> sharedQuadState;
};
class CCTestRenderPass: public CCRenderPass {
public:
- static PassOwnPtr<CCRenderPass> create(int id, IntRect outputRect, const WebTransformationMatrix& rootTransform) { return adoptPtr(new CCTestRenderPass(id, outputRect, rootTransform)); }
+ static PassOwnPtr<CCRenderPass> create(CCRenderPass::Id id, IntRect outputRect, const WebTransformationMatrix& rootTransform) { return adoptPtr(new CCTestRenderPass(id, outputRect, rootTransform)); }
void appendQuad(PassOwnPtr<CCDrawQuad> quad) { m_quadList.append(quad); }
protected:
- CCTestRenderPass(int id, IntRect outputRect, const WebTransformationMatrix& rootTransform) : CCRenderPass(id, outputRect, rootTransform) { }
+ CCTestRenderPass(CCRenderPass::Id id, IntRect outputRect, const WebTransformationMatrix& rootTransform) : CCRenderPass(id, outputRect, rootTransform) { }
};
class CCTestRenderer : public CCRendererGL, public CCRendererClient {
@@ -3833,9 +3903,9 @@ public:
}
void clearCachedTextures() { m_textures.clear(); }
- void setHaveCachedResourcesForRenderPassId(int id) { m_textures.add(id); }
+ void setHaveCachedResourcesForRenderPassId(CCRenderPass::Id id) { m_textures.add(id); }
- virtual bool haveCachedResourcesForRenderPassId(int id) const OVERRIDE { return m_textures.contains(id); }
+ virtual bool haveCachedResourcesForRenderPassId(CCRenderPass::Id id) const OVERRIDE { return m_textures.contains(id); }
// CCRendererClient implementation.
virtual const IntSize& deviceViewportSize() const OVERRIDE { return m_viewportSize; }
@@ -3852,7 +3922,7 @@ protected:
private:
CCLayerTreeSettings m_settings;
IntSize m_viewportSize;
- HashSet<int> m_textures;
+ HashSet<CCRenderPass::Id> m_textures;
};
static void configureRenderPassTestData(const char* testScript, RenderPassRemovalTestData& testData, CCTestRenderer* renderer)
@@ -3865,13 +3935,18 @@ static void configureRenderPassTestData(const char* testScript, RenderPassRemova
const char* currentChar = testScript;
// Pre-create root pass
- char rootRenderPassId = testScript[0];
+ CCRenderPass::Id rootRenderPassId = CCRenderPass::Id(testScript[0], testScript[1]);
OwnPtr<CCRenderPass> rootRenderPass = CCTestRenderPass::create(rootRenderPassId, IntRect(), WebTransformationMatrix());
- testData.renderPassCache.insert(std::pair<char, RenderPassCacheEntry>(rootRenderPassId, RenderPassCacheEntry(rootRenderPass.release())));
+ testData.renderPassCache.insert(std::pair<CCRenderPass::Id, RenderPassCacheEntry>(rootRenderPassId, RenderPassCacheEntry(rootRenderPass.release())));
while (*currentChar) {
- char renderPassId = currentChar[0];
+ int layerId = *currentChar;
+ currentChar++;
+ ASSERT_TRUE(currentChar);
+ int index = *currentChar;
currentChar++;
+ CCRenderPass::Id renderPassId = CCRenderPass::Id(layerId, index);
+
OwnPtr<CCRenderPass> renderPass;
bool isReplica = false;
@@ -3890,9 +3965,13 @@ static void configureRenderPassTestData(const char* testScript, RenderPassRemova
currentChar++;
} else if ((*currentChar >= 'A') && (*currentChar <= 'Z')) {
// RenderPass draw quad
- char newRenderPassId = *currentChar;
- ASSERT_NE(rootRenderPassId, newRenderPassId);
+ int layerId = *currentChar;
+ currentChar++;
+ ASSERT_TRUE(currentChar);
+ int index = *currentChar;
currentChar++;
+ CCRenderPass::Id newRenderPassId = CCRenderPass::Id(layerId, index);
+ ASSERT_NE(rootRenderPassId, newRenderPassId);
bool hasTexture = false;
bool contentsChanged = true;
@@ -3918,7 +3997,7 @@ static void configureRenderPassTestData(const char* testScript, RenderPassRemova
renderer->setHaveCachedResourcesForRenderPassId(newRenderPassId);
OwnPtr<CCRenderPass> renderPass = CCTestRenderPass::create(newRenderPassId, IntRect(), WebTransformationMatrix());
- testData.renderPassCache.insert(std::pair<char, RenderPassCacheEntry>(newRenderPassId, RenderPassCacheEntry(renderPass.release())));
+ testData.renderPassCache.insert(std::pair<CCRenderPass::Id, RenderPassCacheEntry>(newRenderPassId, RenderPassCacheEntry(renderPass.release())));
}
IntRect quadRect = IntRect(0, 0, 1, 1);
@@ -3939,7 +4018,9 @@ void dumpRenderPassTestData(const RenderPassRemovalTestData& testData, char* buf
char* pos = buffer;
for (CCRenderPassList::const_reverse_iterator it = testData.renderPasses.rbegin(); it != testData.renderPasses.rend(); ++it) {
const CCRenderPass* currentPass = *it;
- *pos = currentPass->id();
+ *pos = currentPass->id().layerId;
+ pos++;
+ *pos = currentPass->id().index;
pos++;
CCQuadList::const_iterator quadListIterator = currentPass->quadList().begin();
@@ -3951,7 +4032,9 @@ void dumpRenderPassTestData(const RenderPassRemovalTestData& testData, char* buf
pos++;
break;
case CCDrawQuad::RenderPass:
- *pos = CCRenderPassDrawQuad::materialCast(currentQuad)->renderPassId();
+ *pos = CCRenderPassDrawQuad::materialCast(currentQuad)->renderPassId().layerId;
+ pos++;
+ *pos = CCRenderPassDrawQuad::materialCast(currentQuad)->renderPassId().index;
pos++;
break;
default:
@@ -3995,115 +4078,115 @@ TestCase removeRenderPassesCases[] =
{
{
"Single root pass",
- "Rssss\n",
- "Rssss\n"
+ "R0ssss\n",
+ "R0ssss\n"
}, {
"Single pass - no quads",
- "R\n",
- "R\n"
+ "R0\n",
+ "R0\n"
}, {
"Two passes, no removal",
- "RssssAsss\n"
- "Assss\n",
- "RssssAsss\n"
- "Assss\n"
+ "R0ssssA0sss\n"
+ "A0ssss\n",
+ "R0ssssA0sss\n"
+ "A0ssss\n"
}, {
"Two passes, remove last",
- "RssssA[ct]sss\n"
- "Assss\n",
- "RssssAsss\n"
+ "R0ssssA0[ct]sss\n"
+ "A0ssss\n",
+ "R0ssssA0sss\n"
}, {
"Have texture but contents changed - leave pass",
- "RssssA[t]sss\n"
- "Assss\n",
- "RssssAsss\n"
- "Assss\n"
+ "R0ssssA0[t]sss\n"
+ "A0ssss\n",
+ "R0ssssA0sss\n"
+ "A0ssss\n"
}, {
"Contents didn't change but no texture - leave pass",
- "RssssA[c]sss\n"
- "Assss\n",
- "RssssAsss\n"
- "Assss\n"
+ "R0ssssA0[c]sss\n"
+ "A0ssss\n",
+ "R0ssssA0sss\n"
+ "A0ssss\n"
}, {
"Replica: two quads reference the same pass; remove",
- "RssssA[ct]A[ct]sss\n"
- "Assss\n",
- "RssssAAsss\n"
+ "R0ssssA0[ct]A0[ct]sss\n"
+ "A0ssss\n",
+ "R0ssssA0A0sss\n"
}, {
"Replica: two quads reference the same pass; leave",
- "RssssA[c]A[c]sss\n"
- "Assss\n",
- "RssssAAsss\n"
- "Assss\n",
+ "R0ssssA0[c]A0[c]sss\n"
+ "A0ssss\n",
+ "R0ssssA0A0sss\n"
+ "A0ssss\n",
}, {
"Many passes, remove all",
- "RssssA[ct]sss\n"
- "AsssB[ct]C[ct]s\n"
- "BsssD[ct]ssE[ct]F[ct]\n"
- "Essssss\n"
- "CG[ct]\n"
- "Dsssssss\n"
- "Fsssssss\n"
- "Gsss\n",
-
- "RssssAsss\n"
+ "R0ssssA0[ct]sss\n"
+ "A0sssB0[ct]C0[ct]s\n"
+ "B0sssD0[ct]ssE0[ct]F0[ct]\n"
+ "E0ssssss\n"
+ "C0G0[ct]\n"
+ "D0sssssss\n"
+ "F0sssssss\n"
+ "G0sss\n",
+
+ "R0ssssA0sss\n"
}, {
"Deep recursion, remove all",
- "RsssssA[ct]ssss\n"
- "AssssBsss\n"
- "BC\n"
- "CD\n"
- "DE\n"
- "EF\n"
- "FG\n"
- "GH\n"
- "HsssIsss\n"
- "IJ\n"
- "Jssss\n",
+ "R0sssssA0[ct]ssss\n"
+ "A0ssssB0sss\n"
+ "B0C0\n"
+ "C0D0\n"
+ "D0E0\n"
+ "E0F0\n"
+ "F0G0\n"
+ "G0H0\n"
+ "H0sssI0sss\n"
+ "I0J0\n"
+ "J0ssss\n",
- "RsssssAssss\n"
+ "R0sssssA0ssss\n"
}, {
"Wide recursion, remove all",
- "RA[ct]B[ct]C[ct]D[ct]E[ct]F[ct]G[ct]H[ct]I[ct]J[ct]\n"
- "As\n"
- "Bs\n"
- "Cssss\n"
- "Dssss\n"
- "Es\n"
- "F\n"
- "Gs\n"
- "Hs\n"
- "Is\n"
- "Jssss\n",
+ "R0A0[ct]B0[ct]C0[ct]D0[ct]E0[ct]F0[ct]G0[ct]H0[ct]I0[ct]J0[ct]\n"
+ "A0s\n"
+ "B0s\n"
+ "C0ssss\n"
+ "D0ssss\n"
+ "E0s\n"
+ "F0\n"
+ "G0s\n"
+ "H0s\n"
+ "I0s\n"
+ "J0ssss\n",
- "RABCDEFGHIJ\n"
+ "R0A0B0C0D0E0F0G0H0I0J0\n"
}, {
"Remove passes regardless of cache state",
- "RssssA[ct]sss\n"
- "AsssBCs\n"
- "BsssD[c]ssE[t]F\n"
- "Essssss\n"
- "CG\n"
- "Dsssssss\n"
- "Fsssssss\n"
- "Gsss\n",
-
- "RssssAsss\n"
+ "R0ssssA0[ct]sss\n"
+ "A0sssB0C0s\n"
+ "B0sssD0[c]ssE0[t]F0\n"
+ "E0ssssss\n"
+ "C0G0\n"
+ "D0sssssss\n"
+ "F0sssssss\n"
+ "G0sss\n",
+
+ "R0ssssA0sss\n"
}, {
"Leave some passes, remove others",
- "RssssA[c]sss\n"
- "AsssB[t]C[ct]s\n"
- "BsssD[c]ss\n"
- "CG\n"
- "Dsssssss\n"
- "Gsss\n",
-
- "RssssAsss\n"
- "AsssBCs\n"
- "BsssDss\n"
- "Dsssssss\n"
+ "R0ssssA0[c]sss\n"
+ "A0sssB0[t]C0[ct]s\n"
+ "B0sssD0[c]ss\n"
+ "C0G0\n"
+ "D0sssssss\n"
+ "G0sss\n",
+
+ "R0ssssA0sss\n"
+ "A0sssB0C0s\n"
+ "B0sssD0ss\n"
+ "D0sssssss\n"
}, {
0, 0, 0
}