/*
 * Copyright (C) 2011 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.
 *
 */

/*
 * Hardware Composer Commit Points
 *
 * Synopsis
 *   hwcCommit [options] graphicFormat ...
 *     options:
 *       -s [width, height] - Starting dimension
 *       -v - Verbose
 *
 *      graphic formats:
 *        RGBA8888 (reference frame default)
 *        RGBX8888
 *        RGB888
 *        RGB565
 *        BGRA8888
 *        RGBA5551
 *        RGBA4444
 *        YV12
 *
 * Description
 *   The Hardware Composer (HWC) Commit test is a benchmark that
 *   discovers the points at which the HWC will commit to rendering an
 *   overlay(s).  Before rendering a set of overlays, the HWC is shown
 *   the list through a prepare call.  During the prepare call the HWC
 *   is able to examine the list and specify which overlays it is able
 *   to handle.  The overlays that it can't handle are typically composited
 *   by a higher level (e.g. Surface Flinger) and then the original list
 *   plus a composit of what HWC passed on are provided back to the HWC
 *   for rendering.
 *
 *   Once an implementation of the HWC has been shipped, a regression would
 *   likely occur if a latter implementation started passing on conditions
 *   that it used to commit to.  The primary purpose of this benchmark
 *   is the automated discovery of the commit points, where an implementation
 *   is on the edge between committing and not committing.  These are commonly
 *   referred to as commit points.  Between implementations changes to the
 *   commit points are allowed, as long as they improve what the HWC commits
 *   to.  Once an implementation of the HWC is shipped, the commit points are
 *   not allowed to regress in future implementations.
 *
 *   This benchmark takes a sampling and then adjusts until it finds a
 *   commit point.  It doesn't exhaustively check all possible conditions,
 *   which do to the number of combinations would be impossible.  Instead
 *   it starts its search from a starting dimension, that can be changed
 *   via the -s option.  The search is also bounded by a set of search
 *   limits, that are hard-coded into a structure of constants named
 *   searchLimits.  Results that happen to reach a searchLimit are prefixed
 *   with >=, so that it is known that the value could possibly be larger.
 *
 *   Measurements are made for each of the graphic formats specified as
 *   positional parameters on the command-line.  If no graphic formats
 *   are specified on the command line, then by default measurements are
 *   made and reported for each of the known graphic format.
 */

#include <algorithm>
#include <assert.h>
#include <cerrno>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <iomanip>
#include <istream>
#include <libgen.h>
#include <list>
#include <sched.h>
#include <sstream>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <vector>

#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>

#include <ui/GraphicBuffer.h>

#define LOG_TAG "hwcCommitTest"
#include <utils/Log.h>
#include <testUtil.h>

#include <hardware/hwcomposer.h>

#include <glTestLib.h>
#include "hwcTestLib.h"

using namespace std;
using namespace android;

// Defaults
const HwcTestDim defaultStartDim = HwcTestDim(100, 100);
const bool defaultVerbose = false;

const uint32_t   defaultFormat = HAL_PIXEL_FORMAT_RGBA_8888;
const int32_t    defaultTransform = 0;
const uint32_t   defaultBlend = HWC_BLENDING_NONE;
const ColorFract defaultColor(0.5, 0.5, 0.5);
const float      defaultAlpha = 1.0; // Opaque
const HwcTestDim defaultSourceDim(1, 1);
const struct hwc_rect defaultSourceCrop = {0, 0, 1, 1};
const struct hwc_rect defaultDisplayFrame = {0, 0, 100, 100};

// Global Constants
const uint32_t printFieldWidth = 2;
const struct searchLimits {
    uint32_t   numOverlays;
    HwcTestDim sourceCrop;
} searchLimits = {
    10,
    HwcTestDim(3000, 2000),
};
const struct transformType {
    const char *desc;
    uint32_t id;
} transformType[] = {
    {"fliph",  HWC_TRANSFORM_FLIP_H},
    {"flipv",  HWC_TRANSFORM_FLIP_V},
    {"rot90",  HWC_TRANSFORM_ROT_90},
    {"rot180", HWC_TRANSFORM_ROT_180},
    {"rot270", HWC_TRANSFORM_ROT_270},
};
const struct blendType {
    const char *desc;
    uint32_t id;
} blendType[] = {
    {"none", HWC_BLENDING_NONE},
    {"premult", HWC_BLENDING_PREMULT},
    {"coverage", HWC_BLENDING_COVERAGE},
};

// Defines
#define MAXCMD               200
#define CMD_STOP_FRAMEWORK   "stop 2>&1"
#define CMD_START_FRAMEWORK  "start 2>&1"

// Macros
#define NUMA(a) (sizeof(a) / sizeof(a [0])) // Num elements in an array

// Local types
class Rectangle {
public:
    Rectangle(uint32_t graphicFormat = defaultFormat,
              HwcTestDim dfDim = HwcTestDim(1, 1),
              HwcTestDim sDim = HwcTestDim(1, 1));
    void setSourceDim(HwcTestDim dim);

    uint32_t     format;
    uint32_t     transform;
    int32_t      blend;
    ColorFract   color;
    float        alpha;
    HwcTestDim   sourceDim;
    struct hwc_rect   sourceCrop;
    struct hwc_rect   displayFrame;
};

class Range {
public:
    Range(void) : _l(0), _u(0) {}
    Range(uint32_t lower, uint32_t upper) : _l(lower), _u(upper) {}
    uint32_t lower(void) { return _l; }
    uint32_t upper(void) { return _u; }

    operator string();

private:
    uint32_t _l; // lower
    uint32_t _u; // upper
};

Range::operator string()
{
    ostringstream out;

    out << '[' << _l << ", " << _u << ']';

    return out.str();
}

class Rational {
public:
    Rational(void) : _n(0), _d(1) {}
    Rational(uint32_t n, uint32_t d) : _n(n), _d(d) {}
    uint32_t numerator(void) { return _n; }
    uint32_t denominator(void) { return _d; }
    void setNumerator(uint32_t numerator) { _n = numerator; }

    bool operator==(const Rational& other) const;
    bool operator!=(const Rational& other) const { return !(*this == other); }
    bool operator<(const Rational& other) const;
    bool operator>(const Rational& other) const {
        return (!(*this == other) && !(*this < other));
    }
    static void double2Rational(double f, Range nRange, Range dRange,
                               Rational& lower, Rational& upper);
        
    operator string() const;
    operator double() const { return (double) _n / (double) _d; }


private:
    uint32_t _n;
    uint32_t _d;
};

