/////////////////////////////////////////////////////////////////////////////
// Name:        graphics.cpp
// Purpose:     Some benchmarks for measuring graphics operations performance
// Author:      Vadim Zeitlin
// Created:     2008-04-13
// Copyright:   (c) 2008 Vadim Zeitlin <vadim@wxwidgets.org>
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

#include "wx/app.h"
#include "wx/frame.h"
#include "wx/cmdline.h"
#include "wx/dcclient.h"
#include "wx/dcmemory.h"
#include "wx/dcgraph.h"
#include "wx/image.h"
#include "wx/rawbmp.h"
#include "wx/stopwatch.h"
#include "wx/crt.h"

#if wxUSE_GLCANVAS
    #include "wx/glcanvas.h"
    #ifdef _MSC_VER
        #pragma comment(lib, "opengl32")
    #endif
#endif // wxUSE_GLCANVAS

#if wxUSE_GLCANVAS

GLuint g_texture;
wxImage g_image;

void InitializeTexture(int w, int h)
{
    glGenTextures(1, &g_texture);
    glBindTexture(GL_TEXTURE_2D, g_texture);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

    g_image.Create(w, h, false /* don't clear */);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexImage2D(GL_TEXTURE_2D, 0,
                 GL_RGB, g_image.GetWidth(), g_image.GetHeight(), 0,
                 GL_RGB, GL_UNSIGNED_BYTE, g_image.GetData());
}
#endif // wxUSE_GLCANVAS

struct GraphicsBenchmarkOptions
{
    GraphicsBenchmarkOptions()
    {
        mapMode = 0;
        penWidth = 0;
        penStyle = wxPENSTYLE_INVALID;

        width = 800;
        height = 600;

        numIters = 1000;

        testBitmaps =
        testImages =
        testLines =
        testRawBitmaps =
        testRectangles =
        testCircles =
        testEllipses =
        testTextExtent =
        testMultiLineTextExtent =
        testPartialTextExtents = false;

        usePaint =
        useClient =
        useMemory = false;

        useDC =
        useGC =
        useGL = false;

        renderer = Default;
    }

    long mapMode,
         penWidth,
         width,
         height,
         numIters;

    wxPenStyle penStyle;

    bool testBitmaps,
         testImages,
         testLines,
         testRawBitmaps,
         testRectangles,
         testCircles,
         testEllipses,
         testTextExtent,
         testMultiLineTextExtent,
         testPartialTextExtents;

    bool usePaint,
         useClient,
         useMemory;

    bool useDC,
         useGC,
         useGL;

#ifdef __WXMSW__
    enum GraphicsRenderer { Default, GDIPlus, Direct2D, Cairo };
#else
    enum GraphicsRenderer { Default };
#endif // __WXMSW__ / !__WXMSW__
    GraphicsRenderer renderer;
} opts;

