jueves, 24 de octubre de 2013

Qt 5 and SFML 2.0 Integration

Hi all!

Today, I'm gonna show you my last fight and victory (for now :)). The goal: integrating SFML with Qt. Yeah, too many abbreviations... ok, let's go step by step.

SFML, as mentioned in earlier posts, is an efficient, easy to learn library that wraps OpenGL as a set of higher-level classes and methods. As its main purpose is to draw (or technically, render) things on the screen, it is usually referred as a renderer library. But it doesn't go beyond that: it's not a framework that manages the screen by screen graphs or trees, such as Ogre3D, for example. It is just a nicer entry point to OpenGL.

On the other hand, Qt (pronounced 'cute'), is a framework for developing Graphical User Interfaces (GUI) applications. As such, it provides an API to let developers create buttons, windows, labels, etc in an easy manner. Its (very) complete API also allows developers to render things on the windows. 

The question is: why would it be a good idea to integrate these two libraries? Well, SFML is a very good renderer, but it lacks the capability to create complex GUIs, whereas Qt has the opposite pros and cons. Therefore, the integration yields the breeding ground for nice game tools development, for instance, a level editor. And this is actually the reason why I wanted this integration. I can take advantage of my current knowledge of SFML, and just need to learn some concrete parts of Qt, concretely, the parts for drawing windows stuff (toolbars, menus, etc) and for receiving interaction events from the user.

In addition, Qt brings a very complete IDE (Integrated Development Environment) for C++, which according to many people in forums, it is better if you want to work in C++ than XCode (the Mac OS default IDE), which is more fine-tuned to work with Objective-C. The Qt IDE is called Qt Creator.

First of all, the specification of the problem:
Operating System: Mac OS X 10.7.5
Qt: 5.5.1 (Clang 3.1, 64 bit) -> Clang is the name of the C++ compiler to be used and is installed automatically when you install Qt.
Qt Creator: 2.8.1
SFML: 2.0

Once you have downloaded everything you need (correct SFML and Qt packages), you must follow the following steps:

1) Open a new Qt Creator project. You have to let Qt Creator know where it can find the headers and libraries of SFML. You can copy the following lines and paste them in the .pro file of the Qt Creator project. IMPORTANT: Everything between '< >' must be set appropriately according to your installation settings.

