SNES Emulator (very early test)

Post Reply
ducalex
Posts: 80
Joined: Sun May 19, 2019 3:29 am
languages_spoken: english, french
Has thanked: 18 times
Been thanked: 86 times
Contact:

SNES Emulator (very early test)

Unread post by ducalex » Sun May 26, 2019 9:24 am

Hello!

I've ported snes9x to the Odroid GO. Although I consider this very cool, it is very very early in development and it is not currently playable and might never be.

The ESP32 is, on paper, capable of running SNES at full speed. However snes9x is written to be portable rather than ultra efficient on each specific architectures, which limits the amount of optimization that can be done short of rewriting it. In other words my experiment will probably never run perfectly but I will push it as far as my skills allow me to!



Previous test: https://www.youtube.com/watch?v=eMd9S6ZRh4A

Couple images: https://imgur.com/a/3xQHGuI

Instructions:
Put snes9x.fw in /sdcard/odroid/firmware and put your roms in /sdcard/odroid/roms/snes/.

Caveats:
- ROMS bigger than 2MB won't work, there isn't enough RAM
- It is running very slow
- No sound
- No saves
- No accelerator chips supported

Button mapping:

Code: Select all

SNES     Odroid
A             A
B             B
X           START
Y           SELECT
START   VOLUME
SELECT  MENU
L           None
R          None

Source code
https://github.com/ducalex/snes9x-esp32

Downloads
Attachments
snes9x.zip
Third test build (newest)
Enabled g++ optimization, gained another 5%
(488.62 KiB) Downloaded 161 times
Last edited by ducalex on Thu May 30, 2019 11:44 am, edited 4 times in total.
These users thanked the author ducalex for the post (total 3):
mad_ady (Sun May 26, 2019 2:05 pm) • ripper121 (Sun May 26, 2019 3:49 pm) • odroid (Tue May 28, 2019 10:01 am)

lordhardware
Posts: 110
Joined: Sat Sep 20, 2014 11:56 pm
languages_spoken: english
ODROIDs: U3
Odroid-W
Has thanked: 16 times
Been thanked: 10 times
Contact:

Re: SNES Emulator (very early test)

Unread post by lordhardware » Mon May 27, 2019 2:20 pm

Odd that there's still no replies.

I'll check this out tonight, from what I remember from early emu days, Uniraces was a pretty low req. SNES game so maybe benchmark with that?

pmprog
Posts: 74
Joined: Thu Oct 18, 2018 4:01 am
languages_spoken: english
ODROIDs: ODROID-GO
Has thanked: 19 times
Been thanked: 1 time
Contact:

Re: SNES Emulator (very early test)

Unread post by pmprog » Mon May 27, 2019 2:38 pm

I must admit, looks impressive...

I seem to be struggling to get more than 15fps doing very little, never mind emulating anything

User avatar
odroid
Site Admin
Posts: 33368
Joined: Fri Feb 22, 2013 11:14 pm
languages_spoken: English
ODROIDs: ODROID
Has thanked: 398 times
Been thanked: 461 times
Contact:

Re: SNES Emulator (very early test)

Unread post by odroid » Tue May 28, 2019 10:01 am

Very interesting :o
I couldn't expect a running SNES on the GO even it looks quite slow at this moment.

ducalex
Posts: 80
Joined: Sun May 19, 2019 3:29 am
languages_spoken: english, french
Has thanked: 18 times
Been thanked: 86 times
Contact:

Re: SNES Emulator (very early test)

Unread post by ducalex » Tue May 28, 2019 11:46 am

I've made some modifications, it is running a bit faster! Still not playable unfortunately. Video has been updated as well as the zip.
Super Mario World is currently running 3.5x too slow and in the previous build it was about 5x.
lordhardware wrote:
Mon May 27, 2019 2:20 pm
I'll check this out tonight, from what I remember from early emu days, Uniraces was a pretty low req. SNES game so maybe benchmark with that?
Uniracers doesn't seem to run but I didn't investigate why yet. The game starts but eventually black screen.