class GraphicsBenchmarkFrame : public wxFrame
{
public:
    GraphicsBenchmarkFrame()
        : wxFrame(NULL, wxID_ANY, "wxWidgets Graphics Benchmark")
    {
        SetClientSize(opts.width, opts.height);

#if wxUSE_GLCANVAS
        m_glCanvas = NULL;
        m_glContext = NULL;

        if ( opts.useGL )
        {
            m_glCanvas = new wxGLCanvas(this, wxID_ANY, NULL,
                                        wxPoint(0, 0),
                                        wxSize(opts.width, opts.height));
            m_glContext = new wxGLContext(m_glCanvas);
            m_glContext->SetCurrent(*m_glCanvas);

            glViewport(0, 0, opts.width, opts.height);
            glMatrixMode(GL_PROJECTION);
            glLoadIdentity();
            glOrtho(-1, 1, -1, 1, -1, 1);
            glMatrixMode(GL_MODELVIEW);
            glLoadIdentity();

            InitializeTexture(opts.width, opts.height);

            m_glCanvas->Connect(
                wxEVT_PAINT,
                wxPaintEventHandler(GraphicsBenchmarkFrame::OnGLRender),
                NULL,
                this
            );
        }
        else // Not using OpenGL
#endif // wxUSE_GLCANVAS
        {
            Connect(wxEVT_PAINT,
                    wxPaintEventHandler(GraphicsBenchmarkFrame::OnPaint));
        }

        Connect(wxEVT_SIZE, wxSizeEventHandler(GraphicsBenchmarkFrame::OnSize));

        m_bitmapARGB.Create(64, 64, 32);
#if defined(__WXMSW__) || defined(__WXOSX__)
        m_bitmapARGB.UseAlpha(true);
#endif // __WXMSW__ || _WXOSX__
        m_bitmapRGB.Create(64, 64, 24);

        wxBitmap bmpMask(64, 64, 1);
        {
            wxMemoryDC dc(bmpMask);
            dc.SetBackground(*wxBLACK_BRUSH);
            dc.Clear();
        }
        m_bitmapARGBwithMask.Create(64, 64, 32);
#if defined(__WXMSW__) || defined(__WXOSX__)
        m_bitmapARGBwithMask.UseAlpha(true);
#endif // __WXMSW__ || __WXOSX__
        m_bitmapARGBwithMask.SetMask(new wxMask(bmpMask));

        m_bitmapRGBwithMask.Create(64, 64, 24);
        m_bitmapRGBwithMask.SetMask(new wxMask(bmpMask));

        m_renderer = NULL;
        if ( opts.useGC )
        {
#ifdef __WXMSW__
            if ( opts.renderer == GraphicsBenchmarkOptions::GDIPlus )
                m_renderer = wxGraphicsRenderer::GetGDIPlusRenderer();
            else if ( opts.renderer == GraphicsBenchmarkOptions::Direct2D )
                m_renderer = wxGraphicsRenderer::GetDirect2DRenderer();
            else if ( opts.renderer == GraphicsBenchmarkOptions::Cairo )
                m_renderer = wxGraphicsRenderer::GetCairoRenderer();
            // Check if selected renderer is operational.
            if ( m_renderer )
            {
                wxBitmap bmp(16, 16);
                wxMemoryDC memDC(bmp);
                wxGraphicsContext* gc = m_renderer->CreateContext(memDC);
                if ( !gc )
                {
                    wxPrintf("Couldn't initialize '%s' graphics renderer.\n", m_renderer->GetName().c_str());
                    m_renderer = NULL;
                }
                delete gc;
            }
#endif // __WXMSW__

            if( !m_renderer )
                m_renderer = wxGraphicsRenderer::GetDefaultRenderer();
        }

        Show();
    }

#if wxUSE_GLCANVAS
    virtual ~GraphicsBenchmarkFrame()
    {
        delete m_glContext;
    }
#endif // wxUSE_GLCANVAS

private:
    // Just change the image in some (quick) way to show that it's really being
    // updated on screen.
    void UpdateRGB(unsigned char* data, int n)
    {
        for ( int y = 0; y < opts.height; ++y )
        {
            memset(data, n % 256, 3*opts.width);

            data += 3*opts.width;
            n++;
        }
    }

#if wxUSE_GLCANVAS
    void OnGLRender(wxPaintEvent& WXUNUSED(event))
    {
        m_glContext->SetCurrent(*m_glCanvas);
        glEnable(GL_TEXTURE_2D);

        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        wxPrintf("Benchmarking %s: ", "OpenGL images");
        fflush(stdout);

        wxStopWatch sw;
        for ( int n = 0; n < opts.numIters; n++ )
        {
            UpdateRGB(g_image.GetData(), n);

            glTexSubImage2D(GL_TEXTURE_2D, 0,
                            0, 0, opts.width, opts.height,
                            GL_RGB, GL_UNSIGNED_BYTE, g_image.GetData());
            glBegin(GL_QUADS);
                glTexCoord2f(0, 0);
                glVertex2f(-1.0, -1.0);

                glTexCoord2f(0, 1);
                glVertex2f(-1.0, 1.0);

                glTexCoord2f(1, 1);
                glVertex2f(1.0, 1.0);

                glTexCoord2f(1, 0);
                glVertex2f(1.0, -1.0);
            glEnd();

            m_glCanvas->SwapBuffers();
        }

        const long t = sw.Time();

        wxPrintf("%ld images done in %ldms = %gus/image or %ld FPS\n",
                 opts.numIters, t, (1000. * t)/opts.numIters,
                 (1000*opts.numIters + t - 1)/t);

        wxTheApp->ExitMainLoop();
    }
#endif // wxUSE_GLCANVAS

