{"id":49685,"date":"2021-08-17T01:23:26","date_gmt":"2021-08-17T01:23:26","guid":{"rendered":"https:\/\/www.libretro.com\/?p=49685"},"modified":"2022-04-03T00:51:47","modified_gmt":"2022-04-03T00:51:47","slug":"miniretro-testing-emulators-at-scale","status":"publish","type":"post","link":"https:\/\/www.libretro.com\/index.php\/miniretro-testing-emulators-at-scale\/","title":{"rendered":"Miniretro: testing emulators at scale"},"content":{"rendered":"<p>This is a guest article written by David GF Net republished with his authorization. Original blog article can be viewed <a href=\"https:\/\/www.davidgf.net\/2021\/08\/15\/miniretro\/\">here<\/a>.<\/p>\n<p>Last year I got involved in Libretro\/Retroarch development after buying an Odroid Go Advance. During this time I\u2019ve been working mainly on <a href=\"https:\/\/github.com\/libretro\/gpsp\">gpsp<\/a> and porting it to new devices and such.<\/p>\n<p>One of the main issues about working in software is testing, and as you can probably imagine there\u2019s no tests in most emulators. Partly due to bad practices and because it\u2019s hard to write tests for them. That\u2019s why I came up with miniretro. It is a libretro frontend designed for headless operation, so that it can be used for end-to-end\/integration testing. The frontend runs a core and a rom with fake inputs and grabs the output.<\/p>\n<h2>Cross platform testing<\/h2>\n<p>Some of the emulators (like gpsp) feature a dynamic recompiler (aka dynarec), which have platform specific (CPU, OS and\/or device specific) backends targeted at them. These can translate original console instructions into your device\u2019s instruction set (for speed). Since this is device specific, it\u2019s hard to write, debug and test. One needs a toolchain for the platform and a physical device to test it. Or at least that\u2019s the theory!<\/p>\n<p>With miniretro it gets easier to test other platforms like for instance ARM and MIPS devices. Since it is simple (almost no dependencies) and just a Linux binary, we can use Qemu userspace emulation to run our tests! This way there\u2019s not need for a physical device, nor manual testing.<\/p>\n<p>The following diagram shows how it works: miniretro and the libretro core are built for the specific device\/architecture and they run under qemu. Qemu takes care of translating syscalls into host syscalls, so there\u2019s no need for a whole OS to run the program. Miniretro can open pipes and other IPC communication channels to input\/output any data.<\/p>\n<p><strong>Diagram of Miniretro running under Qemu<\/strong><\/p>\n<p><img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.libretro.com\/wp-content\/uploads\/2021\/08\/miniretro-diag.png\" alt=\"\" width=\"800\" height=\"451\" class=\"aligncenter size-full wp-image-49686\" \/><\/p>\n<p>Just as an example, let\u2019s build picodrive, gpsp and pcsx for armv6 and mips32. To do this you will need a toolchain. In my case I\u2019ve been using some Linux-generic toolchains for a variety of platforms, such as ARM, MIPS, x86 and PowerPC, freshly built using Buildroot (you can find them at my Copr if you use Fedora, see this repo). This, coupled with the regular Qemu userspace emulators (available in most Linux distros), enables us to test our emulators on a bunch of platforms effortlessly.<\/p>\n<blockquote><p>  # Build the three emus for arm and mips<br \/>\n  git clone &#8211;recurse-submodules https:\/\/github.com\/irixxxx\/picodrive.git &#038;&#038; cd picodrive<br \/>\n  make platform=armv CC=\/opt\/buildroot-armv6el-eabi-uclibc\/bin\/arm-linux-gcc -j10 -f Makefile.libretro all &#038;&#038; \\<br \/>\n    mv picodrive_libretro.so picodrive_libretro_arm.so &#038;&#038; make -f Makefile.libretro clean<br \/>\n  make platform=unix CC=\/opt\/buildroot-mipsel32-o32-uclibc\/bin\/mipsel-linux-gcc -j10 -f Makefile.libretro all &#038;&#038; \\<br \/>\n    mv picodrive_libretro.so picodrive_libretro_mipsel.so &#038;&#038; make -f Makefile.libretro clean<\/p>\n<p>  git clone https:\/\/github.com\/libretro\/gpsp.git &#038;&#038; cd gpsp<br \/>\n  make platform=armv CC=\/opt\/buildroot-armv6el-eabi-uclibc\/bin\/arm-linux-gcc -j10 all &#038;&#038; \\<br \/>\n    mv gpsp_libretro.so gpsp_libretro_arm.so &#038;&#038; make platform=armv clean<br \/>\n  make platform=mips32 CC=\/opt\/buildroot-mipsel32-o32-uclibc\/bin\/mipsel-linux-gcc -j10 all &#038;&#038; \\<br \/>\n    mv gpsp_libretro.so gpsp_libretro_mipsel.so &#038;&#038; make platform=mips32 clean<\/p>\n<p>  git clone https:\/\/github.com\/libretro\/pcsx_rearmed.git &#038;&#038; cd pcsx_rearmed<br \/>\n  make platform=armv CC=\/opt\/buildroot-armv6el-eabi-uclibc\/bin\/arm-linux-gcc -j10 -f Makefile.libretro all &#038;&#038; \\<br \/>\n    mv pcsx_rearmed_libretro.so pcsx_rearmed_libretro_arm.so &#038;&#038; make -f Makefile.libretro clean<br \/>\n  make platform=unix CC=\/opt\/buildroot-mipsel32-o32-uclibc\/bin\/mipsel-linux-gcc -j10 -f Makefile.libretro all &#038;&#038; \\<br \/>\n    mv pcsx_rearmed_libretro.so pcsx_rearmed_libretro_mipsel.so &#038;&#038; make -f Makefile.libretro clean<\/p>\n<p>  # Build miniretro fro arm and mips too<br \/>\n  git clone https:\/\/github.com\/davidgfnet\/miniretro &#038;&#038; cd miniretro<br \/>\n  PREFIX=\/opt\/buildroot-mipsel32-o32-uclibc\/bin\/mipsel-linux- make &#038;&#038; mv miniretro miniretro.mipsel &#038;&#038; make clean<br \/>\n  PREFIX=\/opt\/buildroot-armv6el-eabi-uclibc\/bin\/arm-linux- make &#038;&#038; mv miniretro miniretro.arm &#038;&#038; make clean<\/p><\/blockquote>\n<p>And finally, let\u2019s run a full matrix of tests, choose your favourite ROMs of course!<\/p>\n<blockquote><p>  export ARCHS=&#8221;arm mipsel&#8221; EMUS=&#8221;pcsx_rearmed picodrive gpsp&#8221;<br \/>\n  export FRAMES=&#8221;10000&#8243; OUTPUT=&#8221;output\/&#8221; SYSTEM=&#8221;$HOME\/.config\/retroarch\/system\/&#8221;<br \/>\n  declare -A SYSROOTS=([&#8220;arm&#8221;]=&#8221;\/opt\/buildroot-armv6el-eabi-uclibc\/arm-buildroot-linux-uclibcgnueabi\/sysroot\/&#8221; \\<br \/>\n     [&#8220;mipsel&#8221;]=&#8221;\/opt\/buildroot-mipsel32-o32-uclibc\/mipsel-buildroot-linux-uclibc\/sysroot\/&#8221;)<br \/>\n  declare -A ROMDIR=([&#8220;gpsp&#8221;]=&#8221;gbaroms\/path\/&#8221; [&#8220;picodrive&#8221;]=&#8221;mdroms\/path&#8221;)<\/p>\n<p>  mkdir -p ${OUTPUT}<\/p>\n<p>  for emu in $EMUS; do<br \/>\n    for arch in $ARCHS; do<br \/>\n      echo &#8220;Running $emu for $arch&#8221;<br \/>\n      .\/regression.py &#8211;core ..\/${emu}\/${emu}_libretro_${arch}.so &#8211;system ~\/.config\/retroarch\/system\/ \\<br \/>\n        &#8211;input ${ROMDIR[${emu}]} &#8211;output ${OUTPUT}\/${emu}-${arch} &#8211;threads=`nproc` &#8211;frames=${FRAMES} \\<br \/>\n        &#8211;driver &#8220;qemu-${arch} -L ${SYSROOTS[${arch}]} .\/miniretro.${arch}&#8221;<br \/>\n    done<\/p>\n<p>    .\/report.py compare &#8211;results ${OUTPUT}\/${emu}-* &#8211;output ${emu}-report.html<br \/>\n  done<\/p><\/blockquote>\n<p>This will produce a comparison report for every emulator, where you can see a screenshot (plus some info) for each rom and each platform. It will compare any different screenshot and report it in a red background. This is very useful to compare devices but can be also be used to compare across versions of the emulator (say, on a new commit or PR).<\/p>\n<h2>Reports: HTML and video!<\/h2>\n<p>It is also possible to produce video output directly using miniretro (if ffmpeg is installed on the system). It will output BMP and RAW PCM frames to ffmpeg which will encode them as video and audio tracks. Unfortunately there\u2019s a bug in ffmpeg which prevents using two pipes to feed data and we are forced to produce separate streams (which can then later be muxed easily without reencoding).<\/p>\n<p>I ran the above example with gpsp and arm, mips and x86 (+interpreter on x64) and got the following video (after a bit of editing with ffmpeg :P)<\/p>\n<p><iframe loading=\"lazy\" width=\"750\" height=\"563\" src=\"https:\/\/www.youtube.com\/embed\/MI8__NAFaww?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><\/p>\n<p>Likewise I ran a full test for picodrive, running 32x games:<\/p>\n<p><iframe loading=\"lazy\" width=\"750\" height=\"563\" src=\"https:\/\/www.youtube.com\/embed\/bEycs0jHDAo?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><\/p>\n<h2>Bonus content: dualretro<\/h2>\n<p>Tooling is better than debugging for sure. I\u2019d rather spend 1 hour creating some tool rather than manually debugging something and digging too deep. Perhaps it\u2019s part of becoming an adult \ud83d\ude42 While doing a small code change I found some weird bug in gpsp that didn\u2019t make any sense. Instead of debugging it, I tried to compare the emulator before and after my changes. Usually this involves finding the smallest change that triggers the bug and then, compare them. Thanks to miniretro I managed to found a couple of games that would trigger the error and a small test case that caused it.<\/p>\n<p>Next step was to create a tool that allows us to compare emulator cores: let me introduce you to dualretro. This simple tool takes two cores and a rom and runs them side by side in lockstep. On each frame it will create a savestate and compare them, letting you know when it found a difference. This allowed me to find the bug in a matter of minutes.<\/p>\n<p><b>Diagram of Dualretro running under Qemu<\/b><\/p>\n<p><img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.libretro.com\/wp-content\/uploads\/2021\/08\/dualretro-diag.png\" alt=\"\" width=\"696\" height=\"295\" class=\"aligncenter size-full wp-image-49687\" \/><\/p>\n<p>This concept can be generalized to compare frames, memory regions, audio, etc. libretro\u2019s API provides a lot of information in a standarized way that can be used.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is a guest article written by David GF Net republished with his authorization. Original blog article can be viewed here. Last year I got involved in Libretro\/Retroarch development after buying an Odroid Go Advance. During this time I\u2019ve been working mainly on gpsp and porting it to new devices and such. One of the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[343,341,342],"tags":[],"amp_enabled":true,"_links":{"self":[{"href":"https:\/\/www.libretro.com\/index.php\/wp-json\/wp\/v2\/posts\/49685"}],"collection":[{"href":"https:\/\/www.libretro.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.libretro.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.libretro.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.libretro.com\/index.php\/wp-json\/wp\/v2\/comments?post=49685"}],"version-history":[{"count":2,"href":"https:\/\/www.libretro.com\/index.php\/wp-json\/wp\/v2\/posts\/49685\/revisions"}],"predecessor-version":[{"id":49689,"href":"https:\/\/www.libretro.com\/index.php\/wp-json\/wp\/v2\/posts\/49685\/revisions\/49689"}],"wp:attachment":[{"href":"https:\/\/www.libretro.com\/index.php\/wp-json\/wp\/v2\/media?parent=49685"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.libretro.com\/index.php\/wp-json\/wp\/v2\/categories?post=49685"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.libretro.com\/index.php\/wp-json\/wp\/v2\/tags?post=49685"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}