/*-------------------------------------------------------------------------
 * drawElements Internal Test 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 Miscellaneous framework tests.
 *//*--------------------------------------------------------------------*/

#include "ditFrameworkTests.hpp"
#include "ditTextureFormatTests.hpp"
#include "ditAstcTests.hpp"
#include "ditVulkanTests.hpp"

#include "tcuFloatFormat.hpp"
#include "tcuEither.hpp"
#include "tcuTestLog.hpp"
#include "tcuCommandLine.hpp"

#include "rrRenderer.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuVectorUtil.hpp"
#include "tcuFloat.hpp"

#include "deRandom.hpp"
#include "deArrayUtil.hpp"

#include <stdexcept>

namespace dit
{

namespace
{

using std::string;
using std::vector;
using tcu::TestLog;

struct MatchCase
{
    enum Expected
    {
        NO_MATCH,
        MATCH_GROUP,
        MATCH_CASE,
        EXPECTED_LAST
    };

    const char *path;
    Expected expected;
};

const char *getMatchCaseExpectedDesc(MatchCase::Expected expected)
{
    static const char *descs[] = {"no match", "group to match", "case to match"};
    return de::getSizedArrayElement<MatchCase::EXPECTED_LAST>(descs, expected);
}

class CaseListParserCase : public tcu::TestCase
{
public:
    CaseListParserCase(tcu::TestContext &testCtx, const char *name, const char *caseList, const MatchCase *subCases,
                       int numSubCases)
        : tcu::TestCase(testCtx, name, "")
        , m_caseList(caseList)
        , m_subCases(subCases)
        , m_numSubCases(numSubCases)
    {
    }

    IterateResult iterate(void)
    {
        TestLog &log = m_testCtx.getLog();
        tcu::CommandLine cmdLine;
        de::MovePtr<tcu::CaseListFilter> caseListFilter;
        int numPass = 0;

        log << TestLog::Message << "Input:\n\"" << m_caseList << "\"" << TestLog::EndMessage;

        {
            const char *argv[] = {"deqp", "--deqp-caselist", m_caseList};

            if (!cmdLine.parse(DE_LENGTH_OF_ARRAY(argv), argv))
                TCU_FAIL("Failed to parse command line");
        }

        caseListFilter = cmdLine.createCaseListFilter(m_testCtx.getArchive());

        for (int subCaseNdx = 0; subCaseNdx < m_numSubCases; subCaseNdx++)
        {
            const MatchCase &curCase = m_subCases[subCaseNdx];
            bool matchGroup;
            bool matchCase;

            log << TestLog::Message << "Checking \"" << curCase.path << "\""
                << ", expecting " << getMatchCaseExpectedDesc(curCase.expected) << TestLog::EndMessage;

            matchGroup = caseListFilter->checkTestGroupName(curCase.path);
            matchCase  = caseListFilter->checkTestCaseName(curCase.path);

            if ((matchGroup == (curCase.expected == MatchCase::MATCH_GROUP)) &&
                (matchCase == (curCase.expected == MatchCase::MATCH_CASE)))
            {
                log << TestLog::Message << "   pass" << TestLog::EndMessage;
                numPass += 1;
            }
            else
                log << TestLog::Message << "   FAIL!" << TestLog::EndMessage;
        }

        m_testCtx.setTestResult((numPass == m_numSubCases) ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                                (numPass == m_numSubCases) ? "All passed" : "Unexpected match result");

        return STOP;
    }

private:
    const char *const m_caseList;
    const MatchCase *const m_subCases;
    const int m_numSubCases;
};

class NegativeCaseListCase : public tcu::TestCase
{
public:
    NegativeCaseListCase(tcu::TestContext &testCtx, const char *name, const char *caseList)
        : tcu::TestCase(testCtx, name, "")
        , m_caseList(caseList)
    {
    }

    IterateResult iterate(void)
    {
        TestLog &log = m_testCtx.getLog();
        tcu::CommandLine cmdLine;

        log << TestLog::Message << "Input:\n\"" << m_caseList << "\"" << TestLog::EndMessage;

        {
            const char *argv[] = {"deqp", "--deqp-caselist", m_caseList};

            TCU_CHECK(cmdLine.parse(DE_LENGTH_OF_ARRAY(argv), argv));

            try
            {
                de::UniquePtr<tcu::CaseListFilter> filter(cmdLine.createCaseListFilter(m_testCtx.getArchive()));

                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Parsing passed, should have failed");
            }
            catch (const std::invalid_argument &e)
            {
                log << TestLog::Message << e.what() << TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Parsing failed as expected");
            }
        }

        return STOP;
    }

private:
    const char *const m_caseList;
};

class TrieParserTests : public tcu::TestCaseGroup
{
public:
    TrieParserTests(tcu::TestContext &testCtx) : tcu::TestCaseGroup(testCtx, "trie", "Test case trie parser tests")
    {
    }

