// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "config.h"
#include "CCRendererGL.h"

#include "CCDrawQuad.h"
#include "CCPrioritizedTextureManager.h"
#include "CCResourceProvider.h"
#include "CCSettings.h"
#include "CCSingleThreadProxy.h"
#include "CCTestCommon.h"
#include "FakeWebCompositorOutputSurface.h"
#include "FakeWebGraphicsContext3D.h"
#include "GraphicsContext3D.h"
#include "WebCompositorInitializer.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <public/WebTransformationMatrix.h>

using namespace cc;
using namespace WebKit;
using namespace WebKitTests;

class FrameCountingMemoryAllocationSettingContext : public FakeWebGraphicsContext3D {
public:
    FrameCountingMemoryAllocationSettingContext() : m_frame(0) { }

    // WebGraphicsContext3D methods.

    // This method would normally do a glSwapBuffers under the hood.
    virtual void prepareTexture() { m_frame++; }
    virtual void setMemoryAllocationChangedCallbackCHROMIUM(WebGraphicsMemoryAllocationChangedCallbackCHROMIUM* callback) { m_memoryAllocationChangedCallback = callback; }
    virtual WebString getString(WebKit::WGC3Denum name)
    {
        if (name == GraphicsContext3D::EXTENSIONS)
            return WebString("GL_CHROMIUM_set_visibility GL_CHROMIUM_gpu_memory_manager GL_CHROMIUM_discard_framebuffer");
        return WebString();
    }

    // Methods added for test.
    int frameCount() { return m_frame; }
    void setMemoryAllocation(WebGraphicsMemoryAllocation allocation)
    {
        ASSERT(CCProxy::isImplThread());
        // In single threaded mode we expect this callback on main thread.
        DebugScopedSetMainThread main;
        m_memoryAllocationChangedCallback->onMemoryAllocationChanged(allocation);
    }

private:
    int m_frame;
    WebGraphicsMemoryAllocationChangedCallbackCHROMIUM* m_memoryAllocationChangedCallback;
};

class FakeCCRendererClient : public CCRendererClient {
public:
    FakeCCRendererClient()
        : m_setFullRootLayerDamageCount(0)
        , m_rootLayer(CCLayerImpl::create(1))
        , m_memoryAllocationLimitBytes(CCPrioritizedTextureManager::defaultMemoryAllocationLimit())
    {
        m_rootLayer->createRenderSurface();
        CCRenderPass::Id renderPassId = m_rootLayer->renderSurface()->renderPassId();
        scoped_ptr<CCRenderPass> rootRenderPass = CCRenderPass::create(renderPassId, IntRect(), WebTransformationMatrix());
        m_renderPassesInDrawOrder.append(rootRenderPass.get());
        m_renderPasses.set(renderPassId, rootRenderPass.Pass());
    }

    // CCRendererClient methods.
    virtual const IntSize& deviceViewportSize() const OVERRIDE { static IntSize fakeSize(1, 1); return fakeSize; }
    virtual const CCLayerTreeSettings& settings() const OVERRIDE { static CCLayerTreeSettings fakeSettings; return fakeSettings; }
    virtual void didLoseContext() OVERRIDE { }
    virtual void onSwapBuffersComplete() OVERRIDE { }
    virtual void setFullRootLayerDamage() OVERRIDE { m_setFullRootLayerDamageCount++; }
    virtual void releaseContentsTextures() OVERRIDE { }
    virtual void setMemoryAllocationLimitBytes(size_t bytes) OVERRIDE { m_memoryAllocationLimitBytes = bytes; }

    // Methods added for test.
    int setFullRootLayerDamageCount() const { return m_setFullRootLayerDamageCount; }

    CCRenderPass* rootRenderPass() { return m_renderPassesInDrawOrder.last(); }
    const CCRenderPassList& renderPassesInDrawOrder() const { return m_renderPassesInDrawOrder; }
    const CCRenderPassIdHashMap& renderPasses() const { return m_renderPasses; }

    size_t memoryAllocationLimitBytes() const { return m_memoryAllocationLimitBytes; }

private:
    int m_setFullRootLayerDamageCount;
    DebugScopedSetImplThread m_implThread;
    OwnPtr<CCLayerImpl> m_rootLayer;
    CCRenderPassList m_renderPassesInDrawOrder;
    CCRenderPassIdHashMap m_renderPasses;
    size_t m_memoryAllocationLimitBytes;
};

class FakeCCRendererGL : public CCRendererGL {
public:
    FakeCCRendererGL(CCRendererClient* client, CCResourceProvider* resourceProvider) : CCRendererGL(client, resourceProvider) { }

    // CCRendererGL methods.

    // Changing visibility to public.
    using CCRendererGL::initialize;
    using CCRendererGL::isFramebufferDiscarded;
};

class CCRendererGLTest : public testing::Test {
protected:
    CCRendererGLTest()
        : m_suggestHaveBackbufferYes(1, true)
        , m_suggestHaveBackbufferNo(1, false)
        , m_compositorInitializer(0)
        , m_context(FakeWebCompositorOutputSurface::create(adoptPtr(new FrameCountingMemoryAllocationSettingContext())))
        , m_resourceProvider(CCResourceProvider::create(m_context.get()))
        , m_renderer(&m_mockClient, m_resourceProvider.get())
    {
    }

    virtual void SetUp()
    {
        m_renderer.initialize();
    }

    void swapBuffers()
    {
        m_renderer.swapBuffers();
    }

    FrameCountingMemoryAllocationSettingContext* context() { return static_cast<FrameCountingMemoryAllocationSettingContext*>(m_context->context3D()); }

    WebGraphicsMemoryAllocation m_suggestHaveBackbufferYes;
    WebGraphicsMemoryAllocation m_suggestHaveBackbufferNo;

    WebCompositorInitializer m_compositorInitializer;
    OwnPtr<CCGraphicsContext> m_context;
    FakeCCRendererClient m_mockClient;
    OwnPtr<CCResourceProvider> m_resourceProvider;
    FakeCCRendererGL m_renderer;
    CCScopedSettings m_scopedSettings;
};

// Test CCRendererGL discardFramebuffer functionality:
// Suggest recreating framebuffer when one already exists.
// Expected: it does nothing.
TEST_F(CCRendererGLTest, SuggestBackbufferYesWhenItAlreadyExistsShouldDoNothing)
{
    context()->setMemoryAllocation(m_suggestHaveBackbufferYes);
    EXPECT_EQ(0, m_mockClient.setFullRootLayerDamageCount());
    EXPECT_FALSE(m_renderer.isFramebufferDiscarded());

    swapBuffers();
    EXPECT_EQ(1, context()->frameCount());
}

// Test CCRendererGL discardFramebuffer functionality:
// Suggest discarding framebuffer when one exists and the renderer is not visible.
// Expected: it is discarded and damage tracker is reset.
TEST_F(CCRendererGLTest, SuggestBackbufferNoShouldDiscardBackbufferAndDamageRootLayerWhileNotVisible)
{
    m_renderer.setVisible(false);
    context()->setMemoryAllocation(m_suggestHaveBackbufferNo);
    EXPECT_EQ(1, m_mockClient.setFullRootLayerDamageCount());
    EXPECT_TRUE(m_renderer.isFramebufferDiscarded());
}

// Test CCRendererGL discardFramebuffer functionality:
// Suggest discarding framebuffer when one exists and the renderer is visible.
// Expected: the allocation is ignored.
TEST_F(CCRendererGLTest, SuggestBackbufferNoDoNothingWhenVisible)
{
    m_renderer.setVisible(true);
    context()->setMemoryAllocation(m_suggestHaveBackbufferNo);
    EXPECT_EQ(0, m_mockClient.setFullRootLayerDamageCount());
    EXPECT_FALSE(m_renderer.isFramebufferDiscarded());
}


