Open Narann opened 9 years ago
Okay, so the main issue @twinaphex is saying, is that once the frontend does
CoreDoCommand(M64CMD_EXECUTE, ...);
, that's it. The frontend has given control to the emulator, and will not get it back until the ROM is closed. The issue is more about control transfer granularity than reentrancy.
Support in this repository for frontends that request one frame at a time would require:
CoreDoCommand
, M64CMD_EXECUTE_FRAME
.stop
variable: the VI interrupt would need to set stop
if the M64CMD_EXECUTE_FRAME
command was used, in a different way than the use of stop
for critical failures in opcodes (NI
, RESERVED
, MTC0
, etc.) so that it can be undone before the next command. For example, stop |= STOP_END_OF_FRAME;
versus stop |= STOP_CRITICAL_FAILURE;
.init_blocks();
, new_dynarec_init();
and <some variable> = 0xA4000040;
to execute the N64 boot ROM code, which really only needs to be done once.free_blocks();
and new_dynarec_cleanup();
.3. and 4. don't cause any problems as they are entirely contained within the Core. 1. would need consideration in the API and appropriate versioning and documentation, so that frontends can continue to use M64CMD_EXECUTE
. 2. could require adjustments to the asynchronous exit of the Core, for example upon Ctrl+C in mupen64plus-ui-console, and changing that use of the stop
variable. It would also require changes to the New Dynarec [edit: and Hacktarux JIT]'s return from VI interrupts on all architectures to make it check for stop
and return to the caller.
Would that be sufficient?
[P.S. the function is main_run
, not make_run
.]
that's it. The frontend has given control to the emulator, and will not get it back until the ROM is closed. The issue is more about control transfer granularity than reentrancy.
Indeed. In panui I have to run two threads just to get things rolling, and then I have no per-frame accuracy. I would love a non-blocking main execute, but that's beside this point.
The biggest problem I can see with some kind of "per frame execution from the frontend" is how the framelimiter is going to work. How does the frontend know that the core isn't still running a frame? How much work do we want the frontend to have to do with regards to waiting for the next frame when it wants this kind of control? Does it make more sense to implement the level of control a frontend would use frame-level execution control (i.e. realtime rewind) from inside the core itself? Do I even understand the problems at hand enough to talk about them?
It's actually pretty much exactly on point though :)
Also, currently frameskip and synchronisation are the roles of the graphics and audio plugins, respectively. Perhaps that'd need changed, too.
I think letting the graphics and audio plugins do frameskip and sync is a good idea, but you should be able to override it with frontend-specific functions. Similar to how the transitionary audio system works.
1) A new command for CoreDoCommand, M64CMD_EXECUTE_FRAME.
This is something I did not do. I'll admit that the way I went about it was a bit of a dirty hack as I didn't have much reason to care about the internal core API, but I understand that for upstream we'd want to do it properly so that it fits with the rest of the emulator API, and of course I'd be happy to switch to the upstream friendly solution once done.
2) Modifications to the use of the stop variable: the VI interrupt would need to set stop if the M64CMD_EXECUTE_FRAME command was used, in a different way than the use of stop for critical failures in opcodes (NI, RESERVED, MTC0, etc.) so that it can be undone before the next command. For example, stop |= STOP_END_OF_FRAME; versus stop |= STOP_CRITICAL_FAILURE;.
You might want to check the libretro fork - similar work was done about a year ago or so. I don't pretend that any of it is perfect or necessarily done in the right way, you be the judge of that.
3) Given that the emulation function can be repeated once it returns, any R4300 emulator that does anything before its execution would need its initialisation and its execution to be split into 2 functions. That's for things like init_blocks();, new_dynarec_init(); and
= 0xA4000040; to execute the N64 boot ROM code, which really only needs to be done once.
Some of this work has already been done in the libretro fork, so you might want to check it out for inspiration. Any suggestions on how to improve it or problems you spot with it are welcome.
4) Given that the emulation function can be repeated once it returns, any R4300 emulator that does anything after its execution would need its execution and its finalisation to be split into 2 functions. That's for things like free_blocks(); and new_dynarec_cleanup();.
Same here.
Also, I'd mention that the savestate code will need similar work - it will also need splitting up into several functions. Some of this work has also been done in the libretro fork. Note however that I don't do file I/O of any kind in the libretro version of mupen64plus-core, so all of the file I/O in the savestate code has been taken out. Just to let you know in advance in case your'e wondering why the code diverges from upstream there.
An addendum if you do intend to check out the libretro fork for inspiration: the define 'SINGLE_THREAD' has to be defined at compile time. There might still be edge cases with it.
Normally, this define is left undefined for regular builds, since there are still some issues with it. The splitting up of functions like 'main_run' has been done regardless of whether SINGLE_THREAD is defined or not.
'SINGLE_THREAD' tries to make it possible to run Mupen64plus on the same main thread that libretro is running at, effectively negating the need for a second thread to run Mupen64 on. Note however that 'SINGLE_THREAD' currently does not work with 'new_dynarec' - so a solution that makes it possible for the 'old dynarec' and 'new dynarec' to both work would have to be explored for upstream.
It would be great if this could be looked at, this introduces some big portability problems. Some sort of M64CMD_EXECUTE_FRAME
function would make it a lot easier to port this to other platforms (like RetroArch)
@twinaphex discussed about making
make_run
function reentrant here. Here are his words:@Nebuleon's answer:
This ticket has been created to discuss about if this system need to be put in the core and how it would be efficiently integrated.
Please continue this discussion here. :)