【问题标题】:wxWidgets best control for drawing realtime graphics [closed]wxWidgets 绘制实时图形的最佳控件 [关闭]
【发布时间】:2023-04-06 16:57:02
【问题描述】:

我使用 SDL 制作了一个小应用程序来显示一些精灵动画,它工作得非常好。渲染尽可能平滑很重要。

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

在深入 wxWidget 的 wiki 之后,我发现有不同的方法可以绘制到窗口中。

例如:

  • wxClientDC
  • wxPaintDC
  • wxGLCanvas

我还发现了其他类型的类,例如“图形上下文类”,我仍然不确定它们是否对我的需要有用。

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

所以我的问题是在 wxWidgets 上最合适的方法是什么? 而且,该解决方案会带来多少性能/开销?

wxWidgets 在窗口内绘制像素的不同方法在性能/开销方面有何不同?为什么有人使用 OnPaint 事件而其他人不使用。

【问题讨论】:

    标签: c++ wxwidgets


    【解决方案1】:

    这是一种方法:

    // 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方法,例如鼠标光标在渲染表面上运行。通过这种方式,可以为您和系统的调用绘制一个位图以刷新表面。

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

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

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

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

    【讨论】:

    • 有趣的解决方案。它实际上适用于我的用例。我仍然想知道为什么还有一个 no-OnPaint 事件版本。有什么不同?
    • 我不确定我是否理解这个问题。如果您问是否可以不处理正在绘制的窗口中的绘制事件,答案不是真的。当鼠标光标移到窗口上、另一个应用程序的窗口移到窗口上或包含框架最小化和恢复时,需要 OnPaint 方法来刷新窗口。如果没有 OnPaint,在这些情况下,窗口将被损坏,直到下次刷新。不必像上面那样将更新图像的调用与 OnPaint 方法结合起来,但应该处理绘制事件。
    猜你喜欢
    • 2019-09-13
    • 2011-02-21
    • 1970-01-01
    • 1970-01-01
    • 2014-09-10
    • 2012-03-22
    • 2015-04-23
    • 1970-01-01
    • 2010-12-15
    相关资源
    最近更新 更多