// Globals
static const int texUsage = GraphicBuffer::USAGE_HW_TEXTURE |
        GraphicBuffer::USAGE_SW_WRITE_RARELY;
static hwc_composer_device_1_t *hwcDevice;
static EGLDisplay dpy;
static EGLSurface surface;
static EGLint width, height;
static size_t maxHeadingLen;
static vector<string> formats;

// Measurements
struct meas {
    uint32_t format;
    uint32_t startDimOverlays;
    uint32_t maxNonOverlapping;
    uint32_t maxOverlapping;
    list<uint32_t> transforms;
    list<uint32_t> blends;
    struct displayFrame {
        uint32_t minWidth;
        uint32_t minHeight;
        HwcTestDim minDim;
        uint32_t maxWidth;
        uint32_t maxHeight;
        HwcTestDim maxDim;
    } df;
    struct sourceCrop {
        uint32_t minWidth;
        uint32_t minHeight;
        HwcTestDim minDim;
        uint32_t maxWidth;
        uint32_t maxHeight;
        HwcTestDim maxDim;
        Rational hScale;
        HwcTestDim hScaleBestDf;
        HwcTestDim hScaleBestSc;
        Rational vScale;
        HwcTestDim vScaleBestDf;
        HwcTestDim vScaleBestSc;
    } sc;
    vector<uint32_t> overlapBlendNone;
    vector<uint32_t> overlapBlendPremult;
    vector<uint32_t> overlapBlendCoverage;
};
vector<meas> measurements;

// Function prototypes
uint32_t numOverlays(list<Rectangle>& rectList);
uint32_t maxOverlays(uint32_t format, bool allowOverlap);
list<uint32_t> supportedTransforms(uint32_t format);
list<uint32_t> supportedBlends(uint32_t format);
uint32_t dfMinWidth(uint32_t format);
uint32_t dfMinHeight(uint32_t format);
uint32_t dfMaxWidth(uint32_t format);
uint32_t dfMaxHeight(uint32_t format);
HwcTestDim dfMinDim(uint32_t format);
HwcTestDim dfMaxDim(uint32_t format);
uint32_t scMinWidth(uint32_t format, const HwcTestDim& dfDim);
uint32_t scMinHeight(uint32_t format, const HwcTestDim& dfDim);
uint32_t scMaxWidth(uint32_t format, const HwcTestDim& dfDim);
uint32_t scMaxHeight(uint32_t format, const HwcTestDim& dfDim);
HwcTestDim scMinDim(uint32_t format, const HwcTestDim& dfDim);
HwcTestDim scMaxDim(uint32_t format, const HwcTestDim& dfDim);
Rational scHScale(uint32_t format,
                  const HwcTestDim& dfMin, const HwcTestDim& dfMax,
                  const HwcTestDim& scMin, const HwcTestDim& scMax,
                  HwcTestDim& outBestDf, HwcTestDim& outBestSc);
Rational scVScale(uint32_t format,
                  const HwcTestDim& dfMin, const HwcTestDim& dfMax,
                  const HwcTestDim& scMin, const HwcTestDim& scMax,
                  HwcTestDim& outBestDf, HwcTestDim& outBestSc);
uint32_t numOverlapping(uint32_t backgroundFormat, uint32_t foregroundFormat,
                        uint32_t backgroundBlend, uint32_t foregroundBlend);
string transformList2str(const list<uint32_t>& transformList);
string blendList2str(const list<uint32_t>& blendList);
void init(void);
void printFormatHeadings(size_t indent);
void printOverlapLine(size_t indent, const string formatStr,
                      const vector<uint32_t>& results);
void printSyntax(const char *cmd);

// Command-line option settings
static bool verbose = defaultVerbose;
static HwcTestDim startDim = defaultStartDim;

/*
 * Main
 *
 * Performs the following high-level sequence of operations:
 *
 *   1. Command-line parsing
 *
 *   2. Form a list of command-line specified graphic formats.  If
 *      no formats are specified, then form a list of all known formats.
 *
 *   3. Stop framework
 *      Only one user at a time is allowed to use the HWC.  Surface
 *      Flinger uses the HWC and is part of the framework.  Need to
 *      stop the framework so that Surface Flinger will stop using
 *      the HWC.
 *   
 *   4. Initialization
 *
 *   5. For each graphic format in the previously formed list perform
 *      measurements on that format and report the results.
 *
 *   6. Start framework
 */
