© The Author(s), under exclusive license to APress Media, LLC , part of Springer Nature 2021
B. E. HollisterCore Blender Developmenthttps://doi.org/10.1007/978-1-4842-6415-7_1

1. Intro to the “Core” Blender Source Code

Brad E. Hollister1  
(1)
Computer Science Department, CSUDH, Carson, CA, USA
 

The goal of this chapter is to familiarize you with the Blender “core” source1, and is intended to be the starting point for the rest of the book. Blender’s codebase is written primarily in the C programming language. Part of this introduction is a preliminary discussion of Blender’s CMake build system. Additionally, we provide an execution trace of how Blender registers an operator, and of the execution of its callback. Much of this book will be easier to digest, if you make an effort to browse the source code in a text editor—ideally, after reading the associated passage. If you have not yet downloaded and compiled the Blender source code, you will eventually want to do so. However, this chapter may be read first.

The Blender Project

Throughout this book, we do not want to merely replicate the wealth of information already present at https://developer.blender.org. Nonetheless, we direct your attention to these valuable resources at the appropriate times. You should use those external documents in support of, and in tandem with, the material of this text.

We endeavor to provide a separate perspective from the official guides. This book’s goals are the following:
  • To fill in “missing” parts of the Blender Foundation’s documentation

  • To present a simpler starting point in becoming a Blender “core” developer

  • To go beyond existing documentation, so as to jump-start your efforts

Such experience could otherwise only be gained by direct inspection (and significant experimentation) of the source code. Ultimately, as you progress, that approach will be necessary as well.

Official Documentation

The official developer site is available at https://developer.blender.org (see Figure 1-1). There you will find the Blender Foundation’s Phabricator site, responsible for being a portal to their code repositories, bug tracker, developer documents, etc.
../images/495914_1_En_1_Chapter/495914_1_En_1_Fig1_HTML.jpg
Figure 1-1

The Blender Foundation’s portal for developers: https://​developer.​blender.​org

As aspects can change concerning how to clone and build Blender, and building is both platform specific and configurable, the best way to obtain up-to-date instructions is to visit https://developer.blender.org.

Communication Channels

It is helpful to speak with developers actively working on the core source code. While the Blender developer community maintains an Internet Relay Chat (IRC) room, located on Freenode in the channel #blendercoders, the latest way to interact in real-time is on https://blender.chat. #blender-coders is the relevant channel there, for developers (Figure 1-2).
../images/495914_1_En_1_Chapter/495914_1_En_1_Fig2_HTML.jpg
Figure 1-2

Blender’s browser-based chat server interface

As with most open source projects, Blender has an email list. This provides another key resource for communication with the Blender developer community. To post a message for “core” developers to read, and potentially answer, the email address is bf-committers@blender.org (see Figure 1-3). This is the appropriate list for developers (and, hopefully, eventually committers), as there are other mailing lists dedicated only to users, and other aspects of the Blender project apart from development.
../images/495914_1_En_1_Chapter/495914_1_En_1_Fig3_HTML.jpg
Figure 1-3

Bf-committers subscription web page. This sign-up page is accessible from the official Blender project wiki

The Blender Source Tree

To begin, you should become familiar with three essential documents. These are

There are other important documents that we will mention. But for starters, you should at least read the aforementioned material.

The key aspect of the codelayout.jpg information is the calling hierarchy of the modules. “Modules” in the Blender Project’s parlance represent directories containing code related to a particular set of functions (much as a component is, in an object-oriented design). Some modules have interfaces for client modules. The Blender terminology for these interfaces is module “APIs.”

The easiest way to begin browsing Blender’s source is by using the project’s repository server: https://developer.blender.org/diffusion/B/. There, you will also find the latest instructions on cloning the git repository.2

Start by looking at the top-level directory in the repository (Figure 1-4). The “core” Blender source code is in source/ (Figure 1-5).
../images/495914_1_En_1_Chapter/495914_1_En_1_Fig4_HTML.jpg
Figure 1-4

