Making a render loop

From WxWiki

Jump to: navigation, search

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); 
}
Personal tools