int
main(int argc, char *argv[])
{
    int     rv, opt;
    bool    error;
    string  str;
    char cmd[MAXCMD];
    list<Rectangle> rectList;

    testSetLogCatTag(LOG_TAG);

    // Parse command line arguments
    while ((opt = getopt(argc, argv, "s:v?h")) != -1) {
        switch (opt) {

          case 's': // Start Dimension
            // Use arguments until next starts with a dash
            // or current ends with a > or ]
            str = optarg;
            while (optind < argc) {
                if (*argv[optind] == '-') { break; }
                char endChar = (str.length() > 1) ? str[str.length() - 1] : 0;
                if ((endChar == '>') || (endChar == ']')) { break; }
                str += " " + string(argv[optind++]);
            }
            {
                istringstream in(str);
                startDim = hwcTestParseDim(in, error);
                // Any parse error or characters not used by parser
                if (error
                    || (((unsigned int) in.tellg() != in.str().length())
                        && (in.tellg() != (streampos) -1))) {
                    testPrintE("Invalid command-line specified start "
                               "dimension of: %s", str.c_str());
                    exit(8);
                }
            }
            break;

          case 'v': // Verbose
            verbose = true;
            break;

          case 'h': // Help
          case '?':
          default:
            printSyntax(basename(argv[0]));
            exit(((optopt == 0) || (optopt == '?')) ? 0 : 11);
        }
    }

    // Positional parameters
    // Positional parameters provide the names of graphic formats that
    // measurements are to be made on.  Measurements are made on all
    // known graphic formats when no positional parameters are provided.
    if (optind == argc) {
        // No command-line specified graphic formats
        // Add all graphic formats to the list of formats to be measured
        for (unsigned int n1 = 0; n1 < NUMA(hwcTestGraphicFormat); n1++) {
            formats.push_back(hwcTestGraphicFormat[n1].desc);
        }
    } else {
        // Add names of command-line specified graphic formats to the
        // list of formats to be tested
        for (; argv[optind] != NULL; optind++) {
            formats.push_back(argv[optind]);
        }
    }

    // Determine length of longest specified graphic format.
    // This value is used for output formating
    for (vector<string>::iterator it = formats.begin();
         it != formats.end(); ++it) {
         maxHeadingLen = max(maxHeadingLen, it->length());
    }

    // Stop framework
    rv = snprintf(cmd, sizeof(cmd), "%s", CMD_STOP_FRAMEWORK);
    if (rv >= (signed) sizeof(cmd) - 1) {
        testPrintE("Command too long for: %s", CMD_STOP_FRAMEWORK);
        exit(14);
    }
    testExecCmd(cmd);
    testDelay(1.0); // TODO - needs means to query whether asynchronous stop
                    // framework operation has completed.  For now, just wait
                    // a long time.

    testPrintI("startDim: %s", ((string) startDim).c_str());

    init();

    // For each of the graphic formats
    for (vector<string>::iterator itFormat = formats.begin();
         itFormat != formats.end(); ++itFormat) {

        // Locate hwcTestLib structure that describes this format
        const struct hwcTestGraphicFormat *format;
        format = hwcTestGraphicFormatLookup((*itFormat).c_str());
        if (format == NULL) {
            testPrintE("Unknown graphic format of: %s", (*itFormat).c_str());
            exit(1);
        }

        // Display format header
        testPrintI("format: %s", format->desc);

        // Create area to hold the measurements
        struct meas meas;
        struct meas *measPtr;
        meas.format = format->format;
        measurements.push_back(meas);
        measPtr = &measurements[measurements.size() - 1];

        // Start dimension num overlays
        Rectangle rect(format->format, startDim);
        rectList.clear();
        rectList.push_back(rect);
        measPtr->startDimOverlays = numOverlays(rectList);
        testPrintI("  startDimOverlays: %u", measPtr->startDimOverlays);

        // Skip the rest of the measurements, when the start dimension
        // doesn't produce an overlay
        if (measPtr->startDimOverlays == 0) { continue; }

        // Max Overlays
        measPtr->maxNonOverlapping = maxOverlays(format->format, false);
        testPrintI("  max nonOverlapping overlays: %s%u",
                   (measPtr->maxNonOverlapping == searchLimits.numOverlays)
                       ? ">= " : "",
                   measPtr->maxNonOverlapping);
        measPtr->maxOverlapping = maxOverlays(format->format, true);
        testPrintI("  max Overlapping overlays: %s%u",
                   (measPtr->maxOverlapping == searchLimits.numOverlays)
                       ? ">= " : "",
                   measPtr->maxOverlapping);

        // Transforms and blends
        measPtr->transforms = supportedTransforms(format->format);
        testPrintI("  transforms: %s",
                   transformList2str(measPtr->transforms).c_str());
        measPtr->blends = supportedBlends(format->format);
        testPrintI("  blends: %s",
                   blendList2str(measPtr->blends).c_str());

        // Display frame measurements
        measPtr->df.minWidth = dfMinWidth(format->format);
        testPrintI("  dfMinWidth: %u", measPtr->df.minWidth);

        measPtr->df.minHeight = dfMinHeight(format->format);
        testPrintI("  dfMinHeight: %u", measPtr->df.minHeight);

        measPtr->df.maxWidth = dfMaxWidth(format->format);
        testPrintI("  dfMaxWidth: %u", measPtr->df.maxWidth);

        measPtr->df.maxHeight = dfMaxHeight(format->format);
        testPrintI("  dfMaxHeight: %u", measPtr->df.maxHeight);

        measPtr->df.minDim = dfMinDim(format->format);
        testPrintI("  dfMinDim: %s", ((string) measPtr->df.minDim).c_str());

        measPtr->df.maxDim = dfMaxDim(format->format);
        testPrintI("  dfMaxDim: %s", ((string) measPtr->df.maxDim).c_str());

        // Source crop measurements
        measPtr->sc.minWidth = scMinWidth(format->format, measPtr->df.minDim);
        testPrintI("  scMinWidth: %u", measPtr->sc.minWidth);

        measPtr->sc.minHeight = scMinHeight(format->format, measPtr->df.minDim);
        testPrintI("  scMinHeight: %u", measPtr->sc.minHeight);

        measPtr->sc.maxWidth = scMaxWidth(format->format, measPtr->df.maxDim);
        testPrintI("  scMaxWidth: %s%u", (measPtr->sc.maxWidth
                   == searchLimits.sourceCrop.width()) ? ">= " : "",
                   measPtr->sc.maxWidth);

        measPtr->sc.maxHeight = scMaxHeight(format->format, measPtr->df.maxDim);
        testPrintI("  scMaxHeight: %s%u", (measPtr->sc.maxHeight
                   == searchLimits.sourceCrop.height()) ? ">= " : "",
                   measPtr->sc.maxHeight);

        measPtr->sc.minDim = scMinDim(format->format, measPtr->df.minDim);
        testPrintI("  scMinDim: %s", ((string) measPtr->sc.minDim).c_str());

        measPtr->sc.maxDim = scMaxDim(format->format, measPtr->df.maxDim);
        testPrintI("  scMaxDim: %s%s", ((measPtr->sc.maxDim.width()
                         >= searchLimits.sourceCrop.width())
                         || (measPtr->sc.maxDim.width() >=
                         searchLimits.sourceCrop.height())) ? ">= " : "",
                   ((string) measPtr->sc.maxDim).c_str());

        measPtr->sc.hScale = scHScale(format->format,
                                      measPtr->df.minDim, measPtr->df.maxDim,
                                      measPtr->sc.minDim, measPtr->sc.maxDim,
                                      measPtr->sc.hScaleBestDf,
                                      measPtr->sc.hScaleBestSc);
        testPrintI("  scHScale: %s%f",
                   (measPtr->sc.hScale
                       >= Rational(searchLimits.sourceCrop.width(),
                                   measPtr->df.minDim.width())) ? ">= " : "",
                   (double) measPtr->sc.hScale);
        testPrintI("    HScale Best Display Frame: %s",
                   ((string) measPtr->sc.hScaleBestDf).c_str());
        testPrintI("    HScale Best Source Crop: %s",
                   ((string) measPtr->sc.hScaleBestSc).c_str());

        measPtr->sc.vScale = scVScale(format->format,
                                      measPtr->df.minDim, measPtr->df.maxDim,
                                      measPtr->sc.minDim, measPtr->sc.maxDim,
                                      measPtr->sc.vScaleBestDf,
                                      measPtr->sc.vScaleBestSc);
        testPrintI("  scVScale: %s%f",
                   (measPtr->sc.vScale
                       >= Rational(searchLimits.sourceCrop.height(),
                                   measPtr->df.minDim.height())) ? ">= " : "",
                   (double) measPtr->sc.vScale);
        testPrintI("    VScale Best Display Frame: %s",
                   ((string) measPtr->sc.vScaleBestDf).c_str());
        testPrintI("    VScale Best Source Crop: %s",
                   ((string) measPtr->sc.vScaleBestSc).c_str());

        // Overlap two graphic formats and different blends
        // Results displayed after all overlap measurments with
        // current format in the foreground
        // TODO: make measurments with background blend other than
        //       none.  All of these measurements are done with a
        //       background blend of HWC_BLENDING_NONE, with the
        //       blend type of the foregound being varied.
        uint32_t foregroundFormat = format->format;
        for (vector<string>::iterator it = formats.begin();
             it != formats.end(); ++it) {
            uint32_t num;

            const struct hwcTestGraphicFormat *backgroundFormatPtr
                = hwcTestGraphicFormatLookup((*it).c_str());
            uint32_t backgroundFormat = backgroundFormatPtr->format;

            num = numOverlapping(backgroundFormat, foregroundFormat,
                                 HWC_BLENDING_NONE, HWC_BLENDING_NONE);
            measPtr->overlapBlendNone.push_back(num);

            num = numOverlapping(backgroundFormat, foregroundFormat,
                                 HWC_BLENDING_NONE, HWC_BLENDING_PREMULT);
            measPtr->overlapBlendPremult.push_back(num);

            num = numOverlapping(backgroundFormat, foregroundFormat,
                                 HWC_BLENDING_NONE, HWC_BLENDING_COVERAGE);
            measPtr->overlapBlendCoverage.push_back(num);
        }

    }

    // Display overlap results
    size_t indent = 2;
    testPrintI("overlapping blend: none");
    printFormatHeadings(indent);
    for (vector<string>::iterator it = formats.begin();
         it != formats.end(); ++it) {
        printOverlapLine(indent, *it, measurements[it
                         - formats.begin()].overlapBlendNone);
    }
    testPrintI("");

    testPrintI("overlapping blend: premult");
    printFormatHeadings(indent);
    for (vector<string>::iterator it = formats.begin();
         it != formats.end(); ++it) {
        printOverlapLine(indent, *it, measurements[it
                         - formats.begin()].overlapBlendPremult);
    }
    testPrintI("");

    testPrintI("overlapping blend: coverage");
    printFormatHeadings(indent);
    for (vector<string>::iterator it = formats.begin();
         it != formats.end(); ++it) {
        printOverlapLine(indent, *it, measurements[it
                         - formats.begin()].overlapBlendCoverage);
    }
    testPrintI("");

    // Start framework
    rv = snprintf(cmd, sizeof(cmd), "%s", CMD_START_FRAMEWORK);
    if (rv >= (signed) sizeof(cmd) - 1) {
        testPrintE("Command too long for: %s", CMD_START_FRAMEWORK);
        exit(21);
    }
    testExecCmd(cmd);

    return 0;
}

