Let experiment with prebuilt CEF alone (meaning without Godot). We will
understand how the cefsimple
application given in the tests
folder is
compiled. This application demonstrates the minimal functionality required to
create a browser window. This is the minimal code source needed to embed inside
Godot. For its integration with Godot, please read the following doc.
These steps have been tested on Debian 11 64-bits and Ubuntu 18.04 64-bits.
Firstly, let name some folders. This will make our code shorter in this document. Do not forget to adapt the CEF version to your operating system with the desired version on https://cef-builds.spotifycdn.com/index.html:
TMP=/tmp
CEF_LINK=https://cef-builds.spotifycdn.com/cef_binary_96.0.14%2Bg28ba5c8%2Bchromium-96.0.4664.55_linux64.tar.bz2
CEF=$TMP/cef_binary_96.0.14+g28ba5c8+chromium-96.0.4664.55_linux64
CEF_TARBALL=$CEF.tar.bz2
CEFSIMPLE=$CEF/tests/cefsimple
CEFSIMPLE2=$CEF/tests/cefsimple2
Download and decompress it inside a temporary folder.
wget $CEF_LINK
tar jxvf $CEF_TARBALL
For later in this document, copy the cefsimple
folder as cefsimple2
and remove its CMakeLists.txt
file:
cp -r $CEFSIMPLE $CEFSIMPLE2
rm -f $CEFSIMPLE2/CMakeLists.txt
On Debian 11, the cmake version is too old. You can look at this bash script to upgrade your cmake: https://github.com/stigmee/doc/blob/master/doc/install_latest_cmake.sh
More information can be found at: https://trendoceans.com/how-to-install-cmake-on-debian-10-11/
This compilation step is important not only for compiling examples but also because compiled packages and library will be needed for the Stigmee project.
mkdir $CEF/build
cd $CEF/build
cmake -DCMAKE_BUILD_TYPE=Debug ..
make -j$(nproc)
# make -j$(nproc) cefclient cefsimple ceftests
You can also follow https://github.com/Zabrimus/cef-makefile-sample in where a pkg-config
file is created making simpler
the integration of CEF inside other projects.
Now, let check if our compiled binaries are functional!
This application demonstrates the minimal functionality required to create a browser window.
You can launch cefsimple
:
./tests/cefsimple/Release/cefsimple
If you try to run the application ./cefsimple
it will show the Google page. Else you can provide your URL:
./cefsimple --url='https://cef-builds.spotifycdn.com/index.html'
This application demonstrates a wide range of CEF functionalities.
You can launch cefclient
:
./tests/cefclient/Release/cefclient --use-views --load-extension=set_page_color
Note: there is a minor bug with CEF CMakefile: the folder cefclient/resources/extensions/set_page_color/
is
not correctly copied inside the build folder /build/tests/cefclient/Release/cefclient_files/
if you show this error
message, copy them manually:
[0211/111941.665199:ERROR:extension_util.cc(90)] Failed to load manifest from extensions/set_page_color/manifest.json
[0211/111941.762079:WARNING:extension_system.cc(139)] Failed to read extension manifest from set_page_color/manifest.json
Contains unit tests that exercise the CEF APIs.
You can launch ceftests
:
./tests/ceftests/Release/ceftests
Since cefsimple
is working, we no longer need it and we are using the copied folder cefsimple2
and try to redo manually steps that cmake made for us, as seen in the previous section.
The application cefsimple2
needs two local libs (one shared and one static) to be compiled:
- libcef.so (~1 Gb) already present within the tarball.
- libcef_dll_wrapper.a (~5 Mb) compiled thru cmake in the previous step.
The libcef shared
library exports a C API that isolates the user from the CEF runtime and code base. The libcef_dll_wrapper
project, which is distributed in source code form as part of the binary release, wraps this exported C API in a C++ API that is then linked into the client application.
Let copy them inside cefsimple2
:
cp -v $CEF/Debug/libcef.so $CEF/build/libcef_dll_wrapper/libcef_dll_wrapper.a $CEFSIMPLE2
Let compile cefsimple2
(c++ >= version 14 is needed). Since this example creates a X11 window, you'll need the X11 library -lX11
:
g++ --std=c++14 -W -Wall -Wno-unused-parameter -DCEF_USE_SANDBOX -DNDEBUG -D_FILE_OFFSET_BITS=64 -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -I$CEF -I$CEF/include cefsimple_linux.cc simple_app.cc simple_handler.cc simple_handler_linux.cc -o cefsimple2 ./libcef.so ./libcef_dll_wrapper.a -lX11
- The
-DNDEBUG
is for disabling the#include <assert>
when compiled in release mode (See https://stackoverflow.com/a/5354352/8877076) - The
-DCEF_USE_SANDBOX
is explained here https://bitbucket.org/chromiumembedded/cef/wiki/SandboxSetup and https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/design/sandbox.md - The
-D_FILE_OFFSET_BITS=64
is probably for working with files larger than 2Gb. - The
-D__STDC_FORMAT_MACROS
is for https://www.cplusplus.com/reference/cinttypes/ - The
-I
are for searching header files in given folders.
Be sure your LD_LIBRARY_PATH
is refering to the local folder (.
), else add it (or save it insie your ~/.bashrc
file):
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
The other solution, more complex, is to update your ld.so.conf.d
but I think the simplest solution will consit to modify the CMake to create a static library for libcef (libcef.a
) to force loading symbols inside the binary.
If you try to run the application ./cefsimple2
it will halt. It needs some local packages. Let copy them (for the moment I do not know how they are compiled):
(cd $CEF/build/tests/cefsimple/Debug/
cp -v icudtl.dat resources.pak chrome_100_percent.pak chrome_200_percent.pak v8_context_snapshot.bin $CEFSIMPLE2
)
mkdir -p $CEFSIMPLE2/locales
cp -v $CEF/build/tests/cefsimple/Debug/locales/en-US.pak $CEFSIMPLE2/locales
Summary files, from the CEF official documentation:
- chrome-sandbox: sandbox support binary.
- libcef.so: main CEF library.
- libcef_dll_wrapper.a: static library that all applications using the CEF C++ API must link against.
- icudtl.dat: unicode support data.
- cef.pak, devtools_resources.pak: non-localized resources and strings.
- natives_blob.bin, snapshot_blob.bin: V8 initial snapshot.
- locales/*.pak: locale-specific resources and strings.
- files/binding.html: cefclient application resources.
Here a screenshot of what you are supposed to have inside your folder:
If you try to run the application ./cefsimple2
it will show the Google page. Else you can provide your URL:
./cefsimple2 --url='https://cef-builds.spotifycdn.com/index.html'
I also copy:
cp $CEF/Debug/libGLESv2.so $CEF/Debug/libEGL.so $CEFSIMPLE2
To fix these errors:
[1122/222502.068976:ERROR:egl_util.cc(74)] Failed to load GLES library: ...
And I installed this system packages:
sudo apt-get install libxcb-sync-dev libxcb-dri3-dev libxcb-present-dev
To fix these errors:
[1122/222607.278155:WARNING:gpu_sandbox_hook_linux.cc(445)] dlopen(libxcb-dri3.so) failed with error: libxcb-dri3.so: Ne peut ouvrir le fichier d'objet partagé: Aucun fichier ou dossier de ce type
[1122/222607.278459:WARNING:gpu_sandbox_hook_linux.cc(447)] dlopen(libxcb-present.so) failed with error: libxcb-present.so: Ne peut ouvrir le fichier d'objet partagé: Aucun fichier ou dossier de ce type
[1122/222607.278567:WARNING:gpu_sandbox_hook_linux.cc(450)] dlopen(libxcb-sync.so) failed with error: libxcb-sync.so: Ne peut ouvrir le fichier d'objet partagé: Aucun fichier ou dossier de ce type
Note: For the moment we hardly understood CEF guts. The following explanations in this document are probably false due to our poor understandings of Chromium. They come from our understanding of the reading of the official documentation and mainly on our numerous painful hours of failures.
The Official CEF documentation at https://bitbucket.org/chromiumembedded/cef/wiki/Tutorial.md says:
- CEF uses multiple processes. The main application process is called the "browser" process. Sub-processes will be created for renderers, plugins, GPU, etc.
- On Windows and Linux the same executable can be used for the main process and sub-processes. On OS X you are required to create a separate executable and app bundle for sub-processes.
- Most processes in CEF have multiple threads. CEF provides functions and interfaces for posting tasks between these various threads.
Let traduce these sentences.
- For Linux, through any "activity monitor" applications able to show running processes, we can see all that the cefsimple process is not a single running process but several as shown in the following picture (TODO ADD picture):
- For Mac OS X, the following picture shows the content of the cefsimple application for Mac OS X compiled with the CMake command seen previously:
Let remind that a Mac OS X application is a special folder holding binaries and
resources. The main cefsimple binary is stored in Contents/MacOS/
while the
others binaries (in fact they are another Mac applications) renderers, plugins,
GPU are stored in Contents/Frameworks/
. Libraries are stored in
Contents/Frameworks/Chromium Embedded Framework.framework/Libraries
and assets
are stored in Libraries are stored in Contents/Frameworks/Chromium Embedded Framework.framework/Resources
.
The documentation does not explain well how threads and processes are started and how they are linked. The following picture is the one found on this page. Processes are communicating through an inter-process communication (IPC) as described here
How all these processes are started and how to use them for inside Godot for the Stigmee application ? The official documentation at https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage.md says in section "Entry-Point Function" :
- Single Executable: When running as a single executable the entry-point function is required to differentiate between the different process types. The single executable structure is supported on Windows and Linux but not on MacOS.
- Separate Sub-Process Executable: When using a separate sub-process executable you need two separate executable projects and two separate entry-point functions.
Let rephrase these sentences. CEF can be embedded inside a C++ application and started by one of the following ways:
- Single Executable. This is the simple way when the
int main(int argc, char** argv)
function of the host application is reachable. When Chromium is starting its forks, it forks this process and modifies the command line to give different behavior for each process. - Separate Sub-Process Executable: if the
int main(int argc, char** argv)
function of the host application cannot be reached (or you are on Mac OS X), you have to launch another application which will access to its own main function and will be able to start Chromium and its forks through the command line as described in the first point.
Let remind that all C/C++ applications start from the int main(int argc, char** argv)
function. This function takes two arguments: argc
and argv
which hold
the command lines passed to the application when launched from a Linux
console. For example the following command line myapplication --help
. The
argv
is an array of strings holding all arguments as strings (even integers
are stored as strings). In our example the 0th argument is always the
application name "myapplication"
and the 1st argument is "--help"
. The
argc
indicates the number of arguments (therefore the size of the array). In
our example 2.
The documentation for CefExecuteProcess
says:
This function should be called from the application entry point function to
execute a secondary process. It can be used to run secondary processes from
the browser client executable (default behavior) or from a separate
executable specified by the CefSettings.browser_subprocess_path value. If
called for the browser process (identified by no "type" command-line value)
it will return immediately with a value of -1. If called for a recognized
secondary process it will block until the process should exit and then return
the process exit code. The |application| parameter may be empty. The
|windows_sandbox_info| parameter is only used on Windows and may be NULL (see
cef_sandbox_win.h for details).
Let traduce this sentence. CefExecuteProcess
forks the process of your host
application and starts another executable (Chromium or sub-process executable).
Forking a process consists to duplicate the whole current process (its memory,
its file descriptors, registers such as pointer counter ...) therefore you will
have two identical processes running the exact the same code source. They are
named parent
(or father
) and child
. The parent process is your current
process and the child is newly duplicated process. To differentiate them and
make them running different code source the return code of the fork function
returns the process identifier. An if-then-else is used to switch the pointer
counter to execute different code source. See this example.
int main()
{
pid_t pid = fork();
if (pid == -1)
{
// Error during the fork
perror("fork");
return EXIT_FAILURE;
}
else if (pid == 0)
{
// We are inside the child process
printf("Mon PID est %i et celui de mon père est %i\n", getpid(), getppid());
}
else
{
// We are inside the child parent
printf("Mon PID est %i et celui de mon fils est %i\n", getpid(), pid);
}
return EXIT_SUCCESS;
}
CefExecuteProcess
will block until the Chromium process ends and once Chromium
has done we immediately call exit
to avoid calling the code of the parent
process. Chromium process will keep forking and therefore the same int main(int argc, char** argv)
function of our host application will be executed several
times. To differentiate the processes, CEF modifies the command line to give
orders to sub-processes. When the command line is not for a CEF process the
CefExecuteProcess returns -1 and in this case we do not kill the process.
Let see, what we did initially for Godot, inside the
Godot/platform/x11/godot_x11.cpp
file. Note that for this case Godot also uses
the command line which is shared. To deal with this, we made a backup of the
command line before starting the process, then restoring the command line for
Godot.
int main(int argc, char** argv)
{
// Backup command line is needed if the host application is also using
// the command line with CEF. CEF is modifying it, so backup it to restore
// values for the host.
std::vector<std::string> backup_args;
std::cout << ::getpid() << "::" << ::getppid() << ": "
<< __FILE__ << ": " << __PRETTY_FUNCTION__ << std::endl;
for (int i = 0; i < argc; ++i)
{
std::cerr << "Before arg " << i << ": " << argv[i] << std::endl;
backup_args.push_back(argv[i]);
}
// Call sub-process
CefMainArgs args(argc, argv);
int exit_code = CefExecuteProcess(args, nullptr, nullptr);
if (exit_code >= 0)
{
// Sub proccess has ended, so exit this process
exit(exit_code);
}
else if (exit_code == -1)
{
// If called for the browser process (identified by no "type" command-line value)
// it will return immediately with a value of -1
}
// Restore command line since for the host application.
std::cout << ::getpid() << "::" << ::getppid() << ": "
<< "[CEF_start] Apres CEF_start " << std::endl;
for (int i = 0; i < argc; ++i)
{
std::cerr << "arg " << i << ": " << argv[i] << std::endl;
argv[i] = &(backup_args[i][0]);
}
// Configurate Chromium
CefSettings settings;
settings.windowless_rendering_enabled = true;
#if !defined(CEF_USE_SANDBOX)
settings.no_sandbox = true;
#endif
bool result = CefInitialize(args, settings, nullptr, nullptr);
if (!result)
{
std::cerr << "CefInitialize: failed" << std::endl;
exit(-2);
}
// Code for the host application
...
}
While modifying the Godot source of Godot was functional (point of view) for Linux architecture. The team preferred avoiding that but calling CEF from an arbitrary function and for example, from the init of GDScript. Therefore we have to use the second method: starting CEF from a separate sub-process executable.
A simple example not using Godot can be find here https://github.com/Lecrapouille/OffScreenCEF/tree/master/cefsimple_separate
To achieve starting CEF from a separate sub-process executable, the code source
of the cefsimple has to be duplicated. The first cefsimple is a pure standalone
executable which can be run as it. It is named the Separate Sub-Process Executable
. The difference is that CefExecuteProcess
and CefInitialize
use
a pointer from an instance class deriving from CefApp
. In the official
cefsimple when CEF wants to refresh the virtual callback method onPaint
is
triggered but in this application we do not have to override it to display HTML
page (the process has builtin rendering). This application is run inside a
windows.
The primary process does not have to call CefExecuteProcess
but just
CefInitialize
in where a setting is referring to the path of the sub-process
executable to run. We also define code for configuring the sub-process as
off-screen process. The primary process override the virtual callback method
onPaint
to display HTML with its personal method (SDL2, OpenGL, Godot).
WIP
Note: modifying cefsimple for OpenGL Core or SDL2 was an intermediate step for us to understand it better for its integration inside Godot or as Godot module. Code can be find here https://github.com/Lecrapouille/OffScreenCEF
There are non-maintained GitHub repos to replace the libX11 by:
- SDL2: https://github.com/gotnospirit/cef3-sdl2
- OpenGL Core: https://github.com/if1live/cef-gl-example
These repos are outdated (> 4 years), they do not compile and when I run them they crashed because of an infinite loop forking the application and finally the system will fall down. I have updated them into https://github.com/Lecrapouille/OffScreenCEF (the OpenGL version needs a better conversion of key pressed between glfw3 types and CEF types). One of the main reason is the non respect of the logic:
int main(int argc, char* argv[])
{
CefMainArgs main_args(argc, argv);
// CEF applications have multiple sub-processes (render, plugin, GPU, etc)
// that share the same executable. This function checks the command-line and,
// if this is a sub-process, executes the appropriate logic.
int exit_code = CefExecuteProcess(main_args, nullptr, nullptr);
if (exit_code >= 0) {
// The sub-process has completed so exit the application.
return exit_code;
}
argc, argv
are used by CEF/Chromium when forking they are passing information
Explanation of the source code (OpenGL version) cefsimple_opengl
-
GLCore.hpp it's just a collection of static methods (hence functions in a namespace) as a help to OpenGL to compile shaders:
- The vertex shader, which launches for each summit. Its input
position
is the position of the vertices.MVP
(model view projection) is to apply the rotation on the vertices.Texcoord
is the positions of the texture on the vertices. - Le fragment shader this is the fragment shader: the colors of the rectangle come from the colors of the texture. We remove the transparent pixels.
- The vertex shader, which launches for each summit. Its input
-
GLWindow.hpp it is a class which encapsulates a classic window created by the lib glfw3 (method
init ()
) there are the classic private and virtual methodssetup ()
andupdate ()
which are called by the methodstart ()
and which allow the daughter class to implement the game init and the game update. -
main.hpp c'est la classe qui hérite de la fenetre GLWindow, ajoute/supprime des "browsers" CEF (ligne 217
std::vector<std::shared_ptr<BrowserView>> m_browsers;
) et implemente les methodessetup()
etupdate()
. -
main.hpp c'est la classe qui encapsule 3 classes privées qui dérivent de l'API CEF:
CefRenderHandler
etCefClient
(ligne 70 et 139).BrowserView::RenderHandler::OnPaint
(ligne 100) est appellée par CEF quand il veut que l'on dessine. Cette classe contient tout l'artirail OpenGL: chargement + compilation, du shader + l'envoi du buffer CEF vers la texture OpenGL. La classeBrowserView
encapsule tout ca. -
main.cpp on demarre CEF qui lance tous ces forks.
-
main.cpp je créé 2 browser avec leurs URL. Ligne 364 je définie où OpenGL doit dessiner sur la fenetre (c'est en pourcentage de la dimension de la fenetre). Donc j'ai demandé de dessiner sur 2 régions verticales. Ligne 368: bidouille pour dire au a la classe renderer de faire tourner la texture. Ligne 371 je créé des callbacks sur les événements de la fenetre: click souris, souris bouge, touche du clavier, redimensionnement de la fenetre. Les callbacks sont dés la ligne 8: reshape_callback ... elles dispatch les événements vers les browsers (et convertissent les types glfw3 vers CEF. D'ailleurs mon code pour le clavier est buggé).
-
[main.cpp](https://github.com/Lecrapouille/OffScreenCEF/blob/master/cefsimple_opengl/main.cpp#L84 chargement du shader OpenGL) On crée un rectangle (2 triangles). On charge un shader pour chaque browser. https://github.com/Lecrapouille/OffScreenCEF/blob/master/cefsimple_opengl/main.cpp#L84 On dessine la texture dans le view port. https://github.com/Lecrapouille/OffScreenCEF/blob/master/cefsimple_opengl/main.cpp#L84 CEF nous demande le viewport qu'on lui envoie. https://github.com/Lecrapouille/OffScreenCEF/blob/master/cefsimple_opengl/main.cpp#L84 CEF nous envoie son image que l'on place dans la texture.
-
main.cpp c'est la fonction qui faudra remplacer car elle lit les messages et j'ai l'impression que c'est ca qui rend l'application peut reactive (clic souris ...) sinon lire une vidéo youtube est rapide (pas de temps de l'attence) mais le scrollbar d'une fenetre est super long.
Pour Godot on aura besoin du viewport: a gauche la scene 3D, à droite la page web (uniquement si on a cliquer sur un lien web). On n'aura pas besoin de shader OpenGL je pense qu'un sprite Godot devrait faire l'affaire.