Dolphin - From Vision to Architecture
Learning about the architecture of Dolphin is not an easy task. The developers did not document their system well and a lot of the limited documentation that is available is outdated. Even one of the developers said that the only way to learn about the structure of the system is to just look at the directories of the source code.
An explicit main architectural style seems to be missing, but traces of a model-view-controller architecture1 can be found. The controller would be all the input components, the view would be the audio, video, and user interface (UI) components, and the model would be the core components of Dolphin.
The functionality of the controller is to map native input to input to use for emulation. The main use of this is to use keyboard & mouse, XInput or DirectInput controllers, as GameCube (GC) controllers or Wii Remotes in the emulation. Users have the freedom to map the buttons however they see fit and they can even simulate more complex input, such as Wii MotionPlus. Users can also connect real GC and Wii controllers to get a more authentic experience. Very niche input, such as the GC microphone, DK bongos, Guitar Hero controller, Wii balance board, and even the mGBA emulator for GBA connectivity, is supported as well.
The model is the core of the emulation. This includes the reading, decrypting, and loading of the game files, the x86-64 and AArch64 just-in-time (JIT) compilation for emulating the Gekko (GC) and Broadway (Wii) CPUs, the asynchronous shader compilation and Ubershaders for emulating the Flipper (GC) and Hollywood (Wii) GPUs, and Netplay connectivity.
The functionality of the view is to show the output of the emulation. This includes the DolphinQT UI, the DSP-HLE (Digital Signal Processor - High-Level Emulation) and DSP-LLE (Low-Level Emulation) audio backends, and the DirectX, OpenGL, Vulkan, and software renderer video backends.
Container view
Looking at the architecture of Dolphin from a container view as described by the C4 model2, a few distinct containers can be identified. First of all, there is a desktop application that can run on Windows (7 SP1 and newer), Linux, and macOS (10.13 High Sierra and up)3. This is the main container of Dolphin that boots the games, allows users to edit their settings and make use of the other functionalities of Dolphin.
Another container of Dolphin that is deployed in its own environment is the mobile application, which is only available for Android as of now. This uses code from the desktop container to emulate the consoles and play the games. The mobile application is a separate component because it has its own UI written separately from the desktop code. It does not have all the functionalities of the desktop application either, for example, it does not support Netplay. However, you are able to emulate and enjoy the GC/Wii games which is the main functionality of Dolphin. The mobile application is coded in Java and developed in Android Studio in contrast to the desktop application which is mostly written in C++ and developed in Visual Studio.
The final container inside the Dolphin application is the Dolphin website which can host lobbies for users to play online. Dolphin offers the Netplay functionality which allows users to create private lobbies and play games together. By making HTTP requests, the desktop application interacts with a part of the Dolphin website.
Outside of the Dolphin software, one more container can be identified. The local file system of the user is needed to store user data and game dumps. So in contrast to many applications, Dolphin is not using a database. The most important reason for this is that the game dumps are not allowed to be shared. Therefore, Dolphin does not have to store things online for everyone to access, but should just be able to access local files for user settings.
Component view
Because the desktop application container is the main container of Dolphin, it is investigated further by looking into the different components. The main component visible to the user is the UI. It creates, changes and removes all the windows a user can interact with and interacts with almost all the other components to make Dolphin function.
A few components can be seen because they focus on one functionality. The tool-assisted speedrun (TAS) creates its own windows and handles all the actions related to creating a TAS. The updater component is called when the application starts to check if there are any updates. It creates different windows depending on the operating system of the user but has the same functionality for both of them. The controllers component does all the work related to the emulation of GC and Wii controllers to be able to play the games.
Together with the UI component, the core interacts with the Netplay component in order to host or join online lobbies. They also both interact with the filesystem component that reads information from and writes information to the local file system of the user. All these components together form the Dolphin desktop application.
Connectors view
The connector view provides an overview of the techniques to enable communication between components and containers. The user can control Dolphin by a number of input devices. The main menu and settings will most likely be controlled by a keyboard and mouse. The actual games will probably be played using a game controller. All these input devices send a one-way stream of inputs to Dolphin. These will be translated and sent to the proper components in Dolphin.
The connections between the different components in the desktop application are handled by simple imports and function calls. There is still an emphasis to limit the number of cross-component connections. A lot of connections and thus dependencies on another component are often referred to as spaghetti code4. Such code is unstructured and difficult to maintain and should be avoided.
The asynchronous connection with the Dolphin website for using Netplay is initialized by an HTTP request from the Desktop application. When the request is successful, a WebSocket connection is created to ensure reliable and fast communication. This is vital when playing a multiplayer game with your friends. Finally, the connection with the file system is handled by the C++ standard library. This library contains methods to read and write files to the host’s file system.
Dolphin does not contain any form of API, since there is no need for communication between the system and other applications.
Development view
To see the structure of Dolphin for developers, a development view can be described. For Dolphin the system code is structured into (sub-)directories with some directories having the suffix “Common” to indicate the common classes that are used regularly for any implementation. These directories can be seen in the Figure. Note that almost all components have their own directory in the code where DolphinQt is the directory for the UI since Qt5 is used as the UI design library. Since the system’s Android variant is not solely the same software running on a different operating system but built for mobile applications, its app and UI are written entirely separate from the system’s main code and even written in the Java language instead of C++, which is used in all other cases.
Since the system is a virtual machine based on actual systems, it is structured as such. Components such as audio, video, but also discIO and the UI are separated by their respective subdirectories. For accurately simulating the GC, its documentation5 listing opcodes and all interfaces is used. For the Wii, this documentation is not accessible, hence the developers needed to figure it out themselves.
The GitHub repository has a page6 dedicated to the coding style format used across the system’s source code. It describes specific coding formats as well as general guidelines, such as a maximum code line length of 100 characters.
Dolphin’s dependencies include: OpenGL, Vulkan, DirectX, MoltenVK and Qt5. OpenGL, Vulkan and DirectX are used as graphics APIs. MoltenVK is used to run Vulkan on macOS devices. Finally, Qt5 is a C++ library used for UI design.
Run time view
To get an idea of how components work together during run time, we assume the user is playing a GC game using an XInput controller with an x86-64 CPU and has configured DSP-HLE as the audio backend and DirectX 11 as the video backend.
First, the game is booted by the core component. A number of tasks are performed by the boot and core classes in order to create some threads and boot the game. The input components map the XInput to GC input. The user can alter the mappings with the UI component. The input is then passed on to the x86-64 JIT. This does the CPU emulation and starts the shader compilation as well. By default, whenever a shader needs to be compiled, the Ubershaders interpret it while the asynchronous shader compilation runs in the background. Once the asynchronous shader compilation is done, the compiled shaders are used instead of the Ubershaders. It is then up to DirectX 11 to render the shaders and output a video signal. In the meantime, DSP-HLE is running on another thread and outputs an audio signal.
Architectural trade-offs
In our previous blog post, we described a number of key quality attributes. Underneath, we will explain what architectural decisions were made to realize them.
One of the key quality attributes of Dolphin is performance. A major architectural decision that influences the performance is the choice of C++ as programming language. This is in general an efficient language compared to e.g. Java7. Dolphin uses threads extensively to parallel processes and make the application more efficient. These threads are used to separate the input controls, audio, video, and core. The emulation itself can unfortunately not be parallel processed as the games developed for the original systems are designed to run on a single core. A trade-off of using threads is that the developers should be more thoughtful as parallel logic is more complex than sequential logic.
Other key quality attributes are reliability and correctness. For the Netplay connection, the developers specifically chose the TCP protocol instead of the UDP protocol. The TCP protocol is reliable but adds a small overhead. It ensures that the data is without errors and ordered. The UDP protocol is unreliable as the packages can arrive incomplete and out of order8. There is clearly a trade-off between reliability and correctness on the one end and performance on the other.
-
Rafael D. Hernandez. (April, 2021). Retrieved from: https://www.freecodecamp.org/news/the-model-view-controller-pattern-mvc-architecture-and-frameworks-explained/ ↩︎
-
Simon Brown. (March, 2022). Retrieved from: https://c4model.com/ ↩︎
-
Dolphin. (March, 2022). Retrieved from: https://dolphin-emu.org/docs/faq/ ↩︎
-
Stephen Watts. (June, 2020). Retrieved from: https://www.bmc.com/blogs/spaghetti-code/ ↩︎
-
Yet Another Gamecube Documentation. (December, 2006). Retrieved from: https://www.gc-forever.com/yagcd/ ↩︎
-
Dolphin. (March, 2022). Retrieved from: https://github.com/dolphin-emu/dolphin/blob/master/Contributing.md ↩︎
-
Benchmarks Game. (March, 2022). Retrieved from: https://benchmarksgame-team.pages.debian.net/benchmarksgame/box-plot-summary-charts.html ↩︎
-
Matt Cook. (October, 2017). Retrieved from: https://www.lifesize.com/en/blog/tcp-vs-udp ↩︎