// Determine the maximum number of overlays that are all of the same format
// that the HWC will commit to.  If allowOverlap is true, then the rectangles
// are laid out on a diagonal starting from the upper left corner.  With
// each rectangle adjust one pixel to the right and one pixel down.
// When allowOverlap is false, the rectangles are tiled in column major
// order.  Note, column major ordering is used so that the initial rectangles
// are all on different horizontal scan rows.  It is common that hardware
// has limits on the number of objects it can handle on any single row.
uint32_t maxOverlays(uint32_t format, bool allowOverlap)
{
    unsigned int max = 0;

    for (unsigned int numRects = 1; numRects <= searchLimits.numOverlays;
         numRects++) {
        list<Rectangle> rectList;

        for (unsigned int x = 0;
             (x + startDim.width()) < (unsigned int) width;
             x += (allowOverlap) ? 1 : startDim.width()) {
            for (unsigned int y = 0;
                 (y + startDim.height()) < (unsigned int) height;
                 y += (allowOverlap) ? 1 : startDim.height()) {
                Rectangle rect(format, startDim, startDim);
                rect.displayFrame.left = x;
                rect.displayFrame.top = y;
                rect.displayFrame.right = x + startDim.width();
                rect.displayFrame.bottom = y + startDim.height();

                rectList.push_back(rect);

                if (rectList.size() >= numRects) { break; }
            }
            if (rectList.size() >= numRects) { break; }
        }

        uint32_t num = numOverlays(rectList);
        if (num > max) { max = num; }
    }

    return max;
}

// Measures what transforms (i.e. flip horizontal, rotate 180) are
// supported by the specified format
list<uint32_t> supportedTransforms(uint32_t format)
{
    list<uint32_t> rv;
    list<Rectangle> rectList;
    Rectangle rect(format, startDim);

    // For each of the transform types
    for (unsigned int idx = 0; idx < NUMA(transformType); idx++) {
        unsigned int id = transformType[idx].id;

        rect.transform = id;
        rectList.clear();
        rectList.push_back(rect);
        uint32_t num = numOverlays(rectList);

        if (num == 1) {
            rv.push_back(id);
        }
    }

    return rv;
}

// Determines which types of blends (i.e. none, premult, coverage) are
// supported by the specified format
list<uint32_t> supportedBlends(uint32_t format)
{
    list<uint32_t> rv;
    list<Rectangle> rectList;
    Rectangle rect(format, startDim);

    // For each of the blend types
    for (unsigned int idx = 0; idx < NUMA(blendType); idx++) {
        unsigned int id = blendType[idx].id;

        rect.blend = id;
        rectList.clear();
        rectList.push_back(rect);
        uint32_t num = numOverlays(rectList);

        if (num == 1) {
            rv.push_back(id);
        }
    }

    return rv;
}

// Determines the minimum width of any display frame of the given format
// that the HWC will commit to.
uint32_t dfMinWidth(uint32_t format)
{
    uint32_t w;
    list<Rectangle> rectList;

    for (w = 1; w <= startDim.width(); w++) {
        HwcTestDim dim(w, startDim.height());
        Rectangle rect(format, dim);
        rectList.clear();
        rectList.push_back(rect);
        uint32_t num = numOverlays(rectList);
        if (num > 0) {
            return w;
        }
    }
    if (w > startDim.width()) {
        testPrintE("Failed to locate display frame min width");
        exit(33);
    }

    return w;
}

