Skip to content

Commit 26fc59a

Browse files
committed
LibGodot Feature
1 parent 41443e8 commit 26fc59a

File tree

37 files changed

+1756
-2
lines changed

37 files changed

+1756
-2
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build/

.gitmodules

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[submodule "godot"]
2+
path = godot
3+
url = ../godot
4+
[submodule "SwiftGodot"]
5+
path = SwiftGodot
6+
url = ../SwiftGodot
7+
[submodule "SwiftGodotKit"]
8+
path = SwiftGodotKit
9+
url = ../SwiftGodotKit
10+
[submodule "godot-cpp"]
11+
path = godot-cpp
12+
url = ../godot-cpp

README.md

Lines changed: 184 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,184 @@
1-
LibGodot Project
2-
----------------
1+
Migeran LibGodot Project
2+
------------------------
3+
4+
## Features
5+
6+
* Compile Godot Engine as either static or shared library.
7+
* Expose Godot Engine controls (startup, iteration, shutdown) over the GDExtension API to a host process.
8+
* On Apple Platforms, Godot Engine windows (both the main window, and other windows created after startup) may be rendered into surfaces provided by the host process.
9+
* This support can be extended to other platforms, read on for details.
10+
* Sample applications
11+
* C++ Sample (tested on MacOS and Linux)
12+
* SwiftUI Sample (tested on iOS)
13+
14+
## Why LibGodot?
15+
16+
LibGodot has a number of different use cases.
17+
* Control Godot Engine from a host application
18+
* E.g. Start Godot from a .NET process to use standard .NET tooling
19+
* Use it for automation of development tasks (e.g. building an asset pipeline using a Python GDExtension API)
20+
* Embed Godot into another application while displaying Godot Windows as part of the host application's UI.
21+
22+
## Screenshot
23+
24+
<img src="doc/ios_test.jpg" width="600" align="center">
25+
26+
This screenshot shows an iOS application displaying 4 Godot Engine Windows each laid out using SwiftUI.
27+
28+
## Quickstart
29+
30+
This repository is set up for easy testing of LibGodot.
31+
32+
Execute the following commands
33+
```
34+
git clone --recursive https://github.com/migeran/libgodot_project
35+
36+
cd libgodot_project
37+
38+
# Build for the host platform (Mac and Linux)
39+
./build_libgodot.sh
40+
41+
# Build for iOS (Mac only)
42+
./build_libgodot.sh --target ios
43+
```
44+
45+
### C++ Sample
46+
47+
The C++ sample shows how Godot can be controlled by a host process, by displaying its own window.
48+
49+
After libgodot is compiled successfully, run the following commands to test the C++ sample:
50+
51+
```
52+
cd samples/cpp_sample
53+
mkdir build
54+
cmake -S . -B build
55+
cd build
56+
./sample
57+
```
58+
59+
### SwiftUI Sample
60+
61+
To test the SwiftUI sample, just open the ios_sample project in XCode 15.1+, build it and run on any iOS device. (iOS Simulator is not supported due to Godot limitations.)
62+
63+
Note: You will need to select a development team in the XCode project to sign the application.
64+
65+
## Migeran LibGodot Design
66+
67+
The core idea of the LibGodot feature is to build upon Godot's strong extension capabilities: its modularity, powerful type system and the GDExtension API.
68+
69+
The main advantage of this approach is, that this way LibGodot may be used from any language that has a GDExtension binding (e.g. C++, Swift, Rust, Python, ... etc.), with only minimal changes to the binding.
70+
71+
### Godot Instance Lifecycle Management
72+
73+
The main class added by the LibGodot design is the GodotInstance:
74+
75+
```cpp
76+
class GodotInstance : public Object {
77+
GDCLASS(GodotInstance, Object);
78+
79+
static void _bind_methods();
80+
81+
bool started = false;
82+
83+
public:
84+
GodotInstance();
85+
~GodotInstance();
86+
87+
bool start();
88+
bool is_started();
89+
bool iteration();
90+
void shutdown();
91+
};
92+
```
93+
94+
This class is made accessible over the GDExtension API. This class can be used to control a Godot instance.
95+
96+
To actually create a Godot instance a new symbol is added to the GDExtension API:
97+
98+
```cpp
99+
GDExtensionObjectPtr gdextension_create_godot_instance(int p_argc, char *p_argv[], GDExtensionInitializationFunction p_init_func);
100+
```
101+
102+
This function can be used to create a new Godot instance and return a GodotInstance object reference to control it. Both samples show how easy it is to bind this function and then use the generated GDExtension API bindings with the returned GodotInstance object.
103+
104+
To properly destroy a Godot instance the GDExtension API is extended by another symbol:
105+
106+
```cpp
107+
typedef void (*GDExtensionInterfaceDestroyGodotInstance)(GDExtensionObjectPtr p_godot_instance);
108+
```
109+
110+
This function is made available through the GDExtension API's getProcAddress mechanism.
111+
112+
Note: Due to Godot's internal architecture (the use of global data structures and singletons) only one Godot instance may be created in a process.
113+
114+
### Embedding Godot UI
115+
116+
Note: UI embedding is currently implemented for Apple platforms. Please read the [Next Steps](#next-steps) section for more information on the status of other platforms.
117+
118+
To allow for embedding the Godot UI into a host process the host process needs to be able to pass the necessary data about a native surface where
119+
Godot may render its contents. The form of this native surface is entirely platform and rendering backend specific.
120+
121+
The Migeran LibGodot design adds the following types to Godot to allow this:
122+
123+
* A new ``DisplayServerEmbedded`` implementation which uses externally provided native surfaces as its rendering targets.
124+
* A ``RenderingNativeSurface`` class and its associated platform specific subclasses, e.g. ``RenderingNativeSurfaceApple``.
125+
* The ``Window`` class is extended by a ``set_native_surface(Ref<RenderingNativeSurface>)`` method which allows specifying the rendering target of a Window in a typesafe, platform independent manner. It is even possible to change the rendering target of a Window dynamically during its lifecycle.
126+
127+
These classes are all exposed to the GDExtension API, so it is easy to use them from the host process.
128+
129+
The ``DisplayServerEmbedded`` class also exposes methods that allow the injection of input events from the host process into the Godot instance.
130+
131+
The RenderingNativeSurface class hierarchy has an additional function: provides a mechanism using the **Factory Method** design pattern to create compatible RenderingContextDrivers for a native surface.
132+
133+
This allowed us to make the ``DisplayServerEmbedded`` class platform agnostic.
134+
135+
We also refactored the other ``DisplayServer`` implementations to use ``RenderingNativeSurface`` during initialization.
136+
137+
Since all these classes are exposed over the GDExtension API, these can be seamlessly used from any supported language with a GDExtension binding.
138+
139+
### Rationale for RenderingNativeSurface Design
140+
141+
For those who are familiar with Godot Engine internals: there was already a way to pass in platform specific native surface data (called ``WindowPlatformData``) during initialization.
142+
143+
Why is this refactoring necessary to a full fledged reference counted object?
144+
145+
* We chose reference counting because it makes it easier to use on the client side, no need to manage the lifecycle manually. Earlier versions of this PR used simple Godot Objects, but they required manual memory management which was error prone.
146+
147+
* While on Apple platforms it is very easy to pass in a ``CAMetalLayer`` reference, and Metal (and thus MoltenVk) will happily render into it, other platforms impose way more restrictions.
148+
* For example: On Windows and Linux a separate Vulkan instance and the use of external textures is required to render Godot on a separate thread from the host application's main renderer thread.
149+
150+
* This is not just a theoretical option: We already implemented a prototype on Linux and Windows based on Godot 4.2, where the host process and Godot are using Vulkan External Textures to pass the rendered frames from Godot to the host process. We intend to upgrade and refactor this patch to the current version of the LibGodot patch.
151+
152+
* To use External Textures a thread-safe queue has to be implemented between between the host process and Godot, and a reference-counted RenderingNativeSurface subclass would be an ideal place to implement it, e.g. ``RenderingNativeSurfaceVulkanExternalTextureWin32``.
153+
154+
## Next Steps
155+
156+
### Open for Discussion
157+
158+
We are happy to discuss any part of the design, naming conventions, additional features ... etc.
159+
160+
### Merging for Godot 4.3 as an Experimental Feature
161+
162+
* We propose to merge the current LibGodot patch as an experimental feature in Godot 4.3, because it is already very usable in its current form for many use cases.
163+
164+
### Godot 4.4 Developments
165+
166+
During the Godot 4.4 development cycle additional features may be added, for Example:
167+
168+
* Support for additional platforms for UI embedding: Android, Windows, Linux (both X11 and Wayland).
169+
* Support for OpenGL (Native / ANGLE) UI embedding on all platforms.
170+
* Add a Configuration API that can be used on startup instead of supplying command line parameters.
171+
172+
## Sponsors & Acknowledgements
173+
174+
* Initial development sponsored by [Smirk Software](https://www.smirk.gg/)
175+
* Rebasing to Godot 4.3 and further development sponsored by [Xibbon Inc.](https://xibbon.com)
176+
177+
* The GDExtension registration of the host process & build system changes were based on @Faolan-Rad's LibGodot PR: https://github.com/godotengine/godot/pull/72883
178+
179+
## About the Authors
180+
181+
This feature was implemented by [Migeran - Godot Engine & Robotics Experts](https://migeran.com).
182+
183+
We are available for new software development projects in Robotics, Godot Engine, AR/VR and other domains. If you have any questions, you may [contact us through our website](https://migeran.com/contact).
184+

SwiftGodot

Submodule SwiftGodot added at 343458e

SwiftGodotKit

Submodule SwiftGodotKit added at 64b5431

build_libgodot.sh

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#!/bin/bash
2+
3+
set -eux
4+
5+
BASE_DIR="$( cd "$(dirname "$0")" ; pwd -P )"
6+
7+
GODOT_DIR="$BASE_DIR/godot"
8+
GODOT_CPP_DIR="$BASE_DIR/godot-cpp"
9+
SWIFT_GODOT_DIR="$BASE_DIR/SwiftGodot"
10+
SWIFT_GODOT_KIT_DIR="$BASE_DIR/SwiftGodotKit"
11+
BUILD_DIR=$BASE_DIR/build
12+
13+
host_system="$(uname -s)"
14+
host_arch="$(uname -m)"
15+
host_target="editor"
16+
target="editor"
17+
target_arch=""
18+
host_build_options=""
19+
target_build_options=""
20+
lib_suffix="so"
21+
host_debug=1
22+
debug=1
23+
force_host_rebuild=0
24+
force_regenerate=0
25+
26+
case "$host_system" in
27+
Linux)
28+
host_platform="linuxbsd"
29+
cpus="$(nproc)"
30+
target_platform="linuxbsd"
31+
;;
32+
Darwin)
33+
host_platform="macos"
34+
cpus="$(sysctl -n hw.logicalcpu)"
35+
target_platform="macos"
36+
lib_suffix="dylib"
37+
;;
38+
*)
39+
echo "System $host_system is unsupported"
40+
exit 1
41+
;;
42+
esac
43+
44+
45+
while [ "${1:-}" != "" ]
46+
do
47+
case "$1" in
48+
--host-rebuild)
49+
force_host_rebuild=1
50+
;;
51+
--host-debug)
52+
host_debug=1
53+
;;
54+
--regenerate)
55+
force_regenerate=1
56+
;;
57+
--debug)
58+
debug=1
59+
;;
60+
--target)
61+
shift
62+
target_platform="${1:-}"
63+
;;
64+
*)
65+
echo "Usage: $0 [--host-debug] [--host-rebuild] [--debug] [--regenerate] --target <target platform>"
66+
exit 1
67+
;;
68+
esac
69+
shift
70+
done
71+
72+
if [ "$target_platform" = "ios" ]
73+
then
74+
target_arch="arm64"
75+
target="template_debug"
76+
lib_suffix="a"
77+
fi
78+
79+
if [ "$target_arch" = "" ]
80+
then
81+
target_arch="$host_arch"
82+
fi
83+
84+
host_godot_suffix="$host_platform.$host_target"
85+
86+
if [ $host_debug -eq 1 ]
87+
then
88+
host_build_options="$host_build_options dev_build=yes"
89+
host_godot_suffix="$host_godot_suffix.dev"
90+
fi
91+
92+
host_godot_suffix="$host_godot_suffix.$host_arch"
93+
94+
target_godot_suffix="$target_platform.$target"
95+
96+
if [ $debug -eq 1 ]
97+
then
98+
target_build_options="$target_build_options dev_build=yes"
99+
target_godot_suffix="$target_godot_suffix.dev"
100+
fi
101+
102+
target_godot_suffix="$target_godot_suffix.$target_arch"
103+
104+
host_godot="$GODOT_DIR/bin/godot.$host_godot_suffix"
105+
target_godot="$GODOT_DIR/bin/libgodot.$target_godot_suffix.$lib_suffix"
106+
107+
if [ ! -x $host_godot ] || [ $force_host_rebuild -eq 1 ]
108+
then
109+
rm -f $host_godot
110+
cd $GODOT_DIR
111+
scons p=$host_platform target=$host_target $host_build_options
112+
fi
113+
114+
mkdir -p $BUILD_DIR
115+
116+
if [ ! -f $BUILD_DIR/extension_api.json ] || [ $force_regenerate -eq 1 ]
117+
then
118+
cd $BUILD_DIR
119+
$host_godot --dump-extension-api
120+
fi
121+
122+
cd $GODOT_DIR
123+
scons p=$target_platform target=$target $target_build_options library_type=shared_library
124+
cp -v $target_godot $BUILD_DIR/libgodot.$lib_suffix
125+
126+
cp -v $BUILD_DIR/extension_api.json $GODOT_CPP_DIR/gdextension/
127+
cp -v $GODOT_DIR/core/extension/gdextension_interface.h $GODOT_CPP_DIR/gdextension/
128+
129+
if [ "$target_platform" = "ios" ]
130+
then
131+
$SWIFT_GODOT_DIR/scripts/make-libgodot.framework $GODOT_DIR $BUILD_DIR
132+
cp -v $BUILD_DIR/extension_api.json $SWIFT_GODOT_DIR/Sources/ExtensionApi/
133+
cp -v $GODOT_DIR/core/extension/gdextension_interface.h $SWIFT_GODOT_DIR/Sources/GDExtension/include/
134+
fi

