This essay will cover the software architecture of Blender, i.e. the structure and architectural patterns of Blender’s codebase.
When architecting any software, it is important to look at the architecture from different angles or views, because trying to encapsulate all the viewpoints that every stakeholder has, in one diagram, becomes complicated. Therefore, common practice dictates creating multiple viewpoints, each of which can focus on a different aspect. In Rozanski and Woods’ Software Systems Architecture1, a viewpoint is defined as “a collection of patterns, templates, and conventions for constructing one type of view. It defines the stakeholders whose concerns are reflected in the viewpoint and the guidelines, principles, and template models for constructing its views.”
For the case of Blender, we have chosen to focus on the following views:
- Context viewpoint; aims to expand upon the system context and visually showcases the system scope, external entities, services and dependencies.
- Development viewpoint; is meant for the developers and illustrates the different components of the system as well as the code structure.
- Process viewpoint; focuses on the run time behaviour of Blender including what functions are being invoked and called.
- Deployment, or physical, viewpoint; takes a brief look at how Blender is deployed and built on different operating systems.
- Scenarios, or use cases; describes the use cases for Blender through sequences of interactions between objects and between processes.2
When examining the architecture of Blender, it becomes clear to see that Blender uses a combination of two distinct architectural patterns, the main one being model-view-controller (MVC) pattern, even if not very apparant from the large codebase. Blender, by near definition of 3D computer graphics software, needs a GUI (view) for the end-user to be able to interact (controller) with the underlying logic (model). For this to be achieved, the code for each part needs to be, and is, separated in the architecture. When we zoom into the controller and view of Blender, we see that a domain-driven design lies underneath, because data are divided into specific domains. In update 2.5, context structs were added into the code that clearly defined the data that each type of code can assume to be in its own context. The following figure gives an overview of the different contexts and modules of Blender (From 3 Shared under CC BY-SA 3.0.)
The user preferences that sit on top of everything are the different preferences the end-user sets. The main is/are the
.blend file(s) that is/are currently open. From there, there is a window manager which encapsulates drawing and editing code, which always works on three levels; screen, area or region, all containing certain data. The evaluation and rendering module should not have access to the three level structs, but should be run in the background. The only thing they can do is pass along a certain scene, for example to render it. The structure for the underlying logic is not very well documented yet. However, it is evident from the fact that the developmemt of Blender is divided into the modules mentioned in the previous blog post that all workspaces have their own module in the code architecture as well.
We present Blender’s context in the following diagram, using what we have learned so far of its contextual scope.
This viewpoint looks at the architectural structure of the source code. Blender is a huge project, and we will therefore list only the most important components and parts. The full source code can be explored online at 4. Besides code for document generation, platform-specific release files, tests, configuration files, and the build system, the root folder of Blender’s source code contains three salient components:
source, which holds Blender’s main application code;
internal, containing “source-code maintained by Blender developers but kept as isolated modules/libraries”; and
external, containing (stand-alone) code libraries imported “because they aren’t common system libraries we can rely on the system providing” 5. A slightly outdated, but still insightful, overview of Blender’s source code directory structure is given below (From 5, shared under CC BY-SA 3.0):
source, in turn, contains two important components:
creator. The former contains the actual application code, while the latter contains the main() entry point of the application. The
blender component consists of many subcomponents. The Blender kernel, i.e. the low-level data structure manipulation and memory allocation code, is located in its
bmesh comprises a mesh editing API, while
python comprises the Python scripting API. According to the developer wiki, “most of the interesting code” is in the
editors component, which contains all the code for Blender’s graphical interface and its editors.
A curious thing we noted was the fact that the Cycles rendering engine is located in intern/cycles, while the newer EEVEE renderer is in source/blender/draw/engines/eevee.
It is difficult to discern any kind of coherent architectural patterns from the dependency graph generated by the source code (figure 4). Furthermore, from the high-level ratings provided by SIG, it is apparent that Blender’s codebase is rather complex, as the ratings are very low across the board with the exception of code duplication. We did not have access to SIG’s Sigrid analysis results of Blender.
This viewpoint analyzes the interoperation between components at run-time. Again, because Blender is a massive program and the numerous components are tightly coupled (see the dependency graph), we will start by treating the core loop of the program. When Blender is launched, it first executes its
main() function in
source/creator/creator.c, which initiates subsystems and runs Blender’s event loop:
WM_main(). This window manager continuously (
- gets events from GHOST (General Handy OS Toolkit), handles window events, and adds these to window-specific queues. GHOST is Blender’s own GUI toolkit and windowing system, which handles peripheral input and provides access to the windows. GHOST then draws the GUI directly using OpenGL, so it doesn’t rely on any OS-native windowing system. Python scripts constitute the most abstract layer of the Blender GUI chain, which issues the most general commands (e.g. “draw button”) to GHOST and receives the most general commands (“the user clicked the button”) from GHOST. GHOST replaced GLUT in order to make Blender cross-platform. Events are caused by user input.
- handles window-specific events.
- handles and caches notifications (from other internal Blender systems) that events have left behind about changes
- draws the cached changes to the screen
Like we said in step 1., “the UI layout is defined in Python, but the drawing is done in C, and some of the UI is defined in C too” 6. To illustrate this, we consider adding a cube mesh. Python code (in
release/scripts/startup/bl_ui/space_view3d.py) defines a button,
mesh.primitive_cube_add, for adding a cube. During startup, the function
MESH_OT_primitive_cube_add registers itself as an operator, with
add_primitive_cube_exec as its callback function. When a user clicks the Add Cube button, Blender’s own RNA library translates
mesh.primitive_cube_add to the operator type
MESH_OT_primitive_cube_add, which in turn calls its
What’s RNA? Blender has two systems that handle data structures: DNA and RNA, names after the genetic nucleic acids. DNA defines how Blender’s data — user settings, mesh data, object data, scene data, etc. — is serialized as
.blend files, Blender’s file format for projects. This ensures compatibility with old
.blend files: a coded description of the layout of each DNA struct is also written to and read from disk, meaning that this layout does not have to stay fixed. RNA then is a “nicer”, more abstract interface to DNA 7. Though not much used in the C/C++, all Python code relies heavily on this data API.
Blender is a large software product with many dependencies, to be deployed to multiple runtime environments: Windows, macOS and Linux. To avoid version mismatching with the runtime’s own libraries, all dependencies are packaged along with Blender. Even a Python interpreter is bundled in the final Blender build. Because of this, final package size is about 150MB, but third-party software requirements are eliminated for the runtime platforms.
To create a Blender build,
make package can be run on each of the supported target platforms. An artifact archive including dependencies is then generated. These artifacts can be uploaded to Blender’s website by official developers, such that end users can download and install the application. Official releases can be downloaded8, as well as daily builds9 and builds of experimental branches10 for testing of cutting edge features. The package hosting environment and build bot are maintained by system administrators.
The scenario we present here are meant to showcase the rendering of a scene to the end-users and the sequences of events that happen in the code. This will give an idea of how the code executes when an end-user is interacting with Blender.
Scenario: End-user switches between workspaces. In order to update the scene efficiently, Blender uses a dependency graph.11 In short, the dependency graph makes it possible to only update what was dependent on the modified value and to not update anything which was not changed. Each workspace has its own window that the end-user can switch between. These windows own the dependency graphs and re-create it whenever workspaces are switched. The dependency graph holds pointers to the objects in a scene (DNA, RNA) and their relationships (which object is owned by whom). Pointers are used in order to save time by not deep copying objects in a dependency graph when re-creating them.
Functional requirements are requirements that describe what a system should do whereas non-functional requirements describe what a system should be.
Blender is an open source system. They host their own codebase alongside their website, wiki, and other services. Blender allows not only developers to contribute to the development of the application but also regular users can contribute. Users can request for features to be added or report an issue they encounter. Furthermore, anyone can join their chat rooms, or post threads on their developer forums. They also host weekly meetings in the developer chatrooms with an alternating schedule to accomodate for people living in different timezones.
Blender used to have a reputation for having an unintuitive user interface which can be especially daunting to beginners. One of the key points in the 2.80 update was to revamp the user interface to provide a better user experience. One example that many people found unintuitive is the fact that right click is used to select things by default. This was changed to left click in the 2.80 update alongside many other changes to the UI like providing a better way to organize your workflow with the addition of Workspaces. More widgets have been added.
Naturally, rendering scenes (either real-time or photorealistic) need to be as efficient as possible. Blender values the performance of the application as new patches (changes/commits) have to be evaluated for whether the have a negative impact on the performance.12 Furthermore, Blender has a platform for collecting and displaying results of hardware and software test using their benchmark tool. These resulting benchmarks are used to compare differences in performances across different systems as well as to support the Blender development process.
Blender ensures forward and backward compatibility of their blend files. There is an exception from Blender version 2.80 onwards as files created in those versions of Blender are not always backwards compatible with (older verions)[https://developer.blender.org/T59120], because of the extensive amount of changes in Blender 2.80. Forwards compatibility is still ensured; Blender files created using older Blender versions can still be opened using the latest version of Blender. Compatibility is also part of their Quality Checklist.12
Blender’s blend files can be bundled with Pythons scripts, which poses a security risk. Blender has attempted to provide better security, but this has usually resulted in users voicing their discontent about discontent about driver constraints. A user expressed his worries in another thread at the fact that Python scripts are executed by default. Blender has taken measures in order to try and alleviate some of these concerns but security is not of high priority: “Blender does not attempt to achieve the same level of security as many other applications.”13
In essence, Blender’s non-functional priorities are ease-of-use and performance, the former in terms of GUI/lay-out, and the latter in terms of viewport responsiveness, rendering times, and speed of simulation calculations. There isn’t any inherent trade-off between these properties; either of them can be optimized without impacting the other. Any other non-functional properties are less of a priority.
Nick Rozanski and Eoin Woods, Software Systems Architecture: Working with Stakeholders Using Viewpoints and Perspectives. Addison-Wesley, 2012, 2nd edition. https://www.viewpoints-and-perspectives.info/ ↩
Wikipedia, 4+1 architectural view model. https://en.wikipedia.org/wiki/4%2B1_architectural_view_model ↩
Blender wiki, Contexts. Last accessed 2020-03-19. https://wiki.blender.org/wiki/Source/Architecture/Context ↩
Blender Developer Wiki, Blender source code directories explained. https://wiki.blender.org/wiki/Source/File_Structure. Last accessed 2020-03-03. ↩ ↩2
Blender, RNA. https://wiki.blender.org/wiki/Source/Architecture/RNA. Last accessed 2020-03-15. ↩
Blender, Quality Checklist https://wiki.blender.org/wiki/Tools/CodeReview#Quality_Checklist. Last accessed 2020-03-19. ↩ ↩2
Blender, How Does Blender Deal with Security? https://wiki.blender.org/wiki/Reference/FAQ#How_Does_Blender_Deal_with_Security.3F. Last accessed 2020-03-19. ↩