Accéder au contenu principal

retro-gtk: Renaissance

This is the second article in a small series about retro-gtk, I recommend you to read the first one, retro-gtk: Postmortem, before this one.

In the previous article I listed some problems I encountered while developing and using retro-gtk; in this one I will present some solutions I implemented to fix them! ☺ All that is presented in this article is part of the newly-released retro-gtk 0.13.1, which is the first version of the 0.14 development cycle.

Changing the Scope

The Libretro API is tricky: lots of little details need to be handled properly and it isn't always very clear how to do so. By mimicking this API, retro-gtk inherited its complexity, making it way more complex than it should be as there aren't many different ways for a Libretro frontend to handle the cores correctly. retro-gtk was forwarding the complexity of the Libretro API to its users rather than abstracting it.

About a year ago I decided to slowly change the scope of the library. In the previous article, I described retro-gtk as "a GObject-based plugin system based on the Libretro plugin definition", and this still holds true, what changed is how its users will handle the cores. By taking inspiration from how it was used by the GamesRetroRunner class of Games, I slowly moved retro-gtk away from a Libretro reimplementation for frontends and turned it into a Libretro frontend as a library, offering higher level building block and taking care of most of the complexity of the original API internally.

Do you remember the pseudo-code example from the first article, implementing a load_game() function? It was overly complicated compared to what we actually wanted to do, wasn't it? Well, here is how to implement it in C with the new simplified API.

void load_game (RetroCore           *core,
                const gchar * const *media_uris)
{
    GError *error = NULL;

    retro_core_set_medias (core, media_uris);
    retro_core_boot (core, &error);
    if (error != NULL) {
        g_debug ("Couldn't boot the Libretro core: %s", error->message);
        g_error_free (error);
    }
}

With the new API, even C code with GError handling is shorter than the previous pseudo-code example!

As you can see that's much simpler to use, most of the complexity is now handled internally by retro-gtk. Instead of having to use the many components inherited from the Libretro API, you now simply give the medias to the core prior to booting it, booting up the core will take care of its initialization and of loading the game. This also means that retro-gtk doesn't have to expose the game info or disk interface types, making the API smaller and hence simpler to understand.

Many other similar changes were implemented all around the API, way too many to list. Many features that were implemented as complex to use classes tied to RetroCore have been merged into it, removing lot's of the artificially introduced complexity in-between them.

A noteworthy improvement is the introduction of the RetroCoreView widget. Previously, the video output of the core was handled by RetroCairoDisplay, the audio output by RetroPaPlayer and widget specific input devices — forwarding keyboard and mouse inputs to the core or using the keyboard as a gamepad — were handled by input device objects taking a GtkWidget and listening to its events to implement a specific Libretro input device. It worked somewhat well but demanded lots of code to the user, and interaction between these objects was more complex than it should be.

RetroCoreView implement all these features in a single GTK+ widget with a simple API. There are two main functions to this widget. The first one is to allow you to set a core it should handle, it will display its video and play its audio without requiring you to take care of how to do so. The second one is to allow you to simply access the user inputs it receives by exposing it as controllers of the desired RetroControllerType.

Having all of this inside one widget avoid the user to deal with multiple layers of widgets and objects, rendering the video or capturing events. Handling the video output under the cover gives us more freedom on how to implement it. For example when a hardware accelerated renderer will be introduced, we should be able to change it without breaking the ABI and the users should automatically benefit from it, with no change to their codes or their binaries. This also allows to handle very easily interdependencies between the controllers and the outputs, for example a pointer controller is dependent on where the events are happening on the rendered video. All of this makes this widget simpler to use but also simpler to maintain as lot's of the code became way simpler in the transformation proccess.

For you curiosity, here is a slightly simplified version of RetroCoreView in the generated VAPI.

public class Retro.CoreView : Gtk.EventBox {
 public CoreView ();
 public Retro.Controller as_controller (Retro.ControllerType controller_type);
 public uint64 get_controller_capabilities ();
 public int16 get_input_state (Retro.ControllerType controller_type, uint index, uint id);
 public void set_core (Retro.Core? core);
 public void set_filter (Retro.VideoFilter filter);
 public bool can_grab_pointer { get; set; }
 public Gdk.Pixbuf pixbuf { get; set; }
 public bool snap_pointer_to_borders { get; set; }
}

There are a few things I'm not sure how to handle properly yet:

  • how to make it clear the keyboard can't be used at the same time as a keyboard and a gamepad;
  • how to make it clear the mouse can't be used at the same time as a mouse and a pointer, the can-grab-pointer property toggles it for the moment but I don't think the wording is right;
  • how to handle user-defined filters (GLSL shaders, etc.) once we will support them.

Porting retro-gtk to C

Porting retro-gtk to C comes with downsides:

  • the porting process is a lot of work, lots of working code will be rewritten in a lower level language which means that lots of code will introduce bugs and memory leaks;
  • C being harder to read and write than Vala, ported code will be harder to maintain and new code will be harder to write correctly;
  • the Vala API will need to be generated from C code instead of Vala code, making it harder to produce and less accurate.