The Eclipse IDE’s “project explorer” view, from the CMake generated .proj. We can see that Blender contains many more directories than those of the “core” source code, located in source/. There is a directory for unit tests, that is, tests/. And, doc/ is for documentation. We will stay focused on source/, and go beyond it only when required

Libraries not considered “core,” but maintained by Blender, are in the intern directory. Notably, the ghost module is located there. We will, however, talk more about ghost (Generic Handy Operating System Toolkit) as it provides an abstraction to the underlying platform.
../images/495914_1_En_1_Chapter/495914_1_En_1_Fig5_HTML.jpg
Figure 1-5

The Eclipse IDE’s “project explorer” view of source/. Note that in codelayout.jpg and https://​wiki.​blender.​org/​wiki/​Source/​File_​Structure, this path would be written as blender/source/. Most of the official documents refer to the repository’s directory as blender/. However, during cloning with git, you can name this directory

Also in intern is an internal module called guardedalloc . This module wraps the dynamic memory allocation functions from the C standard library. The allocation functions are of the form:
MEM_[mc]allocN(unsigned int len, char * str)

Whenever Blender dynamically allocates (or deallocates) memory, you will see the MEM_* API being used instead of malloc, calloc, etc. The MEM_* API prototypes are available from [Source Directory]/intern/guardedalloc/MEM_guardedalloc.h.

Note

The [Source Directory]/doc/guides/ directory contains additional official documentation on guardedalloc and the user interface API. The two text files are blender-guardedalloc.txt and interface_API.txt. We will talk more about the UI_* API in Chapter 8. Much of the interface_API.txt document is now obsolete, but still contains useful information.

There are modules that we will not concern ourselves with, due to the scope of the codebase. Therefore, this book focuses on UI and geometric modeling, along with the essential modules from Blender (e.g., windowmanager, blenloader, etc.). Figure 1-6 shows a listing of the “core” Blender modules.

For now, realize that modules serving as support for other modules have an intern subdirectory of their own. There will also be a number of header files above their intern/, which allow export (inclusion) of the interface function prototypes. For the module blenloader (Figure 1-7), we can see this convention in the source tree.
../images/495914_1_En_1_Chapter/495914_1_En_1_Fig6_HTML.jpg
Figure 1-6

The “core” blender modules, showing the entire set located in source/blender/. The directory layout is split over two subfigures, left-to-right

In a general sense, the editors module is one of the highest in the module hierarchy, in that it only calls to lower-level functions. This module’s contents (each subdirectory being an editor or tool, not to be confused with source/tools) ties together the support functionality of other modules. We will explore editors/mesh/ (mesh operators or “tool” code), and editors/space_view3d/ (an editor “view”), further in subsequent chapters.
../images/495914_1_En_1_Chapter/495914_1_En_1_Fig7_HTML.jpg
Figure 1-7

The blenloader module . The intern subdirectory contains the implementation of its interface and static “internal” functions. Blenloader’s interface prototypes are located at the same level as intern/, in C header files. There is a similar layout for all modules with an API

The Blender CMake Build System

Now let us discuss how Blender makes use of the CMake build system and how various modules can be included or excluded from a build.

The Basics

CMake is a “meta” build system, developed by Kitware. By “meta,” it is implied that CMake acts as an abstraction layer over platform-specific build systems, such as autotools or Visual Studio. The official documentation for CMake is available at https://cmake.org. Kitware provides a complete description of their scripting language used to write CMakeLists.txt files there.

The responsibility of CMake is to produce build files for any given platform which is supported by Blender. CMake uses CMakeLists.txt files to direct the generation of platform-specific build scripts. For example, Blender’s CMakeLists.txt files are written to support build script generation for Linux, Windows, and Mac OS, and for a chosen build script type on that platform, that is, Visual Studio vs. Makefile on Linux.

