QGraphicsScene not repainting
-
I am writing a GameBoy emulator using Qt as the front-end. Right now, I am just trying to figure out how to redraw the screen, frame by frame. I have a Qthread subclass called RenderThread defined here:
@class RenderThread : public QThread
{
Q_OBJECT
public:
explicit RenderThread(QGraphicsScene *scene, QObject *parent = 0);
void run() Q_DECL_OVERRIDE;signals:
public slots:
private:
QGraphicsScene *scene;};@
The run method is implemented as follows:
@void RenderThread::run(){
for(;;){ for(QGraphicsItem *o : scene->items()){ Pixel *p = (Pixel*) o; p->setColor(qrand() & 1 ? Qt::blue : Qt::red ); } scene->update(); }
}@
The Pixel class is simply a QGraphicsItem subclass that contains a setColor method. The Paint method is as follows:
@void Pixel::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget){
painter->setBrush(QBrush(color)); painter->drawRect(rect); //rect as of now is 1 unit by 1 unit
} @
When I first start up the program, I initialize all of the Pixel objects to black. However, as you can see in RenderThread, I want to have each pixel flashing red and blue, but all the screen does is stay black. I know this is far from the most efficient solution (the GUI has some slow down due to this), but I just want something to redraw on to the screen. What is one of the better ways of doing this? Should I be using pure OpenGL?
Thank you very much!
-
Painting can only be done from the main ("gui") thread. Same goes for changing the scene content.
-
Thank you very much!
What do you think would be the best approach for drawing to the screen? I am reading this example: http://qt-project.org/doc/qt-4.8/widgets-scribble.html
I am thinking of subclassing QWidget and overriding paintEvent() which will draw my pixels onto the widget. According to the example, this may not be the best solution and thus it uses QImage. Is QImage the best class to draw onto? Should I subclass QGraphicsView?
Thanks!
-
Is there any special reason you need to paint in a separate thread? Because this will severely restrict your options, and will cause some overheads.
My personal choice for something game-like would be GraphicsView. If you need to draw frames at a certain framerate, you might take a look at QGraphicsScene::advance (). It basically notifies all items in your scene that the next frame is about to be drawn (allowing them to do their updates, move to new positions, do collision detection,...).
-
I was using a separate thread because I was modeling it after another GameBoy emulator called GearBoy on GitHub. But, he was using a QGLWidget, so maybe that's why he did it.
In any case, I tried what you asked. I looked up some documentation on how to use QGraphicsScene::advance() and I found this: http://www.bogotobogo.com/Qt/Qt5_QGraphicsView_animation.php
I overrided the advance method in Pixel here:
@
void Pixel::advance(int phase){
if(phase == 1){
color = qrand() & 1 ? Qt::blue : Qt::red;
}
}@I also set up a timer that should fire the scene's advance() slot every 500ms
@MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);QGraphicsScene *scene = new QGraphicsScene(this); QTimer *timer = new QTimer(this); connect(timer, SIGNAL(timeout()), scene, SLOT(advance())); for(int i = 0; i < GBEmu::GameBoy::SCREEN_LENGTH; i++){ for(int j = 0; j < GBEmu::GameBoy::SCREEN_HEIGHT; j++){ scene->addItem(new Pixel(Qt::black, i, j, 1)); } } ui->graphicsView->setScene(scene); timer->start(500);
}@
But it seems the Pixels stay all black and never change. I put a breakpoint at Pixel::advance() and Pixel::paint() and they are being called. Is there some method I am missing for it to update to the screen?
Thanks again!
EDIT: I actually got it to update by connecting both advance() and update(). However, it seems only the bottom right hand corner pixel is updating. I shall update this post if I can fix it, but any help is greatly appreciated. Thanks!
-
I believe you are missing a call to update() in the advance() virtual method of the pixel. In the sample you linked, the setPos call does this implicitly. In your case, where you only change a member variable, you have to do it yourself.
Calling update() on the scene directly should do the trick as well, however. So to check the issue, I would go back a step and give each Pixel a different color in your constructor (e.g. increase red component for x, green component for y, which should give you a nice interpolation effect).
It might also help if you posted the complete code of your Pixel class. Maybe the issue is somewhere in there.
-
Hi,
even you would succeed to update a scene "pixel" by "pixel" in this way perhaps your application would be inefficient and slow, as you have already pointed out; I think it could be done other way around, by pursuing the random pixel color effect :
-
prepare a set of pre rendered images with pixel colors randomized, with photoshop or other appropriate tool: I read something on how to do it and it seems not easy though but I think is reachable; prepare more than the magic "24 fps", let's say 30-40 and store them somewhere; the bigger the number the much will be the randomization of their selection
-
load them as pixmap and store in a container
-
us just one graphics pixmap item for showing them
-
randomize the pixmap container's index selection: this can be done in a thread's loop but I think is there a chance to integrate this randomization in the app's event loop (and geting its result in an async way ) by the qt's machine state mechanism: in this later case you still need a thread for emitting signals for randomization, signal which would be associated with state machine's transitions
Cheers!
-
-
Try QGraphicsView::setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
This should force the entire viewport to update.