    void OnPaint(wxPaintEvent& WXUNUSED(event))
    {
        if ( opts.usePaint )
        {
            {
                wxPaintDC dc(this);
                wxGCDC gcdc;
                if ( m_renderer )
                {
                    wxGraphicsContext* gc = m_renderer->CreateContext(dc);
                    gcdc.SetGraphicsContext(gc);
                }
                BenchmarkDCAndGC("paint", dc, gcdc);
            }
            // Since some renderers use back buffers and hence
            // drawing results are not displayed when the test
            // is running then wait a second after graphics
            // contents is commited to DC to present the output.
            wxSleep(1);
        }

        if ( opts.useClient )
        {
            {
                wxClientDC dc(this);
                wxGCDC gcdc;
                if ( m_renderer )
                {
                    wxGraphicsContext* gc = m_renderer->CreateContext(dc);
                    gcdc.SetGraphicsContext(gc);
                }
                BenchmarkDCAndGC("client", dc, gcdc);
            }
            // Since some renderers use back buffers and hence
            // drawing results are not displayed when the test
            // is running then wait a second after graphics
            // contents is commited to DC to present the output.
            wxSleep(1);
        }

        if ( opts.useMemory )
        {
            {
                wxBitmap bmp(opts.width, opts.height);
                wxMemoryDC dc(bmp);
                wxGCDC gcdc;
                if ( m_renderer )
                {
                    wxGraphicsContext* gc = m_renderer->CreateContext(dc);
                    gcdc.SetGraphicsContext(gc);
                }
                BenchmarkDCAndGC("default memory", dc, gcdc);
            }
            {
                wxBitmap bmp(opts.width, opts.height, 24);
                wxMemoryDC dc(bmp);
                wxGCDC gcdc;
                if ( m_renderer )
                {
                    wxGraphicsContext* gc = m_renderer->CreateContext(dc);
                    gcdc.SetGraphicsContext(gc);
                }
                BenchmarkDCAndGC("RGB memory", dc, gcdc);
            }
            {
#if defined(__WXMSW__) || defined(__WXOSX__)
                wxBitmap bmp(opts.width, opts.height, 32);
                bmp.UseAlpha(false);
                wxMemoryDC dc(bmp);
                wxGCDC gcdc;
                if ( m_renderer )
                {
                    wxGraphicsContext* gc = m_renderer->CreateContext(dc);
                    gcdc.SetGraphicsContext(gc);
                }
                BenchmarkDCAndGC("0RGB memory", dc, gcdc);
#endif // __WXMSW__ ||__WXOSX__
            }
            {
                wxBitmap bmp(opts.width, opts.height, 32);
#if defined(__WXMSW__) || defined(__WXOSX__)
                bmp.UseAlpha(true);
#endif // __WXMSW__ || __WXOSX__
                wxMemoryDC dc(bmp);
                wxGCDC gcdc;
                if ( m_renderer )
                {
                    wxGraphicsContext* gc = m_renderer->CreateContext(dc);
                    gcdc.SetGraphicsContext(gc);
                }
                BenchmarkDCAndGC("ARGB memory", dc, gcdc);
            }

        }

        wxTheApp->ExitMainLoop();
    }

    void BenchmarkDCAndGC(const char* dckind, wxDC& dc, wxGCDC& gcdc)
    {
        if ( opts.useDC )
        {
            BenchmarkAll(wxString::Format("%6s DC", dckind), dc);
        }
        else if ( opts.useGC && gcdc.IsOk() )
        {
            wxString rendName = gcdc.GetGraphicsContext()->GetRenderer()->GetName();
            BenchmarkAll(wxString::Format("%6s GC (%s)", dckind, rendName.c_str()), gcdc);
        }
    }