// Display frame minimum height
uint32_t dfMinHeight(uint32_t format)
{
    uint32_t h;
    list<Rectangle> rectList;

    for (h = 1; h <= startDim.height(); h++) {
        HwcTestDim dim(startDim.width(), h);
        Rectangle rect(format, dim);
        rectList.clear();
        rectList.push_back(rect);
        uint32_t num = numOverlays(rectList);
        if (num > 0) {
            return h;
        }
    }
    if (h > startDim.height()) {
        testPrintE("Failed to locate display frame min height");
        exit(34);
    }

    return h;
}

// Display frame maximum width
uint32_t dfMaxWidth(uint32_t format)
{
    uint32_t w;
    list<Rectangle> rectList;

    for (w = width; w >= startDim.width(); w--) {
        HwcTestDim dim(w, startDim.height());
        Rectangle rect(format, dim);
        rectList.clear();
        rectList.push_back(rect);
        uint32_t num = numOverlays(rectList);
        if (num > 0) {
            return w;
        }
    }
    if (w < startDim.width()) {
        testPrintE("Failed to locate display frame max width");
        exit(35);
    }

    return w;
}

// Display frame maximum height
uint32_t dfMaxHeight(uint32_t format)
{
    uint32_t h;

    for (h = height; h >= startDim.height(); h--) {
        HwcTestDim dim(startDim.width(), h);
        Rectangle rect(format, dim);
        list<Rectangle> rectList;
        rectList.push_back(rect);
        uint32_t num = numOverlays(rectList);
        if (num > 0) {
            return h;
        }
    }
    if (h < startDim.height()) {
        testPrintE("Failed to locate display frame max height");
        exit(36);
    }

    return h;
}

// Determine the minimum number of pixels that the HWC will ever commit to.
// Note, this might be different that dfMinWidth * dfMinHeight, in that this
// function adjusts both the width and height from the starting dimension.
HwcTestDim dfMinDim(uint32_t format)
{
    uint64_t bestMinPixels = 0;
    HwcTestDim bestDim;
    bool bestSet = false; // True when value has been assigned to
                          // bestMinPixels and bestDim

    bool origVerbose = verbose;  // Temporarily turn off verbose
    verbose = false;
    for (uint32_t w = 1; w <= startDim.width(); w++) {
        for (uint32_t h = 1; h <= startDim.height(); h++) {
            if (bestSet && ((w > bestMinPixels) || (h > bestMinPixels))) {
                break;
            }

            HwcTestDim dim(w, h);
            Rectangle rect(format, dim);
            list<Rectangle> rectList;
            rectList.push_back(rect);
            uint32_t num = numOverlays(rectList);
            if (num > 0) {
                uint64_t pixels = dim.width() * dim.height();
                if (!bestSet || (pixels < bestMinPixels)) {
                    bestMinPixels = pixels;
                    bestDim = dim;
                    bestSet = true;
                }
            }
        }
    }
    verbose = origVerbose;

    if (!bestSet) {
        testPrintE("Unable to locate display frame min dimension");
        exit(20);
    }

    return bestDim;
}

// Display frame maximum dimension
HwcTestDim dfMaxDim(uint32_t format)
{
    uint64_t bestMaxPixels = 0;
    HwcTestDim bestDim;
    bool bestSet = false; // True when value has been assigned to
                          // bestMaxPixels and bestDim;

    // Potentially increase benchmark performance by first checking
    // for the common case of supporting a full display frame.
    HwcTestDim dim(width, height);
    Rectangle rect(format, dim);
    list<Rectangle> rectList;
    rectList.push_back(rect);
    uint32_t num = numOverlays(rectList);
    if (num == 1) { return dim; }

    // TODO: Use a binary search
    bool origVerbose = verbose;  // Temporarily turn off verbose
    verbose = false;
    for (uint32_t w = startDim.width(); w <= (uint32_t) width; w++) {
        for (uint32_t h = startDim.height(); h <= (uint32_t) height; h++) {
            if (bestSet && ((w * h) <= bestMaxPixels)) { continue; }

            HwcTestDim dim(w, h);
            Rectangle rect(format, dim);
            list<Rectangle> rectList;
            rectList.push_back(rect);
            uint32_t num = numOverlays(rectList);
            if (num > 0) {
                uint64_t pixels = dim.width() * dim.height();
                if (!bestSet || (pixels > bestMaxPixels)) {
                    bestMaxPixels = pixels;
                    bestDim = dim;
                    bestSet = true;
                }
            }
        }
    }
    verbose = origVerbose;

    if (!bestSet) {
        testPrintE("Unable to locate display frame max dimension");
        exit(21);
    }

    return bestDim;
}

// Source crop minimum width
uint32_t scMinWidth(uint32_t format, const HwcTestDim& dfDim)
{
    uint32_t w;
    list<Rectangle> rectList;

    // Source crop frame min width
    for (w = 1; w <= dfDim.width(); w++) {
        Rectangle rect(format, dfDim, HwcTestDim(w, dfDim.height()));
        rectList.clear();
        rectList.push_back(rect);
        uint32_t num = numOverlays(rectList);
        if (num > 0) {
            return w;
        }
    }
    testPrintE("Failed to locate source crop min width");
    exit(35);
}

// Source crop minimum height
uint32_t scMinHeight(uint32_t format, const HwcTestDim& dfDim)
{
    uint32_t h;
    list<Rectangle> rectList;

    for (h = 1; h <= dfDim.height(); h++) {
        Rectangle rect(format, dfDim, HwcTestDim(dfDim.width(), h));
        rectList.clear();
        rectList.push_back(rect);
        uint32_t num = numOverlays(rectList);
        if (num > 0) {
            return h;
        }
    }
    testPrintE("Failed to locate source crop min height");
    exit(36);
}

// Source crop maximum width
uint32_t scMaxWidth(uint32_t format, const HwcTestDim& dfDim)
{
    uint32_t w;
    list<Rectangle> rectList;

    for (w = searchLimits.sourceCrop.width(); w >= dfDim.width(); w--) {
        Rectangle rect(format, dfDim, HwcTestDim(w, dfDim.height()));
        rectList.clear();
        rectList.push_back(rect);
        uint32_t num = numOverlays(rectList);
        if (num > 0) {
            return w;
        }
    }
    testPrintE("Failed to locate source crop max width");
    exit(35);
}

