Open Poikilos opened 6 months ago
An exact CPU cycle match is not a goal of this project, it really isn't possible due to the way QB64 works. As such a setting to emulate a specific MHz CPU speed just isn't going to be implemented (mostly because it's not really possible to do in an effective way). That said, the other things are a bit different:
TIMER
counting is limited by the speed of the timer interrupt in the old hardware. QB64 matches the same TIMER
resolution by default so I think this should work (though note what I say about this at the end).TIMER
and in weird ways that would be hard to match (and we wouldn't necessarily want to match them anyway).WAIT
examples are interesting. Currently QB64 does not offer a way to wait for a screen refresh, it's an outstanding issue here. That said, once that issue is fixed it sounds like it may be possible to make those WAIT
commands work as well. It would not be directly tied to the screen refresh but QB64's screen redraw (which is always 60 FPS at the moment)SOUND
thing just seems like a bug, but I think @a740g should chime in on that one over on #494.I would say overall though that delays like looping on TIMER
are bad in QB64 because they cause 100% CPU usage while you're looping. New commands like _Delay
and _Limit
can be used to replace these delays and bring down CPU usage. Those do require code changes to add (we can't detect where they should go) but they're hopefully minimal for your programs.
Thanks for providing the alternatives and input on other issues. I suppose (with the suggested optional mode) accessing TIMER could actually sleep CPU when the TIMER global is accessed (let CPU rest 1ms maybe? The math would have to be worked out, but that may be fine enough since even locking to 60fps is 16.6... [repeating 6] ms so it is probably fine-grained enough) so then you'd know where the delay should go (:edit: except for games that just assume a clock speed and don't even do frame locking. That's why I mentioned FLOPS...it would be possible to count the number of FLOPS and limit the FLOPS that way, placing the delay wherever math is done too many times per second, though maybe that is ugly). My goal is to get old programs to work. Certain games have awkward custom licenses not allowing redistribution, but I understand you don't want to encourage bad patterns. I'll try to find a timer example used in this way (which would max CPU if code is used literally) but so far I only found:
Just demos, so it is hard to tell if speed is as expected:
@Poikilos thanks so much for sharing this repo: https://github.com/robhagemans/hoard-of-gwbasic/tree/master !
@grymmjack You're welcome. I found it almost by chance! GitHub (using Linguist) often doesn't label repos as BASIC correctly, but:
while TIMER path:*.bas
.@Poikilos Oh that is a great trick! I will add my own language detection fixes to my repos.
Smart thinking on how to search too! You might find some more by using path:*.bi
and/or path:*.bm
:)
Is your feature request related to a problem? Please describe. Back when QBasic was popular for making indie games, a "tick" wasn't a fixed millisecond or microsecond. It could be a CPU cycle, and entirely dependent on the clock speed of the CPU. Certain old QBasic games, like certain other DOS games, depend on the speed of the CPU in order to run properly. For example, if a game programmed for a 66 MHz processor were played on a 133Mhz processor, the game would be approximately double the speed and be unplayable. Specifically, TIMER seems to be the CPU cycle divided by 18.2 (according to https://www.tek-tips.com/faqs.cfm?fid=917), which seems like entirely different behavior than described at https://qb64phoenix.com/qb64wiki/index.php/TIMER. There were some games that tried to count the number of TIMER ticks in one second, which I tried to do, but once the clock speed got above a certain point, that wouldn't work anymore and would just max out the CPU then cause an integer overflow...unless my math didn't account for it flipping around to 0 more quickly than expected. Even if it did, my code isn't the only code out there. Games that expected TIMER not to flip to 0 (or not to flip to 0 more than once) over the course of 1 frame still wouldn't work. In the worse case, some games don't even attempt to use frame locking--they just assume the computer's speed, and only work correctly on old computers (games of this era typically only run at the correct speed on a 33MHz or 66MHz processor).
Describe the solution you'd like A "virtual MHz" compile option to affect either or both (maybe allow enabling each separately):
Describe alternatives you've considered DOSBox or changing the code: but neither is ideal since compatibility with old code is a goal of this project.
Additional context List of framerate locking methods of old QBasic games (may require being split into different issues):
WAIT &H3DA, 8
: waits for next screen refresh--This is probably the most reliable method, though is technically affected by hardware (such as 120 Hz displays or CRTs with odd refresh rates) and therefore would also need underclocking to produce expected behavior. Examples:WAIT &H3DA, 8, 8
on next line, not sure why)WAIT &H3DA, 8
: https://github.com/goalieca/ancient-code1/blob/master/breakout-qbasic/BREAKOUT.BASWAIT 938, 8
I'm not sure why this is used instead ofWAIT &H3DA, 8
(3DA is 986 in decimal; 938 would be 3AA), but it runs a little fast on my 60Hz display on Fedora 38 and the reason is not clear. Example: Spinball at https://www.qbasic.net/en/qbasic-downloads/games/action-3.htm