    void BenchmarkAll(const wxString& msg, wxDC& dc)
    {
        BenchmarkBitmaps(msg, dc);
        BenchmarkImages(msg, dc);
        BenchmarkLines(msg, dc);
        BenchmarkRawBitmaps(msg, dc);
        BenchmarkRectangles(msg, dc);
        BenchmarkRoundedRectangles(msg, dc);
        BenchmarkCircles(msg, dc);
        BenchmarkEllipses(msg, dc);
        BenchmarkTextExtent(msg, dc);
        BenchmarkPartialTextExtents(msg, dc);
    }

    void SetupDC(wxDC& dc)
    {
        if ( opts.mapMode != 0 )
            dc.SetMapMode((wxMappingMode)opts.mapMode);
        if ( opts.penWidth != 0 )
            dc.SetPen(wxPen(*wxWHITE, opts.penWidth));
        if ( opts.penStyle != wxPENSTYLE_INVALID )
        {
            wxPen pen = dc.GetPen();
            if ( !pen.IsOk() )
                pen = wxPen(*wxWHITE, 1);
            pen.SetStyle(opts.penStyle);
            dc.SetPen(pen);
        }
    }

    void BenchmarkLines(const wxString& msg, wxDC& dc)
    {
        if ( !opts.testLines )
            return;

        SetupDC(dc);

        wxPrintf("Benchmarking %s: ", msg);
        fflush(stdout);

        wxStopWatch sw;
        int x0 = 0,
            y0 = 0;
        for ( int n = 0; n < opts.numIters; n++ )
        {
            int x1 = rand() % opts.width,
                y1 = rand() % opts.height;

            dc.DrawLine(x0, y0, x1, y1);

            x0 = x1;
            y0 = y1;
        }

        const long t = sw.Time();

        wxPrintf("%ld lines done in %ldms = %gus/line\n",
                 opts.numIters, t, (1000. * t)/opts.numIters);

        // Horizontal lines
        wxPrintf("Benchmarking %s: ", msg);
        fflush(stdout);

        sw.Start();
        x0 = 0;
        for ( int n = 0; n < opts.numIters; n++ )
        {
            int x1 = rand() % opts.width;
            int y = rand() % opts.height;

            dc.DrawLine(x0, y, x1, y);

            x0 = x1;
        }

        const long t2 = sw.Time();

        wxPrintf("%ld horizontal lines done in %ldms = %gus/line\n",
            opts.numIters, t2, (1000. * t2) / opts.numIters);

        // Vertical lines
        wxPrintf("Benchmarking %s: ", msg);
        fflush(stdout);

        sw.Start();
        y0 = 0;
        for ( int n = 0; n < opts.numIters; n++ )
        {
            int x = rand() % opts.width;
            int y1 = rand() % opts.height;

            dc.DrawLine(x, y0, x, y1);

            y0 = y1;
        }

        const long t3 = sw.Time();

        wxPrintf("%ld vertical lines done in %ldms = %gus/line\n",
            opts.numIters, t3, (1000. * t3) / opts.numIters);

        // Cross hair
        wxPrintf("Benchmarking %s: ", msg);
        fflush(stdout);

        sw.Start();
        for ( int n = 0; n < opts.numIters; n++ )
        {
            x0 = rand() % opts.width;
            y0 = rand() % opts.height;

            dc.CrossHair(x0, y0);
        }
        const long t4 = sw.Time();

        wxPrintf("%ld cross hairs done in %ldms = %gus/line\n",
            opts.numIters, t4, (1000. * t4) / (2*opts.numIters));
    }


    void BenchmarkRectangles(const wxString& msg, wxDC& dc)
    {
        if ( !opts.testRectangles )
            return;

        SetupDC(dc);

        dc.SetBrush( *wxRED_BRUSH );

        wxPrintf("Benchmarking %s: ", msg);
        fflush(stdout);

        wxStopWatch sw;
        for ( int n = 0; n < opts.numIters; n++ )
        {
            int x = rand() % opts.width,
                y = rand() % opts.height;

            dc.DrawRectangle(x, y, 32, 32);
        }

        const long t = sw.Time();

        wxPrintf("%ld rects done in %ldms = %gus/rect\n",
                 opts.numIters, t, (1000. * t)/opts.numIters);
    }