In Blender’s source tree, a CMakeLists.txt file is provided at the top-level (shown in Figure 1-4) as the entry point. There is also a CMakeLists.txt file above the individual “core” modules (Figure 1-6) and in each of blender/, creator/, and tools/ (Figure 1-5). There is at least one CMakeLists.txt file in every module directory (not necessarily one in a module’s subdirectory, however) directing CMake to include the module’s source files and headers. For instance, see the CMakeLists.txt file in Figure 1-7, for the blenloader module.

Listing 1-1 shows an excerpt from the entry-point CMakeLists.txt. In addition to setting various CMake built-in environment variables (e.g., CMAKE_BUILD_TYPE_INIT), new ones are defined (here, OpenGL_GL_PREFERENCE, for instance). Also shown in Listing 1-1 is an inclusion of a number of modules (externally referenced CMake scripts), located at ${CMAKE_SOURCE_DIR}/build_files/cmake/Modules and ${CMAKE_SOURCE_DIR}/build_files/cmake/platform.
...
cmake_minimum_required(VERSION 3.5)
# Prever LEGACY OpenGL to eb compatible with all the existing releases and
# platforms which don't hare GLVND yet. Only do it if preference was not set
# externally.
if(NOT DEFINED OpenGL_GL_PREFERENCE)
  set(OpenGL_GL_PREFERENCE "LEGACY")
endif()
if(NOT EXECUTABLE_OUTPUT_PATH)
  set(FIRST_RUN TRUE)
else()
  set(FIRST_RUN FALSE)
endif()
# this starts out unset
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/build_files/cmake/Modules")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/build_files/cmake/platform")
# avoid having empty buildtype
if(NOT DEFINED CMAKE_BUILD_TYPE_INIT)
  set(CMAKE_BUILD_TYPE_INIT "Release")
...
Listing 1-1

Excerpt from the repository top-level CMakeLists.txt file3

As this book is about “core” Blender development, we focus on the CMakeLists.txt files that direct the build system for the primary source code. You should verify that Blender’s build system is more extensive than the CMakeLists.txt files in the repository’s source/ directory alone.

In Figure 1-8, we see the repository’s build_files/, where “support” CMake modules for the “core” build system are located. Other types of build scripts (e.g., bash scripts or Python scripts) are also placed in build_files/ for things such as downloading and building dependencies either for the development platform or internal and external libraries (e.g., the ones in intern/ and extern/ from Figure 1-4).
../images/495914_1_En_1_Chapter/495914_1_En_1_Fig8_HTML.jpg
Figure 1-8

The directories in the repository that comprise the build system. Most of the build system for “core” Blender is in the source/ directory, alongside the source files. We have CMake script support functions (defined in build_files/ subdirectories) or scripts for building Blender dependencies

Module Build Options

CMake allows for exclusion of modules and their dependencies. Listing 1-2 has an excerpt from source/blender/CMakeLists.txt, showing modules that are included always in a build, and then the optional ones. The WITH_* variables are set by default in source/CMakeLists.txt and can be reconfigured after running the initial configuration with the cmake executable.
...
add_subdirectory(datatoc)
add_subdirectory(editors)
add_subdirectory(windowmanager)
add_subdirectory(blenkernel)
add_subdirectory(blenlib)
add_subdirectory(bmesh)
add_subdirectory(draw)
add_subdirectory(render)
add_subdirectory(blenfont)
add_subdirectory(blentranslation)
add_subdirectory(blenloader)
add_subdirectory(depsgraph)
add_subdirectory(ikplugin)
add_subdirectory(physics)
add_subdirectory(gpu)
add_subdirectory(imbuf)
add_subdirectory(nodes)
add_subdirectory(modifiers)
add_subdirectory(gpencil_modifiers)
add_subdirectory(shader_fx)
add_subdirectory(makesdna)
add_subdirectory(makesrna)
if(WITH_COMPOSITOR)
  add_subdirectory(compositor)
