//
// Copyright 2016 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// QueryVk.cpp:
//    Implements the class methods for QueryVk.
//

#include "libANGLE/renderer/vulkan/QueryVk.h"
#include "libANGLE/Context.h"
#include "libANGLE/TransformFeedback.h"
#include "libANGLE/renderer/vulkan/ContextVk.h"
#include "libANGLE/renderer/vulkan/RendererVk.h"
#include "libANGLE/renderer/vulkan/TransformFeedbackVk.h"

#include "common/debug.h"

namespace rx
{

QueryVk::QueryVk(gl::QueryType type)
    : QueryImpl(type),
      mTransformFeedbackPrimitivesDrawn(0),
      mCachedResult(0),
      mCachedResultValid(false)
{}

QueryVk::~QueryVk() = default;

void QueryVk::onDestroy(const gl::Context *context)
{
    ContextVk *contextVk = vk::GetImpl(context);
    if (getType() != gl::QueryType::TransformFeedbackPrimitivesWritten)
    {
        vk::DynamicQueryPool *queryPool = contextVk->getQueryPool(getType());
        queryPool->freeQuery(contextVk, &mQueryHelper);
        queryPool->freeQuery(contextVk, &mQueryHelperTimeElapsedBegin);
    }
}

angle::Result QueryVk::begin(const gl::Context *context)
{
    ContextVk *contextVk = vk::GetImpl(context);

    mCachedResultValid = false;

    // Transform feedback query is a handled by a CPU-calculated value when emulated.
    if (getType() == gl::QueryType::TransformFeedbackPrimitivesWritten)
    {
        mTransformFeedbackPrimitivesDrawn = 0;
        if (contextVk->commandGraphEnabled())
        {
            contextVk->getCommandGraph()->beginTransformFeedbackEmulatedQuery();
        }
        else
        {
            // We could consider using VK_QUERY_TYPE_TRANSFORM_FEEDBACK_STREAM_EXT.
        }
        return angle::Result::Continue;
    }

    if (!mQueryHelper.getQueryPool())
    {
        ANGLE_TRY(contextVk->getQueryPool(getType())->allocateQuery(contextVk, &mQueryHelper));
    }

    // Note: TimeElapsed is implemented by using two Timestamp queries and taking the diff.
    if (getType() == gl::QueryType::TimeElapsed)
    {
        if (!mQueryHelperTimeElapsedBegin.getQueryPool())
        {
            ANGLE_TRY(contextVk->getQueryPool(getType())->allocateQuery(
                contextVk, &mQueryHelperTimeElapsedBegin));
        }

        ANGLE_TRY(mQueryHelperTimeElapsedBegin.writeTimestamp(contextVk));
    }
    else
    {
        ANGLE_TRY(mQueryHelper.beginQuery(contextVk));
    }

    return angle::Result::Continue;
}

angle::Result QueryVk::end(const gl::Context *context)
{
    ContextVk *contextVk = vk::GetImpl(context);

    if (getType() == gl::QueryType::TransformFeedbackPrimitivesWritten)
    {
        mCachedResult = mTransformFeedbackPrimitivesDrawn;

        // There could be transform feedback in progress, so add the primitives drawn so far from
        // the current transform feedback object.
        gl::TransformFeedback *transformFeedback =
            context->getState().getCurrentTransformFeedback();
        if (transformFeedback)
        {
            mCachedResult += transformFeedback->getPrimitivesDrawn();
        }
        mCachedResultValid = true;
        if (contextVk->commandGraphEnabled())
        {
            contextVk->getCommandGraph()->endTransformFeedbackEmulatedQuery();
        }
        else
        {
            // We could consider using VK_QUERY_TYPE_TRANSFORM_FEEDBACK_STREAM_EXT.
        }
    }
    else if (getType() == gl::QueryType::TimeElapsed)
    {
        ANGLE_TRY(mQueryHelper.writeTimestamp(contextVk));
    }
    else
    {
        ANGLE_TRY(mQueryHelper.endQuery(contextVk));
    }

    return angle::Result::Continue;
}

angle::Result QueryVk::queryCounter(const gl::Context *context)
{
    ContextVk *contextVk = vk::GetImpl(context);

    mCachedResultValid = false;

    if (!mQueryHelper.getQueryPool())
    {
        ANGLE_TRY(contextVk->getQueryPool(getType())->allocateQuery(contextVk, &mQueryHelper));
    }

    ASSERT(getType() == gl::QueryType::Timestamp);

    return mQueryHelper.writeTimestamp(contextVk);
}

angle::Result QueryVk::getResult(const gl::Context *context, bool wait)
{
    if (mCachedResultValid)
    {
        return angle::Result::Continue;
    }

    ContextVk *contextVk = vk::GetImpl(context);
    RendererVk *renderer = contextVk->getRenderer();

    // glGetQueryObject* requires an implicit flush of the command buffers to guarantee execution in
    // finite time.
    // Note regarding time-elapsed: end should have been called after begin, so flushing when end
    // has pending work should flush begin too.
    if (mQueryHelper.hasPendingWork(contextVk))
    {
        ANGLE_TRY(contextVk->flushImpl(nullptr));

        ASSERT(!mQueryHelperTimeElapsedBegin.hasPendingWork(contextVk));
        ASSERT(!mQueryHelper.hasPendingWork(contextVk));
    }

    // If the command buffer this query is being written to is still in flight, its reset command
    // may not have been performed by the GPU yet.  To avoid a race condition in this case, wait
    // for the batch to finish first before querying (or return not-ready if not waiting).
    ANGLE_TRY(contextVk->checkCompletedCommands());
    if (contextVk->isSerialInUse(mQueryHelper.getStoredQueueSerial()))
    {
        if (!wait)
        {
            return angle::Result::Continue;
        }
        ANGLE_TRY(contextVk->finishToSerial(mQueryHelper.getStoredQueueSerial()));
    }

    VkQueryResultFlags flags = (wait ? VK_QUERY_RESULT_WAIT_BIT : 0) | VK_QUERY_RESULT_64_BIT;

    VkResult result = mQueryHelper.getQueryPool()->getResults(
        contextVk->getDevice(), mQueryHelper.getQuery(), 1, sizeof(mCachedResult), &mCachedResult,
        sizeof(mCachedResult), flags);
    // If the results are not ready, do nothing.  mCachedResultValid remains false.
    if (result == VK_NOT_READY)
    {
        // If VK_QUERY_RESULT_WAIT_BIT was given, VK_NOT_READY cannot have been returned.
        ASSERT(!wait);
        return angle::Result::Continue;
    }
    ANGLE_VK_TRY(contextVk, result);

    double timestampPeriod = renderer->getPhysicalDeviceProperties().limits.timestampPeriod;

    // Fix up the results to what OpenGL expects.
    switch (getType())
    {
        case gl::QueryType::AnySamples:
        case gl::QueryType::AnySamplesConservative:
            // OpenGL query result in these cases is binary
            mCachedResult = !!mCachedResult;
            break;
        case gl::QueryType::Timestamp:
            mCachedResult = static_cast<uint64_t>(mCachedResult * timestampPeriod);
            break;
        case gl::QueryType::TimeElapsed:
        {
            uint64_t timeElapsedEnd = mCachedResult;

            result = mQueryHelperTimeElapsedBegin.getQueryPool()->getResults(
                contextVk->getDevice(), mQueryHelperTimeElapsedBegin.getQuery(), 1,
                sizeof(mCachedResult), &mCachedResult, sizeof(mCachedResult), flags);
            // Since the result of the end query of time-elapsed is already available, the
            // result of begin query must be available too.
            ASSERT(result != VK_NOT_READY);
            ANGLE_VK_TRY(contextVk, result);

            mCachedResult = timeElapsedEnd - mCachedResult;
            mCachedResult = static_cast<uint64_t>(mCachedResult * timestampPeriod);

            break;
        }
        default:
            UNREACHABLE();
            break;
    }

    mCachedResultValid = true;
    return angle::Result::Continue;
}
angle::Result QueryVk::getResult(const gl::Context *context, GLint *params)
{
    ANGLE_TRY(getResult(context, true));
    *params = static_cast<GLint>(mCachedResult);
    return angle::Result::Continue;
}

angle::Result QueryVk::getResult(const gl::Context *context, GLuint *params)
{
    ANGLE_TRY(getResult(context, true));
    *params = static_cast<GLuint>(mCachedResult);
    return angle::Result::Continue;
}

angle::Result QueryVk::getResult(const gl::Context *context, GLint64 *params)
{
    ANGLE_TRY(getResult(context, true));
    *params = static_cast<GLint64>(mCachedResult);
    return angle::Result::Continue;
}

angle::Result QueryVk::getResult(const gl::Context *context, GLuint64 *params)
{
    ANGLE_TRY(getResult(context, true));
    *params = mCachedResult;
    return angle::Result::Continue;
}

angle::Result QueryVk::isResultAvailable(const gl::Context *context, bool *available)
{
    ANGLE_TRY(getResult(context, false));
    *available = mCachedResultValid;

    return angle::Result::Continue;
}

void QueryVk::onTransformFeedbackEnd(const gl::Context *context)
{
    gl::TransformFeedback *transformFeedback = context->getState().getCurrentTransformFeedback();
    ASSERT(transformFeedback);

    mTransformFeedbackPrimitivesDrawn += transformFeedback->getPrimitivesDrawn();
}

}  // namespace rx
