wxWidgets 绘制实时图形的最佳控件
wxWidgets best control for drawing realtime graphics
我使用 SDL 制作了一个小应用程序来显示一些精灵动画,而且效果非常好。渲染尽可能流畅很重要。
因为现在我需要一些 GUI,所以我决定使用 wxWidgets 3 创建相同的应用程序,但我现在还可以添加一些 GUI 元素,如菜单、模式等。
深入了解 wxWidget 的 wiki 后,我发现有不同的方法可以绘制成 window。
例如:
- wxClientDC
- wxPaintDC
- wxGLCanvas
我还发现了其他类型的 类,例如“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 来完成,并且应该非常快。希望对您有所帮助。
我使用 SDL 制作了一个小应用程序来显示一些精灵动画,而且效果非常好。渲染尽可能流畅很重要。
因为现在我需要一些 GUI,所以我决定使用 wxWidgets 3 创建相同的应用程序,但我现在还可以添加一些 GUI 元素,如菜单、模式等。
深入了解 wxWidget 的 wiki 后,我发现有不同的方法可以绘制成 window。
例如:
- wxClientDC
- wxPaintDC
- wxGLCanvas
我还发现了其他类型的 类,例如“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 来完成,并且应该非常快。希望对您有所帮助。