doc/ios_test.jpg

376 KB
Loading

godot

Submodule godot added at a82f487

godot-cpp

Submodule godot-cpp added at b021245

samples/cpp_sample/CMakeLists.txt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
cmake_minimum_required(VERSION 3.2)
2+
3+
project(sample)
4+
5+
set(THREADS_PREFER_PTHREAD_FLAG ON)
6+
find_package(Threads REQUIRED)
7+
8+
set(CMAKE_CXX_STANDARD 17)
9+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -g")
10+
11+
set(GODOT_CPP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../godot-cpp)
12+
set(LIBGODOT_BUILD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../build)
13+
14+
add_subdirectory(${GODOT_CPP_DIR} ${GODOT_CPP_DIR}/build)
15+
16+
set(SOURCES src/main.cpp)
17+
18+
add_executable(${PROJECT_NAME} ${SOURCES})
19+
target_include_directories(${PROJECT_NAME} SYSTEM
20+
PRIVATE
21+
${GODOT_CPP_DIR}/include
22+
${GODOT_CPP_DIR}/gen/include
23+
${GODOT_CPP_DIR}/gdextension
24+
)
25+
target_link_libraries(${PROJECT_NAME} dl godot-cpp Threads::Threads)
26+
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
27+
COMMAND ${CMAKE_COMMAND} -E copy
28+
${LIBGODOT_BUILD_DIR}/libgodot${CMAKE_SHARED_LIBRARY_SUFFIX}
29+
${CMAKE_BINARY_DIR}
30+
COMMENT "Copying ${LIBGODOT_BUILD_DIR}/libgodot.${CMAKE_SHARED_LIBRARY_SUFFIX} to '${CMAKE_BINARY_DIR}'")

0 commit comments

Comments
 (0)