    void BenchmarkRoundedRectangles(const wxString& msg, wxDC& dc)
    {
        if ( !opts.testRectangles )
            return;

        SetupDC(dc);

        dc.SetBrush( *wxCYAN_BRUSH );

        wxPrintf("Benchmarking %s: ", msg);
        fflush(stdout);

        wxStopWatch sw;
        for ( int n = 0; n < opts.numIters; n++ )
        {
            int x = rand() % opts.width,
                y = rand() % opts.height;

            dc.DrawRoundedRectangle(x, y, 48, 32, 8);
        }

        const long t = sw.Time();

        wxPrintf("%ld rounded rects done in %ldms = %gus/rect\n",
                 opts.numIters, t, (1000. * t)/opts.numIters);
    }

    void BenchmarkCircles(const wxString& msg, wxDC& dc)
    {
        if ( !opts.testCircles )
            return;

        SetupDC(dc);

        dc.SetBrush( *wxGREEN_BRUSH );

        wxPrintf("Benchmarking %s: ", msg);
        fflush(stdout);

        wxStopWatch sw;
        for ( long n = 0; n < opts.numIters; n++ )
        {
            int x = rand() % opts.width,
                y = rand() % opts.height;

            dc.DrawCircle(x, y, 32);
        }

        const long t = sw.Time();

        wxPrintf("%ld circles done in %ldms = %gus/circle\n",
                 opts.numIters, t, (1000. * t)/opts.numIters);
    }

    void BenchmarkEllipses(const wxString& msg, wxDC& dc)
    {
        if ( !opts.testEllipses )
            return;

        SetupDC(dc);

        dc.SetBrush( *wxBLUE_BRUSH );

        wxPrintf("Benchmarking %s: ", msg);
        fflush(stdout);

        wxStopWatch sw;
        for ( long n = 0; n < opts.numIters; n++ )
        {
            int x = rand() % opts.width,
                y = rand() % opts.height;

            dc.DrawEllipse(x, y, 48, 32);
        }

        const long t = sw.Time();

        wxPrintf("%ld ellipses done in %ldms = %gus/ellipse\n",
                 opts.numIters, t, (1000. * t)/opts.numIters);
    }

    void BenchmarkTextExtent(const wxString& msg, wxDC& dc)
    {
        if ( !opts.testTextExtent )
            return;

        SetupDC(dc);

        wxPrintf("Benchmarking %s: ", msg);
        fflush(stdout);

        const wxString str("The quick brown fox jumps over the lazy dog");
        wxSize size;

        wxStopWatch sw;
        for ( long n = 0; n < opts.numIters; n++ )
        {
            if ( opts.testMultiLineTextExtent )
                size += dc.GetMultiLineTextExtent(str);
            else
                size += dc.GetTextExtent(str);
        }

        const long t = sw.Time();

        wxPrintf("%ld text extent measures done in %ldms = %gus/call\n",
                 opts.numIters, t, (1000. * t)/opts.numIters);
    }

    void BenchmarkPartialTextExtents(const wxString& msg, wxDC& dc)
    {
        if ( !opts.testPartialTextExtents )
            return;

        SetupDC(dc);

        wxPrintf("Benchmarking %s: ", msg);
        fflush(stdout);

        const wxString str("The quick brown fox jumps over the lazy dog");
        wxArrayInt widths;

        wxStopWatch sw;
        for ( long n = 0; n < opts.numIters; n++ )
        {
            if ( !dc.GetPartialTextExtents(str, widths) )
            {
                wxPrintf("ERROR: GetPartialTextExtents() failed\n");
                return;
            }
        }

        const long t = sw.Time();

        wxPrintf("%ld partial text extents measures done in %ldms = %gus/call\n",
                 opts.numIters, t, (1000. * t)/opts.numIters);
    }

