Add THREADS_ENABLED macro in order to compile Godot to run on the main thread

This commit is contained in:
Adam Scott 2023-12-01 13:39:09 -05:00
parent 107f2961cc
commit bd70b8e1f6
No known key found for this signature in database
GPG Key ID: 1352C2919D96DDDF
33 changed files with 447 additions and 72 deletions

View File

@ -17,7 +17,24 @@ concurrency:
jobs: jobs:
web-template: web-template:
runs-on: "ubuntu-22.04" runs-on: "ubuntu-22.04"
name: Template (target=template_release) name: ${{ matrix.name }}
strategy:
fail-fast: false
matrix:
include:
- name: Template w/ threads (target=template_release, threads=yes)
cache-name: web-template
target: template_release
sconsflags: threads=yes
tests: false
artifact: true
- name: Template w/o threads (target=template_release, threads=no)
cache-name: web-nothreads-template
target: template_release
sconsflags: threads=no
tests: false
artifact: true
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -34,6 +51,8 @@ jobs:
- name: Setup Godot build cache - name: Setup Godot build cache
uses: ./.github/actions/godot-cache uses: ./.github/actions/godot-cache
with:
cache-name: ${{ matrix.cache-name }}
continue-on-error: true continue-on-error: true
- name: Setup python and scons - name: Setup python and scons
@ -42,10 +61,13 @@ jobs:
- name: Compilation - name: Compilation
uses: ./.github/actions/godot-build uses: ./.github/actions/godot-build
with: with:
sconsflags: ${{ env.SCONSFLAGS }} sconsflags: ${{ env.SCONSFLAGS }} ${{ matrix.sconsflags }}
platform: web platform: web
target: template_release target: ${{ matrix.target }}
tests: false tests: ${{ matrix.tests }}
- name: Upload artifact - name: Upload artifact
uses: ./.github/actions/upload-artifact uses: ./.github/actions/upload-artifact
if: ${{ matrix.artifact }}
with:
name: ${{ matrix.cache-name }}

View File

@ -183,6 +183,7 @@ opts.Add(BoolVariable("separate_debug_symbols", "Extract debugging symbols to a
opts.Add(EnumVariable("lto", "Link-time optimization (production builds)", "none", ("none", "auto", "thin", "full"))) opts.Add(EnumVariable("lto", "Link-time optimization (production builds)", "none", ("none", "auto", "thin", "full")))
opts.Add(BoolVariable("production", "Set defaults to build Godot for use in production", False)) opts.Add(BoolVariable("production", "Set defaults to build Godot for use in production", False))
opts.Add(BoolVariable("generate_apk", "Generate an APK/AAB after building Android library by calling Gradle", False)) opts.Add(BoolVariable("generate_apk", "Generate an APK/AAB after building Android library by calling Gradle", False))
opts.Add(BoolVariable("threads", "Enable threading support", True))
# Components # Components
opts.Add(BoolVariable("deprecated", "Enable compatibility code for deprecated and removed features", True)) opts.Add(BoolVariable("deprecated", "Enable compatibility code for deprecated and removed features", True))
@ -832,6 +833,10 @@ if selected_platform in platform_list:
suffix += ".double" suffix += ".double"
suffix += "." + env["arch"] suffix += "." + env["arch"]
if not env["threads"]:
suffix += ".nothreads"
suffix += env.extra_suffix suffix += env.extra_suffix
sys.path.remove(tmppath) sys.path.remove(tmppath)
@ -972,6 +977,9 @@ if selected_platform in platform_list:
env.Tool("compilation_db") env.Tool("compilation_db")
env.Alias("compiledb", env.CompilationDatabase()) env.Alias("compiledb", env.CompilationDatabase())
if env["threads"]:
env.Append(CPPDEFINES=["THREADS_ENABLED"])
Export("env") Export("env")
# Build subdirs, the build order is dependent on link order. # Build subdirs, the build order is dependent on link order.

View File

@ -47,6 +47,7 @@ WorkerThreadPool *WorkerThreadPool::singleton = nullptr;
thread_local CommandQueueMT *WorkerThreadPool::flushing_cmd_queue = nullptr; thread_local CommandQueueMT *WorkerThreadPool::flushing_cmd_queue = nullptr;
void WorkerThreadPool::_process_task(Task *p_task) { void WorkerThreadPool::_process_task(Task *p_task) {
#ifdef THREADS_ENABLED
int pool_thread_index = thread_ids[Thread::get_caller_id()]; int pool_thread_index = thread_ids[Thread::get_caller_id()];
ThreadData &curr_thread = threads[pool_thread_index]; ThreadData &curr_thread = threads[pool_thread_index];
Task *prev_task = nullptr; // In case this is recursively called. Task *prev_task = nullptr; // In case this is recursively called.
@ -69,6 +70,7 @@ void WorkerThreadPool::_process_task(Task *p_task) {
curr_thread.current_task = p_task; curr_thread.current_task = p_task;
task_mutex.unlock(); task_mutex.unlock();
} }
#endif
if (p_task->group) { if (p_task->group) {
// Handling a group // Handling a group
@ -143,6 +145,7 @@ void WorkerThreadPool::_process_task(Task *p_task) {
} }
} }
#ifdef THREADS_ENABLED
{ {
curr_thread.current_task = prev_task; curr_thread.current_task = prev_task;
if (p_task->low_priority) { if (p_task->low_priority) {
@ -159,6 +162,7 @@ void WorkerThreadPool::_process_task(Task *p_task) {
} }
set_current_thread_safe_for_nodes(safe_for_nodes_backup); set_current_thread_safe_for_nodes(safe_for_nodes_backup);
#endif
} }
void WorkerThreadPool::_thread_function(void *p_user) { void WorkerThreadPool::_thread_function(void *p_user) {
@ -542,6 +546,7 @@ bool WorkerThreadPool::is_group_task_completed(GroupID p_group) const {
} }
void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) { void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) {
#ifdef THREADS_ENABLED
task_mutex.lock(); task_mutex.lock();
Group **groupp = groups.getptr(p_group); Group **groupp = groups.getptr(p_group);
task_mutex.unlock(); task_mutex.unlock();
@ -574,6 +579,7 @@ void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) {
task_mutex.lock(); // This mutex is needed when Physics 2D and/or 3D is selected to run on a separate thread. task_mutex.lock(); // This mutex is needed when Physics 2D and/or 3D is selected to run on a separate thread.
groups.erase(p_group); groups.erase(p_group);
task_mutex.unlock(); task_mutex.unlock();
#endif
} }
int WorkerThreadPool::get_thread_index() { int WorkerThreadPool::get_thread_index() {

View File

@ -33,6 +33,8 @@
#include "core/os/mutex.h" #include "core/os/mutex.h"
#ifdef THREADS_ENABLED
#ifdef MINGW_ENABLED #ifdef MINGW_ENABLED
#define MINGW_STDTHREAD_REDUNDANCY_WARNING #define MINGW_STDTHREAD_REDUNDANCY_WARNING
#include "thirdparty/mingw-std-threads/mingw.condition_variable.h" #include "thirdparty/mingw-std-threads/mingw.condition_variable.h"
@ -66,4 +68,16 @@ public:
} }
}; };
#else // No threads.
class ConditionVariable {
public:
template <class BinaryMutexT>
void wait(const MutexLock<BinaryMutexT> &p_lock) const {}
void notify_one() const {}
void notify_all() const {}
};
#endif // THREADS_ENABLED
#endif // CONDITION_VARIABLE_H #endif // CONDITION_VARIABLE_H