// Source crop maximum height
uint32_t scMaxHeight(uint32_t format, const HwcTestDim& dfDim)
{
    uint32_t h;
    list<Rectangle> rectList;

    for (h = searchLimits.sourceCrop.height(); h >= dfDim.height(); h--) {
        Rectangle rect(format, dfDim, HwcTestDim(dfDim.width(), h));
        rectList.clear();
        rectList.push_back(rect);
        uint32_t num = numOverlays(rectList);
        if (num > 0) {
            return h;
        }
    }
    testPrintE("Failed to locate source crop max height");
    exit(36);
}

// Source crop minimum dimension
// Discovers the source crop with the least number of pixels that the
// HWC will commit to.  Note, this may be different from scMinWidth
// * scMinHeight, in that this function searches for a combination of
// width and height.  While the other routines always keep one of the
// dimensions equal to the corresponding start dimension.
HwcTestDim scMinDim(uint32_t format, const HwcTestDim& dfDim)
{
    uint64_t bestMinPixels = 0;
    HwcTestDim bestDim;
    bool bestSet = false; // True when value has been assigned to
                          // bestMinPixels and bestDim

    bool origVerbose = verbose;  // Temporarily turn off verbose
    verbose = false;
    for (uint32_t w = 1; w <= dfDim.width(); w++) {
        for (uint32_t h = 1; h <= dfDim.height(); h++) {
            if (bestSet && ((w > bestMinPixels) || (h > bestMinPixels))) {
                break;
            }

            HwcTestDim dim(w, h);
            Rectangle rect(format, dfDim, HwcTestDim(w, h));
            list<Rectangle> rectList;
            rectList.push_back(rect);
            uint32_t num = numOverlays(rectList);
            if (num > 0) {
                uint64_t pixels = dim.width() * dim.height();
                if (!bestSet || (pixels < bestMinPixels)) {
                    bestMinPixels = pixels;
                    bestDim = dim;
                    bestSet = true;
                }
            }
        }
    }
    verbose = origVerbose;

    if (!bestSet) {
        testPrintE("Unable to locate source crop min dimension");
        exit(20);
    }

    return bestDim;
}

// Source crop maximum dimension
HwcTestDim scMaxDim(uint32_t format, const HwcTestDim& dfDim)
{
    uint64_t bestMaxPixels = 0;
    HwcTestDim bestDim;
    bool bestSet = false; // True when value has been assigned to
                          // bestMaxPixels and bestDim;

    // Potentially increase benchmark performance by first checking
    // for the common case of supporting the maximum checked source size
    HwcTestDim dim = searchLimits.sourceCrop;
    Rectangle rect(format, dfDim, searchLimits.sourceCrop);
    list<Rectangle> rectList;
    rectList.push_back(rect);
    uint32_t num = numOverlays(rectList);
    if (num == 1) { return dim; }

    // TODO: Use a binary search
    bool origVerbose = verbose;  // Temporarily turn off verbose
    verbose = false;
    for (uint32_t w = dfDim.width();
         w <= searchLimits.sourceCrop.width(); w++) {
        for (uint32_t h = dfDim.height();
             h <= searchLimits.sourceCrop.height(); h++) {
            if (bestSet && ((w * h) <= bestMaxPixels)) { continue; }

            HwcTestDim dim(w, h);
            Rectangle rect(format, dfDim, dim);
            list<Rectangle> rectList;
            rectList.push_back(rect);
            uint32_t num = numOverlays(rectList);
            if (num > 0) {
                uint64_t pixels = dim.width() * dim.height();
                if (!bestSet || (pixels > bestMaxPixels)) {
                    bestMaxPixels = pixels;
                    bestDim = dim;
                    bestSet = true;
                }
            }
        }
    }
    verbose = origVerbose;

    if (!bestSet) {
        testPrintE("Unable to locate source crop max dimension");
        exit(21);
    }

    return bestDim;
}

// Source crop horizontal scale
// Determines the maximum factor by which the source crop can be larger
// that the display frame.  The commit point is discovered through a
// binary search of rational numbers.  The numerator in each of the
// rational numbers contains the dimension for the source crop, while
// the denominator specifies the dimension for the display frame.  On
// each pass of the binary search the mid-point between the greatest
// point committed to (best) and the smallest point in which a commit
// has failed is calculated.  This mid-point is then passed to a function
// named double2Rational, which determines the closest rational numbers
// just below and above the mid-point.  By default the lower rational
// number is used for the scale factor on the next pass of the binary
// search.  The upper value is only used when best is already equal
// to the lower value.  This only occurs when the lower value has already
// been tried.
Rational scHScale(uint32_t format,
                      const HwcTestDim& dfMin, const HwcTestDim& dfMax,
                      const HwcTestDim& scMin, const HwcTestDim& scMax,
                      HwcTestDim& outBestDf, HwcTestDim& outBestSc)
{
    HwcTestDim scDim, dfDim; // Source crop and display frame dimension
    Rational best(0, 1), minBad;  // Current bounds for a binary search
                                  // MinGood is set below the lowest
                                  // possible scale.  The value of minBad,
                                  // will be set by the first pass
                                  // of the binary search.

    // Perform the passes of the binary search
    bool firstPass = true;
    do {
        // On first pass try the maximum scale within the search limits
        if (firstPass) {
            // Try the maximum possible scale, within the search limits
            scDim = HwcTestDim(searchLimits.sourceCrop.width(), scMin.height());
            dfDim = dfMin;
        } else {
            // Subsequent pass
            // Halve the difference between best and minBad.
            Rational lower, upper, selected;

            // Try the closest ratio halfway between minBood and minBad;
            // TODO: Avoid rounding issue by using Rational type for
            //       midpoint.  For now will use double, which should
            //       have more than sufficient resolution.
            double mid = (double) best
                         + ((double) minBad - (double) best) / 2.0;
            Rational::double2Rational(mid,
                            Range(scMin.width(), scMax.width()),
                            Range(dfMin.width(), dfMax.width()),
                            lower, upper);
            if (((lower == best) && (upper == minBad))) {
                return best;
            }

            // Use lower value unless its already been tried
            selected = (lower != best) ? lower : upper;

            // Assign the size of the source crop and display frame
            // from the selected ratio of source crop to display frame.
            scDim = HwcTestDim(selected.numerator(), scMin.height());
            dfDim = HwcTestDim(selected.denominator(), dfMin.height());
        }

        // See if the HWC will commit to this combination
        Rectangle rect(format, dfDim, scDim);
        list<Rectangle> rectList;
        rectList.push_back(rect);
        uint32_t num = numOverlays(rectList);

        if (verbose) {
            testPrintI("  scHscale num: %u scale: %f dfDim: %s scDim: %s",
                       num, (float) Rational(scDim.width(), dfDim.width()),
                       ((string) dfDim).c_str(), ((string) scDim).c_str());
        }
        if (num == 1) {
            // HWC committed to the combination
            // This is the best scale factor seen so far.  Report the
            // dimensions to the caller, in case nothing better is seen.
            outBestDf = dfDim;
            outBestSc = scDim;

            // Success on the first pass means the largest possible scale
            // is supported, in which case no need to search any further.
            if (firstPass) { return Rational(scDim.width(), dfDim.width()); }

            // Update the lower bound of the binary search
            best = Rational(scDim.width(), dfDim.width());
        } else {
            // HWC didn't commit to this combination, so update the
            // upper bound of the binary search.
            minBad = Rational(scDim.width(), dfDim.width());
        }

        firstPass = false;
    } while (best != minBad);

    return best;
}

