wxWidgets 绘制实时图形的最佳控件

wxWidgets best control for drawing realtime graphics

我使用 SDL 制作了一个小应用程序来显示一些精灵动画,而且效果非常好。渲染尽可能流畅很重要。

因为现在我需要一些 GUI,所以我决定使用 wxWidgets 3 创建相同的应用程序,但我现在还可以添加一些 GUI 元素,如菜单、模式等。

深入了解 wxWidget 的 wiki 后,我发现有不同的方法可以绘制成 window。

例如:

我还发现了其他类型的 类,例如“Graphics Context 类”,我仍然不确定它们是否对我需要的。

我的应用程序已经使用了一个只需要复制的内部缓冲区。

所以我的问题是在 wxWidgets 上最合适的方法是什么? 而且,该解决方案强加了多少 performance/overhead?

wxWidgets 在 window 中绘制像素的不同方法在 performance/overhead 中有什么区别?为什么有人使用 OnPaint 事件而其他人不使用。

这是一种方法:

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

// for all others, include the necessary headers (this file is usually all you
// need because it includes almost all "standard" wxWidgets headers)
#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif

#include <wx/timer.h>
#include <wx/dcclient.h>
#include <wx/rawbmp.h>

///////////// Declarations

class MyFrame : public wxFrame
{
    public:
        MyFrame( wxWindow* parent, int id = wxID_ANY, wxString title = "Demo",
                 wxPoint pos = wxDefaultPosition, wxSize size = wxDefaultSize,
                 int style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL );
        ~MyFrame();

    private:
        // Event Handlers
        void OnPaint( wxPaintEvent& event );
        void OnTimer(wxTimerEvent& event);

        // Helper function
        void RebuildBufferAndRefresh();

        // Private data
        wxWindow* m_renderSurface;
        int m_width;
        int m_height;
        wxBitmap m_bitmapBuffer;
        wxTimer m_timer;
        int m_curRGB;
        unsigned char* m_pixelData;
};

class MyApp : public wxApp
{
    public:
        virtual bool OnInit() wxOVERRIDE;
};

wxDECLARE_APP(MyApp);


///////////// Implementation

MyFrame::MyFrame( wxWindow* parent, int id, wxString title, wxPoint pos,
                  wxSize size, int style )
        :wxFrame( parent, id, title, pos, size, style )
{
    m_width=100;
    m_height=100;
    m_pixelData = new unsigned char[3*m_width*m_height];

    m_renderSurface = new wxWindow(this, wxID_ANY, wxDefaultPosition,
                                     wxSize(m_width,m_height));
    m_renderSurface->SetBackgroundStyle(wxBG_STYLE_PAINT);
    m_renderSurface->Bind(wxEVT_PAINT,&MyFrame::OnPaint,this);

    wxBoxSizer* bSizer = new wxBoxSizer( wxVERTICAL );
    bSizer->Add( m_renderSurface, 0 );
    this->SetSizer( bSizer );
    Layout();

    m_timer.SetOwner(this);
    m_timer.Start(17);
    this->Bind(wxEVT_TIMER,&MyFrame::OnTimer,this);
    m_curRGB=0;
}

MyFrame::~MyFrame()
{
    m_timer.Stop();
    delete[] m_pixelData;
}

void MyFrame::OnPaint( wxPaintEvent& event )
{
    wxPaintDC dc(m_renderSurface);
    if(m_bitmapBuffer.IsOk())
    {
        dc.DrawBitmap(m_bitmapBuffer,0,0);
    }
}

void MyFrame::OnTimer(wxTimerEvent& event)
{
    RebuildBufferAndRefresh();
}

void MyFrame::RebuildBufferAndRefresh()
{
    // Build the pixel buffer here, for this simple example just set all
    // pixels to the same value and then increment that value.
    for ( int y = 0; y < m_height; ++y )
    {
        for ( int x = 0; x < m_width; ++x )
        {
            m_pixelData[3*y*m_width+3*x]=m_curRGB;
            m_pixelData[3*y*m_width+3*x+1]=m_curRGB;
            m_pixelData[3*y*m_width+3*x+2]=m_curRGB;
        }
    }

    ++m_curRGB;
    if(m_curRGB>255)
    {
        m_curRGB=0;
    }

    // Now transfer the pixel data into a wxBitmap
    wxBitmap b(m_width,m_height,24);
    wxNativePixelData data(b);

    if ( !data )
    {
        // ... raw access to bitmap data unavailable, do something else ...
        return;
    }

    wxNativePixelData::Iterator p(data);

    int curPixelDataLoc = 0;
    for ( int y = 0; y < m_height; ++y )
    {
        wxNativePixelData::Iterator rowStart = p;
        for ( int x = 0; x < m_width; ++x, ++p )
        {
            p.Red() = m_pixelData[curPixelDataLoc++];
            p.Green() = m_pixelData[curPixelDataLoc++];
            p.Blue() = m_pixelData[curPixelDataLoc++];
        }
        p = rowStart;
        p.OffsetY(data, 1);
    }

    m_bitmapBuffer=b;
    m_renderSurface->Refresh();
    m_renderSurface->Update();
}


 bool MyApp::OnInit()
{
    MyFrame* frame = new MyFrame(NULL);
    frame->Show();
    return true;
}

wxIMPLEMENT_APP(MyApp);

这里的基本思想是将缓冲区复制到 wxBitmap 中,然后在绘制处理程序中绘制该位图。然后调用 Refresh 和 Update 触发绘制处理程序。这样做的原因是系统也会由于各种原因调用paint方法,例如鼠标光标运行在渲染表面上。通过这种方式,可以为您和系统调用刷新表面绘制一个位图。

在上面发布的简单示例中,我只是使用一个简单的计时器每秒大约调用 RebuildBufferAndRefresh 方法 60 次。您的应用程序可能有更好的方法来确定何时需要刷新,您可以使用该方法调用 RebuildBufferAndRefresh。

显然大部分工作是在 RebuildBufferAndRefresh 方法中完成的。该方法分为 3 个部分。第一部分构建内部缓冲区。出于上述原因,第二部分将该缓冲区复制到 wxBitmap 中。第三部分只是调用refresh和update强制重绘render surface

可能有更好的方法来做到这一点,但我认为这是最直接的 wxWidgets-ey 不断渲染像素数据缓冲区到 window。

至于这种方法需要多少开销,它基本上是从缓冲区到位图的内存复制,但嵌套循环的效率会低于真正的内存复制。之后,绘制处理程序只绘制位图。这可能会通过对系统后备缓冲区的 blit 来完成,并且应该非常快。希望对您有所帮助。