Edit: Uniracers DO run now, but as slow as you can imagine :)
Last edited by ducalex on Sat Jun 15, 2019 2:09 am, edited 1 time in total.

ducalex
Posts: 80
Joined: Sun May 19, 2019 3:29 am
languages_spoken: english, french
Has thanked: 18 times
Been thanked: 86 times
Contact:

Re: SNES Emulator (very early test)

Unread post by ducalex » Sat Jun 15, 2019 2:08 am

Hello!

This is a quick update to let you know that I will stop working on this project. The biggest reason is that I cannot figure out how to profile function calls on the esp32 (or if it is indeed possible). The snes9x execution flow is pretty complicated so manually adding tracking code proved to be futile.

If anyone is willing to teach me how to profile function calls on the esp32 I'd be very happy to have a second look.


But for the record, and if someome wants to pick up where I left it, here's a list of things I've tried or considered so far:
  • Modify the emulation timings. I managed about 5% speed gain before the games were too badly glitchy. It's not an ideal route to break games so I dismissed it.
  • Use both cpu cores. I moved some of the PPU rendering logic to the second score. While it does provide good performance, it is incredibly glitchy if not synchronized. Once synchronized the performance gain was a lot more modest, 10% perhaps. I'm sure this is an avenue worth exploring again in the future. Sound can also be in its own thread, when implemented. But the rest of the emulator cannot be broken down further.
  • Reassess the types used everywhere. I've gained a few percents by using smaller types where possible, but the code is large and it takes time to test nothing breaks. Some structures benefited from being realigned as well.
  • Dynamic recompiler. I have not tried it, but the ESP32 is indeed able to load code in RAM and execute it.
  • Move key ram blocks to internal RAM instead of SPIRAM. I've gained a few percent. I was hoping for more of a difference but apparently SPIRAM and Internal ram are similar in speed..? Further experimentation on which blocks of RAM is been put in internal RAM could be needed.
These users thanked the author ducalex for the post:
Melon Bread (Sat Jun 15, 2019 11:59 am)

pmprog
Posts: 74
Joined: Thu Oct 18, 2018 4:01 am
languages_spoken: english
ODROIDs: ODROID-GO
Has thanked: 19 times
Been thanked: 1 time
Contact:

Re: SNES Emulator (very early test)

Unread post by pmprog » Tue Jun 18, 2019 2:11 pm

I downloaded Neil Kolban's Book on ESP32. I haven't read much of it, because my time with the ESP32 and go have been rather limited, but there's a section on debugging, and it sounds like there's no profiling type tools available, just output logging.
Even though i haven't read much of it, I'd probably recommend it, as what little i have read seems very thorough.

I'm impressed you got add far as you did though

fileoffset
Posts: 3
Joined: Mon Jul 22, 2019 7:18 pm
languages_spoken: english
ODROIDs: go,xu2,c2
Has thanked: 0
Been thanked: 0
Contact:

Re: SNES Emulator (very early test)

Unread post by fileoffset » Mon Jul 22, 2019 7:23 pm

I used this admittedly low-tech method when I was testing some performance improvements to the NES emu:

Code: Select all

int cycles = xthal_get_ccount();

function_to_test();

int now_cycles = xthal_get_ccount();
printf("Frame took: %d\n", now_cycles - cycles);

ducalex
Posts: 80
Joined: Sun May 19, 2019 3:29 am
languages_spoken: english, french
Has thanked: 18 times
Been thanked: 86 times
Contact:

Re: SNES Emulator (very early test)

Unread post by ducalex » Mon Jul 22, 2019 11:34 pm

fileoffset wrote:
Mon Jul 22, 2019 7:23 pm
I used this admittedly low-tech method when I was testing some performance improvements to the NES emu:

Code: Select all

int cycles = xthal_get_ccount();

function_to_test();

int now_cycles = xthal_get_ccount();
printf("Frame took: %d\n", now_cycles - cycles);
That is good advice. I used a similar method with esp_timer_get_time(). The problem was that the SNES codebase is fairly large and what I need is the total time spent in each function (the sum of all its calls) and tracking them one by one is a total pain.