    void BenchmarkBitmaps(const wxString& msg, wxDC& dc)
    {
        if ( !opts.testBitmaps )
            return;

        SetupDC(dc);

        wxPrintf("Benchmarking %s: ", msg);
        fflush(stdout);

        wxStopWatch sw;
        for ( int n = 0; n < opts.numIters; n++ )
        {
            int x = rand() % opts.width,
                y = rand() % opts.height;

            dc.DrawBitmap(m_bitmapARGB, x, y, true);
        }

        const long t = sw.Time();

        wxPrintf("%ld ARGB bitmaps done in %ldms = %gus/bitmap\n",
                 opts.numIters, t, (1000. * t)/opts.numIters);

        wxPrintf("Benchmarking %s: ", msg);
        fflush(stdout);

        sw.Start();
        for ( int n = 0; n < opts.numIters; n++ )
        {
            int x = rand() % opts.width,
                y = rand() % opts.height;

            dc.DrawBitmap(m_bitmapRGB, x, y, true);
        }
        const long t2 = sw.Time();

        wxPrintf("%ld RGB bitmaps done in %ldms = %gus/bitmap\n",
                 opts.numIters, t2, (1000. * t2)/opts.numIters);

        wxPrintf("Benchmarking %s: ", msg);
        fflush(stdout);

        sw.Start();
        for ( int n = 0; n < opts.numIters; n++ )
        {
            int x = rand() % opts.width,
                y = rand() % opts.height;

            dc.DrawBitmap(m_bitmapARGBwithMask, x, y, true);
        }
        const long t3 = sw.Time();

        wxPrintf("%ld ARGB bitmaps with mask done in %ldms = %gus/bitmap\n",
            opts.numIters, t3, (1000. * t3) / opts.numIters);

        wxPrintf("Benchmarking %s: ", msg);
        fflush(stdout);

        sw.Start();
        for ( int n = 0; n < opts.numIters; n++ )
        {
            int x = rand() % opts.width,
                y = rand() % opts.height;

            dc.DrawBitmap(m_bitmapRGBwithMask, x, y, true);
        }
        const long t4 = sw.Time();

        wxPrintf("%ld RGB bitmaps with mask done in %ldms = %gus/bitmap\n",
            opts.numIters, t4, (1000. * t4) / opts.numIters);
    }

    void BenchmarkImages(const wxString& msg, wxDC& dc)
    {
        if ( !opts.testImages )
            return;

        if ( opts.mapMode != 0 )
            dc.SetMapMode((wxMappingMode)opts.mapMode);

        wxPrintf("Benchmarking %s: ", msg);
        fflush(stdout);

        wxImage image(wxSize(opts.width, opts.height), false /* don't clear */);

        wxStopWatch sw;
        for ( int n = 0; n < opts.numIters; n++ )
        {
            UpdateRGB(image.GetData(), n);
            dc.DrawBitmap(image, 0, 0);
        }

        const long t = sw.Time();

        wxPrintf("%ld images done in %ldms = %gus/image or %ld FPS\n",
                 opts.numIters, t, (1000. * t)/opts.numIters,
                 (1000*opts.numIters + t - 1)/t);
    }

    void BenchmarkRawBitmaps(const wxString& msg, wxDC& dc)
    {
        if ( !opts.testRawBitmaps )
            return;

        if ( opts.mapMode != 0 )
            dc.SetMapMode((wxMappingMode)opts.mapMode);

        wxPrintf("Benchmarking %s: ", msg);
        fflush(stdout);

        wxBitmap bitmap(opts.width, opts.height, 24);
        wxNativePixelData data(bitmap);

        wxStopWatch sw;
        for ( int n = 0; n < opts.numIters; n++ )
        {
            unsigned char c = n % 256;
            {
                wxNativePixelData::Iterator p(data);
                for ( int y = 0; y < opts.height; ++y )
                {
                    wxNativePixelData::Iterator rowStart = p;

                    for ( int x = 0; x < opts.width; ++x )
                    {
                        p.Red() =
                        p.Green() =
                        p.Blue() = c;
                        ++p;
                    }

                    p = rowStart;
                    p.OffsetY(data, 1);
                    c++;
                }
            }

            dc.DrawBitmap(bitmap, 0, 0);
        }

        const long t = sw.Time();

        wxPrintf("%ld raw bitmaps done in %ldms = %gus/bitmap or %ld FPS\n",
                 opts.numIters, t, (1000. * t)/opts.numIters,
                 (1000*opts.numIters + t - 1)/t);
    }