endif()
if(WITH_IMAGE_OPENEXR)
  add_subdirectory(imbuf/intern/openexr)
endif()
if(WITH_OPENIMAGEIO)
  add_subdirectory(imbuf/intern/oiio)
endif()
if(WITH_IMAGE_DDS)
  add_subdirectory(imbuf/intern/dds)
endif()
if(WITH_IMAGE_CINEON)
  add_subdirectory(imbuf/intern/cineon)
endif()
if(WITH_CODEC_AVI)
  add_subdirectory(avi)
endif()
if(WITH_PYTHON)
  add_subdirectory(python)
endif()
if(WITH_OPENCOLLADA)
  add_subdirectory(collada)
endif()
if(WITH_FREESTYLE)
  add_subdirectory(freestyle)
endif()
if(WITH_ALEMBIC)
  add_subdirectory(alembic)
endif()
...
Listing 1-2

From source/blender/CMakeLists.txt, we see the “core” modules included by the add_subdirectory() CMake script function, along with conditional modules near the end of the snippet

You should find it useful to reset some of these options when configuring your build. Beyond what is shown here, there are more fine-grained configurations for builds. CMake actually defines C preprocessor macros to allow inclusion of individual lines of source code, instead of the coarser-level “whole” directories we show in Listing 1-2.

It is advisable that when building Blender for the first time, that you build in debug mode, by setting CMAKE_BUILD_TYPE_INIT to “Debug.” This can be done through the cmake executable after the initial configuration, or by changing the source/CMakeLists.txt file itself.

A Representative Code Example

Let us commence with exploring the Blender code, by a concrete example. It will relate UI interaction with the operator code tied to particular UI events. The purpose of this relatively simple example is to lay some groundwork for more future descriptions about operators and related topics.

Geometric Modeling and Operators

We wish to get the “ball rolling,” as it were, by introducing operator registration and associated callback invocation. Thus, we will trace the execution path taken by Blender when a user creates a spherical model, regarding the operator registration for that case.

Operators in Blender are essentially functions (and state) that are called when they are triggered by the UI. They are more than just a callback in Blender. We will mostly focus on the callback portion, in our example trace.

First, let us investigate operator registration. As we are interested in creating a sphere primitive, an icosphere modeled with polygons, we note the registration of this operator via MESH_OT_primitive_ico_sphere_add(). This function is defined in source/blender/editors/mesh/editmesh_add.c.

The actual registration entails the windowmanager’s WM_operatortype_append() and wmOperatorType struct. We will eventually describe these items more specifically further along in the book, together with naming conventions for module APIs. Relevant now is ED_operatortypes_mesh(), shown in Figure 1-9 and Listing 1-3.
../images/495914_1_En_1_Chapter/495914_1_En_1_Fig9_HTML.jpg
Figure 1-9

During Blender initialization, mesh operators are registered. The windowmanager module is called from main(), located in source/creator/creator.c, which subsequently calls the editor module’s ED_operatortypes_mesh(). The call stack is shown here (from gdb within the Eclipse IDE). Hexadecimal numbers are the logical addresses of the functions. These numbers are unimportant here, and sometimes may be partially abbreviated for clarity