// Source crop vertical scale
// Determines the maximum factor by which the source crop can be larger
// that the display frame.  The commit point is discovered through a
// binary search of rational numbers.  The numerator in each of the
// rational numbers contains the dimension for the source crop, while
// the denominator specifies the dimension for the display frame.  On
// each pass of the binary search the mid-point between the greatest
// point committed to (best) and the smallest point in which a commit
// has failed is calculated.  This mid-point is then passed to a function
// named double2Rational, which determines the closest rational numbers
// just below and above the mid-point.  By default the lower rational
// number is used for the scale factor on the next pass of the binary
// search.  The upper value is only used when best is already equal
// to the lower value.  This only occurs when the lower value has already
// been tried.
Rational scVScale(uint32_t format,
                      const HwcTestDim& dfMin, const HwcTestDim& dfMax,
                      const HwcTestDim& scMin, const HwcTestDim& scMax,
                      HwcTestDim& outBestDf, HwcTestDim& outBestSc)
{
    HwcTestDim scDim, dfDim; // Source crop and display frame dimension
    Rational best(0, 1), minBad;  // Current bounds for a binary search
                                  // MinGood is set below the lowest
                                  // possible scale.  The value of minBad,
                                  // will be set by the first pass
                                  // of the binary search.

    // Perform the passes of the binary search
    bool firstPass = true;
    do {
        // On first pass try the maximum scale within the search limits
        if (firstPass) {
            // Try the maximum possible scale, within the search limits
            scDim = HwcTestDim(scMin.width(), searchLimits.sourceCrop.height());
            dfDim = dfMin;
        } else {
            // Subsequent pass
            // Halve the difference between best and minBad.
            Rational lower, upper, selected;

            // Try the closest ratio halfway between minBood and minBad;
            // TODO: Avoid rounding issue by using Rational type for
            //       midpoint.  For now will use double, which should
            //       have more than sufficient resolution.
            double mid = (double) best
                         + ((double) minBad - (double) best) / 2.0;
            Rational::double2Rational(mid,
                            Range(scMin.height(), scMax.height()),
                            Range(dfMin.height(), dfMax.height()),
                            lower, upper);
            if (((lower == best) && (upper == minBad))) {
                return best;
            }

            // Use lower value unless its already been tried
            selected = (lower != best) ? lower : upper;

            // Assign the size of the source crop and display frame
            // from the selected ratio of source crop to display frame.
            scDim = HwcTestDim(scMin.width(), selected.numerator());
            dfDim = HwcTestDim(dfMin.width(), selected.denominator());
        }

        // See if the HWC will commit to this combination
        Rectangle rect(format, dfDim, scDim);
        list<Rectangle> rectList;
        rectList.push_back(rect);
        uint32_t num = numOverlays(rectList);

        if (verbose) {
            testPrintI("  scHscale num: %u scale: %f dfDim: %s scDim: %s",
                       num, (float) Rational(scDim.height(), dfDim.height()),
                       ((string) dfDim).c_str(), ((string) scDim).c_str());
        }
        if (num == 1) {
            // HWC committed to the combination
            // This is the best scale factor seen so far.  Report the
            // dimensions to the caller, in case nothing better is seen.
            outBestDf = dfDim;
            outBestSc = scDim;

            // Success on the first pass means the largest possible scale
            // is supported, in which case no need to search any further.
            if (firstPass) { return Rational(scDim.height(), dfDim.height()); }

            // Update the lower bound of the binary search
            best = Rational(scDim.height(), dfDim.height());
        } else {
            // HWC didn't commit to this combination, so update the
            // upper bound of the binary search.
            minBad = Rational(scDim.height(), dfDim.height());
        }

        firstPass = false;
    } while (best != minBad);

    return best;
}

uint32_t numOverlapping(uint32_t backgroundFormat, uint32_t foregroundFormat,
                        uint32_t backgroundBlend, uint32_t foregroundBlend)
{
    list<Rectangle> rectList;

    Rectangle background(backgroundFormat, startDim, startDim);
    background.blend = backgroundBlend;
    rectList.push_back(background);

    // TODO: Handle cases where startDim is so small that adding 5
    //       causes frames not to overlap.
    // TODO: Handle cases where startDim is so large that adding 5
    //       cause a portion or all of the foreground displayFrame
    //       to be off the display.
    Rectangle foreground(foregroundFormat, startDim, startDim);
    foreground.displayFrame.left += 5;
    foreground.displayFrame.top += 5;
    foreground.displayFrame.right += 5;
    foreground.displayFrame.bottom += 5;
    background.blend = foregroundBlend;
    rectList.push_back(foreground);

    uint32_t num = numOverlays(rectList);

    return num;
}

Rectangle::Rectangle(uint32_t graphicFormat, HwcTestDim dfDim,
                     HwcTestDim sDim) :
    format(graphicFormat), transform(defaultTransform),
    blend(defaultBlend), color(defaultColor), alpha(defaultAlpha),
    sourceCrop(sDim), displayFrame(dfDim)
{
    // Set source dimension
    // Can't use a base initializer, because the setting of format
    // must be done before setting the sourceDimension.
    setSourceDim(sDim);
}

void Rectangle::setSourceDim(HwcTestDim dim)
{
    this->sourceDim = dim;

    const struct hwcTestGraphicFormat *attrib;
    attrib = hwcTestGraphicFormatLookup(this->format);
    if (attrib != NULL) {
        if (sourceDim.width() % attrib->wMod) {
            sourceDim.setWidth(sourceDim.width() + attrib->wMod
            - (sourceDim.width() % attrib->wMod));
        }
        if (sourceDim.height() % attrib->hMod) {
            sourceDim.setHeight(sourceDim.height() + attrib->hMod
            - (sourceDim.height() % attrib->hMod));
        }
    }
}

