/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Sample shading tests
 *//*--------------------------------------------------------------------*/

#include "es31fSampleShadingTests.hpp"
#include "es31fMultisampleShaderRenderCase.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuSurface.hpp"
#include "glsStateQueryUtil.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluContextInfo.hpp"
#include "gluShaderProgram.hpp"
#include "gluRenderContext.hpp"
#include "gluPixelTransfer.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "deStringUtil.hpp"
#include "deRandom.hpp"

#include <map>

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

static bool checkSupport(Context &ctx)
{
    auto contextType = ctx.getRenderContext().getType();
    return contextSupports(contextType, glu::ApiType::es(3, 2)) ||
           contextSupports(contextType, glu::ApiType::core(4, 5)) ||
           ctx.getContextInfo().isExtensionSupported("GL_OES_sample_shading");
}

using namespace gls::StateQueryUtil;

class SampleShadingStateCase : public TestCase
{
public:
    SampleShadingStateCase(Context &ctx, const char *name, const char *desc, QueryType);

    void init(void);
    IterateResult iterate(void);

private:
    const QueryType m_verifier;
};

SampleShadingStateCase::SampleShadingStateCase(Context &ctx, const char *name, const char *desc, QueryType type)
    : TestCase(ctx, name, desc)
    , m_verifier(type)
{
}

void SampleShadingStateCase::init(void)
{
    if (!checkSupport(m_context))
        throw tcu::NotSupportedError(
            "Test requires GL_OES_sample_shading extension or a context version 3.2 or higher.");
}

SampleShadingStateCase::IterateResult SampleShadingStateCase::iterate(void)
{
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    gl.enableLogging(true);

    // initial
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying initial value" << tcu::TestLog::EndMessage;
        verifyStateBoolean(result, gl, GL_SAMPLE_SHADING, false, m_verifier);
    }

    // true and false too
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying random values" << tcu::TestLog::EndMessage;

        gl.glEnable(GL_SAMPLE_SHADING);
        verifyStateBoolean(result, gl, GL_SAMPLE_SHADING, true, m_verifier);

        gl.glDisable(GL_SAMPLE_SHADING);
        verifyStateBoolean(result, gl, GL_SAMPLE_SHADING, false, m_verifier);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class MinSampleShadingValueCase : public TestCase
{
public:
    MinSampleShadingValueCase(Context &ctx, const char *name, const char *desc, QueryType);

    void init(void);
    IterateResult iterate(void);

private:
    const QueryType m_verifier;
};

MinSampleShadingValueCase::MinSampleShadingValueCase(Context &ctx, const char *name, const char *desc, QueryType type)
    : TestCase(ctx, name, desc)
    , m_verifier(type)
{
}

void MinSampleShadingValueCase::init(void)
{
    if (!checkSupport(m_context))
        throw tcu::NotSupportedError(
            "Test requires GL_OES_sample_shading extension or a context version 3.2 or higher.");
}