View File

@ -40,7 +40,11 @@ void _global_unlock() {
_global_mutex.unlock(); _global_mutex.unlock();
} }
#ifdef THREADS_ENABLED
template class MutexImpl<THREADING_NAMESPACE::recursive_mutex>; template class MutexImpl<THREADING_NAMESPACE::recursive_mutex>;
template class MutexImpl<THREADING_NAMESPACE::mutex>; template class MutexImpl<THREADING_NAMESPACE::mutex>;
template class MutexLock<MutexImpl<THREADING_NAMESPACE::recursive_mutex>>; template class MutexLock<MutexImpl<THREADING_NAMESPACE::recursive_mutex>>;
template class MutexLock<MutexImpl<THREADING_NAMESPACE::mutex>>; template class MutexLock<MutexImpl<THREADING_NAMESPACE::mutex>>;
#endif

View File

@ -43,6 +43,8 @@
#define THREADING_NAMESPACE std #define THREADING_NAMESPACE std
#endif #endif
#ifdef THREADS_ENABLED
template <class MutexT> template <class MutexT>
class MutexLock; class MutexLock;
@ -125,8 +127,8 @@ class MutexLock {
THREADING_NAMESPACE::unique_lock<typename MutexT::StdMutexType> lock; THREADING_NAMESPACE::unique_lock<typename MutexT::StdMutexType> lock;
public: public:
_ALWAYS_INLINE_ explicit MutexLock(const MutexT &p_mutex) : explicit MutexLock(const MutexT &p_mutex) :
lock(p_mutex.mutex){}; lock(p_mutex.mutex) {}
}; };
// This specialization is needed so manual locking and MutexLock can be used // This specialization is needed so manual locking and MutexLock can be used
@ -155,4 +157,38 @@ extern template class MutexImpl<THREADING_NAMESPACE::mutex>;
extern template class MutexLock<MutexImpl<THREADING_NAMESPACE::recursive_mutex>>; extern template class MutexLock<MutexImpl<THREADING_NAMESPACE::recursive_mutex>>;
extern template class MutexLock<MutexImpl<THREADING_NAMESPACE::mutex>>; extern template class MutexLock<MutexImpl<THREADING_NAMESPACE::mutex>>;
#else // No threads.
class MutexImpl {
mutable THREADING_NAMESPACE::mutex mutex;
public:
void lock() const {}
void unlock() const {}
bool try_lock() const { return true; }
};
template <int Tag>
class SafeBinaryMutex : public MutexImpl {
static thread_local uint32_t count;
};
template <class MutexT>
class MutexLock {
public:
MutexLock(const MutexT &p_mutex) {}
};
template <int Tag>
class MutexLock<SafeBinaryMutex<Tag>> {
public:
MutexLock(const SafeBinaryMutex<Tag> &p_mutex) {}
~MutexLock() {}
};
using Mutex = MutexImpl;
using BinaryMutex = MutexImpl;
#endif // THREADS_ENABLED
#endif // MUTEX_H #endif // MUTEX_H

View File

@ -504,6 +504,12 @@ bool OS::has_feature(const String &p_feature) {
} }
#endif #endif
#ifdef THREADS_ENABLED
if (p_feature == "threads") {
return true;
}
#endif
if (_check_internal_feature_support(p_feature)) { if (_check_internal_feature_support(p_feature)) {
return true; return true;
} }

View File

@ -31,6 +31,10 @@
#ifndef SEMAPHORE_H #ifndef SEMAPHORE_H
#define SEMAPHORE_H #define SEMAPHORE_H
#include <cstdint>
#ifdef THREADS_ENABLED
#include "core/error/error_list.h" #include "core/error/error_list.h"
#include "core/typedefs.h" #include "core/typedefs.h"
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
@ -132,4 +136,17 @@ public:
#endif #endif
}; };
#else // No threads.
class Semaphore {
public:
void post(uint32_t p_count = 1) const {}
void wait() const {}
bool try_wait() const {
return true;
}
};
#endif // THREADS_ENABLED
#endif // SEMAPHORE_H #endif // SEMAPHORE_H

View File