// Rational member functions
bool Rational::operator==(const Rational& other) const
{
    if (((uint64_t) _n * other._d)
        == ((uint64_t) _d * other._n)) { return true; }

    return false;
}

bool Rational::operator<(const Rational& other) const
{
    if (((uint64_t) _n * other._d)
        < ((uint64_t) _d * other._n)) { return true; }

    return false;
}

Rational::operator string() const
{
    ostringstream out;

    out << _n << '/' << _d;

    return out.str();
}

void Rational::double2Rational(double f, Range nRange, Range dRange,
                    Rational& lower, Rational& upper)
{
    Rational bestLower(nRange.lower(), dRange.upper());
    Rational bestUpper(nRange.upper(), dRange.lower());

    // Search for a better solution
    for (uint32_t d = dRange.lower(); d <= dRange.upper(); d++) {
        Rational val(d * f, d);  // Lower, because double to int cast truncates

        if ((val.numerator() < nRange.lower())
            || (val.numerator() > nRange.upper())) { continue; }

        if (((double) val > (double) bestLower) && ((double) val <= f)) {
            bestLower = val;
        } 

        val.setNumerator(val.numerator() + 1);
        if (val.numerator() > nRange.upper()) { continue; }

        if (((double) val < (double) bestUpper) && ((double) val >= f)) {
            bestUpper = val;
        }
    }

    lower = bestLower;
    upper = bestUpper;
}

// Local functions

// Num Overlays
// Given a list of rectangles, determine how many HWC will commit to render
uint32_t numOverlays(list<Rectangle>& rectList)
{
    hwc_display_contents_1_t *hwcList;
    list<sp<GraphicBuffer> > buffers;

    hwcList = hwcTestCreateLayerList(rectList.size());
    if (hwcList == NULL) {
        testPrintE("numOverlays create hwcList failed");
        exit(30);
    }

    hwc_layer_1_t *layer = &hwcList->hwLayers[0];
    for (std::list<Rectangle>::iterator it = rectList.begin();
         it != rectList.end(); ++it, ++layer) {
        // Allocate the texture for the source frame
        // and push it onto the buffers list, so that it
        // stays in scope until a return from this function.
        sp<GraphicBuffer> texture;
        texture  = new GraphicBuffer(it->sourceDim.width(),
                                     it->sourceDim.height(),
                                     it->format, texUsage);
        buffers.push_back(texture);

        layer->handle = texture->handle;
        layer->blending = it->blend;
        layer->transform = it->transform;
        layer->sourceCrop = it->sourceCrop;
        layer->displayFrame = it->displayFrame;

        layer->visibleRegionScreen.numRects = 1;
        layer->visibleRegionScreen.rects = &layer->displayFrame;
    }

    // Perform prepare operation
    if (verbose) { testPrintI("Prepare:"); hwcTestDisplayList(hwcList); }
    hwcDevice->prepare(hwcDevice, 1, &hwcList);
    if (verbose) {
        testPrintI("Post Prepare:");
        hwcTestDisplayListPrepareModifiable(hwcList);
    }

    // Count the number of overlays
    uint32_t total = 0;
    for (unsigned int n1 = 0; n1 < hwcList->numHwLayers; n1++) {
        if (hwcList->hwLayers[n1].compositionType == HWC_OVERLAY) {
            total++;
        }
    }

    // Free the layer list and graphic buffers
    hwcTestFreeLayerList(hwcList);

    return total;
}

string transformList2str(const list<uint32_t>& transformList)
{
    ostringstream out;

    for (list<uint32_t>::const_iterator it = transformList.begin();
         it != transformList.end(); ++it) {
        uint32_t id = *it;

        if (it != transformList.begin()) {
            out << ", ";
        }
        out << id;

        for (unsigned int idx = 0; idx < NUMA(transformType); idx++) {
            if (id == transformType[idx].id) {
                out << " (" << transformType[idx].desc << ')';
                break;
            }
        }
    }

    return out.str();
}

string blendList2str(const list<uint32_t>& blendList)
{
    ostringstream out;

    for (list<uint32_t>::const_iterator it = blendList.begin();
         it != blendList.end(); ++it) {
        uint32_t id = *it;

        if (it != blendList.begin()) {
            out << ", ";
        }
        out << id;

        for (unsigned int idx = 0; idx < NUMA(blendType); idx++) {
            if (id == blendType[idx].id) {
                out << " (" << blendType[idx].desc << ')';
                break;
            }
        }
    }

    return out.str();
}

void init(void)
{
    srand48(0);

    hwcTestInitDisplay(verbose, &dpy, &surface, &width, &height);

    hwcTestOpenHwc(&hwcDevice);
}

void printFormatHeadings(size_t indent)
{
    for (size_t row = 0; row <= maxHeadingLen; row++) {
        ostringstream line;
        for(vector<string>::iterator it = formats.begin();
            it != formats.end(); ++it) {
            if ((maxHeadingLen - row) <= it->length()) {
                if (row != maxHeadingLen) {
                    char ch = (*it)[it->length() - (maxHeadingLen - row)];
                    line << ' ' << setw(printFieldWidth) << ch;
                } else {
                    line << ' ' << string(printFieldWidth, '-');
                }
            } else {
               line << ' ' << setw(printFieldWidth) << "";
            }
        }
        testPrintI("%*s%s", indent + maxHeadingLen, "",
                   line.str().c_str());
    }
}

void printOverlapLine(size_t indent, const string formatStr,
                        const vector<uint32_t>& results)
{
    ostringstream line;

    line << setw(indent + maxHeadingLen - formatStr.length()) << "";

    line << formatStr;

    for (vector<uint32_t>::const_iterator it = results.begin();
         it != results.end(); ++it) {
        line << ' ' << setw(printFieldWidth) << *it;
    }

    testPrintI("%s", line.str().c_str());
}

void printSyntax(const char *cmd)
{
    testPrintE("  %s [options] [graphicFormat] ...",
               cmd);
    testPrintE("    options:");
    testPrintE("      -s [width, height] - start dimension");
    testPrintE("      -v - Verbose");
    testPrintE("");
    testPrintE("    graphic formats:");
    for (unsigned int n1 = 0; n1 < NUMA(hwcTestGraphicFormat); n1++) {
        testPrintE("      %s", hwcTestGraphicFormat[n1].desc);
    }
}
