Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
root of all eval
Dec 28, 2002

Instead of continuing to post in other threads I figured now is a good time to accumulate my posts into their own thread!

Why a GUI?
First of all, yes, I completely understand that the MiSTer platform is pretty engineer brained and that's a feature, not a bug.

I am also a fan of the built in menu system!

What I am seeking is a completely optional no-impact way to let some subset of users explore meta data about their libraries. Other emulation platforms like Retroarch, Emulation Station, LaunchBox already encourage this kind of controller based rich-media browsing. I'd just like to bring some very basic features of those experiences to the MiSTer.


This is the onboard menu that is great if you know what kind of game you are looking at just by filename.

I assume many of us have vast archival collections, and name alone isn't very fun for exploring or trying new titles.

Design elements



A listing with basic movement and pagination is step one, but this alone isn't really worth an entire GUI. It's no better than the default menu.




Add some rich media and now we've got a GUI!

Data sources and storage
This problem already has many many solutions so I spent a bit of time mulling it over. For a first pass here are my goals:
- For Images (Screenshots, Titles, Boxart) use Libretro public repos
- For Meta data (Developer, Rom hashes, Genre, etc) use Libretro public repos
- For overviews/synopsis use public dumps of thegamesdb.net

From these data sources do the following:
- Create script(s) to fetch, parse, dedupe images, combine data sets and store in sqlite DBs per core
- Host these ready made 'full' sets on archive.org, freely distributable.
- Drop them in to core game directory and you're ready to roll
- Provide culling script to clean the local database based on your surrounding roms as requested

Why not use scrapers, XML, etc
This was a hard philosophical question but I wanted to lean into open source repos and data sets.

Places like screenscraper are technically open under creative commons, but kinda weird and semi-commercial too.
At the end of the day I think it's an unnecessary burden to have to run 500k requests to populate a library against known collections like no-intro/redump when those collections are relatively stable at this point.

Scripts and Cores
I am NOT an electrical engineer or low level hardware dev so creating a core was out of the question at this time.

There was at least some option to compile linux native app to handle GUI tasks, but that seemed overly burdensome too considering the custom kernel/distro.

Enter GroovyMiSTer core! This core was written to act as an analog GPU for RGB bytestreams sent over UDP networking. It works beautifully on a reliable connection and recently began broadcasting input states too.

What this means for me is: I can write a completely platform agnostic app in Golang, that simply prepares and blits uncompressed image data over the network. This is convenient for local dev, but PERFECT for running on the MiSTer in the background. The UDP loopback interface is instantaneous and clean and runs at 64k per packet MTU size. What you get is no stutter display and input processing from the otherwise low power ARM chip by allowing the FPGA core to do some heavy lifting on input and display.

The compiled binary can be run as a single file from the Scripts directory on demand from the MiSTer menu. It can open the Groovy core and selected games via MGL. It may self-close on game load or maybe later go into low power mode to listen for 'return home' and 'exit' key sequences.

Project URL
https://github.com/BossRighteous/MiSTer_Games_GUI

Notice
This is my first public open source project and is a TON of work. MiSTer front-end attempts are a very sore subject for some and I'm sure the urge to poo poo on design decisions is strong! I get it!

But this is also a ton of personal work and I'm passionate about it and it's been very rewarding to learn new skills. please be kind if it's just not for you, that's fine by me!

I'll be updating this thread with some background and kinda treat it as a project log moving forward. Feel free to ask questions or make comments! Thanks for reading.

Adbot
ADBOT LOVES YOU

root of all eval
Dec 28, 2002

How it started
Honestly I just saw the GroovyMiSTer core capabilities and thought, "this is cool." At the time I was kicking around ways to connect MiniPCs to arcade cabinets and this seemed to be a (very expensive without having a MiSTer already but) great solution.

Initially I tried to create a basic client in Godot, since I was vibing a lot with that engine. I did not have any experience with blitting, analog display timings and modelines, or even byte array composition and UDP packets but I have written REST web services for quite a few years, so what the hell. The creator of the Groovy core didn't exactly document his work but the API was pretty easy to grok all told.