    void init(void)
    {
        {
            static const char *const caseList = "{test}";
            static const MatchCase subCases[] = {
                {"test", MatchCase::MATCH_CASE},
                {"test.cd", MatchCase::NO_MATCH},
            };
            addChild(
                new CaseListParserCase(m_testCtx, "single_case", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }
        {
            static const char *const caseList = "{a{b}}";
            static const MatchCase subCases[] = {
                {"a", MatchCase::MATCH_GROUP},
                {"b", MatchCase::NO_MATCH},
                {"a.b", MatchCase::MATCH_CASE},
                {"a.a", MatchCase::NO_MATCH},
            };
            addChild(
                new CaseListParserCase(m_testCtx, "simple_group_1", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }
        {
            static const char *const caseList = "{a{b,c}}";
            static const MatchCase subCases[] = {
                {"a", MatchCase::MATCH_GROUP}, {"b", MatchCase::NO_MATCH},     {"a.b", MatchCase::MATCH_CASE},
                {"a.a", MatchCase::NO_MATCH},  {"a.c", MatchCase::MATCH_CASE},
            };
            addChild(
                new CaseListParserCase(m_testCtx, "simple_group_2", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }
        {
            static const char *const caseList = "{a{b},c{d,e}}";
            static const MatchCase subCases[] = {
                {"a", MatchCase::MATCH_GROUP},  {"b", MatchCase::NO_MATCH},   {"a.b", MatchCase::MATCH_CASE},
                {"a.c", MatchCase::NO_MATCH},   {"a.d", MatchCase::NO_MATCH}, {"a.e", MatchCase::NO_MATCH},
                {"c", MatchCase::MATCH_GROUP},  {"c.b", MatchCase::NO_MATCH}, {"c.d", MatchCase::MATCH_CASE},
                {"c.e", MatchCase::MATCH_CASE},
            };
            addChild(new CaseListParserCase(m_testCtx, "two_groups", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }
        {
            static const char *const caseList = "{a,c{d,e}}";
            static const MatchCase subCases[] = {
                {"a", MatchCase::MATCH_CASE},   {"b", MatchCase::NO_MATCH},   {"a.b", MatchCase::NO_MATCH},
                {"a.c", MatchCase::NO_MATCH},   {"a.d", MatchCase::NO_MATCH}, {"a.e", MatchCase::NO_MATCH},
                {"c", MatchCase::MATCH_GROUP},  {"c.b", MatchCase::NO_MATCH}, {"c.d", MatchCase::MATCH_CASE},
                {"c.e", MatchCase::MATCH_CASE},
            };
            addChild(new CaseListParserCase(m_testCtx, "case_group", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }
        {
            static const char *const caseList = "{c{d,e},a}";
            static const MatchCase subCases[] = {
                {"a", MatchCase::MATCH_CASE},   {"b", MatchCase::NO_MATCH},   {"a.b", MatchCase::NO_MATCH},
                {"a.c", MatchCase::NO_MATCH},   {"a.d", MatchCase::NO_MATCH}, {"a.e", MatchCase::NO_MATCH},
                {"c", MatchCase::MATCH_GROUP},  {"c.b", MatchCase::NO_MATCH}, {"c.d", MatchCase::MATCH_CASE},
                {"c.e", MatchCase::MATCH_CASE},
            };
            addChild(new CaseListParserCase(m_testCtx, "group_case", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }
        {
            static const char *const caseList = "{test}\r";
            static const MatchCase subCases[] = {
                {"test", MatchCase::MATCH_CASE},
                {"test.cd", MatchCase::NO_MATCH},
            };
            addChild(
                new CaseListParserCase(m_testCtx, "trailing_cr", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }
        {
            static const char *const caseList = "{test}\n";
            static const MatchCase subCases[] = {
                {"test", MatchCase::MATCH_CASE},
                {"test.cd", MatchCase::NO_MATCH},
            };
            addChild(
                new CaseListParserCase(m_testCtx, "trailing_lf", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }
        {
            static const char *const caseList = "{test}\r\n";
            static const MatchCase subCases[] = {
                {"test", MatchCase::MATCH_CASE},
                {"test.cd", MatchCase::NO_MATCH},
            };
            addChild(
                new CaseListParserCase(m_testCtx, "trailing_crlf", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }

        // Negative tests
        addChild(new NegativeCaseListCase(m_testCtx, "empty_string", ""));
        addChild(new NegativeCaseListCase(m_testCtx, "empty_line", "\n"));
        addChild(new NegativeCaseListCase(m_testCtx, "empty_root", "{}"));
        addChild(new NegativeCaseListCase(m_testCtx, "empty_group", "{test{}}"));
        addChild(new NegativeCaseListCase(m_testCtx, "empty_group_name_1", "{{}}"));
        addChild(new NegativeCaseListCase(m_testCtx, "empty_group_name_2", "{{test}}"));
        addChild(new NegativeCaseListCase(m_testCtx, "unterminated_root_1", "{"));
        addChild(new NegativeCaseListCase(m_testCtx, "unterminated_root_2", "{test"));
        addChild(new NegativeCaseListCase(m_testCtx, "unterminated_root_3", "{test,"));
        addChild(new NegativeCaseListCase(m_testCtx, "unterminated_root_4", "{test{a}"));
        addChild(new NegativeCaseListCase(m_testCtx, "unterminated_root_5", "{a,b"));
        addChild(new NegativeCaseListCase(m_testCtx, "unterminated_group_1", "{test{"));
        addChild(new NegativeCaseListCase(m_testCtx, "unterminated_group_2", "{test{a"));
        addChild(new NegativeCaseListCase(m_testCtx, "unterminated_group_3", "{test{a,"));
        addChild(new NegativeCaseListCase(m_testCtx, "unterminated_group_4", "{test{a,b"));
        addChild(new NegativeCaseListCase(m_testCtx, "empty_case_name_1", "{a,,b}"));
        addChild(new NegativeCaseListCase(m_testCtx, "empty_case_name_2", "{,b}"));
        addChild(new NegativeCaseListCase(m_testCtx, "empty_case_name_3", "{a,}"));
        addChild(new NegativeCaseListCase(m_testCtx, "no_separator", "{a{b}c}"));
        addChild(new NegativeCaseListCase(m_testCtx, "invalid_char_1", "{a.b}"));
        addChild(new NegativeCaseListCase(m_testCtx, "invalid_char_2", "{a[]}"));
        addChild(new NegativeCaseListCase(m_testCtx, "trailing_char_1", "{a}}"));
        addChild(new NegativeCaseListCase(m_testCtx, "trailing_char_2", "{a}x"));
        addChild(new NegativeCaseListCase(m_testCtx, "embedded_newline_1", "{\na}"));
        addChild(new NegativeCaseListCase(m_testCtx, "embedded_newline_2", "{a\n,b}"));
        addChild(new NegativeCaseListCase(m_testCtx, "embedded_newline_3", "{a,\nb}"));
        addChild(new NegativeCaseListCase(m_testCtx, "embedded_newline_4", "{a{b\n}}"));
        addChild(new NegativeCaseListCase(m_testCtx, "embedded_newline_5", "{a{b}\n}"));
    }
};

class ListParserTests : public tcu::TestCaseGroup
{
public:
    ListParserTests(tcu::TestContext &testCtx) : tcu::TestCaseGroup(testCtx, "list", "Test case list parser tests")
    {
    }

    void init(void)
    {
        {
            static const char *const caseList = "test";
            static const MatchCase subCases[] = {
                {"test", MatchCase::MATCH_CASE},
                {"test.cd", MatchCase::NO_MATCH},
            };
            addChild(
                new CaseListParserCase(m_testCtx, "single_case", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }
        {
            static const char *const caseList = "a.b";
            static const MatchCase subCases[] = {
                {"a", MatchCase::MATCH_GROUP},
                {"b", MatchCase::NO_MATCH},
                {"a.b", MatchCase::MATCH_CASE},
                {"a.a", MatchCase::NO_MATCH},
            };
            addChild(
                new CaseListParserCase(m_testCtx, "simple_group_1", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }
        {
            static const char *const caseList = "a.b\na.c";
            static const MatchCase subCases[] = {
                {"a", MatchCase::MATCH_GROUP}, {"b", MatchCase::NO_MATCH},     {"a.b", MatchCase::MATCH_CASE},
                {"a.a", MatchCase::NO_MATCH},  {"a.c", MatchCase::MATCH_CASE},
            };
            addChild(
                new CaseListParserCase(m_testCtx, "simple_group_2", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }
        {
            static const char *const caseList = "a.b\na.c";
            static const MatchCase subCases[] = {
                {"a", MatchCase::MATCH_GROUP}, {"b", MatchCase::NO_MATCH},     {"a.b", MatchCase::MATCH_CASE},
                {"a.a", MatchCase::NO_MATCH},  {"a.c", MatchCase::MATCH_CASE},
            };
            addChild(
                new CaseListParserCase(m_testCtx, "separator_ln", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }
        {
            static const char *const caseList = "a.b\ra.c";
            static const MatchCase subCases[] = {
                {"a", MatchCase::MATCH_GROUP}, {"b", MatchCase::NO_MATCH},     {"a.b", MatchCase::MATCH_CASE},
                {"a.a", MatchCase::NO_MATCH},  {"a.c", MatchCase::MATCH_CASE},
            };
            addChild(
                new CaseListParserCase(m_testCtx, "separator_cr", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }
        {
            static const char *const caseList = "a.b\r\na.c";
            static const MatchCase subCases[] = {
                {"a", MatchCase::MATCH_GROUP}, {"b", MatchCase::NO_MATCH},     {"a.b", MatchCase::MATCH_CASE},
                {"a.a", MatchCase::NO_MATCH},  {"a.c", MatchCase::MATCH_CASE},
            };
            addChild(
                new CaseListParserCase(m_testCtx, "separator_crlf", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }
        {
            static const char *const caseList = "a.b\na.c\n";
            static const MatchCase subCases[] = {
                {"a", MatchCase::MATCH_GROUP}, {"b", MatchCase::NO_MATCH},     {"a.b", MatchCase::MATCH_CASE},
                {"a.a", MatchCase::NO_MATCH},  {"a.c", MatchCase::MATCH_CASE},
            };
            addChild(new CaseListParserCase(m_testCtx, "end_ln", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }
        {
            static const char *const caseList = "a.b\na.c\r";
            static const MatchCase subCases[] = {
                {"a", MatchCase::MATCH_GROUP}, {"b", MatchCase::NO_MATCH},     {"a.b", MatchCase::MATCH_CASE},
                {"a.a", MatchCase::NO_MATCH},  {"a.c", MatchCase::MATCH_CASE},
            };
            addChild(new CaseListParserCase(m_testCtx, "end_cr", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }
        {
            static const char *const caseList = "a.b\na.c\r\n";
            static const MatchCase subCases[] = {
                {"a", MatchCase::MATCH_GROUP}, {"b", MatchCase::NO_MATCH},     {"a.b", MatchCase::MATCH_CASE},
                {"a.a", MatchCase::NO_MATCH},  {"a.c", MatchCase::MATCH_CASE},
            };
            addChild(new CaseListParserCase(m_testCtx, "end_crlf", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }
        {
            static const char *const caseList = "a.b\nc.d\nc.e";
            static const MatchCase subCases[] = {
                {"a", MatchCase::MATCH_GROUP},  {"b", MatchCase::NO_MATCH},   {"a.b", MatchCase::MATCH_CASE},
                {"a.c", MatchCase::NO_MATCH},   {"a.d", MatchCase::NO_MATCH}, {"a.e", MatchCase::NO_MATCH},
                {"c", MatchCase::MATCH_GROUP},  {"c.b", MatchCase::NO_MATCH}, {"c.d", MatchCase::MATCH_CASE},
                {"c.e", MatchCase::MATCH_CASE},
            };
            addChild(new CaseListParserCase(m_testCtx, "two_groups", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }
        {
            static const char *const caseList = "a\nc.d\nc.e";
            static const MatchCase subCases[] = {
                {"a", MatchCase::MATCH_CASE},   {"b", MatchCase::NO_MATCH},   {"a.b", MatchCase::NO_MATCH},
                {"a.c", MatchCase::NO_MATCH},   {"a.d", MatchCase::NO_MATCH}, {"a.e", MatchCase::NO_MATCH},
                {"c", MatchCase::MATCH_GROUP},  {"c.b", MatchCase::NO_MATCH}, {"c.d", MatchCase::MATCH_CASE},
                {"c.e", MatchCase::MATCH_CASE},
            };
            addChild(new CaseListParserCase(m_testCtx, "case_group", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }
        {
            static const char *const caseList = "c.d\nc.e\na";
            static const MatchCase subCases[] = {
                {"a", MatchCase::MATCH_CASE},   {"b", MatchCase::NO_MATCH},   {"a.b", MatchCase::NO_MATCH},
                {"a.c", MatchCase::NO_MATCH},   {"a.d", MatchCase::NO_MATCH}, {"a.e", MatchCase::NO_MATCH},
                {"c", MatchCase::MATCH_GROUP},  {"c.b", MatchCase::NO_MATCH}, {"c.d", MatchCase::MATCH_CASE},
                {"c.e", MatchCase::MATCH_CASE},
            };
            addChild(new CaseListParserCase(m_testCtx, "group_case", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }
        {
            static const char *const caseList = "a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.x";
            static const MatchCase subCases[] = {
                {"a", MatchCase::MATCH_GROUP},
                {"b", MatchCase::NO_MATCH},
                {"a.b", MatchCase::MATCH_GROUP},
                {"a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.x", MatchCase::MATCH_CASE},
            };
            addChild(new CaseListParserCase(m_testCtx, "long_name", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }
        {
            static const char *const caseList = "a.b.c.d.e\n"
                                                "a.b.c.f\n"
                                                "x.y.z\n"
                                                "a.b.c.d.g\n"
                                                "a.b.c.x\n";
            static const MatchCase subCases[] = {
                {"a", MatchCase::MATCH_GROUP},        {"a.b", MatchCase::MATCH_GROUP},
                {"a.b.c.d.e", MatchCase::MATCH_CASE}, {"a.b.c.d.g", MatchCase::MATCH_CASE},
                {"x.y", MatchCase::MATCH_GROUP},      {"x.y.z", MatchCase::MATCH_CASE},
                {"a.b.c.f", MatchCase::MATCH_CASE},   {"a.b.c.x", MatchCase::MATCH_CASE},
            };
            addChild(
                new CaseListParserCase(m_testCtx, "partial_prefix", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }
        {
            static const char *const caseList = "a.a.c.d\n"
                                                "a.b.c.d\n";
            static const MatchCase subCases[] = {
                {"a", MatchCase::MATCH_GROUP},
                {"a.a", MatchCase::MATCH_GROUP},
                {"a.b.c.d", MatchCase::MATCH_CASE},
                {"a.b.c.d", MatchCase::MATCH_CASE},
            };
            addChild(
                new CaseListParserCase(m_testCtx, "reparenting", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
        }

        // Negative tests
        addChild(new NegativeCaseListCase(m_testCtx, "empty_string", ""));
        addChild(new NegativeCaseListCase(m_testCtx, "empty_line", "\n"));
        addChild(new NegativeCaseListCase(m_testCtx, "empty_group_name", ".test"));
        addChild(new NegativeCaseListCase(m_testCtx, "empty_case_name", "test."));
    }
};

class CaseListParserTests : public tcu::TestCaseGroup
{
public:
    CaseListParserTests(tcu::TestContext &testCtx)
        : tcu::TestCaseGroup(testCtx, "case_list_parser", "Test case list parser tests")
    {
    }

    void init(void)
    {
        addChild(new TrieParserTests(m_testCtx));
        addChild(new ListParserTests(m_testCtx));
    }
};

inline uint32_t ulpDiff(float a, float b)
{
    const uint32_t ab = tcu::Float32(a).bits();
    const uint32_t bb = tcu::Float32(b).bits();
    return de::max(ab, bb) - de::min(ab, bb);
}

template <int Size>
inline tcu::Vector<uint32_t, Size> ulpDiff(const tcu::Vector<float, Size> &a, const tcu::Vector<float, Size> &b)
{
    tcu::Vector<uint32_t, Size> res;
    for (int ndx = 0; ndx < Size; ndx++)
        res[ndx] = ulpDiff(a[ndx], b[ndx]);
    return res;
}

class ConstantInterpolationTest : public tcu::TestCase
{
public:
    ConstantInterpolationTest(tcu::TestContext &testCtx)
        : tcu::TestCase(testCtx, "const_interpolation", "Constant value interpolation")
    {
        const int supportedMsaaLevels[] = {1, 2, 4, 8, 16};

        for (int msaaNdx = 0; msaaNdx < DE_LENGTH_OF_ARRAY(supportedMsaaLevels); msaaNdx++)
        {
            const int numSamples = supportedMsaaLevels[msaaNdx];
            {
                SubCase c;
                c.rtSize  = tcu::IVec3(128, 128, numSamples);
                c.vtx[0]  = tcu::Vec4(-1.0f, -1.0f, 0.5f, 1.0f);
                c.vtx[1]  = tcu::Vec4(-1.0f, +1.0f, 0.5f, 1.0f);
                c.vtx[2]  = tcu::Vec4(+1.0f, -1.0f, 0.5f, 1.0f);
                c.varying = tcu::Vec4(0.0f, 1.0f, 8.0f, -8.0f);
                m_cases.push_back(c);
            }

            {
                SubCase c;
                c.rtSize  = tcu::IVec3(128, 128, numSamples);
                c.vtx[0]  = tcu::Vec4(-1.0f, +1.0f, 0.5f, 1.0f);
                c.vtx[1]  = tcu::Vec4(+1.0f, -1.0f, 0.5f, 1.0f);
                c.vtx[2]  = tcu::Vec4(+1.0f, +1.0f, 0.5f, 1.0f);
                c.varying = tcu::Vec4(0.0f, 1.0f, 8.0f, -8.0f);
                m_cases.push_back(c);
            }
            {
                SubCase c;
                c.rtSize  = tcu::IVec3(129, 113, numSamples);
                c.vtx[0]  = tcu::Vec4(-1.0f, -1.0f, 0.5f, 1.0f);
                c.vtx[1]  = tcu::Vec4(-1.0f, +1.0f, 0.5f, 1.0f);
                c.vtx[2]  = tcu::Vec4(+1.0f, -1.0f, 0.5f, 1.0f);
                c.varying = tcu::Vec4(0.0f, 1.0f, 8.0f, -8.0f);
                m_cases.push_back(c);
            }
            {
                SubCase c;
                c.rtSize  = tcu::IVec3(107, 131, numSamples);
                c.vtx[0]  = tcu::Vec4(-1.0f, +1.0f, 0.5f, 1.0f);
                c.vtx[1]  = tcu::Vec4(+1.0f, -1.0f, 0.5f, 1.0f);
                c.vtx[2]  = tcu::Vec4(+1.0f, +1.0f, 0.5f, 1.0f);
                c.varying = tcu::Vec4(0.0f, 1.0f, 8.0f, -8.0f);
                m_cases.push_back(c);
            }
        }

        {
            de::Random rnd(0x89423f);
            for (int ndx = 0; ndx < 25; ndx++)
            {
                const float depth = rnd.getFloat() * 2.0f - 1.0f;
                SubCase c;

                c.rtSize.x() = rnd.getInt(16, 256);
                c.rtSize.y() = rnd.getInt(16, 256);
                c.rtSize.z() = rnd.choose<int>(DE_ARRAY_BEGIN(supportedMsaaLevels), DE_ARRAY_END(supportedMsaaLevels));

                for (int vtxNdx = 0; vtxNdx < DE_LENGTH_OF_ARRAY(c.vtx); vtxNdx++)
                {
                    c.vtx[vtxNdx].x() = rnd.getFloat() * 2.0f - 1.0f;
                    c.vtx[vtxNdx].y() = rnd.getFloat() * 2.0f - 1.0f;
                    c.vtx[vtxNdx].z() = depth;
                    c.vtx[vtxNdx].w() = 1.0f;
                }

                for (int compNdx = 0; compNdx < 4; compNdx++)
                {
                    float v;
                    do
                    {
                        v = tcu::Float32(rnd.getUint32()).asFloat();
                    } while (deFloatIsInf(v) || deFloatIsNaN(v));
                    c.varying[compNdx] = v;
                }
                m_cases.push_back(c);
            }
        }
    }

    void init(void)
    {
        m_caseIter = m_cases.begin();
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "All iterations passed");
    }

    IterateResult iterate(void)
    {
        {
            tcu::ScopedLogSection section(m_testCtx.getLog(), "SubCase", "");
            runCase(*m_caseIter);
        }
        return (++m_caseIter != m_cases.end()) ? CONTINUE : STOP;
    }

protected:
    struct SubCase
    {
        tcu::IVec3 rtSize; // (width, height, samples)
        tcu::Vec4 vtx[3];
        tcu::Vec4 varying;
    };

    void runCase(const SubCase &subCase)
    {
        using namespace tcu;

        const uint32_t maxColorUlpDiff = 2;
        const uint32_t maxDepthUlpDiff = 0;

        const int width      = subCase.rtSize.x();
        const int height     = subCase.rtSize.y();
        const int numSamples = subCase.rtSize.z();
        const float zn       = 0.0f;
        const float zf       = 1.0f;

        TextureLevel interpolated(TextureFormat(TextureFormat::RGBA, TextureFormat::FLOAT), numSamples, width, height);
        TextureLevel depthStencil(TextureFormat(TextureFormat::DS, TextureFormat::FLOAT_UNSIGNED_INT_24_8_REV),
                                  numSamples, width, height);

        m_testCtx.getLog() << TestLog::Message << "RT size (w, h, #samples) = " << subCase.rtSize << "\n"
                           << "vtx[0] = " << subCase.vtx[0] << "\n"
                           << "vtx[1] = " << subCase.vtx[1] << "\n"
                           << "vtx[2] = " << subCase.vtx[2] << "\n"
                           << "color = " << subCase.varying << TestLog::EndMessage;

        clear(interpolated.getAccess(), subCase.varying - Vec4(0.0f, 0.0f, 0.0f, 1.0f));
        clearDepth(depthStencil.getAccess(), 0.0f);
        clearStencil(depthStencil.getAccess(), 0);

        {
            class VtxShader : public rr::VertexShader
            {
            public:
                VtxShader(void) : rr::VertexShader(2, 1)
                {
                    m_inputs[0].type  = rr::GENERICVECTYPE_FLOAT;
                    m_inputs[1].type  = rr::GENERICVECTYPE_FLOAT;
                    m_outputs[0].type = rr::GENERICVECTYPE_FLOAT;
                }

                void shadeVertices(const rr::VertexAttrib *inputs, rr::VertexPacket *const *packets,
                                   const int numPackets) const
                {
                    for (int packetNdx = 0; packetNdx < numPackets; packetNdx++)
                    {
                        rr::readVertexAttrib(packets[packetNdx]->position, inputs[0], packets[packetNdx]->instanceNdx,
                                             packets[packetNdx]->vertexNdx);
                        packets[packetNdx]->outputs[0] = rr::readVertexAttribFloat(
                            inputs[1], packets[packetNdx]->instanceNdx, packets[packetNdx]->vertexNdx);
                    }
                }
            } vtxShader;

            class FragShader : public rr::FragmentShader
            {
            public:
                FragShader(void) : rr::FragmentShader(1, 1)
                {
                    m_inputs[0].type  = rr::GENERICVECTYPE_FLOAT;
                    m_outputs[0].type = rr::GENERICVECTYPE_FLOAT;
                }

                void shadeFragments(rr::FragmentPacket *packets, const int numPackets,
                                    const rr::FragmentShadingContext &context) const
                {
                    for (int packetNdx = 0; packetNdx < numPackets; packetNdx++)
                    {
                        for (int fragNdx = 0; fragNdx < rr::NUM_FRAGMENTS_PER_PACKET; fragNdx++)
                        {
                            const tcu::Vec4 interp =
                                rr::readTriangleVarying<float>(packets[packetNdx], context, 0, fragNdx);
                            rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, interp);
                        }
                    }
                }
            } fragShader;

            const rr::Program program(&vtxShader, &fragShader);

            const rr::MultisamplePixelBufferAccess colorAccess =
                rr::MultisamplePixelBufferAccess::fromMultisampleAccess(interpolated.getAccess());
            const rr::MultisamplePixelBufferAccess dsAccess =
                rr::MultisamplePixelBufferAccess::fromMultisampleAccess(depthStencil.getAccess());
            const rr::RenderTarget renderTarget(colorAccess, dsAccess, dsAccess);
            const rr::VertexAttrib vertexAttribs[] = {
                rr::VertexAttrib(rr::VERTEXATTRIBTYPE_FLOAT, 4, 0, 0, subCase.vtx), rr::VertexAttrib(subCase.varying)};
            rr::ViewportState viewport(colorAccess);
            rr::RenderState state(viewport, rr::RenderState::DEFAULT_SUBPIXEL_BITS);
            const rr::PrimitiveList primitives(rr::PRIMITIVETYPE_TRIANGLES, 3, 0);
            const rr::DrawCommand drawCmd(state, renderTarget, program, DE_LENGTH_OF_ARRAY(vertexAttribs),
                                          vertexAttribs, primitives);
            const rr::Renderer renderer;

            viewport.zn = zn;
            viewport.zf = zf;

            state.fragOps.depthTestEnabled                        = true;
            state.fragOps.depthFunc                               = rr::TESTFUNC_ALWAYS;
            state.fragOps.stencilTestEnabled                      = true;
            state.fragOps.stencilStates[rr::FACETYPE_BACK].func   = rr::TESTFUNC_ALWAYS;
            state.fragOps.stencilStates[rr::FACETYPE_BACK].dpPass = rr::STENCILOP_INCR;
            state.fragOps.stencilStates[rr::FACETYPE_FRONT]       = state.fragOps.stencilStates[rr::FACETYPE_BACK];

            renderer.draw(drawCmd);
        }

        // Verify interpolated values
        {
            TextureLevel resolvedColor(interpolated.getFormat(), width, height);        // For debugging
            TextureLevel resolvedDepthStencil(depthStencil.getFormat(), width, height); // For debugging
            TextureLevel errorMask(TextureFormat(TextureFormat::RGB, TextureFormat::UNORM_INT8), width, height);
            const ConstPixelBufferAccess interpAccess = interpolated.getAccess();
            const ConstPixelBufferAccess dsAccess     = depthStencil.getAccess();
            const PixelBufferAccess errorAccess       = errorMask.getAccess();
            int numCoveredSamples                     = 0;
            int numFailedColorSamples                 = 0;
            int numFailedDepthSamples                 = 0;
            const bool verifyDepth =
                (subCase.vtx[0].z() == subCase.vtx[1].z()) && (subCase.vtx[1].z() == subCase.vtx[2].z());
            const float refDepth = subCase.vtx[0].z() * (zf - zn) / 2.0f + (zn + zf) / 2.0f;

            rr::resolveMultisampleBuffer(
                resolvedColor.getAccess(),
                rr::MultisampleConstPixelBufferAccess::fromMultisampleAccess(interpolated.getAccess()));
            rr::resolveMultisampleBuffer(
                resolvedDepthStencil.getAccess(),
                rr::MultisampleConstPixelBufferAccess::fromMultisampleAccess(depthStencil.getAccess()));
            clear(errorAccess, Vec4(0.0f, 1.0f, 0.0f, 1.0f));

            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    for (int sampleNdx = 0; sampleNdx < numSamples; sampleNdx++)
                    {
                        if (dsAccess.getPixStencil(sampleNdx, x, y) != 0)
                        {
                            const Vec4 color      = interpAccess.getPixel(sampleNdx, x, y);
                            const UVec4 colorDiff = ulpDiff(color, subCase.varying);
                            const bool colorOk    = boolAll(lessThanEqual(colorDiff, tcu::UVec4(maxColorUlpDiff)));

                            const float depth        = dsAccess.getPixDepth(sampleNdx, x, y);
                            const uint32_t depthDiff = ulpDiff(depth, refDepth);
                            const bool depthOk       = verifyDepth && (depthDiff <= maxDepthUlpDiff);

                            const int maxMsgs = 10;

                            numCoveredSamples += 1;

                            if (!colorOk)
                            {
                                numFailedColorSamples += 1;

                                if (numFailedColorSamples <= maxMsgs)
                                    m_testCtx.getLog() << TestLog::Message << "FAIL: " << tcu::IVec3(x, y, sampleNdx)
                                                       << " color ulp diff = " << colorDiff << TestLog::EndMessage;
                            }

                            if (!depthOk)
                                numFailedDepthSamples += 1;

                            if (!colorOk || !depthOk)
                                errorAccess.setPixel(errorAccess.getPixel(x, y) +
                                                         Vec4(1.0f, -1.0f, 0.0f, 0.0f) / float(numSamples - 1),
                                                     x, y);
                        }
                    }
                }
            }

            m_testCtx.getLog() << TestLog::Image("ResolvedColor", "Resolved colorbuffer", resolvedColor)
                               << TestLog::Image("ResolvedDepthStencil", "Resolved depth- & stencilbuffer",
                                                 resolvedDepthStencil);

            if (numFailedColorSamples != 0 || numFailedDepthSamples != 0)
            {
                m_testCtx.getLog() << TestLog::Image("ErrorMask", "Error mask", errorMask);

                if (numFailedColorSamples != 0)
                    m_testCtx.getLog() << TestLog::Message << "FAIL: Found " << numFailedColorSamples
                                       << " invalid color samples!" << TestLog::EndMessage;

                if (numFailedDepthSamples != 0)
                    m_testCtx.getLog() << TestLog::Message << "FAIL: Found " << numFailedDepthSamples
                                       << " invalid depth samples!" << TestLog::EndMessage;

                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid samples found");
            }

            m_testCtx.getLog() << TestLog::Message << (numCoveredSamples - numFailedColorSamples) << " / "
                               << numCoveredSamples << " color samples passed" << TestLog::EndMessage;
            m_testCtx.getLog() << TestLog::Message << (numCoveredSamples - numFailedDepthSamples) << " / "
                               << numCoveredSamples << " depth samples passed" << TestLog::EndMessage;
        }
    }

    vector<SubCase> m_cases;
    vector<SubCase>::const_iterator m_caseIter;
};

class CommonFrameworkTests : public tcu::TestCaseGroup
{
public:
    CommonFrameworkTests(tcu::TestContext &testCtx)
        : tcu::TestCaseGroup(testCtx, "common", "Tests for the common utility framework")
    {
    }

    void init(void)
    {
        addChild(
            new SelfCheckCase(m_testCtx, "float_format", "tcu::FloatFormat_selfTest()", tcu::FloatFormat_selfTest));
        addChild(new SelfCheckCase(m_testCtx, "either", "tcu::Either_selfTest()", tcu::Either_selfTest));
    }
};

class ReferenceRendererTests : public tcu::TestCaseGroup
{
public:
    ReferenceRendererTests(tcu::TestContext &testCtx)
        : tcu::TestCaseGroup(testCtx, "reference_renderer", "Reference renderer tests")
    {
    }

    void init(void)
    {
        addChild(new ConstantInterpolationTest(m_testCtx));
    }
};

} // namespace

FrameworkTests::FrameworkTests(tcu::TestContext &testCtx)
    : tcu::TestCaseGroup(testCtx, "framework", "Miscellaneous framework tests")
{
}

FrameworkTests::~FrameworkTests(void)
{
}

void FrameworkTests::init(void)
{
    addChild(new CommonFrameworkTests(m_testCtx));
    addChild(new CaseListParserTests(m_testCtx));
    addChild(new ReferenceRendererTests(m_testCtx));
    addChild(createTextureFormatTests(m_testCtx));
    addChild(createAstcTests(m_testCtx));
    addChild(createVulkanTests(m_testCtx));
}

} // namespace dit