I've been experimenting with gcc's -finstrument-functions recently and the results are promising. Here's what I wrote so far:

Code: Select all

typedef struct {
	void *address;
	uint call_stack;
	uint calls_count;
	uint total_time;
	uint start_time;
} func_entry_t;

func_entry_t functions[2048];
const ushort functions_count = 2048;

extern "C" __attribute__((no_instrument_function))
IRAM_ATTR void __cyg_profile_func_enter(void *this_fn, void *call_site)
{
	for (ushort i = 0; i < functions_count; i++) {
		if (functions[i].address == 0) { 
			functions[i].address = this_fn;
		}
		
		if (functions[i].address == this_fn) {
			functions[i].start_time = esp_timer_get_time();
			functions[i].call_stack++;
			break;
		}
	}
}

extern "C" __attribute__((no_instrument_function))
IRAM_ATTR void __cyg_profile_func_exit(void *this_fn, void *call_site)
{
	for (ushort i = 0; i < functions_count; i++) {
		if (functions[i].address == this_fn) {
			functions[i].calls_count++;
			functions[i].call_stack--;
			functions[i].total_time += esp_timer_get_time() - functions[i].start_time;
			break;
		}
	}
}

void dump_function_stats_task(void *arg)
{
	while (1) {
		printf("Function stats:\n");
		for (uint i = 0; i < functions_count; i++) {
			if (functions[i].address == 0) break;
			func_entry_t *function = &functions[i];
			printf("0x%x: Calls: %d Total time: %.3f\n", 
				(int)function->address, function->calls_count, (float)function->total_time / 1000000);
		}
		vTaskDelay(6000);
	}
}

It doesn't handle nested calls and stacks and it is probably slower than it could be. In fact the emulator with the tracing functions doing NOP runs about 50x slower. Which is fully acceptable, but when I add my code it jumps to 1000x slower, it takes 15 minutes to reach title screen in SMW :(. But it "works"!

Here's an example output taken at the nintendo logo screen in super mario world:
Image
Raw log: https://gist.github.com/ducalex/40bbdb5 ... 53a866fe43

fileoffset
Posts: 3
Joined: Mon Jul 22, 2019 7:18 pm
languages_spoken: english
ODROIDs: go,xu2,c2
Has thanked: 0
Been thanked: 0
Contact:

Re: SNES Emulator (very early test)

Unread post by fileoffset » Sat Jul 27, 2019 12:36 am

That's pretty neat.

I haven't tested this so YMMV but something like this might speed it up, and isn't a big change:

Code: Select all

typedef struct {
        void *address;
        uint call_stack;
        uint calls_count;
        uint total_time;
        uint start_time;
} func_entry_t;

static const int MAX_PROFILE_FUNCTIONS = 2048;

func_entry_t functions[MAX_PROFILE_FUNCTIONS];
const ushort functions_count = MAX_PROFILE_FUNCTIONS;

ushort callstack[MAX_PROFILE_FUNCTIONS];
ulong callstack_top_ptr = 0;

extern "C" __attribute__((no_instrument_function))
IRAM_ATTR void __cyg_profile_func_enter(void *this_fn, void *call_site)
{       
        if (callstack_top_ptr++ > MAX_PROFILE_FUNCTIONS)
                return;

        for (ushort i = 0; i < functions_count; i++)
        {
                if (!functions[i].address) 
                        functions[i].address = this_fn;

                if (functions[i].address == this_fn)
                {
                        functions[i].start_time = esp_timer_get_time();
                        functions[i].call_stack++;

                        callstack[callstack_top_ptr] = i;
                        break;
                }
        }
}

extern "C" __attribute__((no_instrument_function))
IRAM_ATTR void __cyg_profile_func_exit(void *this_fn, void *call_site)
{
        if (--callstack_top_ptr > MAX_PROFILE_FUNCTIONS)
                return;

        auto& fn = functions[callstack_top_ptr];
                        
        fn.total_time += esp_timer_get_time() - fn.start_time;
        fn.calls_count++;
        fn.call_stack--;
}