Everything was configured right but I had to abandon the project. The UDP streams were too drat slow and unreliable. the time to broadcast packets for a single frame often exceeded the 16.7ms refresh rate target. I still like Godot, but this ain't the thing to plug into it.

After someone here in passing mentioned wanting a Front-end I gave it some thought and it seemed like my client work would be pretty easily ported to Python which I am familiar with and there is a python3 binary installed into the main MiSTer images. This got to the point of actually working, albeit when ran from the MiSTer it struggled to maintain 24fps under very simple image composition. Additionally Python has some issues where c bindings that might not be available would be required to do things like image decompression. With only 2 cores on the ARM chip concurrency for IO (loading data from disk or decoding it) would also be a challenge.

On to revision 3! This time I picked Go(lang) and this turned out to be a pretty good choice for a few reasons.
- Compiled binaries, so it can include c libs as static linked
- Image handling and decoding in standard lib
- Fast, strongly typed and relatively memory efficient
- Almost too good at concurrency

The concurrency features are a double edged sword, because you can't actually prioritize work between routines. Something like a vsync timer that needs to be near-perfect gets very complicated. Luckily in my case the Core allows for leniency in true frame rates and will maintain hardware sync from a buffer. If I'm ahead or behind on a frame it will reliably continue to send data and I'll just get frame drops or tearing.

Keywords and Concepts
I'm going to butcher a lot of this which is fine, I don't need to be a SME on all the internals

Blitting - This is the act of compositing multiple images into one, think about layers in photoshop. We need to send the flattened version to the core at ~60fps. The more layers, the more CPU load to finish the job under the few ms needed. We also don't get access to GPU here, which means the math has to be simple enough for an 800MHz chip with all the other stuff going on. Basically I keep a bunch of layers in memory and ONLY want to redraw when something changes. This lets me keep a flattened image to send on-demand without re-compositing all the time.

UDP - This is a fire-and-forget data protocol. When you send network data we generally send over TCP, which involved handshakes and responses. This is great for transactional data but in our case we want to broadcast ASAP and don't need to worry about acknowledgements. The network card has a limit to how many bytes it will send in a single message, called the MTU. When sending over the network this is quite low, about 1500, so we have to send a LOT of packets to render the image. 320w x 240h x 3colors = 230400 bytes per frame. Thats 153 small messages per frame.
The good news Loopback interfaces (connecting to self) allow ~56000 bytes per message. This is means less looping, and because it's local packet loss is nonexistent.

Modelines - These are the incantations to tell a tube tv laser how to draw poo poo on screen. It's engineering magic to me and that's okay. It doesn't make much sense to me as math but luckily there are calculators and tables to give common outputs. Because GroovyMiSTer is first-and-foremost and analog GPU, we are kinda mis-using it as a general rendering platform. It will also output to digital HDMI with a scaler, but we need to support the analog signal as a default. Analog output to a physical display is not a requirement to use in the MiSTer platform. I am happy to force the same 240p@60hz for this project, I gotta have some stable target resolution with the limited compositing and CPU constraints.

root of all eval fucked around with this message at 22:01 on Apr 19, 2024

root of all eval
Dec 28, 2002

It's been a slog the past weeks. I've been mentally mapping and trying out a few different sourcing techniques. Each time I think I'm making progress it either feels like I'm being too opinionated or adding too much work.

I really got excited about using libretro RDBs and thumbnails for sourcing. I imagined being able to pull a 'scraper' together in the app script. This felt good until I started parsing out the RDB files and saw that they are very sparse on meta data. That was a bit of a bummer. I tried to merge data from a JSON dump of thegamesdb.net thinking it would help fill in. It was a nice idea but would require some fuckery to create a slugified filename/title matcher between the TGDB set and the libretro data.

Back to scraping I guess! But that raised a problem in itself with regards to scraper vs API needs. A scraper needs local files to match against, of which there is little guarantee I'll have them all, let alone for all systems the MiSTer supports.