// Test CCRendererGL discardFramebuffer functionality:
// Suggest discarding framebuffer when one does not exist.
// Expected: it does nothing.
TEST_F(CCRendererGLTest, SuggestBackbufferNoWhenItDoesntExistShouldDoNothing)
{
    m_renderer.setVisible(false);
    context()->setMemoryAllocation(m_suggestHaveBackbufferNo);
    EXPECT_EQ(1, m_mockClient.setFullRootLayerDamageCount());
    EXPECT_TRUE(m_renderer.isFramebufferDiscarded());

    context()->setMemoryAllocation(m_suggestHaveBackbufferNo);
    EXPECT_EQ(1, m_mockClient.setFullRootLayerDamageCount());
    EXPECT_TRUE(m_renderer.isFramebufferDiscarded());
}

// Test CCRendererGL discardFramebuffer functionality:
// Begin drawing a frame while a framebuffer is discarded.
// Expected: will recreate framebuffer.
TEST_F(CCRendererGLTest, DiscardedBackbufferIsRecreatedForScopeDuration)
{
    m_renderer.setVisible(false);
    context()->setMemoryAllocation(m_suggestHaveBackbufferNo);
    EXPECT_TRUE(m_renderer.isFramebufferDiscarded());
    EXPECT_EQ(1, m_mockClient.setFullRootLayerDamageCount());

    m_renderer.setVisible(true);
    m_renderer.drawFrame(m_mockClient.renderPassesInDrawOrder(), m_mockClient.renderPasses());
    EXPECT_FALSE(m_renderer.isFramebufferDiscarded());

    swapBuffers();
    EXPECT_EQ(1, context()->frameCount());
}

TEST_F(CCRendererGLTest, FramebufferDiscardedAfterReadbackWhenNotVisible)
{
    m_renderer.setVisible(false);
    context()->setMemoryAllocation(m_suggestHaveBackbufferNo);
    EXPECT_TRUE(m_renderer.isFramebufferDiscarded());
    EXPECT_EQ(1, m_mockClient.setFullRootLayerDamageCount());

    char pixels[4];
    m_renderer.drawFrame(m_mockClient.renderPassesInDrawOrder(), m_mockClient.renderPasses());
    EXPECT_FALSE(m_renderer.isFramebufferDiscarded());

    m_renderer.getFramebufferPixels(pixels, IntRect(0, 0, 1, 1));
    EXPECT_TRUE(m_renderer.isFramebufferDiscarded());
    EXPECT_EQ(2, m_mockClient.setFullRootLayerDamageCount());
}

class ForbidSynchronousCallContext : public FakeWebGraphicsContext3D {
public:
    ForbidSynchronousCallContext() { }

    virtual bool getActiveAttrib(WebGLId program, WGC3Duint index, ActiveInfo&) { ADD_FAILURE(); return false; }
    virtual bool getActiveUniform(WebGLId program, WGC3Duint index, ActiveInfo&) { ADD_FAILURE(); return false; }
    virtual void getAttachedShaders(WebGLId program, WGC3Dsizei maxCount, WGC3Dsizei* count, WebGLId* shaders) { ADD_FAILURE(); }
    virtual WGC3Dint getAttribLocation(WebGLId program, const WGC3Dchar* name) { ADD_FAILURE(); return 0; }
    virtual void getBooleanv(WGC3Denum pname, WGC3Dboolean* value) { ADD_FAILURE(); }
    virtual void getBufferParameteriv(WGC3Denum target, WGC3Denum pname, WGC3Dint* value) { ADD_FAILURE(); }
    virtual Attributes getContextAttributes() { ADD_FAILURE(); return m_attrs; }
    virtual WGC3Denum getError() { ADD_FAILURE(); return 0; }
    virtual void getFloatv(WGC3Denum pname, WGC3Dfloat* value) { ADD_FAILURE(); }
    virtual void getFramebufferAttachmentParameteriv(WGC3Denum target, WGC3Denum attachment, WGC3Denum pname, WGC3Dint* value) { ADD_FAILURE(); }
    virtual void getIntegerv(WGC3Denum pname, WGC3Dint* value)
    {
        if (pname == GraphicsContext3D::MAX_TEXTURE_SIZE)
            *value = 1024; // MAX_TEXTURE_SIZE is cached client side, so it's OK to query.
        else
            ADD_FAILURE();
    }

    // We allow querying the shader compilation and program link status in debug mode, but not release.
    virtual void getProgramiv(WebGLId program, WGC3Denum pname, WGC3Dint* value)
    {
#ifndef NDEBUG
        *value = 1;
#else
        ADD_FAILURE();
#endif
    }

    virtual void getShaderiv(WebGLId shader, WGC3Denum pname, WGC3Dint* value)
    {
#ifndef NDEBUG
        *value = 1;
#else
        ADD_FAILURE();
#endif
    }

    virtual WebString getString(WGC3Denum name)
    {
        // We allow querying the extension string.
        // FIXME: It'd be better to check that we only do this before starting any other expensive work (like starting a compilation)
        if (name != GraphicsContext3D::EXTENSIONS)
            ADD_FAILURE();
        return WebString();
    }

    virtual WebString getProgramInfoLog(WebGLId program) { ADD_FAILURE(); return WebString(); }
    virtual void getRenderbufferParameteriv(WGC3Denum target, WGC3Denum pname, WGC3Dint* value) { ADD_FAILURE(); }

    virtual WebString getShaderInfoLog(WebGLId shader) { ADD_FAILURE(); return WebString(); }
    virtual void getShaderPrecisionFormat(WGC3Denum shadertype, WGC3Denum precisiontype, WGC3Dint* range, WGC3Dint* precision) { ADD_FAILURE(); }
    virtual WebString getShaderSource(WebGLId shader) { ADD_FAILURE(); return WebString(); }
    virtual void getTexParameterfv(WGC3Denum target, WGC3Denum pname, WGC3Dfloat* value) { ADD_FAILURE(); }
    virtual void getTexParameteriv(WGC3Denum target, WGC3Denum pname, WGC3Dint* value) { ADD_FAILURE(); }
    virtual void getUniformfv(WebGLId program, WGC3Dint location, WGC3Dfloat* value) { ADD_FAILURE(); }
    virtual void getUniformiv(WebGLId program, WGC3Dint location, WGC3Dint* value) { ADD_FAILURE(); }
    virtual WGC3Dint getUniformLocation(WebGLId program, const WGC3Dchar* name) { ADD_FAILURE(); return 0; }
    virtual void getVertexAttribfv(WGC3Duint index, WGC3Denum pname, WGC3Dfloat* value) { ADD_FAILURE(); }
    virtual void getVertexAttribiv(WGC3Duint index, WGC3Denum pname, WGC3Dint* value) { ADD_FAILURE(); }
    virtual WGC3Dsizeiptr getVertexAttribOffset(WGC3Duint index, WGC3Denum pname) { ADD_FAILURE(); return 0; }
};

// This test isn't using the same fixture as CCRendererGLTest, and you can't mix TEST() and TEST_F() with the same name, hence LRC2.
TEST(CCRendererGLTest2, initializationDoesNotMakeSynchronousCalls)
{
    CCScopedSettings scopedSettings;
    FakeCCRendererClient mockClient;
    OwnPtr<CCGraphicsContext> context(FakeWebCompositorOutputSurface::create(adoptPtr(new ForbidSynchronousCallContext)));
    OwnPtr<CCResourceProvider> resourceProvider(CCResourceProvider::create(context.get()));
    FakeCCRendererGL renderer(&mockClient, resourceProvider.get());

    EXPECT_TRUE(renderer.initialize());
}

class LoseContextOnFirstGetContext : public FakeWebGraphicsContext3D {
public:
    LoseContextOnFirstGetContext()
        : m_contextLost(false)
    {
    }

    virtual bool makeContextCurrent() OVERRIDE
    {
        return !m_contextLost;
    }

    virtual void getProgramiv(WebGLId program, WGC3Denum pname, WGC3Dint* value) OVERRIDE
    {
        m_contextLost = true;
        *value = 0;
    }

    virtual void getShaderiv(WebGLId shader, WGC3Denum pname, WGC3Dint* value) OVERRIDE
    {
        m_contextLost = true;
        *value = 0;
    }