ducalex
Posts: 80
Joined: Sun May 19, 2019 3:29 am
languages_spoken: english, french
Has thanked: 18 times
Been thanked: 86 times
Contact:

Re: SNES Emulator (very early test)

Unread post by ducalex » Tue Jul 30, 2019 5:00 am

Keeping a stack and simply popping it in func_exit certainly makes more sense than my loop!

Thanks, I will try it, I'm sure it will provide a fair bit of improvement.

sydarn2
Posts: 4
Joined: Tue Dec 10, 2019 9:36 pm
languages_spoken: english
ODROIDs: Odroid-GO
Has thanked: 0
Been thanked: 0
Contact:

Re: SNES Emulator (very early test)

Unread post by sydarn2 » Tue Dec 10, 2019 10:27 pm

ducalex wrote:
Sun May 26, 2019 9:24 am
- ROMS bigger than 2MB won't work, there isn't enough RAM
Is this limit for the ESP32-WROVER (4MB PSRAM)?

SInce 1 year the ordoid-go is shipping ESP32-WROVER-B (8MB PSRAM),

ducalex
Posts: 80
Joined: Sun May 19, 2019 3:29 am
languages_spoken: english, french
Has thanked: 18 times
Been thanked: 86 times
Contact:

Re: SNES Emulator (very early test)

Unread post by ducalex » Tue Dec 10, 2019 10:42 pm

sydarn2 wrote:
Tue Dec 10, 2019 10:27 pm
ducalex wrote:
Sun May 26, 2019 9:24 am
- ROMS bigger than 2MB won't work, there isn't enough RAM
Is this limit for the ESP32-WROVER (4MB PSRAM)?