But this port also comes with advantages:

  • C being the native language of the libraries used by retro-gtk, porting retro-gtk to C avoids to access them via language bindings;
  • C being the native language of the Libretro API, there is no need to abstract it in order to use it inside retro-gtk, no need to tweak the low-level memory access the API requires;
  • the complexity of binding Libretro to Vala is removed, which counterweights a bit the complexity of writing C;
  • it's easier to maintain a stable C API and ABI from C than from Vala;
  • it removes the compile-time warnings caused by the Vala-generated C code;
  • the code should be a tiny bit faster (so little I don't expect it to be noticeable) as variable and string duplications from the Vala-generated C code are removed;
  • the compilation is faster as the Vala to C step is removed;
  • the dependency on Vala is removed, even if the one on vapigen is maintained.

Now, retro-gtk is a C library, it uses GTK-Doc to support documentation and introspection, it is introspectable via GObject Introspection and is usable in Vala by compiling a VAPI from the GIR file.

Emergent Advantages

The combination of these two changes, offering a higher level API which doesn't expose too much of the inner working of the library and developing retro-gtk with the same language as its dependencies, allows more room for the devs to work inside the library and to move things around without hitting and breaking the API ceilling. To continue on the room analogy, writing the API directly instead of compiling it from Vala allows to perfectly control what is part of it and what isn't, there are less risks for unexpected cables to hang from the API ceilling, cables we could hit while working. All of this should allow us to have a more stable API.

With the ability to control the API, and now that I am somewhat happy about it I want to change the stability promise of retro-gtk a bit: we will keep refactoring some bits of the API during the 0.14 development cycle, but after that we will try to keep it somewhat stable. What it means is that if we break it, we will try to keep the break as small as possible and we will document and advertise this explicit API break. If this not totally unstable state doesn't frighten you, you can start using retro-gtk for your own software with 0.14!

To celebrate this important milestone in the life of retro-gtk, the library just got its first logo!

So… What Now‽ 😃

So far I explained what was retro-gtk and what were its major problems in the first article, how I solved some of them but more importantly how I prepared the terrain to solve bigger ones in this article… but what's coming next? Now that the library can be improved upon more freely, the next article will detail the plans for its evolution, introducing shiny new big features to make it rock-solid!

Commentaires

Posts les plus consultés de ce blog

GTK+ Apps on Phones

As some of you may already know, I recently joined Purism to help developing GTK+ apps for the upcoming Librem 5 phone.Purism and GNOME share a lot of ideas and values, so the GNOME HIG and GNOME apps are what we will focus on primarily: we will do all we can to not fork nor to reinvent the wheel but to help allowing existing GTK+ applications to work on phones.How Fit are Existing GTK+ Apps?Phones are very different from laptops and even tablets: their screen is very small and their main input method is a single thumb on a touchscreen. Luckily, many GNOME applications are touch-friendly and are fit for small screens. Many applications present you a tree of information you can browse and I see two main layouts used by for GNOME applications to let you navigate it.A first kind of layout is found in applications like Documents, I'll call it stack UI: it uses all the available space to display the collection of information sources (in that case, documents), clicking an element from t…

One Widget to Adapt Them All and to The Librem 5 Port Them

In my previous article I shared my plans to help porting existing GTK+ applications to Purism's upcoming Librem 5 phone without having to fork them. This article will present the GTK+ widget I developed for Purism to make this happen.For more information on what Purism is working on for the Librem 5, please check Nicole Faerber's latest article.C'est pas sorcierThe underlying idea is to allow applications to dynamically switch between the two main GNOME application layouts: a row of panels — each panel being the view of an element from the previous one — and a stack of panels. The goal isn't to changes applications using the stack paradigm but the ones using the row one, allowing them to reach smaller sizes and to be usable on constrained sizes while keeping their initial paradigm and design when the screen space is sufficient. The development cost to port the applications to this adaptive design should be as low as possible.To achieve that, I wrote a GTK+ widget which…

The Path to GNOME Games 3.26

Games received a non-negligible amount of changes that you will find in 3.26. These changes can be big as much small, and more are to come!Building the Games CollectionGames presents your games collection and if everything goes as expected, it does so without the need of any input from you. From an implementation point of view it sounds simple to do, just ask Tracker “Hey, gimme all the games” and it’s done. If only it was that simple! 😃 The system has no idea which files represent games and which doesn’t, but it can associate a MIME type to each file thanks to shared-mime-info. shared-mime-info already had a few video game related MIME types and we added a lot more such as application/x-genesis-rom.That done, we can query Tracker for files having specific MIME types that we know to often represent video game files. Unfortunately, each of these files doesn’t necessarily represent a game and a game isn’t necessarily represented by a single file: some files may be invalid and hence rep…