### TO ALLOW SFML LIBRARY ###
CONFIG_APP_NAME = <Name of your application (in Qt Creator)>
INCLUDEPATH += <Path of installation of SFML>/SFML/include
DEPENDPATH += <Path of installation of SFML>/SFML/include
CONFIG(release, debug|release): 
LIBS += -L/<Path build of SFML>/SFML-build/lib -lsfml-audio 
-lsfml-graphics -lsfml-network -lsfml-window -lsfml-system
CONFIG(debug, debug|release): 
LIBS += -L<Path build of SFML>/SFML-build/lib -lsfml-audio-d 
-lsfml-graphics-d -lsfml-network-d -lsfml-window-d -lsfml-system-d
macx {
    QMAKE_POST_LINK += $(MKDIR) 
$${CONFIG_APP_NAME}.app/Contents/Frameworks &&
    QMAKE_POST_LINK += $${QMAKE_COPY} -r 
<Path build of SFML>/SFML-build/lib/* 
$${CONFIG_APP_NAME}.app/Contents/Frameworks
}

Before macx, we're simply doing what I explained: more precisely, we are telling to the compiler where it can find the SFML headers, and to the linker where it can find the SFML libraries. The instructions after the macx (which are QMake instructions), make the following: first, it creates a directory called Framework under <Application path>.app/Contents/, and then, it copies the SFML libraries in this directory. Why? Because the Mac OS loader reads the binaries of these libraries from this location. As simple as that. If you want to avoid problems, I suggest you at this point disallowing 'Shadow build' in Qt Creator (more information here).

2) Once these configuration issues are solved, let's perform the actual integration. As explained here, Qt extensions are done by means of extending QWidget class. So, we have to create a new QWidget, which is the SFML RenderWindow. The code changes a bit with respect to the SFML 1.6 tutorial. I'm simply going to paste here the raw code you will need for the different files. For a proper explanation though, I suggest you to read the aforementioned tutorial. 

QSFMLCanvas.h
#ifndef QSFMLCANVAS_H
#define QSFMLCANVAS_H
#include <QWidget>
#include <SFML/Graphics.hpp>
#include <QTimer>
class QSFMLCanvas : public QWidget, public sf::RenderWindow
{
//Q_OBJECT
public:
explicit QSFMLCanvas(QWidget *parent, const QPoint& Position, const QSize& Size, unsigned int FrameTime = 0);
virtual void showEvent(QShowEvent*);
virtual QPaintEngine* paintEngine() const;
virtual void paintEvent(QPaintEvent*);
virtual ~QSFMLCanvas();
virtual void OnInit();
virtual void OnUpdate();
private:
QTimer myTimer;
bool myInitialized;
};
#endif // QSMLCANVAS_H


QSFMLCanvas.cpp
#include "qsfmlcanvas.h"
#ifdef Q_WS_X11
#include <Qt/qx11info_x11.h>
#include <X11/Xlib.h>
#endif
#include <iostream>
QSFMLCanvas::QSFMLCanvas(QWidget* Parent, const QPoint& Position, const QSize& Size, unsigned int FrameTime) : QWidget(Parent),
myInitialized (false)
{
// Setup some states to allow direct rendering into the widget
setAttribute(Qt::WA_PaintOnScreen);
setAttribute(Qt::WA_OpaquePaintEvent);
setAttribute(Qt::WA_NoSystemBackground);
// Set strong focus to enable keyboard events to be received
setFocusPolicy(Qt::StrongFocus);
// Setup the widget geometry
move(Position);
resize(Size);
// Setup the timer
myTimer.setInterval(FrameTime);
}
QSFMLCanvas::~QSFMLCanvas() {}
void QSFMLCanvas::showEvent(QShowEvent*)
{
if (!myInitialized)
{
// Under X11, we need to flush the commands sent to the server to ensure that
// SFML will get an updated view of the windows
#ifdef Q_WS_X11
//XFlush(QX11Info::display());
#endif
// Create the SFML window with the widget handle
RenderWindow::create((void *) winId());
// Let the derived class do its specific stuff
OnInit();
// Setup the timer to trigger a refresh at specified framerate
connect(&myTimer, SIGNAL(timeout()), this, SLOT(repaint()));
myTimer.start();
myInitialized = true;
}
}
QPaintEngine* QSFMLCanvas::paintEngine() const
{
return 0;
}
void QSFMLCanvas::paintEvent(QPaintEvent*)
{
// Let the derived class do its specific stuff
OnUpdate();
// Display on screen
RenderWindow::display();
}
void QSFMLCanvas::OnInit() {}
void QSFMLCanvas::OnUpdate() {}


MyCanvas.h 
 #ifndef MYCANVAS_H
#define MYCANVAS_H
#include "qsfmlcanvas.h"
#include <SFML/Graphics.hpp>
class MyCanvas : public QSFMLCanvas
{
public :
MyCanvas(QWidget* Parent, const QPoint& Position, const QSize& Size);
void OnInit();
void OnUpdate();
private :
sf::Clock myClock;
sf::Texture myImage;
sf::Sprite mySprite;
};
#endif // MYCANVAS_H


MyCanvas.cpp
#include "mycanvas.h"
#include <iostream>
#include <string>
#include <QDir>
MyCanvas::MyCanvas(QWidget* Parent, const QPoint& Position, const QSize& Size) : QSFMLCanvas(Parent, Position, Size)
{
}
void MyCanvas::OnInit()
{
// Load the image
std::cout << "onInit" << std::endl;
QString dir = QDir::currentPath();
std::string utf8_text = dir.toUtf8().constData();
std::cout << "HELLO: " << utf8_text << std::endl;
if (!myImage.loadFromFile(utf8_text + "/chef.png")) {
std::cout << "Loading error"<< std::endl;
} else {
std::cout << "Image was loaded fine" << std::endl;
}
// Setup the sprite
mySprite.setTexture(myImage);
mySprite.setPosition(150, 150);
std::cout << "setting the texture of the sprite" << std::endl;
//mySprite.setCenter(mySprite.GetSize() / 2.f);
myClock.restart();
}
void MyCanvas::OnUpdate()
{
// Clear screen
RenderWindow::clear(sf::Color(0, 128, 0));
// Rotate the sprite
mySprite.rotate(myClock.getElapsedTime().asSeconds() * 100.f);
// Draw it
RenderWindow::draw(mySprite);
myClock.restart();
}


main.cpp 
#include <QApplication>
#include "mycanvas.h"
#include <QFrame>
int main(int argc, char *argv[])
{
QApplication App(argc, argv);
// Create the main frame
QFrame* MainFrame = new QFrame;
MainFrame->setWindowTitle("Qt SFML");
MainFrame->resize(400, 400);
MainFrame->show();
// Create a SFML view inside the main frame
MyCanvas* SFMLView = new MyCanvas(MainFrame, QPoint(20, 20), QSize(360, 360));
SFMLView->show();
return App.exec();
}

There is an important consideration. Qt Creator uses a special way to declare the resources (e.g. images) that your application will use. It does so in order to comply with the cross-platform requirement, but it can be quite weird for someone who is not used to it. Basically, you would need to create a Resource file. However, SFML does not understand this, and when you try to execute loadFromFile in the MyCanvas::OnInit method by using this approach, SFML does not find the image. The solution, at least temporal and for testing purposes, is that you place you image inside the <Path application>.app/Contents/MacOS directory, and that you use the piece of code that I have provided above, which will search the image in that directory.

Figure 1. Output of the program. Basically, it's a Qt frame with a SFML renderer window inside the frame. It is showing the chef, which is a sprite (which as usual has been designed by Manuela) that I'm using for an iOS game that will come out very soon :).

I think that's all. If I forgot something or you need more explanations, just let me know and I'll update the post.

EDIT: Some people following this tutorial have encountered the following error: "error: invalid conversion from 'void*' to 'sf::WindowHandle {aka HWND__*}' [-fpermissive] RenderWindow::create((void *) winId());^". This error occurs in the line RenderWindow::create((void *) winId());

The solution, which was posted here, is to change the aforementioned line to this one: RenderWindow::create(reinterpret_cast<sf::WindowHandle>(winId())); (Thanks to delio and hyde for raising the problem and the fix)

Good luck!

8 comentarios:

  1. I've try to copy your source code that can create SFML widget in qt.
    But it gives many errors.

    I have no idea how can I do
    Please suggest me

    Thanks!

    ResponderEliminar
  2. Are you using the same versions of SFML and Qt? Can you post the error messages that you get?

    ResponderEliminar
  3. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  4. what do you mean by "path build of sfml" I mean which directory should I write here. it's not sfml installation path, then what it is?

    ResponderEliminar
  5. Hi,

    By the time of this post, I had two different directories: one called SFML, where you could find the sources, headers, external libraries, and tools (like SFML templates for Xcode); the other directory was SFML-build, where you could find the libraries (e.g. libsfml-audio.dylib). It's been a while since then, and maybe the current installation of SFML creates different directories.

    In any case, the important thing here is that you navigate through the installed SFML folder, and look for where the headers (include folder) and the libraries (lib folder) are located. These would be the paths that you have to write in the .pro file.

    Hope you find the solution and ask me if you have further questions.

    ResponderEliminar
  6. Works perfectly for me, n-th clone of 2048 is comming! ;-)

    ResponderEliminar