Figure 1-9 exhibits the call stack at the point when ED_operatortypes_mesh() is executed. This is part of the regular initialization of Blender. We started in main() from source/creator/creator.c, and then there is a cascade of calls from the windowmanager module to the editor module, first starting from WM_init()—that is, the high-level windowmanager initialization routine. We see in Listing 1-3 a snippet of ED_operatortypes_mesh() .
/**************************** registration **********************************/
void ED_operatortypes_mesh(void)
{
  ...
  WM_operatortype_append(MESH_OT_primitive_cone_add);
  WM_operatortype_append(MESH_OT_primitive_grid_add);
  WM_operatortype_append(
                MESH_OT_primitive_monkey_add);
  WM_operatortype_append(
                MESH_OT_primitive_uv_sphere_add);
  WM_operatortype_append(
                MESH_OT_primitive_ico_sphere_add);
  ...
Listing 1-3

Excerpt from ED_operatortypes_mesh() from source/blender/editors/mesh/mesh_ops.c. This code snippet shows operator registration for many of the “primitives” that can be easily created by either menu or hotkey combinations. MESH_OT_primitive_ico_sphere_add is a function pointer and is in bold type

The call to WM_operatortype_append() connects the operator to the Blender “RNA” system (Listing 1-4). We defer a discussion about Blender “RNA” until the appropriate chapter, but wm_operatortype_append__begin() from source/blender/windowmanager/intern/wm_operator_type.c forms part of this “gluing” process.
/* all ops in 1 list (for time being... needs evaluation later) */
void WM_operatortype_append(void (*opfunc)(wmOperatorType *))
{
  wmOperatorType *ot =
         wm_operatortype_append__begin();
  opfunc(ot);
  wm_operatortype_append__end(ot);
}
Listing 1-4

The WM_operatortype_append() function . This function calls MESH_OT_primitive_ico_sphere_add() to register the operator shown in bold type

Also in WM_operatortype_append(), we call MESH_OT_primitive_ico_sphere_add(). MESH_OT_primitive_sphere_add() is shown in Listing 1-5. Notice the wmOperatorType object (referenced by ot) has an exec field. MESH_OT_primitive_ico_sphere_add() assigns the callback add_primitive_icosphere_exec() to the wmOperatorType object’s exec field. There are also other aspects of the operator registration, like setting the name, description, id name, and other fields of the wmOperatorType struct.
void MESH_OT_primitive_ico_sphere_add(wmOperatorType *ot)
{
  /* identifiers */
  ot->name = "Add Ico Sphere";
  ot->description = "Construct an Icosphere mesh";
  ot->idname = "MESH_OT_primitive_ico_sphere_add";
  /* api callbacks */
  ot->exec = add_primitive_icosphere_exec;
  ot->poll = ED_operator_scene_editable;
  /* flags */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
  /* props */
  RNA_def_int(ot->srna, "subdivisions", 2, 1, 10, "Subdivisions", "", 1, 8);
  ED_object_add_unit_props_radius(ot);
  ED_object_add_mesh_props(ot);
  ED_object_add_generic_props(ot, true);
}
Listing 1-5

MEST_OT_primitive_ico_sphere_add(), an operator registration function from source/blender/editors/mesh/editmesh_add.c. Setting the callback for the operator is in bold type

Listing 1-6 shows the add_primitive_icosphere_exec() callback implementation, defined in the same source file as MESH_OT_primitive_ico_sphere_add(). It is declared static, so that only the callback mechanism (operator) has access to the function.
static int add_primitive_icosphere_exec(bContext *C, wmOperator *op)
{
  MakePrimitiveData creation_data;
  Object *obedit;
  BMEditMesh *em;
  float loc[3], rot[3];
  bool enter_editmode;
  ushort local_view_bits;
  const bool calc_uvs = RNA_boolean_get(op->ptr, "calc_uvs");
  WM_operator_view3d_unit_defaults(C, op);
  ED_object_add_generic_get_opts(C, op, 'Z', loc, rot, &enter_editmode, &local_view_bits, NULL);
  obedit = make_prim_init(C,
                          CTX_DATA_(BLT_I18NCONTEXT_ID_MESH, "Icosphere"),
                          loc,
                          rot,
                          local_view_bits,
                          &creation_data);
  em = BKE_editmesh_from_object(obedit);
  ...
  return OPERATOR_FINISHED;
}
Listing 1-6

Elided add_primitive_icosphere_exec() callback function, from source/blender/editors/mesh/editmesh_add.c. Notice the bContext struct pointer called C, and the call to the makesrna module via RNA_boolean_get(). These are important parts of the Blender architecture, for maintaining and conveying state information

If a user creates an icosphere from the UI, add_primitive_icosphere_exec() will be called in response. Figure 1-10 shows the call stack for this scenario.
../images/495914_1_En_1_Chapter/495914_1_En_1_Fig10_HTML.jpg
Figure 1-10

WM_main() is the event loop for Blender, located in source/blender/windowmanager/intern/wm.c. wm_event_do_handlers() is the high-level function updating the callback (or handler) mechanism. We see here that add_primitive_icosphere_exec() is called after having been registered, and now invoked from the UI

We will not go further into add_primitive_icosphere_exec(), as the point of this example is to illustrate operator registration and invocation of a callback (other operators are registered similarly). For the present, however, notice the interaction with the module makesrna and the bContext struct called C (Listing 1-6). The global variable named C is significant.

Blender’s Event Loop

WM_main() contains the event loop for Blender, as shown in Listing 1-7. C, the same object of type bContext struct as already mentioned, is passed to each of the high-level update function calls of the event loop. All of these are wrappers who either collect events, process callbacks based on those events, or broadcast notifiers based on event processing.

The last routine in the event loop to execute is wm_draw_update(). Once all model data is updated, then the view is updated. Blender is a Model-View-Controller (MVC) application, and as such, the “model” parts are updated first, and then the “view.”
void WM_main(bContext *C)
{
  /* Single refresh before handling events.
   * This ensures we don't run operators before the depsgraph has been evaluated. */
  wm_event_do_refresh_wm_and_depsgraph(C);
  while (1) {
    /* get events from ghost, handle window events, add to window queues */
    wm_window_process_events(C);
    /* per window, all events to the window, screen, area and region handlers */
    wm_event_do_handlers(C);
    /* events have left notes about changes, we handle and cache it */
    wm_event_do_notifiers(C);
    /* execute cached changes draw */
    wm_draw_update(C);
  }
}
Listing 1-7

Blender’s main event loop, called WM_main(), is defined in source/blender/windowmanager/intern/wm.c

Note

The preceding trace was shown using the call stack and code snippets of the relevant execution path. If you are a visual learner, you will find it beneficial to use Doxygen.4 A sample call graph is shown in Figure 1-11. Because these call graphs are often very large, the proper way to display them is interactively from html. We will discuss Doxygen again in a later chapter.

../images/495914_1_En_1_Chapter/495914_1_En_1_Fig11_HTML.jpg
Figure 1-11

Partial view of WM_main()’s call graph. Such call graphs are easily generated using Doxygen

Road Map for the Remainder of the Book

In this first chapter, we went over some of the basics of the Blender code. We made a distinction between “core” Blender and the other parts of the codebase. We showed how a build of Blender is managed by CMake and how CMake can generate build scripts for various platforms. We also talked a little about operators and callbacks and saw how they worked in the creation of an icosphere.

What Will Not Be Covered

As the Blender codebase is vast, we will not cover things like the game engine and the rendering subsystems (e.g., “Blender Internal,” or Cycles). We will also not describe “core” code related to physics, compositing, animation, or modifiers. Nor will we extensively discuss the full set of editors, such as the outliner, etc.

What Will Be Covered

Going forward, we will break down a series of central “core” modules. We will discuss Blender’s “DNA,” the underlying records that maintain the state for Blender. We will also discuss how Blender integrates its embedded Python interpreter, how its UI is constructed at the C code level, and how Blender’s “RNA” is related to all of this. We will walk through the C code that draws the UI, an OpenGL-based set of widgets internal to Blender itself (and therefore, “core” Blender). The windowmanager module will be analyzed, as it is the primary module of Blender. And, we will decompose polygonal mesh operators to a low level and show how the corresponding mesh data structures are managed. Finally, we will create a new editor within the editor module, using C code to do so.

The focus of this text is how Blender’s code works as an application and how it processes aspects of polygonal models, namely, the basic polygonal mesh editor operations. Knowing this information should eventually allow developers to investigate areas of the Blender codebase beyond the material of this text.