MinSampleShadingValueCase::IterateResult MinSampleShadingValueCase::iterate(void)
{
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");

    gl.enableLogging(true);

    // initial
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying initial value" << tcu::TestLog::EndMessage;
        verifyStateFloat(result, gl, GL_MIN_SAMPLE_SHADING_VALUE, 0.0, m_verifier);
    }

    // special values
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying special values" << tcu::TestLog::EndMessage;

        gl.glMinSampleShading(0.0f);
        verifyStateFloat(result, gl, GL_MIN_SAMPLE_SHADING_VALUE, 0.0, m_verifier);

        gl.glMinSampleShading(1.0f);
        verifyStateFloat(result, gl, GL_MIN_SAMPLE_SHADING_VALUE, 1.0, m_verifier);

        gl.glMinSampleShading(0.5f);
        verifyStateFloat(result, gl, GL_MIN_SAMPLE_SHADING_VALUE, 0.5, m_verifier);
    }

    // random values
    {
        const int numRandomTests = 10;
        de::Random rnd(0xde123);

        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying random values" << tcu::TestLog::EndMessage;

        for (int randNdx = 0; randNdx < numRandomTests; ++randNdx)
        {
            const float value = rnd.getFloat();

            gl.glMinSampleShading(value);
            verifyStateFloat(result, gl, GL_MIN_SAMPLE_SHADING_VALUE, value, m_verifier);
        }
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class MinSampleShadingValueClampingCase : public TestCase
{
public:
    MinSampleShadingValueClampingCase(Context &ctx, const char *name, const char *desc);

    void init(void);
    IterateResult iterate(void);
};

MinSampleShadingValueClampingCase::MinSampleShadingValueClampingCase(Context &ctx, const char *name, const char *desc)
    : TestCase(ctx, name, desc)
{
}

void MinSampleShadingValueClampingCase::init(void)
{
    if (!checkSupport(m_context))
        throw tcu::NotSupportedError(
            "Test requires GL_OES_sample_shading extension or a context version 3.2 or higher.");
}

MinSampleShadingValueClampingCase::IterateResult MinSampleShadingValueClampingCase::iterate(void)
{
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    gl.enableLogging(true);

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

    // special values
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying clamped values. Value is clamped when specified."
                           << tcu::TestLog::EndMessage;

        gl.glMinSampleShading(-0.5f);
        verifyStateFloat(result, gl, GL_MIN_SAMPLE_SHADING_VALUE, 0.0, QUERY_FLOAT);

        gl.glMinSampleShading(-1.0f);
        verifyStateFloat(result, gl, GL_MIN_SAMPLE_SHADING_VALUE, 0.0, QUERY_FLOAT);

        gl.glMinSampleShading(-1.5f);
        verifyStateFloat(result, gl, GL_MIN_SAMPLE_SHADING_VALUE, 0.0, QUERY_FLOAT);

        gl.glMinSampleShading(1.5f);
        verifyStateFloat(result, gl, GL_MIN_SAMPLE_SHADING_VALUE, 1.0, QUERY_FLOAT);

        gl.glMinSampleShading(2.0f);
        verifyStateFloat(result, gl, GL_MIN_SAMPLE_SHADING_VALUE, 1.0, QUERY_FLOAT);

        gl.glMinSampleShading(2.5f);
        verifyStateFloat(result, gl, GL_MIN_SAMPLE_SHADING_VALUE, 1.0, QUERY_FLOAT);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class SampleShadingRenderingCase : public MultisampleShaderRenderUtil::MultisampleRenderCase
{
public:
    enum TestType
    {
        TEST_DISCARD = 0,
        TEST_COLOR,

        TEST_LAST
    };
    SampleShadingRenderingCase(Context &ctx, const char *name, const char *desc, RenderTarget target, int numSamples,
                               TestType type);
    ~SampleShadingRenderingCase(void);

    void init(void);

private:
    void setShadingValue(int sampleCount);

    void preDraw(void);
    void postDraw(void);
    std::string getIterationDescription(int iteration) const;

    bool verifyImage(const tcu::Surface &resultImage);

    std::string genFragmentSource(int numSamples) const;

    enum
    {
        RENDER_SIZE = 128
    };

    const TestType m_type;
};

SampleShadingRenderingCase::SampleShadingRenderingCase(Context &ctx, const char *name, const char *desc,
                                                       RenderTarget target, int numSamples, TestType type)
    : MultisampleShaderRenderUtil::MultisampleRenderCase(ctx, name, desc, numSamples, target, RENDER_SIZE)
    , m_type(type)
{
    DE_ASSERT(type < TEST_LAST);
}

SampleShadingRenderingCase::~SampleShadingRenderingCase(void)
{
    deinit();
}

void SampleShadingRenderingCase::init(void)
{
    // requirements
    if (!checkSupport(m_context))
        throw tcu::NotSupportedError(
            "Test requires GL_OES_sample_shading extension or a context version 3.2 or higher.");
    if (m_renderTarget == TARGET_DEFAULT && m_context.getRenderTarget().getNumSamples() <= 1)
        throw tcu::NotSupportedError("Multisampled default framebuffer required");

    // test purpose and expectations
    m_testCtx.getLog()
        << tcu::TestLog::Message
        << "Verifying that a varying is given at least N different values for different samples within a single "
           "pixel.\n"
        << "    Render high-frequency function, map result to black/white. Modify N with glMinSampleShading().\n"
        << " => Resulting image should contain N+1 shades of gray.\n"
        << tcu::TestLog::EndMessage;

    // setup resources

    MultisampleShaderRenderUtil::MultisampleRenderCase::init();

    // set iterations

    m_numIterations = m_numTargetSamples + 1;
}

void SampleShadingRenderingCase::setShadingValue(int sampleCount)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    if (sampleCount == 0)
    {
        gl.disable(GL_SAMPLE_SHADING);
        gl.minSampleShading(1.0f);
        GLU_EXPECT_NO_ERROR(gl.getError(), "set ratio");
    }
    else
    {
        // Minimum number of samples is max(ceil(<mss> * <samples>),1). Decrease mss with epsilon to prevent
        // ceiling to a too large sample count.
        const float epsilon = 0.25f / (float)m_numTargetSamples;
        const float ratio   = ((float)sampleCount / (float)m_numTargetSamples) - epsilon;

        gl.enable(GL_SAMPLE_SHADING);
        gl.minSampleShading(ratio);
        GLU_EXPECT_NO_ERROR(gl.getError(), "set ratio");

        m_testCtx.getLog() << tcu::TestLog::Message << "Setting MIN_SAMPLE_SHADING_VALUE = " << ratio << "\n"
                           << "Requested sample count: shadingValue * numSamples = " << ratio << " * "
                           << m_numTargetSamples << " = " << (ratio * (float)m_numTargetSamples) << "\n"
                           << "Minimum sample count: ceil(shadingValue * numSamples) = ceil("
                           << (ratio * (float)m_numTargetSamples) << ") = " << sampleCount << tcu::TestLog::EndMessage;

        // can't fail with reasonable values of numSamples
        DE_ASSERT(deFloatCeil(ratio * (float)m_numTargetSamples) == float(sampleCount));
    }
}

void SampleShadingRenderingCase::preDraw(void)
{
    setShadingValue(m_iteration);
}

void SampleShadingRenderingCase::postDraw(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    gl.disable(GL_SAMPLE_SHADING);
    gl.minSampleShading(1.0f);
}

std::string SampleShadingRenderingCase::getIterationDescription(int iteration) const
{
    if (iteration == 0)
        return "Disabled SAMPLE_SHADING";
    else
        return "Samples per pixel: " + de::toString(iteration);
}

bool SampleShadingRenderingCase::verifyImage(const tcu::Surface &resultImage)
{
    const int numShadesRequired = (m_iteration == 0) ? (2) : (m_iteration + 1);
    const int rareThreshold     = 100;
    int rareCount               = 0;
    std::map<uint32_t, int> shadeFrequency;

    // we should now have n+1 different shades of white, n = num samples

    m_testCtx.getLog() << tcu::TestLog::Image("ResultImage", "Result Image", resultImage.getAccess())
                       << tcu::TestLog::Message << "Verifying image has (at least) " << numShadesRequired
                       << " different shades.\n"
                       << "Excluding pixels with no full coverage (pixels on the shared edge of the triangle pair)."
                       << tcu::TestLog::EndMessage;

    for (int y = 0; y < RENDER_SIZE; ++y)
        for (int x = 0; x < RENDER_SIZE; ++x)
        {
            const tcu::RGBA color = resultImage.getPixel(x, y);
            const uint32_t packed =
                ((uint32_t)color.getRed()) + ((uint32_t)color.getGreen() << 8) + ((uint32_t)color.getGreen() << 16);

            // on the triangle edge, skip
            if (x == y)
                continue;

            if (shadeFrequency.find(packed) == shadeFrequency.end())
                shadeFrequency[packed] = 1;
            else
                shadeFrequency[packed] = shadeFrequency[packed] + 1;
        }

    for (std::map<uint32_t, int>::const_iterator it = shadeFrequency.begin(); it != shadeFrequency.end(); ++it)
        if (it->second < rareThreshold)
            rareCount++;

    m_testCtx.getLog() << tcu::TestLog::Message << "Found " << (int)shadeFrequency.size() << " different shades.\n"
                       << "\tRare (less than " << rareThreshold << " pixels): " << rareCount << "\n"
                       << "\tCommon: " << (int)shadeFrequency.size() - rareCount << "\n"
                       << tcu::TestLog::EndMessage;

    if ((int)shadeFrequency.size() < numShadesRequired)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Image verification failed." << tcu::TestLog::EndMessage;
        return false;
    }
    return true;
}