@ -33,19 +33,22 @@
#include "thread.h" #include "thread.h"
#ifdef THREADS_ENABLED
#include "core/object/script_language.h" #include "core/object/script_language.h"
#include "core/templates/safe_refcount.h" #include "core/templates/safe_refcount.h"
Thread::PlatformFunctions Thread::platform_functions;
SafeNumeric<uint64_t> Thread::id_counter(1); // The first value after .increment() is 2, hence by default the main thread ID should be 1. SafeNumeric<uint64_t> Thread::id_counter(1); // The first value after .increment() is 2, hence by default the main thread ID should be 1.
thread_local Thread::ID Thread::caller_id = Thread::UNASSIGNED_ID; thread_local Thread::ID Thread::caller_id = Thread::UNASSIGNED_ID;
#endif
Thread::PlatformFunctions Thread::platform_functions;
void Thread::_set_platform_functions(const PlatformFunctions &p_functions) { void Thread::_set_platform_functions(const PlatformFunctions &p_functions) {
platform_functions = p_functions; platform_functions = p_functions;
} }
#ifdef THREADS_ENABLED
void Thread::callback(ID p_caller_id, const Settings &p_settings, Callback p_callback, void *p_userdata) { void Thread::callback(ID p_caller_id, const Settings &p_settings, Callback p_callback, void *p_userdata) {
Thread::caller_id = p_caller_id; Thread::caller_id = p_caller_id;
if (platform_functions.set_priority) { if (platform_functions.set_priority) {
@ -107,4 +110,6 @@ Thread::~Thread() {
} }
} }
#endif // THREADS_ENABLED
#endif // PLATFORM_THREAD_OVERRIDE #endif // PLATFORM_THREAD_OVERRIDE

View File

@ -53,6 +53,8 @@
class String; class String;
#ifdef THREADS_ENABLED
class Thread { class Thread {
public: public:
typedef void (*Callback)(void *p_userdata); typedef void (*Callback)(void *p_userdata);
@ -86,6 +88,8 @@ public:
private: private:
friend class Main; friend class Main;
static PlatformFunctions platform_functions;
ID id = UNASSIGNED_ID; ID id = UNASSIGNED_ID;
static SafeNumeric<uint64_t> id_counter; static SafeNumeric<uint64_t> id_counter;
static thread_local ID caller_id; static thread_local ID caller_id;
@ -93,8 +97,6 @@ private:
static void callback(ID p_caller_id, const Settings &p_settings, Thread::Callback p_callback, void *p_userdata); static void callback(ID p_caller_id, const Settings &p_settings, Thread::Callback p_callback, void *p_userdata);
static PlatformFunctions platform_functions;
static void make_main_thread() { caller_id = MAIN_ID; } static void make_main_thread() { caller_id = MAIN_ID; }
static void release_main_thread() { caller_id = UNASSIGNED_ID; } static void release_main_thread() { caller_id = UNASSIGNED_ID; }
@ -125,6 +127,64 @@ public:
~Thread(); ~Thread();
}; };
#else // No threads.
class Thread {
public:
typedef void (*Callback)(void *p_userdata);
typedef uint64_t ID;
enum : ID {
UNASSIGNED_ID = 0,
MAIN_ID = 1
};
enum Priority {
PRIORITY_LOW,
PRIORITY_NORMAL,
PRIORITY_HIGH
};
struct Settings {
Priority priority;
Settings() { priority = PRIORITY_NORMAL; }
};
struct PlatformFunctions {
Error (*set_name)(const String &) = nullptr;
void (*set_priority)(Thread::Priority) = nullptr;
void (*init)() = nullptr;
void (*wrapper)(Thread::Callback, void *) = nullptr;
void (*term)() = nullptr;
};
private:
friend class Main;
static PlatformFunctions platform_functions;
static void make_main_thread() {}
static void release_main_thread() {}
public:
static void _set_platform_functions(const PlatformFunctions &p_functions);
_FORCE_INLINE_ ID get_id() const { return 0; }
_FORCE_INLINE_ static ID get_caller_id() { return MAIN_ID; }
_FORCE_INLINE_ static ID get_main_id() { return MAIN_ID; }
_FORCE_INLINE_ static bool is_main_thread() { return true; }
static Error set_name(const String &p_name) { return ERR_UNAVAILABLE; }
void start(Thread::Callback p_callback, void *p_user, const Settings &p_settings = Settings()) {}
bool is_started() const { return false; }
void wait_to_finish() {}
};
#endif // THREADS_ENABLED
#endif // THREAD_H #endif // THREAD_H
#endif // PLATFORM_THREAD_OVERRIDE #endif // PLATFORM_THREAD_OVERRIDE

View File

@ -153,7 +153,9 @@ int OS_Unix::unix_initialize_audio(int p_audio_driver) {
} }
void OS_Unix::initialize_core() { void OS_Unix::initialize_core() {
#ifdef THREADS_ENABLED
init_thread_posix(); init_thread_posix();
#endif
FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_RESOURCES); FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_RESOURCES);
FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_USERDATA); FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_USERDATA);

View File

