tallforasmurf / CHIP8IDE

An interactive development and emulation environment for the CHIP8/SCHIP system
30 stars 1 forks source link

Qt bug(?) display stops updating #22

Closed tallforasmurf closed 7 years ago

tallforasmurf commented 8 years ago

Running kaleid_02.asm at 100inst/tick or above, after a time ranging from seconds to a minute, the virtual screen stops updating, as if the emulated program had frozen. However, it has not, the emulator is still running.

If you obscure the window e.g. by clicking on the WingIDE window so it covers CHIP8IDE windows, then bring the app to the front again via the Dock, the display updates to show a new content, probably reflecting all the DRAWs that have been going on. But it quickly freezes again.

Note you can let JOUST23 run all night with its attract screen running and it does not have this problem.

In this situation if you stop the emulator and click LOAD on the source, which forces a reset_vm() which in turn definitely calls display.clear(), the display does not (apparently) clear. However if you again cover the app and uncover it, then it updates to show it is clear.

It is like Qt gets tired of repeatedly showing the display QLabel over and over and just stops. The OS's repaint signal makes it start up again.

I'm calling this a WONT FIX and hoping it will go away if/when I upgrade to Qt5.7. But it should be tested for on other platforms than Mac.

tallforasmurf commented 7 years ago

Capping inst/tick at 100, which at least delays the onset if not preventing it.

tallforasmurf commented 7 years ago

Delays it, doesn't prevent it. What we are getting now is one of two crashes. Always in thread 0, the thread running the Qt app. Sometimes "pure virtual function called!" which usually indicates an attempt to use a method of an object that has been deallocated. Other times a memory access problem. I still think this is a Qt bug.

tallforasmurf commented 7 years ago

After commit 0d16fb8 with the change of pixmap handling, this problem remains but is (a) harmless (b) perhaps macos-only? (c) a Qt problem not mine.

Refer to the Screen.paint_pixel_list() method (https://github.com/tallforasmurf/CHIP8IDE/blob/master/display.py#L249). Each time the CHIP-8 program does a DRAW instruction, the list of pixels to paint comes here. This code updates the pixels in the QPixmap that is displayed as the emulated screen.

After updating all the pixel-rectangles, the last step is to call self.update() which schedules a repaint of that widget (the QLabel with its pixmap). See http://doc.qt.io/qt-5/qwidget.html#update which says among other things, "Calling update() several times normally results in just one paintEvent() call."

What happens now is that after some seconds, from 30 to 2 or 3, variable, the emulated screen stops updating. The CHIP-8 program (e.g. kaleid_2.asm is a good test case) is still running. If you hid the app and un-hide it, the emulated screen updates to show all changes since it stopped updating, and now updates normally for another 2 or more seconds, then stops again.

I am saying this is totally a Qt problem, perhaps related to the QApplication trying to optimize for frequent update() calls. Whatever, I can't fix it. Capping the inst/tick at 100 minimizes the problem in that kaleid_2.asm goes for quite a while before appearing to stop.

Strangely JOUST23.asm can go in attract mode updating the flying horses all day without stopping.

tallforasmurf commented 7 years ago

Note, I tried using self.repaint() instead of self.update() and not only did it produce bad flicker (as indicated by the Qt doc) but it also led to another crash.

tallforasmurf commented 7 years ago

On Ubuntu this happens very quickly and is a very serious problem in the bundled app only. Running from source, it will run kaleid_02.asm for 30 seconds before freezing but from the bundled exe, barely 1 second. Covering the display window and revealing it updates the display but it almost instantly freezes again. Taking off the won't-fix label. This is serious and has to be fixed. How?

tallforasmurf commented 7 years ago

Yup, even worse on windows, hiding and revealing the display does not update, in fact it does not update even after a LOAD (which should call clear). Invaders freezes quickly. Less quickly running from source, but it does freeze. (Note INVADER has bugs, it forgets to display some saucers while they still exist, that is not the issue).

tallforasmurf commented 7 years ago

Commit #ae73a95 changes the behavior significantly. On Mac, kaleid_02 will run for many minutes, altho it does occasionally freeze, but starts up again if hidden and revealed then runs for more minutes. On Windows however it only runs for a few seconds. Minimize and restore, it updates but quickly stops. Ubuntu same. (These are running from source).

The only effect of #ae73a95 is to drastically reduce the number of times a QPainter is created and then destroyed. I have been suspecting some connection between PyQt/Qt object destruction and Python garbage collection, and this appears to validate that. However, with it out of the way we still have a problem.

To quote the doc re QWidget.update(),

Updates the widget unless updates are disabled or the widget is hidden... does not cause an immediate repaint; instead it schedules a paint event for processing when Qt returns to the main event loop... Calling update() several times normally results in just one paintEvent() call.

But the sequence is this: The emulator thread calls chip8.step() which executes a DRAW instruction which calls paint_pixel_list which does one update() call. Then chip8 returns and the RunThread, before doing another step(), calls QCoreApplication.processEvents(). So every time the display does an update(), there is a return to the main event loop. So there is no reason for the Q(Core/GUI)Application object to batch screen updates.

What it acts like is, either the updatesEnabled property of QWidget is wrongly treated as False, or the isVisible() status is wrongly False ("unless updates are disabled or the widget is hidden") so I am going to insert some debugging code to test this.

tallforasmurf commented 7 years ago

Testing not self.isVisible() and not self.updatesEnable() ahead of every update produced nothing. However, inserting the single line self.setUpdatesEnabled(True) ahead of the self.update() call produced a MAJOR improvement in Ubuntu, like MacOS it now goes for minutes before stopping, and can be restarted by just dragging a corner of the window -- causing a resizeEvent which is ignored by my code because the emulator is running.

However after about 10 minutes it got to a state where it would not update continuously at all, but would just update on a resize or a focus in/out.

Now test that change in windows, because if it goes minutes before the first freeze, that's almost good enough.

tallforasmurf commented 7 years ago

Right, same as Ubuntu: goes for a few minutes, then gets to a mode where it will only update if you resize or focus in/out. Its like update fatigue...

What's annoying is that JOUST23 can run in attract mode forever.

tallforasmurf commented 7 years ago

Yes, adding two lines after self.update() fixed this on all three platforms:

         self.update( )
+        QCoreApplication.processEvents( )
+        QTest.qWait(1)

Neither alone was sufficient. Who knows what magic is being done? I am still convinced this is a Qt bug but now that I have a workaround, I don't care.