    wxBitmap m_bitmapARGB;
    wxBitmap m_bitmapRGB;
    wxBitmap m_bitmapARGBwithMask;
    wxBitmap m_bitmapRGBwithMask;
#if wxUSE_GLCANVAS
    wxGLCanvas* m_glCanvas;
    wxGLContext* m_glContext;
#endif // wxUSE_GLCANVAS
    wxGraphicsRenderer* m_renderer;
};

class GraphicsBenchmarkApp : public wxApp
{
public:
    virtual void OnInitCmdLine(wxCmdLineParser& parser)
    {
        static const wxCmdLineEntryDesc desc[] =
        {
            { wxCMD_LINE_SWITCH, "",  "bitmaps" },
            { wxCMD_LINE_SWITCH, "",  "images" },
            { wxCMD_LINE_SWITCH, "",  "lines" },
            { wxCMD_LINE_SWITCH, "",  "rawbmp" },
            { wxCMD_LINE_SWITCH, "",  "rectangles" },
            { wxCMD_LINE_SWITCH, "",  "circles" },
            { wxCMD_LINE_SWITCH, "",  "ellipses" },
            { wxCMD_LINE_SWITCH, "",  "textextent" },
            { wxCMD_LINE_SWITCH, "",  "multilinetextextent" },
            { wxCMD_LINE_SWITCH, "",  "partialtextextents" },
            { wxCMD_LINE_SWITCH, "",  "paint" },
            { wxCMD_LINE_SWITCH, "",  "client" },
            { wxCMD_LINE_SWITCH, "",  "memory" },
            { wxCMD_LINE_SWITCH, "",  "dc" },
            { wxCMD_LINE_SWITCH, "",  "gc" },
#if wxUSE_GLCANVAS
            { wxCMD_LINE_SWITCH, "",  "gl" },
#endif // wxUSE_GLCANVAS
            { wxCMD_LINE_OPTION, "m", "map-mode", "", wxCMD_LINE_VAL_NUMBER },
            { wxCMD_LINE_OPTION, "p", "pen-width", "", wxCMD_LINE_VAL_NUMBER },
            { wxCMD_LINE_OPTION, "s", "pen-style", "solid | dot | long_dash | short_dash", wxCMD_LINE_VAL_STRING },
            { wxCMD_LINE_OPTION, "w", "width", "", wxCMD_LINE_VAL_NUMBER },
            { wxCMD_LINE_OPTION, "h", "height", "", wxCMD_LINE_VAL_NUMBER },
            { wxCMD_LINE_OPTION, "I", "images", "", wxCMD_LINE_VAL_NUMBER },
            { wxCMD_LINE_OPTION, "N", "number-of-iterations", "", wxCMD_LINE_VAL_NUMBER },
#ifdef __WXMSW__
            { wxCMD_LINE_OPTION, "r", "renderer", "gdiplus | direct2d | cairo", wxCMD_LINE_VAL_STRING },
#endif // __WXMSW__
            { wxCMD_LINE_NONE },
        };

        parser.SetDesc(desc);
    }