@ -2354,7 +2354,11 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) {
reimport_files.sort(); reimport_files.sort();
#ifdef THREADS_ENABLED
bool use_multiple_threads = GLOBAL_GET("editor/import/use_multiple_threads"); bool use_multiple_threads = GLOBAL_GET("editor/import/use_multiple_threads");
#else
bool use_multiple_threads = false;
#endif
int from = 0; int from = 0;
for (int i = 0; i < reimport_files.size(); i++) { for (int i = 0; i < reimport_files.size(); i++) {
@ -2680,6 +2684,10 @@ void EditorFileSystem::remove_import_format_support_query(Ref<EditorFileSystemIm
} }
EditorFileSystem::EditorFileSystem() { EditorFileSystem::EditorFileSystem() {
#ifdef THREADS_ENABLED
use_threads = true;
#endif
ResourceLoader::import = _resource_import; ResourceLoader::import = _resource_import;
reimport_on_missing_imported_files = GLOBAL_GET("editor/import/reimport_missing_imported_files"); reimport_on_missing_imported_files = GLOBAL_GET("editor/import/reimport_missing_imported_files");
singleton = this; singleton = this;

View File

@ -165,7 +165,7 @@ class EditorFileSystem : public Node {
EditorFileSystemDirectory::FileInfo *new_file = nullptr; EditorFileSystemDirectory::FileInfo *new_file = nullptr;
}; };
bool use_threads = true; bool use_threads = false;
Thread thread; Thread thread;
static void _thread_func(void *_userdata); static void _thread_func(void *_userdata);

View File

@ -1615,6 +1615,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
} }
// Initialize WorkerThreadPool. // Initialize WorkerThreadPool.
{
#ifdef THREADS_ENABLED
if (editor || project_manager) { if (editor || project_manager) {
WorkerThreadPool::get_singleton()->init(-1, 0.75); WorkerThreadPool::get_singleton()->init(-1, 0.75);
} else { } else {
@ -1622,6 +1624,10 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
float low_priority_ratio = GLOBAL_GET("threading/worker_pool/low_priority_thread_ratio"); float low_priority_ratio = GLOBAL_GET("threading/worker_pool/low_priority_thread_ratio");
WorkerThreadPool::get_singleton()->init(worker_threads, low_priority_ratio); WorkerThreadPool::get_singleton()->init(worker_threads, low_priority_ratio);
} }
#else
WorkerThreadPool::get_singleton()->init(0, 0);
#endif
}
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
if (editor) { if (editor) {

View File

@ -23,7 +23,7 @@
<link id="-gd-engine-icon" rel="icon" type="image/png" href="favicon.png"> <link id="-gd-engine-icon" rel="icon" type="image/png" href="favicon.png">
<link rel="apple-touch-icon" type="image/png" href="favicon.png"> <link rel="apple-touch-icon" type="image/png" href="favicon.png">
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
<title>Godot Engine Web Editor (@GODOT_VERSION@)</title> <title>Godot Engine Web Editor (___GODOT_VERSION___)</title>
<style> <style>
*:focus { *:focus {
/* More visible outline for better keyboard navigation. */ /* More visible outline for better keyboard navigation. */
@ -294,7 +294,7 @@ a:active {
<br > <br >
<img src="logo.svg" alt="Godot Engine logo" width="1024" height="414" style="width: auto; height: auto; max-width: min(85%, 50vh); max-height: 250px"> <img src="logo.svg" alt="Godot Engine logo" width="1024" height="414" style="width: auto; height: auto; max-width: min(85%, 50vh); max-height: 250px">
<br > <br >
@GODOT_VERSION@ ___GODOT_VERSION___
<br > <br >
<a href="releases/">Need an old version?</a> <a href="releases/">Need an old version?</a>
<br > <br >
@ -384,7 +384,9 @@ window.addEventListener('load', () => {
}); });
} }
const missing = Engine.getMissingFeatures(); const missing = Engine.getMissingFeatures({
threads: ___GODOT_THREADS_ENABLED___,
});
if (missing.length) { if (missing.length) {
// Display error dialog as threading support is required for the editor. // Display error dialog as threading support is required for the editor.
document.getElementById('startButton').disabled = 'disabled'; document.getElementById('startButton').disabled = 'disabled';

View File

@ -136,6 +136,7 @@ body {
<script src="$GODOT_URL"></script> <script src="$GODOT_URL"></script>
<script> <script>
const GODOT_CONFIG = $GODOT_CONFIG; const GODOT_CONFIG = $GODOT_CONFIG;
const GODOT_THREADS_ENABLED = $GODOT_THREADS_ENABLED;
const engine = new Engine(GODOT_CONFIG); const engine = new Engine(GODOT_CONFIG);
(function () { (function () {
@ -213,7 +214,9 @@ const engine = new Engine(GODOT_CONFIG);
initializing = false; initializing = false;
} }
const missing = Engine.getMissingFeatures(); const missing = Engine.getMissingFeatures({
threads: GODOT_THREADS_ENABLED,
});
if (missing.length !== 0) { if (missing.length !== 0) {
const missingMsg = 'Error\nThe following features required to run Godot projects on the Web are missing:\n'; const missingMsg = 'Error\nThe following features required to run Godot projects on the Web are missing:\n';
displayFailureNotice(missingMsg + missing.join('\n')); displayFailureNotice(missingMsg + missing.join('\n'));

View File

@ -3,14 +3,14 @@
// that they need an Internet connection to run the project if desired. // that they need an Internet connection to run the project if desired.
// Incrementing CACHE_VERSION will kick off the install event and force // Incrementing CACHE_VERSION will kick off the install event and force
// previously cached resources to be updated from the network. // previously cached resources to be updated from the network.
const CACHE_VERSION = "@GODOT_VERSION@"; const CACHE_VERSION = "___GODOT_VERSION___";
const CACHE_PREFIX = "@GODOT_NAME@-sw-cache-"; const CACHE_PREFIX = "___GODOT_NAME___-sw-cache-";
const CACHE_NAME = CACHE_PREFIX + CACHE_VERSION; const CACHE_NAME = CACHE_PREFIX + CACHE_VERSION;
const OFFLINE_URL = "@GODOT_OFFLINE_PAGE@"; const OFFLINE_URL = "___GODOT_OFFLINE_PAGE___";
// Files that will be cached on load. // Files that will be cached on load.
const CACHED_FILES = @GODOT_CACHE@; const CACHED_FILES = ___GODOT_CACHE___;
// Files that we might not want the user to preload, and will only be cached on first load. // Files that we might not want the user to preload, and will only be cached on first load.
const CACHABLE_FILES = @GODOT_OPT_CACHE@; const CACHABLE_FILES = ___GODOT_OPT_CACHE___;
const FULL_CACHE = CACHED_FILES.concat(CACHABLE_FILES); const FULL_CACHE = CACHED_FILES.concat(CACHABLE_FILES);
self.addEventListener("install", (event) => { self.addEventListener("install", (event) => {
@ -22,7 +22,7 @@ self.addEventListener("activate", (event) => {
function (keys) { function (keys) {
// Remove old caches. // Remove old caches.
return Promise.all(keys.filter(key => key.startsWith(CACHE_PREFIX) && key != CACHE_NAME).map(key => caches.delete(key))); return Promise.all(keys.filter(key => key.startsWith(CACHE_PREFIX) && key != CACHE_NAME).map(key => caches.delete(key)));
}).then(function() { }).then(function () {
// Enable navigation preload if available. // Enable navigation preload if available.
return ("navigationPreload" in self.registration) ? self.registration.navigationPreload.enable() : Promise.resolve(); return ("navigationPreload" in self.registration) ? self.registration.navigationPreload.enable() : Promise.resolve();
}) })

View File

@ -34,6 +34,12 @@
#include <thorvg.h> #include <thorvg.h>
#ifdef THREADS_ENABLED
#define TVG_THREADS 1
#else
#define TVG_THREADS 0
#endif
static Ref<ImageLoaderSVG> image_loader_svg; static Ref<ImageLoaderSVG> image_loader_svg;
void initialize_svg_module(ModuleInitializationLevel p_level) { void initialize_svg_module(ModuleInitializationLevel p_level) {
@ -42,7 +48,8 @@ void initialize_svg_module(ModuleInitializationLevel p_level) {
} }
tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw;
if (tvg::Initializer::init(tvgEngine, 1) != tvg::Result::Success) {
if (tvg::Initializer::init(tvgEngine, TVG_THREADS) != tvg::Result::Success) {
return; return;
} }

View File

@ -15,5 +15,7 @@ module.exports = {
"Godot": true, "Godot": true,
"Engine": true, "Engine": true,
"$GODOT_CONFIG": true, "$GODOT_CONFIG": true,
"$GODOT_THREADS_ENABLED": true,
"___GODOT_THREADS_ENABLED___": true,
}, },
}; };

View File

@ -13,7 +13,7 @@ if "serve" in COMMAND_LINE_TARGETS or "run" in COMMAND_LINE_TARGETS:
except Exception: except Exception:
print("GODOT_WEB_TEST_PORT must be a valid integer") print("GODOT_WEB_TEST_PORT must be a valid integer")
sys.exit(255) sys.exit(255)
serve(env.Dir("#bin/.web_zip").abspath, port, "run" in COMMAND_LINE_TARGETS) serve(env.Dir(env.GetTemplateZipPath()).abspath, port, "run" in COMMAND_LINE_TARGETS)
sys.exit(0) sys.exit(0)
web_files = [ web_files = [
@ -95,7 +95,7 @@ engine = [
"js/engine/engine.js", "js/engine/engine.js",
] ]
externs = [env.File("#platform/web/js/engine/engine.externs.js")] externs = [env.File("#platform/web/js/engine/engine.externs.js")]
js_engine = env.CreateEngineFile("#bin/godot${PROGSUFFIX}.engine.js", engine, externs) js_engine = env.CreateEngineFile("#bin/godot${PROGSUFFIX}.engine.js", engine, externs, env["threads"])
env.Depends(js_engine, externs) env.Depends(js_engine, externs)
wrap_list = [ wrap_list = [

View File

@ -30,6 +30,8 @@
#include "audio_driver_web.h" #include "audio_driver_web.h"
#include "godot_audio.h"
#include "core/config/project_settings.h" #include "core/config/project_settings.h"
#include <emscripten.h> #include <emscripten.h>
@ -184,6 +186,8 @@ Error AudioDriverWeb::input_stop() {
return OK; return OK;
} }
#ifdef THREADS_ENABLED
/// AudioWorkletNode implementation (threads) /// AudioWorkletNode implementation (threads)
void AudioDriverWorklet::_audio_thread_func(void *p_data) { void AudioDriverWorklet::_audio_thread_func(void *p_data) {
AudioDriverWorklet *driver = static_cast<AudioDriverWorklet *>(p_data); AudioDriverWorklet *driver = static_cast<AudioDriverWorklet *>(p_data);
@ -245,3 +249,51 @@ void AudioDriverWorklet::finish_driver() {
quit = true; // Ask thread to quit. quit = true; // Ask thread to quit.
thread.wait_to_finish(); thread.wait_to_finish();
} }
#else // No threads.
/// AudioWorkletNode implementation (no threads)
AudioDriverWorklet *AudioDriverWorklet::singleton = nullptr;
Error AudioDriverWorklet::create(int &p_buffer_size, int p_channels) {
if (!godot_audio_has_worklet()) {
return ERR_UNAVAILABLE;
}
return (Error)godot_audio_worklet_create(p_channels);
}
void AudioDriverWorklet::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) {
_audio_driver_process();
godot_audio_worklet_start_no_threads(p_out_buf, p_out_buf_size, &_process_callback, p_in_buf, p_in_buf_size, &_capture_callback);
}
void AudioDriverWorklet::_process_callback(int p_pos, int p_samples) {
AudioDriverWorklet *driver = AudioDriverWorklet::get_singleton();
driver->_audio_driver_process(p_pos, p_samples);
}
void AudioDriverWorklet::_capture_callback(int p_pos, int p_samples) {
AudioDriverWorklet *driver = AudioDriverWorklet::get_singleton();
driver->_audio_driver_capture(p_pos, p_samples);
}
/// ScriptProcessorNode implementation
AudioDriverScriptProcessor *AudioDriverScriptProcessor::singleton = nullptr;
void AudioDriverScriptProcessor::_process_callback() {
AudioDriverScriptProcessor::get_singleton()->_audio_driver_capture();
AudioDriverScriptProcessor::get_singleton()->_audio_driver_process();
}
Error AudioDriverScriptProcessor::create(int &p_buffer_samples, int p_channels) {
if (!godot_audio_has_script_processor()) {
return ERR_UNAVAILABLE;
}
return (Error)godot_audio_script_create(&p_buffer_samples, p_channels);
}
void AudioDriverScriptProcessor::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) {
godot_audio_script_start(p_in_buf, p_in_buf_size, p_out_buf, p_out_buf_size, &_process_callback);
}
#endif // THREADS_ENABLED

View File

@ -90,6 +90,7 @@ public:
AudioDriverWeb() {} AudioDriverWeb() {}
}; };
#ifdef THREADS_ENABLED
class AudioDriverWorklet : public AudioDriverWeb { class AudioDriverWorklet : public AudioDriverWeb {
private: private:
enum { enum {
@ -120,4 +121,54 @@ public:
virtual void unlock() override; virtual void unlock() override;
}; };
#else
class AudioDriverWorklet : public AudioDriverWeb {
private:
static void _process_callback(int p_pos, int p_samples);
static void _capture_callback(int p_pos, int p_samples);
static AudioDriverWorklet *singleton;
protected:
virtual Error create(int &p_buffer_size, int p_output_channels) override;
virtual void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) override;
public:
virtual const char *get_name() const override {
return "AudioWorklet";
}
virtual void lock() override {}
virtual void unlock() override {}
static AudioDriverWorklet *get_singleton() { return singleton; }
AudioDriverWorklet() { singleton = this; }
};
class AudioDriverScriptProcessor : public AudioDriverWeb {
private:
static void _process_callback();
static AudioDriverScriptProcessor *singleton;
protected:
virtual Error create(int &p_buffer_size, int p_output_channels) override;
virtual void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) override;
virtual void finish_driver() override;
public:
virtual const char *get_name() const override { return "ScriptProcessor"; }
virtual void lock() override {}
virtual void unlock() override {}
static AudioDriverScriptProcessor *get_singleton() { return singleton; }
AudioDriverScriptProcessor() { singleton = this; }
};
#endif // THREADS_ENABLED
#endif // AUDIO_DRIVER_WEB_H #endif // AUDIO_DRIVER_WEB_H

View File

@ -8,6 +8,7 @@ from emscripten_helpers import (
add_js_pre, add_js_pre,
add_js_externs, add_js_externs,
create_template_zip, create_template_zip,
get_template_zip_path,
) )
from methods import get_compiler_version from methods import get_compiler_version
from SCons.Util import WhereIs from SCons.Util import WhereIs
@ -161,6 +162,9 @@ def configure(env: "Environment"):
# Add method that joins/compiles our Engine files. # Add method that joins/compiles our Engine files.
env.AddMethod(create_engine_file, "CreateEngineFile") env.AddMethod(create_engine_file, "CreateEngineFile")
# Add method for getting the final zip path
env.AddMethod(get_template_zip_path, "GetTemplateZipPath")
# Add method for creating the final zip file # Add method for creating the final zip file
env.AddMethod(create_template_zip, "CreateTemplateZip") env.AddMethod(create_template_zip, "CreateTemplateZip")
@ -209,6 +213,7 @@ def configure(env: "Environment"):
stack_size_opt = "STACK_SIZE" if cc_semver >= (3, 1, 25) else "TOTAL_STACK" stack_size_opt = "STACK_SIZE" if cc_semver >= (3, 1, 25) else "TOTAL_STACK"
env.Append(LINKFLAGS=["-s", "%s=%sKB" % (stack_size_opt, env["stack_size"])]) env.Append(LINKFLAGS=["-s", "%s=%sKB" % (stack_size_opt, env["stack_size"])])
if env["threads"]:
# Thread support (via SharedArrayBuffer). # Thread support (via SharedArrayBuffer).
env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"]) env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"])
env.Append(CCFLAGS=["-s", "USE_PTHREADS=1"]) env.Append(CCFLAGS=["-s", "USE_PTHREADS=1"])
@ -216,6 +221,9 @@ def configure(env: "Environment"):
env.Append(LINKFLAGS=["-s", "DEFAULT_PTHREAD_STACK_SIZE=%sKB" % env["default_pthread_stack_size"]]) env.Append(LINKFLAGS=["-s", "DEFAULT_PTHREAD_STACK_SIZE=%sKB" % env["default_pthread_stack_size"]])
env.Append(LINKFLAGS=["-s", "PTHREAD_POOL_SIZE=8"]) env.Append(LINKFLAGS=["-s", "PTHREAD_POOL_SIZE=8"])
env.Append(LINKFLAGS=["-s", "WASM_MEM_MAX=2048MB"]) env.Append(LINKFLAGS=["-s", "WASM_MEM_MAX=2048MB"])
elif env["proxy_to_pthread"]:
print('"threads=no" support requires "proxy_to_pthread=no", disabling proxy to pthread.')
env["proxy_to_pthread"] = False
if env["lto"] != "none": if env["lto"] != "none":
# Workaround https://github.com/emscripten-core/emscripten/issues/19781. # Workaround https://github.com/emscripten-core/emscripten/issues/19781.
@ -224,7 +232,7 @@ def configure(env: "Environment"):
if env["dlink_enabled"]: if env["dlink_enabled"]:
if env["proxy_to_pthread"]: if env["proxy_to_pthread"]:
print("GDExtension support requires proxy_to_pthread=no, disabling") print("GDExtension support requires proxy_to_pthread=no, disabling proxy to pthread.")
env["proxy_to_pthread"] = False env["proxy_to_pthread"] = False
if cc_semver < (3, 1, 14): if cc_semver < (3, 1, 14):

View File

@ -47,6 +47,10 @@
</member> </member>
<member name="variant/extensions_support" type="bool" setter="" getter=""> <member name="variant/extensions_support" type="bool" setter="" getter="">
</member> </member>
<member name="variant/thread_support" type="bool" setter="" getter="">
If enabled, the exported game will support threads. It requires [url=https://web.dev/articles/coop-coep]a "cross-origin isolated" website[/url], which can be difficult to setup and brings some limitations (e.g. not being able to communicate with third-party websites).
If disabled, the exported game will not support threads. As a result, it is more prone to performance and audio issues, but will only require to be run on a HTTPS website.
</member>
<member name="vram_texture_compression/for_desktop" type="bool" setter="" getter=""> <member name="vram_texture_compression/for_desktop" type="bool" setter="" getter="">
</member> </member>
<member name="vram_texture_compression/for_mobile" type="bool" setter="" getter=""> <member name="vram_texture_compression/for_mobile" type="bool" setter="" getter="">

View File

@ -4,7 +4,12 @@ from SCons.Util import WhereIs
def run_closure_compiler(target, source, env, for_signature): def run_closure_compiler(target, source, env, for_signature):
closure_bin = os.path.join(os.path.dirname(WhereIs("emcc")), "node_modules", ".bin", "google-closure-compiler") closure_bin = os.path.join(
os.path.dirname(WhereIs("emcc")),
"node_modules",
".bin",
"google-closure-compiler",
)
cmd = [WhereIs("node"), closure_bin] cmd = [WhereIs("node"), closure_bin]
cmd.extend(["--compilation_level", "ADVANCED_OPTIMIZATIONS"]) cmd.extend(["--compilation_level", "ADVANCED_OPTIMIZATIONS"])
for f in env["JSEXTERNS"]: for f in env["JSEXTERNS"]:
@ -31,27 +36,29 @@ def get_build_version():
return v return v
def create_engine_file(env, target, source, externs): def create_engine_file(env, target, source, externs, threads_enabled):
if env["use_closure_compiler"]: if env["use_closure_compiler"]:
return env.BuildJS(target, source, JSEXTERNS=externs) return env.BuildJS(target, source, JSEXTERNS=externs)
return env.Textfile(target, [env.File(s) for s in source]) subst_dict = {"___GODOT_THREADS_ENABLED": "true" if threads_enabled else "false"}
return env.Substfile(target=target, source=[env.File(s) for s in source], SUBST_DICT=subst_dict)
def create_template_zip(env, js, wasm, worker, side): def create_template_zip(env, js, wasm, worker, side):
binary_name = "godot.editor" if env.editor_build else "godot" binary_name = "godot.editor" if env.editor_build else "godot"
zip_dir = env.Dir("#bin/.web_zip") zip_dir = env.Dir(env.GetTemplateZipPath())
in_files = [ in_files = [
js, js,
wasm, wasm,
worker,
"#platform/web/js/libs/audio.worklet.js", "#platform/web/js/libs/audio.worklet.js",
] ]
out_files = [ out_files = [
zip_dir.File(binary_name + ".js"), zip_dir.File(binary_name + ".js"),
zip_dir.File(binary_name + ".wasm"), zip_dir.File(binary_name + ".wasm"),
zip_dir.File(binary_name + ".worker.js"),
zip_dir.File(binary_name + ".audio.worklet.js"), zip_dir.File(binary_name + ".audio.worklet.js"),
] ]
if env["threads"]:
in_files.append(worker)
out_files.append(zip_dir.File(binary_name + ".worker.js"))
# Dynamic linking (extensions) specific. # Dynamic linking (extensions) specific.
if env["dlink_enabled"]: if env["dlink_enabled"]:
in_files.append(side) # Side wasm (contains the actual Godot code). in_files.append(side) # Side wasm (contains the actual Godot code).
@ -65,18 +72,20 @@ def create_template_zip(env, js, wasm, worker, side):
"godot.editor.html", "godot.editor.html",
"offline.html", "offline.html",
"godot.editor.js", "godot.editor.js",
"godot.editor.worker.js",
"godot.editor.audio.worklet.js", "godot.editor.audio.worklet.js",
"logo.svg", "logo.svg",
"favicon.png", "favicon.png",
] ]
if env["threads"]:
cache.append("godot.editor.worker.js")
opt_cache = ["godot.editor.wasm"] opt_cache = ["godot.editor.wasm"]
subst_dict = { subst_dict = {
"@GODOT_VERSION@": get_build_version(), "___GODOT_VERSION___": get_build_version(),
"@GODOT_NAME@": "GodotEngine", "___GODOT_NAME___": "GodotEngine",
"@GODOT_CACHE@": json.dumps(cache), "___GODOT_CACHE___": json.dumps(cache),
"@GODOT_OPT_CACHE@": json.dumps(opt_cache), "___GODOT_OPT_CACHE___": json.dumps(opt_cache),
"@GODOT_OFFLINE_PAGE@": "offline.html", "___GODOT_OFFLINE_PAGE___": "offline.html",
"___GODOT_THREADS_ENABLED___": "true" if env["threads"] else "false",
} }
html = env.Substfile(target="#bin/godot${PROGSUFFIX}.html", source=html, SUBST_DICT=subst_dict) html = env.Substfile(target="#bin/godot${PROGSUFFIX}.html", source=html, SUBST_DICT=subst_dict)
in_files.append(html) in_files.append(html)
@ -88,7 +97,9 @@ def create_template_zip(env, js, wasm, worker, side):
out_files.append(zip_dir.File("favicon.png")) out_files.append(zip_dir.File("favicon.png"))
# PWA # PWA
service_worker = env.Substfile( service_worker = env.Substfile(
target="#bin/godot${PROGSUFFIX}.service.worker.js", source=service_worker, SUBST_DICT=subst_dict target="#bin/godot${PROGSUFFIX}.service.worker.js",
source=service_worker,
SUBST_DICT=subst_dict,
) )
in_files.append(service_worker) in_files.append(service_worker)
out_files.append(zip_dir.File("service.worker.js")) out_files.append(zip_dir.File("service.worker.js"))
@ -115,6 +126,10 @@ def create_template_zip(env, js, wasm, worker, side):
) )
def get_template_zip_path(env):
return "#bin/.web_zip"
def add_js_libraries(env, libraries): def add_js_libraries(env, libraries):
env.Append(JS_LIBS=env.File(libraries)) env.Append(JS_LIBS=env.File(libraries))

View File

@ -169,6 +169,13 @@ void EditorExportPlatformWeb::_fix_html(Vector<uint8_t> &p_html, const Ref<Edito
replaces["$GODOT_PROJECT_NAME"] = GLOBAL_GET("application/config/name"); replaces["$GODOT_PROJECT_NAME"] = GLOBAL_GET("application/config/name");
replaces["$GODOT_HEAD_INCLUDE"] = head_include + custom_head_include; replaces["$GODOT_HEAD_INCLUDE"] = head_include + custom_head_include;
replaces["$GODOT_CONFIG"] = str_config; replaces["$GODOT_CONFIG"] = str_config;
if (p_preset->get("variant/thread_support")) {
replaces["$GODOT_THREADS_ENABLED"] = "true";
} else {
replaces["$GODOT_THREADS_ENABLED"] = "false";
}
_replace_strings(replaces, p_html); _replace_strings(replaces, p_html);
} }
@ -216,9 +223,9 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese
const String name = p_path.get_file().get_basename(); const String name = p_path.get_file().get_basename();
bool extensions = (bool)p_preset->get("variant/extensions_support"); bool extensions = (bool)p_preset->get("variant/extensions_support");
HashMap<String, String> replaces; HashMap<String, String> replaces;
replaces["@GODOT_VERSION@"] = String::num_int64(OS::get_singleton()->get_unix_time()) + "|" + String::num_int64(OS::get_singleton()->get_ticks_usec()); replaces["___GODOT_VERSION___"] = String::num_int64(OS::get_singleton()->get_unix_time()) + "|" + String::num_int64(OS::get_singleton()->get_ticks_usec());
replaces["@GODOT_NAME@"] = proj_name.substr(0, 16); replaces["___GODOT_NAME___"] = proj_name.substr(0, 16);
replaces["@GODOT_OFFLINE_PAGE@"] = name + ".offline.html"; replaces["___GODOT_OFFLINE_PAGE___"] = name + ".offline.html";
// Files cached during worker install. // Files cached during worker install.
Array cache_files; Array cache_files;
@ -231,7 +238,7 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese
} }
cache_files.push_back(name + ".worker.js"); cache_files.push_back(name + ".worker.js");
cache_files.push_back(name + ".audio.worklet.js"); cache_files.push_back(name + ".audio.worklet.js");
replaces["@GODOT_CACHE@"] = Variant(cache_files).to_json_string(); replaces["___GODOT_CACHE___"] = Variant(cache_files).to_json_string();
// Heavy files that are cached on demand. // Heavy files that are cached on demand.
Array opt_cache_files; Array opt_cache_files;
@ -243,7 +250,7 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese
opt_cache_files.push_back(p_shared_objects[i].path.get_file()); opt_cache_files.push_back(p_shared_objects[i].path.get_file());
} }
} }
replaces["@GODOT_OPT_CACHE@"] = Variant(opt_cache_files).to_json_string(); replaces["___GODOT_OPT_CACHE___"] = Variant(opt_cache_files).to_json_string();
const String sw_path = dir.path_join(name + ".service.worker.js"); const String sw_path = dir.path_join(name + ".service.worker.js");
Vector<uint8_t> sw; Vector<uint8_t> sw;
@ -335,6 +342,7 @@ void EditorExportPlatformWeb::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "variant/extensions_support"), false)); // Export type. r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "variant/extensions_support"), false)); // Export type.
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "variant/thread_support"), true)); // Thread support (i.e. run with or without COEP/COOP headers).
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_desktop"), true)); // S3TC r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_desktop"), true)); // S3TC
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_mobile"), false)); // ETC or ETC2, depending on renderer r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_mobile"), false)); // ETC or ETC2, depending on renderer
@ -377,10 +385,11 @@ bool EditorExportPlatformWeb::has_valid_export_configuration(const Ref<EditorExp
String err; String err;
bool valid = false; bool valid = false;
bool extensions = (bool)p_preset->get("variant/extensions_support"); bool extensions = (bool)p_preset->get("variant/extensions_support");
bool thread_support = (bool)p_preset->get("variant/thread_support");
// Look for export templates (first official, and if defined custom templates). // Look for export templates (first official, and if defined custom templates).
bool dvalid = exists_export_template(_get_template_name(extensions, true), &err); bool dvalid = exists_export_template(_get_template_name(extensions, thread_support, true), &err);
bool rvalid = exists_export_template(_get_template_name(extensions, false), &err); bool rvalid = exists_export_template(_get_template_name(extensions, thread_support, false), &err);
if (p_preset->get("custom_template/debug") != "") { if (p_preset->get("custom_template/debug") != "") {
dvalid = FileAccess::exists(p_preset->get("custom_template/debug")); dvalid = FileAccess::exists(p_preset->get("custom_template/debug"));
@ -454,7 +463,8 @@ Error EditorExportPlatformWeb::export_project(const Ref<EditorExportPreset> &p_p
template_path = template_path.strip_edges(); template_path = template_path.strip_edges();
if (template_path.is_empty()) { if (template_path.is_empty()) {
bool extensions = (bool)p_preset->get("variant/extensions_support"); bool extensions = (bool)p_preset->get("variant/extensions_support");
template_path = find_export_template(_get_template_name(extensions, p_debug)); bool thread_support = (bool)p_preset->get("variant/thread_support");
template_path = find_export_template(_get_template_name(extensions, thread_support, p_debug));
} }
if (!template_path.is_empty() && !FileAccess::exists(template_path)) { if (!template_path.is_empty() && !FileAccess::exists(template_path)) {

View File

@ -56,11 +56,14 @@ class EditorExportPlatformWeb : public EditorExportPlatform {
Mutex server_lock; Mutex server_lock;
Thread server_thread; Thread server_thread;
String _get_template_name(bool p_extension, bool p_debug) const { String _get_template_name(bool p_extension, bool p_thread_support, bool p_debug) const {
String name = "web"; String name = "web";
if (p_extension) { if (p_extension) {
name += "_dlink"; name += "_dlink";
} }
if (!p_thread_support) {
name += "_nothreads";
}
if (p_debug) { if (p_debug) {
name += "_debug.zip"; name += "_debug.zip";
} else { } else {

View File

@ -72,8 +72,14 @@ const Features = { // eslint-disable-line no-unused-vars
* *
* @returns {Array<string>} A list of human-readable missing features. * @returns {Array<string>} A list of human-readable missing features.
* @function Engine.getMissingFeatures * @function Engine.getMissingFeatures
* @typedef {{ threads: boolean }} SupportedFeatures
* @param {SupportedFeatures} supportedFeatures
*/ */
getMissingFeatures: function () { getMissingFeatures: function (supportedFeatures = {}) {
const {
threads: supportsThreads = true,
} = supportedFeatures;
const missing = []; const missing = [];
if (!Features.isWebGLAvailable(2)) { if (!Features.isWebGLAvailable(2)) {
missing.push('WebGL2 - Check web browser configuration and hardware support'); missing.push('WebGL2 - Check web browser configuration and hardware support');
@ -84,12 +90,16 @@ const Features = { // eslint-disable-line no-unused-vars
if (!Features.isSecureContext()) { if (!Features.isSecureContext()) {
missing.push('Secure Context - Check web server configuration (use HTTPS)'); missing.push('Secure Context - Check web server configuration (use HTTPS)');
} }
if (supportsThreads) {
if (!Features.isCrossOriginIsolated()) { if (!Features.isCrossOriginIsolated()) {
missing.push('Cross Origin Isolation - Check web server configuration (send correct headers)'); missing.push('Cross-Origin Isolation - Check that the web server configuration sends the correct headers.');
} }
if (!Features.isSharedArrayBufferAvailable()) { if (!Features.isSharedArrayBufferAvailable()) {
missing.push('SharedArrayBuffer - Check web server configuration (send correct headers)'); missing.push('SharedArrayBuffer - Check that the web server configuration sends the correct headers.');
} }
}
// Audio is normally optional since we have a dummy fallback. // Audio is normally optional since we have a dummy fallback.
return missing; return missing;
}, },

View File

@ -167,7 +167,7 @@ class GodotProcessor extends AudioWorkletProcessor {
GodotProcessor.write_input(this.input_buffer, input); GodotProcessor.write_input(this.input_buffer, input);
this.input.write(this.input_buffer); this.input.write(this.input_buffer);
} else { } else {
this.port.postMessage('Input buffer is full! Skipping input frame.'); // this.port.postMessage('Input buffer is full! Skipping input frame.'); // Uncomment this line to debug input buffer.
} }
} }
const process_output = GodotProcessor.array_has_data(outputs); const process_output = GodotProcessor.array_has_data(outputs);
@ -184,7 +184,7 @@ class GodotProcessor extends AudioWorkletProcessor {
this.port.postMessage({ 'cmd': 'read', 'data': chunk }); this.port.postMessage({ 'cmd': 'read', 'data': chunk });
} }
} else { } else {
this.port.postMessage('Output buffer has not enough frames! Skipping output frame.'); // this.port.postMessage('Output buffer has not enough frames! Skipping output frame.'); // Uncomment this line to debug output buffer.
} }
} }
this.process_notify(); this.process_notify();

View File

@ -503,7 +503,9 @@ void HTTPRequest::_notification(int p_what) {
void HTTPRequest::set_use_threads(bool p_use) { void HTTPRequest::set_use_threads(bool p_use) {
ERR_FAIL_COND(get_http_client_status() != HTTPClient::STATUS_DISCONNECTED); ERR_FAIL_COND(get_http_client_status() != HTTPClient::STATUS_DISCONNECTED);
#ifdef THREADS_ENABLED
use_threads.set_to(p_use); use_threads.set_to(p_use);
#endif
} }
bool HTTPRequest::is_using_threads() const { bool HTTPRequest::is_using_threads() const {

View File

@ -88,7 +88,11 @@
ShaderTypes *shader_types = nullptr; ShaderTypes *shader_types = nullptr;
static PhysicsServer3D *_createGodotPhysics3DCallback() { static PhysicsServer3D *_createGodotPhysics3DCallback() {
#ifdef THREADS_ENABLED
bool using_threads = GLOBAL_GET("physics/3d/run_on_separate_thread"); bool using_threads = GLOBAL_GET("physics/3d/run_on_separate_thread");
#else
bool using_threads = false;
#endif
PhysicsServer3D *physics_server_3d = memnew(GodotPhysicsServer3D(using_threads)); PhysicsServer3D *physics_server_3d = memnew(GodotPhysicsServer3D(using_threads));
@ -96,7 +100,11 @@ static PhysicsServer3D *_createGodotPhysics3DCallback() {
} }
static PhysicsServer2D *_createGodotPhysics2DCallback() { static PhysicsServer2D *_createGodotPhysics2DCallback() {
#ifdef THREADS_ENABLED
bool using_threads = GLOBAL_GET("physics/2d/run_on_separate_thread"); bool using_threads = GLOBAL_GET("physics/2d/run_on_separate_thread");
#else
bool using_threads = false;
#endif
PhysicsServer2D *physics_server_2d = memnew(GodotPhysicsServer2D(using_threads)); PhysicsServer2D *physics_server_2d = memnew(GodotPhysicsServer2D(using_threads));

View File

@ -395,15 +395,19 @@ RenderingServerDefault::RenderingServerDefault(bool p_create_thread) :
command_queue(p_create_thread) { command_queue(p_create_thread) {
RenderingServer::init(); RenderingServer::init();
#ifdef THREADS_ENABLED
create_thread = p_create_thread; create_thread = p_create_thread;
if (!create_thread) {
if (!p_create_thread) {
server_thread = Thread::get_caller_id(); server_thread = Thread::get_caller_id();
} else { } else {
server_thread = 0; server_thread = 0;
} }
#else
create_thread = false;
server_thread = Thread::get_main_id();
#endif
RSG::threaded = create_thread;
RSG::threaded = p_create_thread;
RSG::canvas = memnew(RendererCanvasCull); RSG::canvas = memnew(RendererCanvasCull);
RSG::viewport = memnew(RendererViewport); RSG::viewport = memnew(RendererViewport);
RendererSceneCull *sr = memnew(RendererSceneCull); RendererSceneCull *sr = memnew(RendererSceneCull);

View File

@ -78,7 +78,7 @@ class RenderingServerDefault : public RenderingServer {
static void _thread_callback(void *_instance); static void _thread_callback(void *_instance);
void _thread_loop(); void _thread_loop();
Thread::ID server_thread; Thread::ID server_thread = 0;
SafeFlag exit; SafeFlag exit;
Thread thread; Thread thread;
SafeFlag draw_thread_up; SafeFlag draw_thread_up;