std::string SampleShadingRenderingCase::genFragmentSource(int numSamples) const
{
    DE_UNREF(numSamples);

    const bool supportsES32orGL45 =
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));

    const glu::GLSLVersion version = supportsES32orGL45 ? glu::GLSL_VERSION_320_ES : glu::GLSL_VERSION_310_ES;
    std::ostringstream buf;

    buf << glu::getGLSLVersionDeclaration(version)
        << "\n"
           "in highp vec4 v_position;\n"
           "layout(location = 0) out mediump vec4 fragColor;\n"
           "void main (void)\n"
           "{\n"
           "    highp float field = dot(v_position.xy, v_position.xy) + dot(21.0 * v_position.xx, sin(3.1 * "
           "v_position.xy));\n"
           "    fragColor = vec4(1.0, 1.0, 1.0, 1.0);\n"
           "\n"
           "    if (fract(field) > 0.5)\n";

    if (m_type == TEST_DISCARD)
        buf << "        discard;\n";
    else if (m_type == TEST_COLOR)
        buf << "        fragColor = vec4(0.0, 0.0, 0.0, 1.0);\n";
    else
        DE_ASSERT(false);

    buf << "}";

    return buf.str();
}

} // namespace

SampleShadingTests::SampleShadingTests(Context &context)
    : TestCaseGroup(context, "sample_shading", "Test sample shading")
{
}