    virtual WGC3Denum getGraphicsResetStatusARB() OVERRIDE
    {
        return m_contextLost ? 1 : 0;
    }

private:
    bool m_contextLost;
};

TEST(CCRendererGLTest2, initializationWithQuicklyLostContextDoesNotAssert)
{
    CCScopedSettings scopedSettings;
    FakeCCRendererClient mockClient;
    OwnPtr<CCGraphicsContext> context(FakeWebCompositorOutputSurface::create(adoptPtr(new LoseContextOnFirstGetContext)));
    OwnPtr<CCResourceProvider> resourceProvider(CCResourceProvider::create(context.get()));
    FakeCCRendererGL renderer(&mockClient, resourceProvider.get());

    renderer.initialize();
}

class ContextThatDoesNotSupportMemoryManagmentExtensions : public FakeWebGraphicsContext3D {
public:
    ContextThatDoesNotSupportMemoryManagmentExtensions() { }

    // WebGraphicsContext3D methods.

    // This method would normally do a glSwapBuffers under the hood.
    virtual void prepareTexture() { }
    virtual void setMemoryAllocationChangedCallbackCHROMIUM(WebGraphicsMemoryAllocationChangedCallbackCHROMIUM* callback) { }
    virtual WebString getString(WebKit::WGC3Denum name) { return WebString(); }
};

TEST(CCRendererGLTest2, initializationWithoutGpuMemoryManagerExtensionSupportShouldDefaultToNonZeroAllocation)
{
    FakeCCRendererClient mockClient;
    OwnPtr<CCGraphicsContext> context(FakeWebCompositorOutputSurface::create(adoptPtr(new ContextThatDoesNotSupportMemoryManagmentExtensions)));
    OwnPtr<CCResourceProvider> resourceProvider(CCResourceProvider::create(context.get()));
    FakeCCRendererGL renderer(&mockClient, resourceProvider.get());

    renderer.initialize();

    EXPECT_GT(mockClient.memoryAllocationLimitBytes(), 0ul);
}

class ClearCountingContext : public FakeWebGraphicsContext3D {
public:
    ClearCountingContext() : m_clear(0) { }

    virtual void clear(WGC3Dbitfield)
    {
        m_clear++;
    }

    int clearCount() const { return m_clear; }

private:
    int m_clear;
};

TEST(CCRendererGLTest2, opaqueBackground)
{
    FakeCCRendererClient mockClient;
    OwnPtr<CCGraphicsContext> ccContext(FakeWebCompositorOutputSurface::create(adoptPtr(new ClearCountingContext)));
    ClearCountingContext* context = static_cast<ClearCountingContext*>(ccContext->context3D());
    OwnPtr<CCResourceProvider> resourceProvider(CCResourceProvider::create(ccContext.get()));
    FakeCCRendererGL renderer(&mockClient, resourceProvider.get());

    mockClient.rootRenderPass()->setHasTransparentBackground(false);

    EXPECT_TRUE(renderer.initialize());

    renderer.drawFrame(mockClient.renderPassesInDrawOrder(), mockClient.renderPasses());

    // On DEBUG builds, render passes with opaque background clear to blue to
    // easily see regions that were not drawn on the screen.
#if defined(NDEBUG)
    EXPECT_EQ(0, context->clearCount());
#else
    EXPECT_EQ(1, context->clearCount());
#endif
}

TEST(CCRendererGLTest2, transparentBackground)
{
    FakeCCRendererClient mockClient;
    OwnPtr<CCGraphicsContext> ccContext(FakeWebCompositorOutputSurface::create(adoptPtr(new ClearCountingContext)));
    ClearCountingContext* context = static_cast<ClearCountingContext*>(ccContext->context3D());
    OwnPtr<CCResourceProvider> resourceProvider(CCResourceProvider::create(ccContext.get()));
    FakeCCRendererGL renderer(&mockClient, resourceProvider.get());

    mockClient.rootRenderPass()->setHasTransparentBackground(true);

    EXPECT_TRUE(renderer.initialize());

    renderer.drawFrame(mockClient.renderPassesInDrawOrder(), mockClient.renderPasses());

    EXPECT_EQ(1, context->clearCount());
}