Making a render loop
From WxWiki
While wxWidgets is not an ideal tool for something like a game that requires a high performance render loop, you might still want to use a render loop in your application.
This article will attempt to give examples of how it can be done. Note that there may be more possibilities than explained here, I will try to extend this article in the future.
[edit] What not to do
The following ways should not be used, as they can result in obscure errors or crashes that will be very difficult to debug
- Use a loop in a seperate thread (bad because wxWidgets requires all GUI calls to happen in the main thread)
- Use wxYield() in a loop
// bad idea while(1) { render(); wxYield(); }
- Use wxPaintDC outside paint events (use wxClientDC instead.)
[edit] Using idle events
Pros :
- doesn't hog the system, as it renders only when it can
- one of the fastest ways possible in wx
Cons :
- framerate is not necessarly regular
- can stop rendering when you do something else, for instance open a menu
#include "wx/sizer.h" #include "wx/wx.h" class BasicDrawPane : public wxPanel { public: BasicDrawPane(wxFrame* parent); void paintEvent(wxPaintEvent& evt); void paintNow(); void render( wxDC& dc ); DECLARE_EVENT_TABLE() }; class MyFrame; class MyApp: public wxApp { bool render_loop_on; bool OnInit(); void onIdle(wxIdleEvent& evt); MyFrame* frame; BasicDrawPane* drawPane; public: void activateRenderLoop(bool on); }; IMPLEMENT_APP(MyApp) class MyFrame : public wxFrame { public: MyFrame() : wxFrame((wxFrame *)NULL, -1, wxT("Hello wxDC"), wxPoint(50,50), wxSize(400,200)) { } void onClose(wxCloseEvent& evt) { wxGetApp().activateRenderLoop(false); evt.Skip(); // don't stop event, we still want window to close } DECLARE_EVENT_TABLE() }; BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_CLOSE(MyFrame::onClose) END_EVENT_TABLE() bool MyApp::OnInit() { render_loop_on = false; wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); frame = new MyFrame(); drawPane = new BasicDrawPane( frame ); sizer->Add(drawPane, 1, wxEXPAND); frame->SetSizer(sizer); frame->Show(); activateRenderLoop(true); return true; } void MyApp::activateRenderLoop(bool on) { if(on and !render_loop_on) { Connect( wxID_ANY, wxEVT_IDLE, wxIdleEventHandler(MyApp::onIdle) ); render_loop_on = true; } else if(!on and render_loop_on) { Disconnect( wxEVT_IDLE, wxIdleEventHandler(MyApp::onIdle) ); render_loop_on = false; } } void MyApp::onIdle(wxIdleEvent& evt) { if(render_loop_on) { drawPane->paintNow(); evt.RequestMore(); // render continuously, not only once on idle } } BEGIN_EVENT_TABLE(BasicDrawPane, wxPanel) EVT_PAINT(BasicDrawPane::paintEvent) END_EVENT_TABLE() BasicDrawPane::BasicDrawPane(wxFrame* parent) : wxPanel(parent) { } void BasicDrawPane::paintEvent(wxPaintEvent& evt) { wxPaintDC dc(this); render(dc); } void BasicDrawPane::paintNow() { wxClientDC dc(this); render(dc); } void BasicDrawPane::render( wxDC& dc ) { static int y = 0; static int y_speed = 2; y += y_speed; if(y<0) y_speed = 2; if(y>200) y_speed = -2; dc.SetBackground( *wxWHITE_BRUSH ); dc.Clear(); dc.DrawText(wxT("Testing"), 40, y); }
[edit] Using a timer
Pros :
- Regular framerate
- Doesn't stop like idle events when you do something else, like open a menu
Cons :
- Not as fast as idle events
- Implementation quality depends on your platform; some platforms provide better timers than others.
#include "wx/sizer.h" #include "wx/wx.h" class BasicDrawPane; class RenderTimer : public wxTimer { BasicDrawPane* pane; public: RenderTimer(BasicDrawPane* pane); void Notify(); void start(); }; class BasicDrawPane : public wxPanel { public: BasicDrawPane(wxFrame* parent); void paintEvent(wxPaintEvent& evt); void paintNow(); void render( wxDC& dc ); DECLARE_EVENT_TABLE() }; class MyFrame; class MyApp: public wxApp { bool OnInit(); MyFrame* frame; public: }; RenderTimer::RenderTimer(BasicDrawPane* pane) : wxTimer() { RenderTimer::pane = pane; } void RenderTimer::Notify() { pane->Refresh(); } void RenderTimer::start() { wxTimer::Start(10); } IMPLEMENT_APP(MyApp) class MyFrame : public wxFrame { RenderTimer* timer; BasicDrawPane* drawPane; public: MyFrame() : wxFrame((wxFrame *)NULL, -1, wxT("Hello wxDC"), wxPoint(50,50), wxSize(400,200)) { wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); drawPane = new BasicDrawPane( this ); sizer->Add(drawPane, 1, wxEXPAND); SetSizer(sizer); timer = new RenderTimer(drawPane); Show(); timer->start(); } ~MyFrame() { delete timer; } void onClose(wxCloseEvent& evt) { timer->Stop(); evt.Skip(); } DECLARE_EVENT_TABLE() }; BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_CLOSE(MyFrame::onClose) END_EVENT_TABLE() bool MyApp::OnInit() { frame = new MyFrame(); frame->Show(); return true; } BEGIN_EVENT_TABLE(BasicDrawPane, wxPanel) EVT_PAINT(BasicDrawPane::paintEvent) END_EVENT_TABLE() BasicDrawPane::BasicDrawPane(wxFrame* parent) : wxPanel(parent) { } void BasicDrawPane::paintEvent(wxPaintEvent& evt) { wxPaintDC dc(this); render(dc); } void BasicDrawPane::paintNow() { wxClientDC dc(this); render(dc); } void BasicDrawPane::render( wxDC& dc ) { static int y = 0; static int y_speed = 2; y += y_speed; if(y<0) y_speed = 2; if(y>200) y_speed = -2; dc.SetBackground( *wxWHITE_BRUSH ); dc.Clear(); dc.DrawText(wxT("Testing"), 40, y); }