SInce 1 year the ordoid-go is shipping ESP32-WROVER-B (8MB PSRAM),
Using the additional RAM is possible but it requires more work because it is accessible only through bank switching. Since the emulator isn't playable I don't think it is worth the effort at this point :(.

sydarn2
Posts: 4
Joined: Tue Dec 10, 2019 9:36 pm
languages_spoken: english
ODROIDs: Odroid-GO
Has thanked: 0
Been thanked: 0
Contact:

Re: SNES Emulator (very early test)

Unread post by sydarn2 » Wed Dec 11, 2019 11:49 pm

I cloned the repo and I'm trying to build it, but get several errors in main/snes9x.cpp:
https://pastebin.com/C4cd0u90

The repo only contains one commit, is this correct? Did you push your last changes?
The only commit is from 30th of May, but you seem to have worked on it another ~2 weeks after that, judging from correspondence in this thread.

ducalex
Posts: 80
Joined: Sun May 19, 2019 3:29 am
languages_spoken: english, french
Has thanked: 18 times
Been thanked: 86 times
Contact:

Re: SNES Emulator (very early test)

Unread post by ducalex » Thu Dec 12, 2019 1:21 pm

You are absolutely correct, I tried several things for a few weeks before giving up, but nothing ended up being worth committing unfortunately.

The multitude of config errors suggest to me that you might be using the newer esp-idf 4 or master when this project was made with 3.3.
If you are doing only GO stuff with esp-idf then I suggest sticking with 3.2/3.3 as most projects are still using 3.x.

But if you need or want 4.x for any reason then I have some free time this weekend, I can try to get it to compile to give you a start.

sydarn2
Posts: 4
Joined: Tue Dec 10, 2019 9:36 pm
languages_spoken: english
ODROIDs: Odroid-GO
Has thanked: 0
Been thanked: 0
Contact:

Re: SNES Emulator (very early test)

Unread post by sydarn2 » Sat Dec 14, 2019 7:41 am

ducalex wrote:
Thu Dec 12, 2019 1:21 pm
You are absolutely correct, I tried several things for a few weeks before giving up, but nothing ended up being worth committing unfortunately.

The multitude of config errors suggest to me that you might be using the newer esp-idf 4 or master when this project was made with 3.3.
If you are doing only GO stuff with esp-idf then I suggest sticking with 3.2/3.3 as most projects are still using 3.x.

But if you need or want 4.x for any reason then I have some free time this weekend, I can try to get it to compile to give you a start.
Yes I just took the latest IDF, as I have never developed for this platform before. Following their latest version we can at least in theory gain performance:
  • The libc that is used could get optimizations.
  • We get a newer gcc which could be better at optimizing.
  • We get support for newer ESP32s which could be faster (e.g. https://www.cnx-software.com/2019/05/21 ... ensa-lx7/ this is a single core, but with updated isa. Which leads me to think that at least single core performance could be higher. Hopefully there will be a dual-core version as well. This also support 128MB of spi RAM, way more than we need but it's nice not to be constrained by memory. Hopefully there will be other version with more SRAM. I guess you want to stick as much as possible of your application into that.)
  • more...?
I personally really like the ordoid-go platform, you get so much for so little! If we can get snes to run enjoyably on ESP32s, maybe there could be a snes-button-compatible version of the go with a newer esp32 that also brings other improvements (Isn't the lcd bandwidth limited? I think I read it was only possible to run it at 45Hz?). Anyway, I would be totally interested in a 'new' go platform like that if there is enjoyable snes support.

I will take a look at the errors and try to port it to latest idf on Monday, are you sure the code builds? If you could make sure that it builds with your idf in beforehand, that would be super nice :) porting it to newer idf would be a nice task for myself. Ideally multiple idf versions would be supported, to simplify distribution. Maybe we can just have multiple sdkconfig files?

Of course there is value in committing, just keep the master branch clean and buildable and do development/tuning/tests in separate branches. Once you are happy with something, merge to master and if not you can let it live in its own branch for historical purposes. The list of things that you tried (viewtopic.php?f=159&t=35143#p259471) seems like a good place to start, you seem to have done valuable work which would be nice to have if someone were to continue.

I have a few questions/comments, I hope it's ok :)

1. "Moving PPU-rendering to second core"/multi-threading - So you moved the PPU-redering to a separate thread? If the hardware can be modeled with threads and emulate correct, this seems like a great idea. This would have been nice to have in a separate branch. This would give me (as a programmer with no former experience from emulation) an idea of how I can contribute.

2. "Reassess the types used everywhere." - This seems like a valid approach as long as it doesn't compromises the correctness. One could do this opportunistically when developing feature X. (You can have two instances of the git repo checked out, one working on feature-x branch and one working on a branch which does this kind of aggressive optimizations. This branch will rebase on master and can be called 'testing' and include experimental optimizations, once you collect statistics if the changes were in fact valid it can be merged to master. In fact I would opt for this workflow for all features. First merge to testing, and then when we have runtime statistics that it is ok we merge it to master)

3. Dynamic recompilation - I am not sure if I know exactly how this works, but maybe you can verify if my theory is correct: since we have two cores this would mean a lot of performance gain as the dynamic recompiler can run in a separate thread on one core while the other core can run native optimized code rather than interpreting foreign machine code. I just read this: https://en.wikipedia.org/wiki/Dynamic_recompilation. In section Gaming it mentions emulators which uses it, unfortunately it doesn't mention any snes emulator. This should probably be the main focus if we want to get significant improvements in playability, or what do you think?
It would also be nice to have in mind, if doing this, to maintain the same machine state and keep the original interpreter and make them changeable.
I did look to see if there were any snes emulator with dynamic recompilation, but couldn't find any. Admittedly I didn't look that hard, so it may still exist.

4. Internal-RAM/SRAM - I don't know the architecture well enough, but yes I would assume that we want as much as possible in the SRAM. When prioritizing things with lowest latency requirements goes there first. Things that can be allowed to be accessed with long latency should not be there. This seems like something that could be easier than the dynamic recompilation, but on the other hand if dynamic recompilation is the only way forward that should be main priority.

What is your opinion? Do you still think its possible to get this running enjoyably on the esp32?

I have some spare time at the moment, and I am interested in helping out :)

ducalex
Posts: 80
Joined: Sun May 19, 2019 3:29 am
languages_spoken: english, french
Has thanked: 18 times
Been thanked: 86 times
Contact:

Re: SNES Emulator (very early test)

Unread post by ducalex » Sat Dec 14, 2019 7:22 pm

I confirm that the code builds and runs using esp-idf v3.3-rc-8-gbeb34b539.

Also you may want to check the patches for the odroid go: https://github.com/OtherCrashOverride/e ... shOverride
My code doesn't require any of them, but other projects sometimes will depend on at least "Allow SD-SPI to share SPI bus.".

If you want to support two esp-idf version the first thing I would do is to isolate the sdkconfig settings that we actually need and put them in sdkconfig.defaults, leaving the rest up to the specific SDK.

I don't know the actual maximum FPS but we can overclock the SPI bus up to 50-60Mhz in my experience. But at the moment this isn't a limiting factor.
  • This is all done with FreeRTOS tasks and semaphores to synchronize. If you are more familiar with pthreads or C++ threads then esp-idf also supports them (they wrap freertos tasks).
  • Some types are 32 bits when they could be 8 or 16. The arithmetic and bitwise speed is almost the same, but the memory transfer size to/from SPI RAM is smaller. I did achieve some gains but nothing fantastic.
  • Exactly, one of the core could recompile the 65816 code to xtensa and the other would run it directly. However the biggest bottleneck (at least in snes9x) is the graphics code which is why I think using both cores fully is a priority before doing advanced things like DRC.
  • There is about 250K of usable RAM on the SOC and half of that we need for things like DMA. Ideally the CPU ram should be put there, or at least half to contain the the 65816 zero page.
Simply for the lack of buttons I don't think the SNES would be very enjoyable on the GO, but I'm certainly interested in the challenge of getting the SNES to run on the ESP32/ESP32-S2 in general and the GO is an awesome platform to thinker!

ducalex
Posts: 80
Joined: Sun May 19, 2019 3:29 am
languages_spoken: english, french
Has thanked: 18 times
Been thanked: 86 times
Contact:

Re: SNES Emulator (very early test)

Unread post by ducalex » Tue Dec 31, 2019 10:12 am

@sydarn2 I had some free time today so I've added CMake support. The project now compiles with esp-idf 4.0 however it doesn't boot (It works in 3.3.1).

sydarn2
Posts: 4
Joined: Tue Dec 10, 2019 9:36 pm
languages_spoken: english
ODROIDs: Odroid-GO
Has thanked: 0
Been thanked: 0
Contact:

Re: SNES Emulator (very early test)

Unread post by sydarn2 » Fri Jan 10, 2020 12:27 am

@ducalex Thanks, I also managed to build it but with some hassle.

This was a nice line, which I was struggling with finding a proper solution for, thanks :)
https://github.com/ducalex/snes9x-esp32 ... 82c86acR83

I also built your odroid-go-firmware-multi. Albeit I didn't test any of it.

I have been gaming some on the retroESP32 app which i also tried to build with the new idf hehe, but it is way more complex project so I stopped rather quick.

I am not familiar with FreeRTOS and how applications are loaded. Does apps loaded with your odroid-go-firmware-multi need to be compiled with same IDF? Or could I potentially flash the standard odroid-go-firmware-multi you built with 3.2/3.3 and then from that load apps built with 4.0?

ducalex
Posts: 80
Joined: Sun May 19, 2019 3:29 am
languages_spoken: english, french
Has thanked: 18 times
Been thanked: 86 times
Contact:

Re: SNES Emulator (very early test)

Unread post by ducalex » Fri Jan 10, 2020 3:01 am

The bootloader and application can be compiled with different esp-idf versions and it is officially supported (probably because of OTA).

You can find more info on the boot sequence here although it doesn't talk about versions https://docs.espressif.com/projects/esp ... notes.html

Post Reply

Return to “Game Emulators”

Who is online

Users browsing this forum: No registered users and 1 guest