Compromise!
I 'trick' the scraper into thinking I have all the ROMs in the libretro RDB. I wrote out a parser that maps MiSTer core game folders to RDB download paths. It saves the RDB, parses it, and for each game in the set- touches an empty file with the ROMname from the RDB record. This creates the hundreds-thousands of ROM stubs needed for scraping. I then turn loose a tool like Skraper on it configured to only check filenames.

This was a cool decision because it allows me pretty automated set building for meta without having to write a bunch of tooling. It also allows resizing to fit 320x240 on the fly and dynamic image paths for shared ROMs to dedupe.

What I am left with are
- gamelist.xml
- libretro.rdb
- media/screenshots
- media/screenshottitle
- (the actual rom directory for the core?

On their own the RDB and XML files aren't huge, but needing to sustain them in memory for looping matches feels wasteful.
The next step is a script that creates an SQLite DB file representing the data from the XML, the RDB, and even the Image blobs.

This gives me a single distributable file representing rich meta for the entire libretro compiled no-intro/redump/TOSEC collections. Or at least as it exists as a skraper snapshot from screenscraper.fr.
Feels like a big weight was lifted. The SNES set for instance is under 250MB. I'll be able to host these on archive.org, add the URLs to an INI file in case they ever change, and add meta download support to cores with a single button.

Using the SQLite DB file the app script can pretty easily do fast selects on rom names, CRCs, or even generic titles.

The only thing I am currently hung up on is whether to take the opportunity to pull full-size images and upload the XML/screen dumps as-is on archive. Then just run a photoshop batch resize for myself for the SQLite merge...
I think I'll just publish the scripts and a guide and let someone worry about the generic dumps themselves if so inclined?

root of all eval
Dec 28, 2002

I hope I wasn't too fragile in the OP, I'm totally open to discussion and feedback!

Weeklyish update:

The SQLite DB work went really well so I'm excited about that! Been a slow week on hacking time due to outside stressors. We're going on vacation in a couple weeks so most free time has been spent planning and reserving activities and transit.

Current WIP activity is around state management. I come from a web background so I'm finding React is my go to mental model for the concept of property observation, callbacks, and minimalist rendering. I'm a little bummed I don't feel like I have the time to reall dig in on best practices in this case, but I feel at least a little comfortable moving forward without painting myself into a wall.

GUI has a mutable GUIState bag with a change flag for value mutations. GUI has it's own state but this is meant to be the sharable 'global' instance between screens.

Screens are relatively self-contained render/logic buckets, that are passed a reference to the shared GUIState.

GUI's onTick evaluates input polling, established which screen is active, and calls the screens equivalent onTick.

The screen can self handle inputs and where appropriate make sync/async changes to the GUIState or change the active Screen.

GUI checks the mutation flag on the state to optionally call the render method of the screen. This enabled limited redraw and breaks up the render implementation to the owner container.

The model is working well in testing, so my next big challenge is the GUIList and GUIItem elements.

I'd like this to be reusable for things like settings, general actions, core list, and game list, despite these all having different handler cases.

I'm working on an interface that allows for generics like:
nextItem, prevItem, onEnter, onExit, onSelect, onCancel, getPageItems, pageCount, nextPage, prevPage,
that would allow more generic object types to have implementations. For instance Game selection and SQLite selects would have different underlying page implantations than something like a settings list.

I need to finish up scraping too, but so far I have the full retroarch rom-name scrape data for:
SMS, Saturn, SNES, PSX, NES, NEOGEO, N64, Genesis, GB/C/A, Atari2600 and Amiga.
Coincidentally all the image packs have weighed in around 200mb.

I think it will be a really nice feature to be able to just press a controller button and download a single ~250mb meta file for any given core.
I spent more time than needed probably in pursuit of that. Also remaining cognizant of the ability to set custom paths for mirrors and forks via INI/JSON in the future, esp after the arcade DB incident recently.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply