// 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 "cc/direct_renderer.h"

#include <vector>

#include "base/debug/trace_event.h"
#include "base/metrics/histogram.h"
#include "cc/math_util.h"
#include "ui/gfx/rect_conversions.h"
#include "ui/gfx/transform.h"

static gfx::Transform orthoProjectionMatrix(float left, float right, float bottom, float top)
{
    // Use the standard formula to map the clipping frustum to the cube from
    // [-1, -1, -1] to [1, 1, 1].
    float deltaX = right - left;
    float deltaY = top - bottom;
    gfx::Transform proj;
    if (!deltaX || !deltaY)
        return proj;
    proj.matrix().setDouble(0, 0, 2.0f / deltaX);
    proj.matrix().setDouble(0, 3, -(right + left) / deltaX);
    proj.matrix().setDouble(1, 1, 2.0f / deltaY);
    proj.matrix().setDouble(1, 3, -(top + bottom) / deltaY);

    // Z component of vertices is always set to zero as we don't use the depth buffer
    // while drawing.
    proj.matrix().setDouble(2, 2, 0);

    return proj;
}

static gfx::Transform windowMatrix(int x, int y, int width, int height)
{
    gfx::Transform canvas;

    // Map to window position and scale up to pixel coordinates.
    canvas.Translate3d(x, y, 0);
    canvas.Scale3d(width, height, 0);

    // Map from ([-1, -1] to [1, 1]) -> ([0, 0] to [1, 1])
    canvas.Translate3d(0.5, 0.5, 0.5);
    canvas.Scale3d(0.5, 0.5, 0.5);

    return canvas;
}

namespace cc {

DirectRenderer::DrawingFrame::DrawingFrame()
    : rootRenderPass(0)
    , currentRenderPass(0)
    , currentTexture(0)
    , flippedY(false)
{
}

DirectRenderer::DrawingFrame::~DrawingFrame()
{
}

//
// static
gfx::RectF DirectRenderer::quadVertexRect()
{
    return gfx::RectF(-0.5, -0.5, 1, 1);
}

// static
void DirectRenderer::quadRectTransform(gfx::Transform* quadRectTransform, const gfx::Transform& quadTransform, const gfx::RectF& quadRect)
{
    *quadRectTransform = quadTransform;
    quadRectTransform->Translate(0.5 * quadRect.width() + quadRect.x(), 0.5 * quadRect.height() + quadRect.y());
    quadRectTransform->Scale(quadRect.width(), quadRect.height());
}

// static
void DirectRenderer::initializeMatrices(DrawingFrame& frame, const gfx::Rect& drawRect, bool flipY)
{
    if (flipY)
        frame.projectionMatrix = orthoProjectionMatrix(drawRect.x(), drawRect.right(), drawRect.bottom(), drawRect.y());
    else
        frame.projectionMatrix = orthoProjectionMatrix(drawRect.x(), drawRect.right(), drawRect.y(), drawRect.bottom());
    frame.windowMatrix = windowMatrix(0, 0, drawRect.width(), drawRect.height());
    frame.flippedY = flipY;
}

// static
gfx::Rect DirectRenderer::moveScissorToWindowSpace(const DrawingFrame& frame, gfx::RectF scissorRect)
{
    gfx::Rect scissorRectInCanvasSpace = gfx::ToEnclosingRect(scissorRect);
    // The scissor coordinates must be supplied in viewport space so we need to offset
    // by the relative position of the top left corner of the current render pass.
    gfx::Rect framebufferOutputRect = frame.currentRenderPass->output_rect;
    scissorRectInCanvasSpace.set_x(scissorRectInCanvasSpace.x() - framebufferOutputRect.x());
    if (frame.flippedY && !frame.currentTexture)
        scissorRectInCanvasSpace.set_y(framebufferOutputRect.height() - (scissorRectInCanvasSpace.bottom() - framebufferOutputRect.y()));
    else
        scissorRectInCanvasSpace.set_y(scissorRectInCanvasSpace.y() - framebufferOutputRect.y());
    return scissorRectInCanvasSpace;
}

DirectRenderer::DirectRenderer(RendererClient* client, ResourceProvider* resourceProvider)
    : Renderer(client)
    , m_resourceProvider(resourceProvider)
{
}

DirectRenderer::~DirectRenderer()
{
}

void DirectRenderer::setEnlargePassTextureAmountForTesting(gfx::Vector2d amount)
{
    m_enlargePassTextureAmount = amount;
}

void DirectRenderer::decideRenderPassAllocationsForFrame(const RenderPassList& renderPassesInDrawOrder)
{
    base::hash_map<RenderPass::Id, const RenderPass*> renderPassesInFrame;
    for (size_t i = 0; i < renderPassesInDrawOrder.size(); ++i)
        renderPassesInFrame.insert(std::pair<RenderPass::Id, const RenderPass*>(renderPassesInDrawOrder[i]->id, renderPassesInDrawOrder[i]));

    std::vector<RenderPass::Id> passesToDelete;
    ScopedPtrHashMap<RenderPass::Id, CachedResource>::const_iterator passIterator;
    for (passIterator = m_renderPassTextures.begin(); passIterator != m_renderPassTextures.end(); ++passIterator) {
        base::hash_map<RenderPass::Id, const RenderPass*>::const_iterator it = renderPassesInFrame.find(passIterator->first);
        if (it == renderPassesInFrame.end()) {
            passesToDelete.push_back(passIterator->first);
            continue;
        }

        const RenderPass* renderPassInFrame = it->second;
        const gfx::Size& requiredSize = renderPassTextureSize(renderPassInFrame);
        GLenum requiredFormat = renderPassTextureFormat(renderPassInFrame);
        CachedResource* texture = passIterator->second;
        DCHECK(texture);

        bool sizeAppropriate = texture->size().width() >= requiredSize.width() &&
                               texture->size().height() >= requiredSize.height();
        if (texture->id() && (!sizeAppropriate || texture->format() != requiredFormat))
            texture->Free();
    }

    // Delete RenderPass textures from the previous frame that will not be used again.
    for (size_t i = 0; i < passesToDelete.size(); ++i)
        m_renderPassTextures.erase(passesToDelete[i]);

    for (size_t i = 0; i < renderPassesInDrawOrder.size(); ++i) {
        if (!m_renderPassTextures.contains(renderPassesInDrawOrder[i]->id)) {
          scoped_ptr<CachedResource> texture = CachedResource::create(m_resourceProvider);
            m_renderPassTextures.set(renderPassesInDrawOrder[i]->id, texture.Pass());
        }
    }
}

void DirectRenderer::drawFrame(RenderPassList& renderPassesInDrawOrder, RenderPassIdHashMap& renderPassesById)
{
    TRACE_EVENT0("cc", "DirectRenderer::drawFrame");
    HISTOGRAM_COUNTS("Renderer4.renderPassCount", renderPassesInDrawOrder.size());

    const RenderPass* rootRenderPass = renderPassesInDrawOrder.back();
    DCHECK(rootRenderPass);

    DrawingFrame frame;
    frame.renderPassesById = &renderPassesById;
    frame.rootRenderPass = rootRenderPass;
    frame.rootDamageRect = capabilities().usingPartialSwap ? rootRenderPass->damage_rect : rootRenderPass->output_rect;
    frame.rootDamageRect.Intersect(gfx::Rect(gfx::Point(), viewportSize()));

    beginDrawingFrame(frame);
    for (size_t i = 0; i < renderPassesInDrawOrder.size(); ++i)
        drawRenderPass(frame, renderPassesInDrawOrder[i]);
    finishDrawingFrame(frame);

    renderPassesInDrawOrder.clear();
    renderPassesById.clear();
}

gfx::RectF DirectRenderer::computeScissorRectForRenderPass(const DrawingFrame& frame)
{
    gfx::RectF renderPassScissor = frame.currentRenderPass->output_rect;

    if (frame.rootDamageRect == frame.rootRenderPass->output_rect)
        return renderPassScissor;

    gfx::Transform inverseTransform = MathUtil::inverse(frame.currentRenderPass->transform_to_root_target);
    gfx::RectF damageRectInRenderPassSpace = MathUtil::projectClippedRect(inverseTransform, frame.rootDamageRect);
    renderPassScissor.Intersect(damageRectInRenderPassSpace);

    return renderPassScissor;
}

void DirectRenderer::setScissorStateForQuad(const DrawingFrame& frame, const DrawQuad& quad)
{
    if (quad.isClipped()) {
        gfx::RectF quadScissorRect = quad.clipRect();
        setScissorTestRect(moveScissorToWindowSpace(frame, quadScissorRect));
    }
    else
        ensureScissorTestDisabled();
}

void DirectRenderer::setScissorStateForQuadWithRenderPassScissor(const DrawingFrame& frame, const DrawQuad& quad, const gfx::RectF& renderPassScissor, bool* shouldSkipQuad)
{
    gfx::RectF quadScissorRect = renderPassScissor;

    if (quad.isClipped())
        quadScissorRect.Intersect(quad.clipRect());

    if (quadScissorRect.IsEmpty()) {
        *shouldSkipQuad = true;
        return;
    }

    *shouldSkipQuad = false;
    setScissorTestRect(moveScissorToWindowSpace(frame, quadScissorRect));
}

void DirectRenderer::finishDrawingQuadList()
{
}

void DirectRenderer::drawRenderPass(DrawingFrame& frame, const RenderPass* renderPass)
{
    TRACE_EVENT0("cc", "DirectRenderer::drawRenderPass");
    if (!useRenderPass(frame, renderPass))
        return;

    bool usingScissorAsOptimization = capabilities().usingPartialSwap;
    gfx::RectF renderPassScissor;

    if (usingScissorAsOptimization) {
        renderPassScissor = computeScissorRectForRenderPass(frame);
        setScissorTestRect(moveScissorToWindowSpace(frame, renderPassScissor));
    }

    if (frame.currentRenderPass != frame.rootRenderPass || m_client->shouldClearRootRenderPass())
        clearFramebuffer(frame);

    const QuadList& quadList = renderPass->quad_list;
    for (QuadList::constBackToFrontIterator it = quadList.backToFrontBegin(); it != quadList.backToFrontEnd(); ++it) {
        const DrawQuad& quad = *(*it);
        bool shouldSkipQuad = false;

        if (usingScissorAsOptimization)
            setScissorStateForQuadWithRenderPassScissor(frame, quad, renderPassScissor, &shouldSkipQuad);
        else
            setScissorStateForQuad(frame, quad);

        if (!shouldSkipQuad)
            drawQuad(frame, *it);
    }
    finishDrawingQuadList();

    CachedResource* texture = m_renderPassTextures.get(renderPass->id);
    if (texture)
        texture->setIsComplete(!renderPass->has_occlusion_from_outside_target_surface);
}

bool DirectRenderer::useRenderPass(DrawingFrame& frame, const RenderPass* renderPass)
{
    frame.currentRenderPass = renderPass;
    frame.currentTexture = 0;

    if (renderPass == frame.rootRenderPass) {
        bindFramebufferToOutputSurface(frame);
        initializeMatrices(frame, renderPass->output_rect, flippedFramebuffer());
        setDrawViewportSize(renderPass->output_rect.size());
        return true;
    }

    CachedResource* texture = m_renderPassTextures.get(renderPass->id);
    DCHECK(texture);

    gfx::Size size = renderPassTextureSize(renderPass);
    size.Enlarge(m_enlargePassTextureAmount.x(), m_enlargePassTextureAmount.y());
    if (!texture->id() && !texture->Allocate(size, renderPassTextureFormat(renderPass), ResourceProvider::TextureUsageFramebuffer))
        return false;

    return bindFramebufferToTexture(frame, texture, renderPass->output_rect);
}

bool DirectRenderer::haveCachedResourcesForRenderPassId(RenderPass::Id id) const
{
    CachedResource* texture = m_renderPassTextures.get(id);
    return texture && texture->id() && texture->isComplete();
}

// static
gfx::Size DirectRenderer::renderPassTextureSize(const RenderPass* pass)
{
    return pass->output_rect.size();
}

// static
GLenum DirectRenderer::renderPassTextureFormat(const RenderPass*)
{
    return GL_RGBA;
}

}  // namespace cc