Having made a good start on [font='courier new']Gx[/font] and having set up a game project that links to the [font='courier new']Gx[/font] headers and lib (in-place in the [font='courier new']Gx[/font] project for ease of update), I realised that a new game will need a new level editor, which for me means using the [font='courier new']Gx[/font] library from within a Qt project.
This has the most impact on the [font='courier new']Gx::GraphicsDevice[/font], which has now undergone some changes to support.
In a game project, [font='courier new']Gx[/font] just creates one graphics window. However, when using [font='courier new']Gx::GrahicsDevice[/font] in a Qt project for a level editor, we commonly want to be able to create any number of graphics views, on the fly, that can be hosted inside widgets.
So in addition to Gx, I have now made a start on a small library called [font='courier new']QGx[/font], which is designed to be a bridge between [font='courier new']Gx[/font] and Qt.
[font='courier new']QGx[/font] currently just contains [font='courier new']QGx::GraphicsDevice[/font], [font='courier new']QGx::GraphicsWidget[/font] and [font='courier new']QGx::Graphics[/font].
In order to facilitate this, [font='courier new']Gx::GraphicsDevice[/font] has been split inside the [font='courier new']Gx[/font] library into [font='courier new']Gx::AbstractGraphicsDevice[/font] and [font='courier new']Gx::GraphicsDevice[/font]. This is not for use in a polymorphic sense - an application will never use [font='courier new']Gx::AbstractGraphicsDevice[/font]. It is just a way to have the [font='courier new']Gx::GraphicsDevice[/font] and [font='courier new']QGx::GraphicsDevice[/font] share most of the methods.
So this is the public interface to [font='courier new']Gx::AbstractGraphicsDevice:[/font]
class AbstractGraphicsDevice{public: AbstractGraphicsDevice(); virtual ~AbstractGraphicsDevice(); void clear(Color color, float z); void setVertexDeclaration(const VertexDeclaration &declaration); void setVertexDeclaration(); void setVertexShader(VertexShader &shader); void setVertexShader(); void setPixelShader(PixelShader &shader); void setPixelShader(); VertexShader &vertexShader(); PixelShader &pixelShader(); void setTexture(Index stage, const Texture &texture); void setTexture(Index stage, const CubeMap &texture); void setTexture(Index stage); void renderTriangleList(const VertexBuffer &buffer); void renderLineList(const VertexBuffer &buffer); void renderTriangleList(const VertexBuffer &buffer, const IndexBuffer &indices); bool isLost() const; bool isReadyToReset() const;};
Extended thus by [font='courier new']Gx::GraphicsDevice:[/font]
class GraphicsDevice : public AbstractGraphicsDevice{public: GraphicsDevice(); ~GraphicsDevice(); bool acquire(HWND hw, const DisplaySettings &settings); bool reset(const DisplaySettings &settings); bool reset(); void release(); void begin(); void end(); DisplaySettings settings() const;};
This gives us the same [font='courier new']Gx::GraphicsDevice[/font] as we had before.
But now, in [font='courier new']QGx[/font] library, the public interface to [font='courier new']QGx::GraphicsDevice[/font] is as follows:
class GraphicsDevice : public Gx::AbstractGraphicsDevice{public: GraphicsDevice(); ~GraphicsDevice(); bool initialise(); void registerWidget(GraphicsWidget *widget); void unregisterWidget(GraphicsWidget *widget); bool reset(); void begin(GraphicsWidget *widget); void end(GraphicsWidget *widget); bool needsResetting() const; bool isReadyToReset() const; void scheduleReset();};
A completely different approach, where you initialise once, then you add one or more [font='courier new']QGx::GraphicsWidget[/font] instances, and you also now specify which [font='courier new']QGx::GraphicsWidget[/font] you are targetting when you [font='courier new']begin()[/font] and [font='courier new']end()[/font].
There is some slight hackery in [font='courier new']QGx::GraphicsDevice[/font] that means we create a dummy device when there are no [font='courier new']QGx::GraphicsWidgets[/font] registered, inside the initialise call. This dummy device is destroyed and removed as soon as we add a [font='courier new']QGx::GraphicsWidget[/font], but it means we have a valid device before we create any [font='courier new']QGx::GraphicsWidgets[/font], so we are thus able to create global resources before we create the widgets. Not ideal, but most of the [font='courier new']Gx::GraphicsResources[/font] map to Direct3D types that need to be passed a valid device when they are created so I can live with it.
[font='courier new']QGx::GraphicsWidget[/font] is designed to be a base class that you derive your graphics views from. You then just implement the [font='courier new']paintEvent()[/font] method on your derived class, and use the accessor [font='courier new']QGx::Graphics &graphics()[/font] to access the [font='courier new']QGx::Graphics[/font] instance, which is an aggregate of [font='courier new']QGx::GraphicsDevice[/font] and a [font='courier new']Gx::GraphicsResources[/font] object (which is a typedef for [font='courier new']Gx::ResourceMap[/font]), all inherited from the [font='courier new']Gx[/font] library.
So you can now use all of the other [font='courier new']Gx::GraphicsResource[/font] classes in the Qt project and any new ones added are available automatically on a rebuild. It is only the set-up and view management methods that are different.
[font='courier new']QGx::GraphicsWidget[/font] provides the following interface:
class Graphics;class GraphicsWidget : public QWidget{ Q_OBJECTpublic: GraphicsWidget(Graphics &graphics, QWidget *parent = 0); ~GraphicsWidget();protected: virtual QPaintEngine* paintEngine() const; virtual void resizeEvent(QResizeEvent *event); Graphics &graphics();private: friend class GraphicsDevice; Graphics *g;};
Internally, it implements the basics of registering and unresigstering itself from the device in the way you would expect:
QGx::GraphicsWidget::GraphicsWidget(Graphics &graphics, QWidget *parent) : QWidget(parent), g(&graphics){ setAttribute(Qt::WA_PaintOnScreen); setAttribute(Qt::WA_NoSystemBackground); setMinimumSize(64, 64); setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); g->device.registerWidget(this);}QGx::GraphicsWidget::~GraphicsWidget(){ if(g) g->device.unregisterWidget(this);}QPaintEngine* QGx::GraphicsWidget::paintEngine() const{ return 0;}void QGx::GraphicsWidget::resizeEvent(QResizeEvent *event){ QWidget::resizeEvent(event); if(g) g->device.scheduleReset();}QGx::Graphics &QGx::GraphicsWidget::graphics(){ return *g;}
So at the moment I have a simple test Qt application that allows you to add and remove graphics views from a [font='courier new']QVBoxLayout[/font] when buttons are clicked. [font='courier new']MyWidget[/font] is an example derived from [font='courier new']QGx::GraphicsWidget[/font], like so:
class MyWidget : public QGx::GraphicsWidget{public: MyWidget(QGx::Graphics &graphics, Gx::Color color, QWidget *parent);protected: virtual void paintEvent(QPaintEvent *event);private: Gx::Color color;};MyWidget::MyWidget(QGx::Graphics &graphics, Gx::Color color, QWidget *parent) : QGx::GraphicsWidget(graphics, parent), color(color){}void MyWidget::paintEvent(QPaintEvent *event){ if(graphics().device.isLost()) return; graphics().device.begin(this); graphics().device.clear(color, 1.0f); graphics().device.end(this);}
So just like any other Qt widget really. In the [font='courier new']paintEvent()[/font], we first check to ensure the device is valid and, if so, we use the protected [font='courier new']graphics()[/font] method to get hold of the device and the resources map and use all the methods we inherit from [font='courier new']Gx::GraphicsDevice[/font] to do whatever rendering we like. The rendering is now constrained to this particular graphics view, whose size is taken from the widget.
If the widget is resized, this triggers a [font='courier new']reset()[/font] internally.
The main loop of the program fires a timer over and over, which calls a method on [font='courier new']MainWindow[/font]. This method calls back a method on [font='courier new']QGx::Graphics[/font] which checks for device lost and so on:
bool QGx::Graphics::testForReset(){ if(device.isLost()) { if(!device.isReadyToReset()) { return false; } } if(device.isReadyToReset()) { device.scheduleReset(); } if(device.needsResetting()) { for(auto &r: resources) { if(r.isDeviceBound()) { r.release(); } } if(!device.reset()) { return false; } for(auto &r: resources) { if(r.isDeviceBound()) { r.reset(device); } } deviceReset(*this); } return true;}
The [font='courier new']MainWindow[/font] timer method then emits a [font='courier new']render()[/font] signal, which we connect to the [font='courier new']update()[/font] slot on any [font='courier new']QGx::GraphicsWidget[/font] classes we create.
void MainWindow::renderTimeout(){ if(!graphics.testForReset()) { return; } emit render();}
So when we hit the button to create a new view in [font='courier new']MainWindow[/font], it is just standard [font='courier new']QWidget[/font] stuff now, except we have to pass the [font='courier new']QGx::Graphics[/font] to the widget's constructor.
void MainWindow::buttonPressed(){ static Gx::Color colors[3] = { Gx::Color(1, 0, 0, 1), Gx::Color(0, 1, 0, 1), Gx::Color(0, 0, 1, 1) }; static int index = 0; MyWidget *widget = new MyWidget(graphics, colors[index++], this); if(index >= 3) index = 0; connect(this, SIGNAL(render()), widget, SLOT(update())); centralWidget()->layout()->addWidget(widget);}
And when we hit the button to delete the last view in the list:
void MainWindow::removePressed(){ QLayoutItem *i = centralWidget()->layout()->takeAt(centralWidget()->layout()->count() - 1); delete i->widget(); delete i;}
That automatically takes care of unregistering the widget from the [font='courier new']QGx::GraphicsDevice[/font]'s internal list.
So all ready to go when I want a GUI application that uses Direct3D windows now. Thankfully my old level editor is working well enough to not have to worry about that quite yet.
I've added the [font='courier new']GxPhysics[/font] module to Gx now as well. It isn't fully formed yet, but it is essentially a [font='courier new']Gx::Physics[/font] object representing the world, a [font='courier new']Gx::Body[/font] object that wraps up Bullet's [font='courier new']btRigidBody[/font] and a whole bunch of shapes deriving from [font='courier new']Gx::Shape[/font] which wrap individual Bullet shape classes such as [font='courier new']Gx::Polyhedron[/font] for [font='courier new']btConvexHull[/font] and [font='courier new']Gx::Sphere[/font] for [font='courier new']btSphere[/font] and so on.
I've started a bare-bones game project now that can load physics objects and graphic meshes from my old level editor and display the level and a debug render of the physics polyhedra. As I'm working now, I'm adding things to [font='courier new']Gx[/font] as and when I need them which is proving a reasonable work-flow - lots of jumping between two projects and lots of clean/rebuild going on, but at least things feel a bit tidier and setting up new projects in the future will be a lot easier.
Hopefully soon I'll achieve something pretty enough to post some screenshots instead of all this code. We'll see :)
Thanks for stopping by.
I kind of wonder if rendering to a renderTarget/frameBuffer and then displaying the image in a Qt window would be better, sure you'll kind of display each image twice but it shouldn't be that slower.