    virtual bool OnCmdLineParsed(wxCmdLineParser& parser)
    {
        if ( parser.Found("m", &opts.mapMode) &&
                (opts.mapMode < 1 || opts.mapMode > wxMM_METRIC) )
            return false;
        if ( parser.Found("p", &opts.penWidth) && opts.penWidth < 1 )
            return false;
        wxString penStyle;
        if ( parser.Found("pen-style", &penStyle) )
        {
            if ( !penStyle.empty() )
            {
                if ( penStyle == wxS("solid") )
                {
                    opts.penStyle = wxPENSTYLE_SOLID;
                }
                else if ( penStyle == wxS("dot") )
                {
                    opts.penStyle = wxPENSTYLE_DOT;
                }
                else if ( penStyle == wxS("long_dash") )
                {
                    opts.penStyle = wxPENSTYLE_LONG_DASH;
                }
                else if ( penStyle == wxS("short_dash") )
                {
                    opts.penStyle = wxPENSTYLE_SHORT_DASH;
                }
                else
                {
                    wxLogError(wxS("Unsupported pen style."));
                    return false;
                }
            }
        }
        if ( parser.Found("w", &opts.width) && opts.width < 1 )
            return false;
        if ( parser.Found("h", &opts.height) && opts.height < 1 )
            return false;
        if ( parser.Found("N", &opts.numIters) && opts.numIters < 1 )
            return false;

        opts.testBitmaps = parser.Found("bitmaps");
        opts.testImages = parser.Found("images");
        opts.testLines = parser.Found("lines");
        opts.testRawBitmaps = parser.Found("rawbmp");
        opts.testRectangles = parser.Found("rectangles");
        opts.testCircles = parser.Found("circles");
        opts.testEllipses = parser.Found("ellipses");
        opts.testTextExtent = parser.Found("textextent");
        opts.testMultiLineTextExtent = parser.Found("multilinetextextent");
        opts.testPartialTextExtents = parser.Found("partialtextextents");
        if ( !(opts.testBitmaps || opts.testImages || opts.testLines
                    || opts.testRawBitmaps || opts.testRectangles
                    || opts.testCircles || opts.testEllipses
                    || opts.testTextExtent || opts.testPartialTextExtents) )
        {
            // Do everything by default.
            opts.testBitmaps =
            opts.testImages =
            opts.testLines =
            opts.testRawBitmaps =
            opts.testRectangles =
            opts.testCircles =
            opts.testEllipses =
            opts.testTextExtent =
            opts.testPartialTextExtents = true;
        }

        opts.usePaint = parser.Found("paint");
        opts.useClient = parser.Found("client");
        opts.useMemory = parser.Found("memory");
        if ( !(opts.usePaint || opts.useClient || opts.useMemory) )
        {
            opts.usePaint =
            opts.useClient =
            opts.useMemory = true;
        }

        opts.useDC = parser.Found("dc");
        opts.useGC = parser.Found("gc");
#if wxUSE_GLCANVAS
        opts.useGL = parser.Found("gl");
        if ( opts.useGL )
        {
            if ( opts.useDC || opts.useGC )
            {
                wxLogError("Can't use both OpenGL and normal graphics.");
                return false;
            }
        }
        else // Not using OpenGL
#endif // wxUSE_GLCANVAS
        {
            if ( !(opts.useDC || opts.useGC) )
            {
                opts.useDC =
                opts.useGC = true;
            }
        }

        opts.renderer = GraphicsBenchmarkOptions::Default;
#ifdef __WXMSW__
        wxString rendererName;
        if ( parser.Found("renderer", &rendererName) )
        {
            if ( !opts.useGC )
            {
                wxLogError("Renderer can be specified only when using graphics.");
                return false;
            }
            if ( !rendererName.empty() )
            {
                if ( rendererName == wxS("gdiplus") )
                {
                    opts.renderer = GraphicsBenchmarkOptions::GDIPlus;
                }
                else if ( rendererName == wxS("direct2d") )
                {
                    opts.renderer = GraphicsBenchmarkOptions::Direct2D;
                }
                else if ( rendererName == wxS("cairo") )
                {
                    opts.renderer = GraphicsBenchmarkOptions::Cairo;
                }
                else
                {
                    wxLogError( wxS("Unknown renderer name.") );
                    return false;
                }
            }
        }
#endif // __WXMSW__

        return true;
    }

    virtual bool OnInit()
    {
        if ( !wxApp::OnInit() )
            return false;

        new GraphicsBenchmarkFrame;

        return true;
    }
};

wxIMPLEMENT_APP_CONSOLE(GraphicsBenchmarkApp);