SampleShadingTests::~SampleShadingTests(void)
{
}

void SampleShadingTests::init(void)
{
    tcu::TestCaseGroup *const stateQueryGroup = new tcu::TestCaseGroup(m_testCtx, "state_query", "State query tests.");
    tcu::TestCaseGroup *const minSamplesGroup =
        new tcu::TestCaseGroup(m_testCtx, "min_sample_shading", "Min sample shading tests.");

    addChild(stateQueryGroup);
    addChild(minSamplesGroup);

    // .state query
    {
        stateQueryGroup->addChild(
            new SampleShadingStateCase(m_context, "sample_shading_is_enabled", "test SAMPLE_SHADING", QUERY_ISENABLED));
        stateQueryGroup->addChild(
            new SampleShadingStateCase(m_context, "sample_shading_get_boolean", "test SAMPLE_SHADING", QUERY_BOOLEAN));
        stateQueryGroup->addChild(
            new SampleShadingStateCase(m_context, "sample_shading_get_integer", "test SAMPLE_SHADING", QUERY_INTEGER));
        stateQueryGroup->addChild(
            new SampleShadingStateCase(m_context, "sample_shading_get_float", "test SAMPLE_SHADING", QUERY_FLOAT));
        stateQueryGroup->addChild(new SampleShadingStateCase(m_context, "sample_shading_get_integer64",
                                                             "test SAMPLE_SHADING", QUERY_INTEGER64));
        stateQueryGroup->addChild(new MinSampleShadingValueCase(m_context, "min_sample_shading_value_get_boolean",
                                                                "test MIN_SAMPLE_SHADING_VALUE", QUERY_BOOLEAN));
        stateQueryGroup->addChild(new MinSampleShadingValueCase(m_context, "min_sample_shading_value_get_integer",
                                                                "test MIN_SAMPLE_SHADING_VALUE", QUERY_INTEGER));
        stateQueryGroup->addChild(new MinSampleShadingValueCase(m_context, "min_sample_shading_value_get_float",
                                                                "test MIN_SAMPLE_SHADING_VALUE", QUERY_FLOAT));
        stateQueryGroup->addChild(new MinSampleShadingValueCase(m_context, "min_sample_shading_value_get_integer64",
                                                                "test MIN_SAMPLE_SHADING_VALUE", QUERY_INTEGER64));
        stateQueryGroup->addChild(new MinSampleShadingValueClampingCase(m_context, "min_sample_shading_value_clamping",
                                                                        "test MIN_SAMPLE_SHADING_VALUE clamping"));
    }

    // .min_sample_count
    {
        static const struct Target
        {
            SampleShadingRenderingCase::RenderTarget target;
            int numSamples;
            const char *name;
        } targets[] = {
            {SampleShadingRenderingCase::TARGET_DEFAULT, 0, "default_framebuffer"},
            {SampleShadingRenderingCase::TARGET_TEXTURE, 2, "multisample_texture_samples_2"},
            {SampleShadingRenderingCase::TARGET_TEXTURE, 4, "multisample_texture_samples_4"},
            {SampleShadingRenderingCase::TARGET_TEXTURE, 8, "multisample_texture_samples_8"},
            {SampleShadingRenderingCase::TARGET_TEXTURE, 16, "multisample_texture_samples_16"},
            {SampleShadingRenderingCase::TARGET_RENDERBUFFER, 2, "multisample_renderbuffer_samples_2"},
            {SampleShadingRenderingCase::TARGET_RENDERBUFFER, 4, "multisample_renderbuffer_samples_4"},
            {SampleShadingRenderingCase::TARGET_RENDERBUFFER, 8, "multisample_renderbuffer_samples_8"},
            {SampleShadingRenderingCase::TARGET_RENDERBUFFER, 16, "multisample_renderbuffer_samples_16"},
        };

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(targets); ++ndx)
        {
            minSamplesGroup->addChild(
                new SampleShadingRenderingCase(m_context, (std::string(targets[ndx].name) + "_color").c_str(),
                                               "Test multiple samples per pixel with color", targets[ndx].target,
                                               targets[ndx].numSamples, SampleShadingRenderingCase::TEST_COLOR));
            minSamplesGroup->addChild(
                new SampleShadingRenderingCase(m_context, (std::string(targets[ndx].name) + "_discard").c_str(),
                                               "Test multiple samples per pixel with", targets[ndx].target,
                                               targets[ndx].numSamples, SampleShadingRenderingCase::TEST_DISCARD));
        }
    }
}

} // namespace Functional
} // namespace gles31
} // namespace deqp
