Add 'engine/' from commit 'a8e37fc010'

git-subtree-dir: engine
git-subtree-mainline: b74841629e
git-subtree-split: a8e37fc010
This commit is contained in:
Sara Gerretsen 2026-03-13 11:22:19 +01:00
commit c3f9669b10
14113 changed files with 7458101 additions and 0 deletions

74
engine/drivers/SCsub Normal file
View file

@ -0,0 +1,74 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
from methods import print_error
Import("env")
env.drivers_sources = []
supported = env.get("supported", [])
# OS drivers
SConscript("unix/SCsub")
SConscript("windows/SCsub")
# Sounds drivers
SConscript("alsa/SCsub")
SConscript("pulseaudio/SCsub")
if env["platform"] == "windows":
SConscript("wasapi/SCsub")
if not env.msvc:
SConscript("backtrace/SCsub")
if env["xaudio2"]:
if "xaudio2" not in supported:
print_error("Target platform '{}' does not support the XAudio2 audio driver".format(env["platform"]))
Exit(255)
SConscript("xaudio2/SCsub")
# Shared Apple platform drivers
if env["platform"] in ["macos", "ios", "visionos"]:
SConscript("apple/SCsub")
SConscript("coreaudio/SCsub")
if env["platform"] in ["ios", "visionos"]:
SConscript("apple_embedded/SCsub")
# Accessibility
if env["accesskit"] and env["platform"] in ["macos", "windows", "linuxbsd"]:
SConscript("accesskit/SCsub")
# Midi drivers
SConscript("alsamidi/SCsub")
if env["platform"] in ["macos"]:
SConscript("coremidi/SCsub")
SConscript("winmidi/SCsub")
# Graphics drivers
if env["vulkan"]:
SConscript("vulkan/SCsub")
if env["d3d12"]:
if "d3d12" not in supported:
print_error("Target platform '{}' does not support the D3D12 rendering driver".format(env["platform"]))
Exit(255)
SConscript("d3d12/SCsub")
if env["opengl3"]:
SConscript("gl_context/SCsub")
SConscript("gles3/SCsub")
SConscript("egl/SCsub")
if env["metal"]:
if "metal" not in supported:
print_error("Target platform '{}' does not support the Metal rendering driver".format(env["platform"]))
Exit(255)
SConscript("metal/SCsub")
# Input drivers
if env["sdl"] and env["platform"] in ["linuxbsd", "macos", "windows", "ios", "visionos"]:
# TODO: Evaluate support for Android and Web.
SConscript("sdl/SCsub")
# Core dependencies
SConscript("png/SCsub")
env.add_source_files(env.drivers_sources, "*.cpp")
lib = env.add_library("drivers", env.drivers_sources)
env.Prepend(LIBS=[lib])

View file

@ -0,0 +1,7 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
# Driver source files
env.add_source_files(env.drivers_sources, "accessibility_server_accesskit.cpp")

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,200 @@
/**************************************************************************/
/* accessibility_server_accesskit.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifdef ACCESSKIT_ENABLED
#include "core/templates/rid_owner.h"
#include "servers/display/accessibility_server.h"
#include <accesskit.h>
class AccessibilityServerAccessKit : public AccessibilityServer {
GDSOFTCLASS(AccessibilityServerAccessKit, AccessibilityServer);
static AccessibilityServer *create_func(Error &r_error);
struct AccessibilityElement {
HashMap<accesskit_action, Callable> actions;
DisplayServerEnums::WindowID window_id = DisplayServerEnums::INVALID_WINDOW_ID;
RID parent;
LocalVector<RID> children;
Vector3i run;
Variant meta;
String name;
String name_extra_info;
Variant value;
int64_t flags = 0;
accesskit_role role = ACCESSKIT_ROLE_UNKNOWN;
accesskit_node *node = nullptr;
};
mutable RID_PtrOwner<AccessibilityElement> rid_owner;
struct WindowData {
// Adapter.
#ifdef WINDOWS_ENABLED
accesskit_windows_subclassing_adapter *adapter = nullptr;
#endif
#ifdef MACOS_ENABLED
accesskit_macos_subclassing_adapter *adapter = nullptr;
#endif
#ifdef LINUXBSD_ENABLED
accesskit_unix_adapter *adapter = nullptr;
#endif
RID root_id;
bool initial_update_completed = false;
HashSet<RID> update;
Callable activate;
Callable deactivate;
bool activated = false;
};
RID focus;
HashMap<DisplayServerEnums::WindowID, WindowData> windows;
HashMap<AccessibilityServerEnums::AccessibilityRole, accesskit_role> role_map;
HashMap<AccessibilityServerEnums::AccessibilityAction, accesskit_action> action_map;
_FORCE_INLINE_ accesskit_role _accessibility_role(AccessibilityServerEnums::AccessibilityRole p_role) const;
_FORCE_INLINE_ accesskit_action _accessibility_action(AccessibilityServerEnums::AccessibilityAction p_action) const;
void _free_recursive(WindowData *p_wd, const RID &p_id);
_FORCE_INLINE_ void _ensure_node(const RID &p_id, AccessibilityElement *p_ae);
static void _accessibility_action_callback(struct accesskit_action_request *p_request, void *p_user_data);
static accesskit_tree_update *_accessibility_initial_tree_update_callback(void *p_user_data);
static void _accessibility_deactivation_callback(void *p_user_data);
static accesskit_tree_update *_accessibility_build_tree_update(void *p_user_data);
bool in_accessibility_update = false;
Callable update_cb;
public:
bool is_supported() const override { return true; }
bool window_create(DisplayServerEnums::WindowID p_window_id, void *p_handle) override;
void window_destroy(DisplayServerEnums::WindowID p_window_id) override;
RID create_element(DisplayServerEnums::WindowID p_window_id, AccessibilityServerEnums::AccessibilityRole p_role) override;
RID create_sub_element(const RID &p_parent_rid, AccessibilityServerEnums::AccessibilityRole p_role, int p_insert_pos = -1) override;
virtual RID create_sub_text_edit_elements(const RID &p_parent_rid, const RID &p_shaped_text, float p_min_height, int p_insert_pos = -1, bool p_is_last_line = false) override;
bool has_element(const RID &p_id) const override;
void free_element(const RID &p_id) override;
void element_set_meta(const RID &p_id, const Variant &p_meta) override;
Variant element_get_meta(const RID &p_id) const override;
void update_if_active(const Callable &p_callable) override;
void update_set_focus(const RID &p_id) override;
RID get_window_root(DisplayServerEnums::WindowID p_window_id) const override;
void set_window_rect(DisplayServerEnums::WindowID p_window_id, const Rect2 &p_rect_out, const Rect2 &p_rect_in) override;
void set_window_focused(DisplayServerEnums::WindowID p_window_id, bool p_focused) override;
void set_window_callbacks(DisplayServerEnums::WindowID p_window_id, const Callable &p_activate_callable, const Callable &p_deativate_callable) override;
void window_activation_completed(DisplayServerEnums::WindowID p_window_id) override;
void window_deactivation_completed(DisplayServerEnums::WindowID p_window_id) override;
void update_set_role(const RID &p_id, AccessibilityServerEnums::AccessibilityRole p_role) override;
void update_set_name(const RID &p_id, const String &p_name) override;
void update_set_braille_label(const RID &p_id, const String &p_name) override;
void update_set_braille_role_description(const RID &p_id, const String &p_description) override;
void update_set_extra_info(const RID &p_id, const String &p_name_extra_info) override;
void update_set_description(const RID &p_id, const String &p_description) override;
void update_set_value(const RID &p_id, const String &p_value) override;
void update_set_tooltip(const RID &p_id, const String &p_tooltip) override;
void update_set_bounds(const RID &p_id, const Rect2 &p_rect) override;
void update_set_transform(const RID &p_id, const Transform2D &p_transform) override;
void update_add_child(const RID &p_id, const RID &p_child_id) override;
void update_add_related_controls(const RID &p_id, const RID &p_related_id) override;
void update_add_related_details(const RID &p_id, const RID &p_related_id) override;
void update_add_related_described_by(const RID &p_id, const RID &p_related_id) override;
void update_add_related_flow_to(const RID &p_id, const RID &p_related_id) override;
void update_add_related_labeled_by(const RID &p_id, const RID &p_related_id) override;
void update_add_related_radio_group(const RID &p_id, const RID &p_related_id) override;
void update_set_active_descendant(const RID &p_id, const RID &p_other_id) override;
void update_set_next_on_line(const RID &p_id, const RID &p_other_id) override;
void update_set_previous_on_line(const RID &p_id, const RID &p_other_id) override;
void update_set_member_of(const RID &p_id, const RID &p_group_id) override;
void update_set_in_page_link_target(const RID &p_id, const RID &p_other_id) override;
void update_set_error_message(const RID &p_id, const RID &p_other_id) override;
void update_set_live(const RID &p_id, AccessibilityServerEnums::AccessibilityLiveMode p_live) override;
void update_add_action(const RID &p_id, AccessibilityServerEnums::AccessibilityAction p_action, const Callable &p_callable) override;
void update_add_custom_action(const RID &p_id, int p_action_id, const String &p_action_description) override;
void update_set_table_row_count(const RID &p_id, int p_count) override;
void update_set_table_column_count(const RID &p_id, int p_count) override;
void update_set_table_row_index(const RID &p_id, int p_index) override;
void update_set_table_column_index(const RID &p_id, int p_index) override;
void update_set_table_cell_position(const RID &p_id, int p_row_index, int p_column_index) override;
void update_set_table_cell_span(const RID &p_id, int p_row_span, int p_column_span) override;
void update_set_list_item_count(const RID &p_id, int p_size) override;
void update_set_list_item_index(const RID &p_id, int p_index) override;
void update_set_list_item_level(const RID &p_id, int p_level) override;
void update_set_list_item_selected(const RID &p_id, bool p_selected) override;
void update_set_list_item_expanded(const RID &p_id, bool p_expanded) override;
void update_set_popup_type(const RID &p_id, AccessibilityServerEnums::AccessibilityPopupType p_popup) override;
void update_set_checked(const RID &p_id, bool p_checekd) override;
void update_set_num_value(const RID &p_id, double p_position) override;
void update_set_num_range(const RID &p_id, double p_min, double p_max) override;
void update_set_num_step(const RID &p_id, double p_step) override;
void update_set_num_jump(const RID &p_id, double p_jump) override;
void update_set_scroll_x(const RID &p_id, double p_position) override;
void update_set_scroll_x_range(const RID &p_id, double p_min, double p_max) override;
void update_set_scroll_y(const RID &p_id, double p_position) override;
void update_set_scroll_y_range(const RID &p_id, double p_min, double p_max) override;
void update_set_text_decorations(const RID &p_id, bool p_underline, bool p_strikethrough, bool p_overline, const Color &p_color) override;
void update_set_text_align(const RID &p_id, HorizontalAlignment p_align) override;
void update_set_text_selection(const RID &p_id, const RID &p_text_start_id, int p_start_char, const RID &p_text_end_id, int p_end_char) override;
void update_set_flag(const RID &p_id, AccessibilityServerEnums::AccessibilityFlags p_flag, bool p_value) override;
void update_set_classname(const RID &p_id, const String &p_classname) override;
void update_set_placeholder(const RID &p_id, const String &p_placeholder) override;
void update_set_language(const RID &p_id, const String &p_language) override;
void update_set_text_orientation(const RID &p_id, bool p_vertical) override;
void update_set_list_orientation(const RID &p_id, bool p_vertical) override;
void update_set_shortcut(const RID &p_id, const String &p_shortcut) override;
void update_set_url(const RID &p_id, const String &p_url) override;
void update_set_role_description(const RID &p_id, const String &p_description) override;
void update_set_state_description(const RID &p_id, const String &p_description) override;
void update_set_color_value(const RID &p_id, const Color &p_color) override;
void update_set_background_color(const RID &p_id, const Color &p_color) override;
void update_set_foreground_color(const RID &p_id, const Color &p_color) override;
static void register_create_func();
AccessibilityServerAccessKit();
~AccessibilityServerAccessKit();
};
#endif // ACCESSKIT_ENABLED

10
engine/drivers/alsa/SCsub Normal file
View file

@ -0,0 +1,10 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
if "alsa" in env and env["alsa"]:
if env["use_sowrap"]:
env.add_source_files(env.drivers_sources, "asound-so_wrap.c")
env.add_source_files(env.drivers_sources, "*.cpp")

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,351 @@
/**************************************************************************/
/* audio_driver_alsa.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "audio_driver_alsa.h"
#ifdef ALSA_ENABLED
#include "core/config/engine.h"
#include "core/config/project_settings.h"
#include "core/math/math_funcs_binary.h"
#include "core/os/os.h"
#include <cerrno>
#if defined(PULSEAUDIO_ENABLED) && defined(SOWRAP_ENABLED)
extern "C" {
extern int initialize_pulse(int verbose);
}
#endif
Error AudioDriverALSA::init_output_device() {
mix_rate = _get_configured_mix_rate();
speaker_mode = SPEAKER_MODE_STEREO;
channels = 2;
// If there is a specified output device check that it is really present
if (output_device_name != "Default") {
PackedStringArray list = get_output_device_list();
if (!list.has(output_device_name)) {
output_device_name = "Default";
new_output_device = "Default";
}
}
int status;
snd_pcm_hw_params_t *hwparams;
snd_pcm_sw_params_t *swparams;
#define CHECK_FAIL(m_cond) \
if (m_cond) { \
fprintf(stderr, "ALSA ERR: %s\n", snd_strerror(status)); \
if (pcm_handle) { \
snd_pcm_close(pcm_handle); \
pcm_handle = nullptr; \
} \
ERR_FAIL_COND_V(m_cond, ERR_CANT_OPEN); \
}
//todo, add
//6 chans - "plug:surround51"
//4 chans - "plug:surround40";
if (output_device_name == "Default") {
status = snd_pcm_open(&pcm_handle, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
} else {
String device = output_device_name;
int pos = device.find_char(';');
if (pos != -1) {
device = device.substr(0, pos);
}
status = snd_pcm_open(&pcm_handle, device.utf8().get_data(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
}
ERR_FAIL_COND_V(status < 0, ERR_CANT_OPEN);
snd_pcm_hw_params_alloca(&hwparams);
status = snd_pcm_hw_params_any(pcm_handle, hwparams);
CHECK_FAIL(status < 0);
status = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
CHECK_FAIL(status < 0);
//not interested in anything else
status = snd_pcm_hw_params_set_format(pcm_handle, hwparams, SND_PCM_FORMAT_S16_LE);
CHECK_FAIL(status < 0);
//todo: support 4 and 6
status = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, 2);
CHECK_FAIL(status < 0);
status = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &mix_rate, nullptr);
CHECK_FAIL(status < 0);
// In ALSA the period size seems to be the one that will determine the actual latency
// Ref: https://www.alsa-project.org/main/index.php/FramesPeriods
unsigned int periods = 2;
int latency = Engine::get_singleton()->get_audio_output_latency();
buffer_frames = Math::closest_power_of_2(latency * mix_rate / 1000);
buffer_size = buffer_frames * periods;
period_size = buffer_frames;
// set buffer size from project settings
status = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &buffer_size);
CHECK_FAIL(status < 0);
status = snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &period_size, nullptr);
CHECK_FAIL(status < 0);
print_verbose("Audio buffer frames: " + itos(period_size) + " calculated latency: " + itos(period_size * 1000 / mix_rate) + "ms");
status = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &periods, nullptr);
CHECK_FAIL(status < 0);
status = snd_pcm_hw_params(pcm_handle, hwparams);
CHECK_FAIL(status < 0);
//snd_pcm_hw_params_free(&hwparams);
snd_pcm_sw_params_alloca(&swparams);
status = snd_pcm_sw_params_current(pcm_handle, swparams);
CHECK_FAIL(status < 0);
status = snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, period_size);
CHECK_FAIL(status < 0);
status = snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams, 1);
CHECK_FAIL(status < 0);
status = snd_pcm_sw_params(pcm_handle, swparams);
CHECK_FAIL(status < 0);
samples_in.resize(period_size * channels);
samples_out.resize(period_size * channels);
return OK;
}
Error AudioDriverALSA::init() {
#ifdef SOWRAP_ENABLED
#ifdef DEBUG_ENABLED
int dylibloader_verbose = 1;
#else
int dylibloader_verbose = 0;
#endif
#ifdef PULSEAUDIO_ENABLED
// On pulse enabled systems Alsa will silently use pulse.
// It doesn't matter if this fails as that likely means there is no pulse
initialize_pulse(dylibloader_verbose);
#endif
if (initialize_asound(dylibloader_verbose)) {
return ERR_CANT_OPEN;
}
#endif
bool ver_ok = false;
String version = String::utf8(snd_asoundlib_version());
Vector<String> ver_parts = version.split(".");
if (ver_parts.size() >= 2) {
ver_ok = ((ver_parts[0].to_int() == 1 && ver_parts[1].to_int() >= 1)) || (ver_parts[0].to_int() > 1); // 1.1.0
}
print_verbose(vformat("ALSA %s detected.", version));
if (!ver_ok) {
print_verbose("Unsupported ALSA library version!");
return ERR_CANT_OPEN;
}
active.clear();
exit_thread.clear();
Error err = init_output_device();
if (err == OK) {
thread.start(AudioDriverALSA::thread_func, this);
}
return err;
}
void AudioDriverALSA::thread_func(void *p_udata) {
AudioDriverALSA *ad = static_cast<AudioDriverALSA *>(p_udata);
while (!ad->exit_thread.is_set()) {
ad->lock();
ad->start_counting_ticks();
if (!ad->active.is_set()) {
for (uint64_t i = 0; i < ad->period_size * ad->channels; i++) {
ad->samples_out.write[i] = 0;
}
} else {
ad->audio_server_process(ad->period_size, ad->samples_in.ptrw());
for (uint64_t i = 0; i < ad->period_size * ad->channels; i++) {
ad->samples_out.write[i] = ad->samples_in[i] >> 16;
}
}
int todo = ad->period_size;
int total = 0;
while (todo && !ad->exit_thread.is_set()) {
int16_t *src = (int16_t *)ad->samples_out.ptr();
int wrote = snd_pcm_writei(ad->pcm_handle, (void *)(src + (total * ad->channels)), todo);
if (wrote > 0) {
total += wrote;
todo -= wrote;
} else if (wrote == -EAGAIN) {
ad->stop_counting_ticks();
ad->unlock();
OS::get_singleton()->delay_usec(1000);
ad->lock();
ad->start_counting_ticks();
} else {
wrote = snd_pcm_recover(ad->pcm_handle, wrote, 0);
if (wrote < 0) {
ERR_PRINT("ALSA: Failed and can't recover: " + String(snd_strerror(wrote)));
ad->active.clear();
ad->exit_thread.set();
}
}
}
// User selected a new output device, finish the current one so we'll init the new device.
if (ad->output_device_name != ad->new_output_device) {
ad->output_device_name = ad->new_output_device;
ad->finish_output_device();
Error err = ad->init_output_device();
if (err != OK) {
ERR_PRINT("ALSA: init_output_device error");
ad->output_device_name = "Default";
ad->new_output_device = "Default";
err = ad->init_output_device();
if (err != OK) {
ad->active.clear();
ad->exit_thread.set();
}
}
}
ad->stop_counting_ticks();
ad->unlock();
}
}
void AudioDriverALSA::start() {
active.set();
}
int AudioDriverALSA::get_mix_rate() const {
return mix_rate;
}
AudioDriver::SpeakerMode AudioDriverALSA::get_speaker_mode() const {
return speaker_mode;
}
PackedStringArray AudioDriverALSA::get_output_device_list() {
PackedStringArray list;
list.push_back("Default");
void **hints;
if (snd_device_name_hint(-1, "pcm", &hints) < 0) {
return list;
}
for (void **n = hints; *n != nullptr; n++) {
char *name = snd_device_name_get_hint(*n, "NAME");
char *desc = snd_device_name_get_hint(*n, "DESC");
if (name != nullptr && !strncmp(name, "plughw", 6)) {
if (desc) {
list.push_back(String::utf8(name) + ";" + String::utf8(desc));
} else {
list.push_back(String::utf8(name));
}
}
if (desc != nullptr) {
free(desc);
}
if (name != nullptr) {
free(name);
}
}
snd_device_name_free_hint(hints);
return list;
}
String AudioDriverALSA::get_output_device() {
return output_device_name;
}
void AudioDriverALSA::set_output_device(const String &p_name) {
lock();
new_output_device = p_name;
unlock();
}
void AudioDriverALSA::lock() {
mutex.lock();
}
void AudioDriverALSA::unlock() {
mutex.unlock();
}
void AudioDriverALSA::finish_output_device() {
if (pcm_handle) {
snd_pcm_close(pcm_handle);
pcm_handle = nullptr;
}
}
void AudioDriverALSA::finish() {
exit_thread.set();
if (thread.is_started()) {
thread.wait_to_finish();
}
finish_output_device();
}
#endif // ALSA_ENABLED

View file

@ -0,0 +1,96 @@
/**************************************************************************/
/* audio_driver_alsa.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifdef ALSA_ENABLED
#include "core/os/mutex.h"
#include "core/os/thread.h"
#include "core/templates/safe_refcount.h"
#include "servers/audio/audio_server.h"
#ifdef SOWRAP_ENABLED
#include "asound-so_wrap.h"
#else
#include <alsa/asoundlib.h>
#endif
class AudioDriverALSA : public AudioDriver {
Thread thread;
Mutex mutex;
snd_pcm_t *pcm_handle = nullptr;
String output_device_name = "Default";
String new_output_device = "Default";
Vector<int32_t> samples_in;
Vector<int16_t> samples_out;
Error init_output_device();
void finish_output_device();
static void thread_func(void *p_udata);
unsigned int mix_rate = 0;
SpeakerMode speaker_mode;
snd_pcm_uframes_t buffer_frames;
snd_pcm_uframes_t buffer_size;
snd_pcm_uframes_t period_size;
int channels = 0;
SafeFlag active;
SafeFlag exit_thread;
public:
virtual const char *get_name() const override {
return "ALSA";
}
virtual Error init() override;
virtual void start() override;
virtual int get_mix_rate() const override;
virtual SpeakerMode get_speaker_mode() const override;
virtual void lock() override;
virtual void unlock() override;
virtual void finish() override;
virtual PackedStringArray get_output_device_list() override;
virtual String get_output_device() override;
virtual void set_output_device(const String &p_name) override;
AudioDriverALSA() {}
~AudioDriverALSA() {}
};
#endif // ALSA_ENABLED

View file

@ -0,0 +1,7 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
# Driver source files
env.add_source_files(env.drivers_sources, "*.cpp")

View file

@ -0,0 +1,147 @@
/**************************************************************************/
/* midi_driver_alsamidi.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifdef ALSAMIDI_ENABLED
#include "midi_driver_alsamidi.h"
#include "core/os/os.h"
#include <cerrno>
MIDIDriverALSAMidi::InputConnection::InputConnection(int p_device_index,
snd_rawmidi_t *p_rawmidi) :
parser(p_device_index), rawmidi_ptr(p_rawmidi) {}
void MIDIDriverALSAMidi::InputConnection::read() {
int read_count;
do {
uint8_t buffer[32];
read_count = snd_rawmidi_read(rawmidi_ptr, buffer, sizeof(buffer));
if (read_count < 0) {
if (read_count != -EAGAIN) {
ERR_PRINT("snd_rawmidi_read error: " + String(snd_strerror(read_count)));
}
} else {
for (int i = 0; i < read_count; i++) {
parser.parse_fragment(buffer[i]);
}
}
} while (read_count > 0);
}
void MIDIDriverALSAMidi::thread_func(void *p_udata) {
MIDIDriverALSAMidi *md = static_cast<MIDIDriverALSAMidi *>(p_udata);
while (!md->exit_thread.is_set()) {
md->lock();
for (InputConnection &conn : md->connected_inputs) {
conn.read();
}
md->unlock();
OS::get_singleton()->delay_usec(1000);
}
}
Error MIDIDriverALSAMidi::open() {
void **hints;
if (snd_device_name_hint(-1, "rawmidi", &hints) < 0) {
return ERR_CANT_OPEN;
}
lock();
int device_index = 0;
for (void **h = hints; *h != nullptr; h++) {
char *name = snd_device_name_get_hint(*h, "NAME");
if (name != nullptr) {
snd_rawmidi_t *midi_in;
int ret = snd_rawmidi_open(&midi_in, nullptr, name, SND_RAWMIDI_NONBLOCK);
if (ret >= 0) {
// Get display name.
snd_rawmidi_info_t *info;
snd_rawmidi_info_malloc(&info);
snd_rawmidi_info(midi_in, info);
connected_input_names.push_back(snd_rawmidi_info_get_name(info));
snd_rawmidi_info_free(info);
connected_inputs.push_back(InputConnection(device_index, midi_in));
// Only increment device_index for successfully connected devices.
device_index++;
}
}
if (name != nullptr) {
free(name);
}
}
snd_device_name_free_hint(hints);
unlock();
exit_thread.clear();
thread.start(MIDIDriverALSAMidi::thread_func, this);
return OK;
}
void MIDIDriverALSAMidi::close() {
exit_thread.set();
if (thread.is_started()) {
thread.wait_to_finish();
}
for (const InputConnection &conn : connected_inputs) {
snd_rawmidi_close(conn.rawmidi_ptr);
}
connected_inputs.clear();
connected_input_names.clear();
}
void MIDIDriverALSAMidi::lock() const {
mutex.lock();
}
void MIDIDriverALSAMidi::unlock() const {
mutex.unlock();
}
MIDIDriverALSAMidi::MIDIDriverALSAMidi() {
exit_thread.clear();
}
MIDIDriverALSAMidi::~MIDIDriverALSAMidi() {
close();
}
#endif // ALSAMIDI_ENABLED

View file

@ -0,0 +1,79 @@
/**************************************************************************/
/* midi_driver_alsamidi.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifdef ALSAMIDI_ENABLED
#include "core/os/midi_driver.h"
#include "core/os/mutex.h"
#include "core/os/thread.h"
#include "core/templates/safe_refcount.h"
#include "core/templates/vector.h"
#ifdef SOWRAP_ENABLED
#include "../alsa/asound-so_wrap.h"
#else
#include <alsa/asoundlib.h>
#endif
class MIDIDriverALSAMidi : public MIDIDriver {
Thread thread;
Mutex mutex;
struct InputConnection {
InputConnection() = default;
InputConnection(int p_device_index, snd_rawmidi_t *p_rawmidi);
Parser parser;
snd_rawmidi_t *rawmidi_ptr = nullptr;
// Read in and parse available data, forwarding complete events to Input.
void read();
};
Vector<InputConnection> connected_inputs;
SafeFlag exit_thread;
static void thread_func(void *p_udata);
void lock() const;
void unlock() const;
public:
virtual Error open() override;
virtual void close() override;
MIDIDriverALSAMidi();
virtual ~MIDIDriverALSAMidi();
};
#endif // ALSAMIDI_ENABLED

View file

@ -0,0 +1,8 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
# Driver source files
env.add_source_files(env.drivers_sources, "*.mm")
env.add_source_files(env.drivers_sources, "*.cpp")

View file

@ -0,0 +1,87 @@
/**************************************************************************/
/* foundation_helpers.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifdef __OBJC__
#import <Foundation/NSString.h>
#else
#include <Foundation/NSString.hpp>
#endif
class String;
template <typename T>
class CharStringT;
using CharString = CharStringT<char>;
template <typename T>
class Span;
namespace conv {
#ifdef __OBJC__
/**
* Converts a Godot String to an NSString without allocating an intermediate UTF-8 buffer.
* */
NSString *to_nsstring(const String &p_str);
/**
* Converts a Godot String to an NSString without allocating an intermediate UTF-8 buffer.
* */
NSString *to_nsstring(Span<char> p_str);
/**
* Converts a Godot CharString to an NSString without allocating an intermediate UTF-8 buffer.
* */
NSString *to_nsstring(const CharString &p_str);
/**
* Converts an NSString to a Godot String without allocating intermediate buffers.
* */
String to_string(NSString *p_str);
#else
/**
* Converts a Godot String to an NSString without allocating an intermediate UTF-8 buffer.
* */
NS::String *to_nsstring(const String &p_str);
/**
* Converts a Godot String to an NSString without allocating an intermediate UTF-8 buffer.
* */
NS::String *to_nsstring(Span<char> p_str);
/**
* Converts a Godot CharString to an NSString without allocating an intermediate UTF-8 buffer.
* */
NS::String *to_nsstring(const CharString &p_str);
/**
* Converts an NSString to a Godot String without allocating intermediate buffers.
* */
String to_string(NS::String *p_str);
#endif
} //namespace conv

View file

@ -0,0 +1,92 @@
/**************************************************************************/
/* foundation_helpers.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#import "foundation_helpers.h"
#import "core/string/ustring.h"
#import "core/templates/span.h"
#import <CoreFoundation/CFString.h>
namespace conv {
NSString *to_nsstring(const String &p_str) {
return [[NSString alloc] initWithBytes:(const void *)p_str.ptr()
length:p_str.length() * sizeof(char32_t)
encoding:NSUTF32LittleEndianStringEncoding];
}
NSString *to_nsstring(Span<char> p_str) {
return [[NSString alloc] initWithBytes:(const void *)p_str.ptr()
length:p_str.size()
encoding:NSASCIIStringEncoding];
}
NSString *to_nsstring(const CharString &p_str) {
return [[NSString alloc] initWithBytes:(const void *)p_str.ptr()
length:p_str.length()
encoding:NSUTF8StringEncoding];
}
String to_string(NSString *p_str) {
CFStringRef str = (__bridge CFStringRef)p_str;
CFStringEncoding fastest = CFStringGetFastestEncoding(str);
// Sometimes, CFString will return a pointer to it's encoded data,
// so we can create the string without allocating intermediate buffers.
const char *p = CFStringGetCStringPtr(str, fastest);
if (p) {
switch (fastest) {
case kCFStringEncodingASCII:
return String::ascii(Span(p, CFStringGetLength(str)));
case kCFStringEncodingUTF8:
return String::utf8(p);
case kCFStringEncodingUTF32LE:
return String::utf32(Span((char32_t *)p, CFStringGetLength(str)));
default:
break;
}
}
CFRange range = CFRangeMake(0, CFStringGetLength(str));
CFIndex byte_len = 0;
// Try to losslessly convert the string directly into a String's buffer to avoid intermediate allocations.
CFIndex n = CFStringGetBytes(str, range, kCFStringEncodingUTF32LE, 0, NO, nil, 0, &byte_len);
if (n == range.length) {
String res;
res.resize_uninitialized((byte_len / sizeof(char32_t)) + 1);
res[n] = 0;
n = CFStringGetBytes(str, range, kCFStringEncodingUTF32LE, 0, NO, (UInt8 *)res.ptrw(), res.length() * sizeof(char32_t), nil);
return res;
}
return String::utf8(p_str.UTF8String);
}
} //namespace conv

View file

@ -0,0 +1,127 @@
/**************************************************************************/
/* os_log_logger.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "os_log_logger.h"
#include "core/object/script_backtrace.h"
#include "core/string/print_string.h"
#include <cstdio> // For vsnprintf.
#include <cstdlib> // For malloc/free.
OsLogLogger::OsLogLogger(const char *p_subsystem) {
const char *subsystem = p_subsystem;
if (!subsystem) {
subsystem = "org.godotengine.godot";
os_log_info(OS_LOG_DEFAULT, "Missing subsystem for os_log logging; using %{public}s", subsystem);
}
log = os_log_create(subsystem, "engine");
error_log = os_log_create(subsystem, error_type_string(ErrorType::ERR_ERROR));
warning_log = os_log_create(subsystem, error_type_string(ErrorType::ERR_WARNING));
script_log = os_log_create(subsystem, error_type_string(ErrorType::ERR_SCRIPT));
shader_log = os_log_create(subsystem, error_type_string(ErrorType::ERR_SHADER));
}
void OsLogLogger::logv(const char *p_format, va_list p_list, bool p_err) {
constexpr int static_buf_size = 1024;
char static_buf[static_buf_size] = { '\0' };
char *buf = static_buf;
va_list list_copy;
va_copy(list_copy, p_list);
int len = vsnprintf(buf, static_buf_size, p_format, p_list);
if (len >= static_buf_size) {
buf = (char *)Memory::alloc_static(len + 1);
vsnprintf(buf, len + 1, p_format, list_copy);
}
va_end(list_copy);
// Choose appropriate log type based on error flag.
os_log_type_t log_type = p_err ? OS_LOG_TYPE_ERROR : OS_LOG_TYPE_INFO;
os_log_with_type(log, log_type, "%{public}s", buf);
if (len >= static_buf_size) {
Memory::free_static(buf);
}
}
void OsLogLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type, const Vector<Ref<ScriptBacktrace>> &p_script_backtraces) {
os_log_t selected_log;
switch (p_type) {
case ERR_WARNING:
selected_log = warning_log;
break;
case ERR_SCRIPT:
selected_log = script_log;
break;
case ERR_SHADER:
selected_log = shader_log;
break;
case ERR_ERROR:
default:
selected_log = error_log;
break;
}
const char *err_details;
if (p_rationale && *p_rationale) {
err_details = p_rationale;
} else {
err_details = p_code;
}
// Choose log level based on error type.
os_log_type_t log_type;
switch (p_type) {
case ERR_WARNING:
log_type = OS_LOG_TYPE_DEFAULT;
break;
case ERR_ERROR:
case ERR_SCRIPT:
case ERR_SHADER:
default:
log_type = OS_LOG_TYPE_ERROR;
break;
}
// Append script backtraces, if any.
String back_trace;
for (const Ref<ScriptBacktrace> &backtrace : p_script_backtraces) {
if (backtrace.is_valid() && !backtrace->is_empty()) {
back_trace += "\n";
back_trace += backtrace->format(strlen(error_type_indent(p_type)));
}
}
if (back_trace.is_empty()) {
os_log_with_type(selected_log, log_type, "%{public}s:%d:%{public}s(): %{public}s %{public}s", p_file, p_line, p_function, err_details, p_code);
} else {
os_log_with_type(selected_log, log_type, "%{public}s:%d:%{public}s(): %{public}s %{public}s%{public}s", p_file, p_line, p_function, err_details, p_code, back_trace.utf8().ptr());
}
}

View file

@ -0,0 +1,55 @@
/**************************************************************************/
/* os_log_logger.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/logger.h"
#include <os/log.h>
/**
* @brief Apple unified logging system integration for Godot Engine.
*/
class OsLogLogger : public Logger {
os_log_t log;
os_log_t error_log;
os_log_t warning_log;
os_log_t script_log;
os_log_t shader_log;
public:
void logv(const char *p_format, va_list p_list, bool p_err) override _PRINTF_FORMAT_ATTRIBUTE_2_0;
void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR, const Vector<Ref<ScriptBacktrace>> &p_script_backtraces = {}) override;
/**
* @brief Constructs an OsLogLogger with the specified subsystem identifier, which is normally the bundle identifier.
*/
OsLogLogger(const char *p_subsystem);
};

View file

@ -0,0 +1,140 @@
/**************************************************************************/
/* thread_apple.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "thread_apple.h"
#include "core/error/error_macros.h"
#include "core/object/script_language.h"
#include "core/string/ustring.h"
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::id_counter.increment();
struct ThreadData {
Thread::Callback callback;
void *userdata;
Thread::ID caller_id;
};
void *Thread::thread_callback(void *p_data) {
ThreadData *thread_data = static_cast<ThreadData *>(p_data);
// Set the caller ID for this thread
caller_id = thread_data->caller_id;
ScriptServer::thread_enter(); // Scripts may need to attach a stack.
// Call the actual callback
thread_data->callback(thread_data->userdata);
ScriptServer::thread_exit();
// Clean up
memdelete(thread_data);
return nullptr;
}
Error Thread::set_name(const String &p_name) {
int err = pthread_setname_np(p_name.utf8().get_data());
return err == 0 ? OK : ERR_INVALID_PARAMETER;
}
Thread::ID Thread::start(Thread::Callback p_callback, void *p_user, const Settings &p_settings) {
ERR_FAIL_COND_V_MSG(id != UNASSIGNED_ID, UNASSIGNED_ID, "A Thread object has been re-started without wait_to_finish() having been called on it.");
id = id_counter.increment();
ThreadData *thread_data = memnew(ThreadData);
thread_data->callback = p_callback;
thread_data->userdata = p_user;
thread_data->caller_id = id;
// Create the thread
pthread_attr_t attr;
pthread_attr_init(&attr);
switch (p_settings.priority) {
case PRIORITY_LOW:
pthread_attr_set_qos_class_np(&attr, QOS_CLASS_UTILITY, 0);
break;
case PRIORITY_NORMAL:
pthread_attr_set_qos_class_np(&attr, QOS_CLASS_USER_INITIATED, 0);
break;
case PRIORITY_HIGH:
pthread_attr_set_qos_class_np(&attr, QOS_CLASS_USER_INTERACTIVE, 0);
break;
}
// The default stack size for secondary threads on Apple platforms is 512KiB.
// This is insufficient when using a library like SPIRV-Cross, which can generate deep stacks and result in a stack overflow.
// It also creates a problematic discrepancy with other platforms, where secondary threads are often at least 1 MiB.
pthread_attr_setstacksize(&attr,
#if __has_feature(address_sanitizer) || __has_feature(thread_sanitizer)
// ASan (and to some degree TSan) needs a lot of extra stack size.
4 * 1024 * 1024 // 4 MiB
#elif !defined(__OPTIMIZE__)
// Unoptimized builds also need a larger stack size.
2 * 1024 * 1024 // 2 MiB
#else
1 * 1024 * 1024 // 1 MiB
#endif
);
// Create the thread
pthread_create(&pthread, &attr, thread_callback, thread_data);
// Clean up attributes
pthread_attr_destroy(&attr);
return id;
}
void Thread::wait_to_finish() {
ERR_FAIL_COND_MSG(id == UNASSIGNED_ID, "Attempt of waiting to finish on a thread that was never started.");
ERR_FAIL_COND_MSG(id == get_caller_id(), "Threads can't wait to finish on themselves, another thread must wait.");
int err = pthread_join(pthread, nullptr);
if (err != 0) {
ERR_FAIL_MSG("Thread::wait_to_finish() failed to join thread.");
}
pthread = pthread_t();
id = UNASSIGNED_ID;
}
Thread::~Thread() {
if (id != UNASSIGNED_ID) {
#ifdef DEBUG_ENABLED
WARN_PRINT(
"A Thread object is being destroyed without its completion having been realized.\n"
"Please call wait_to_finish() on it to ensure correct cleanup.");
#endif
pthread_detach(pthread);
}
}

View file

@ -0,0 +1,108 @@
/**************************************************************************/
/* thread_apple.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/templates/safe_refcount.h"
#include "core/typedefs.h"
#include <pthread.h>
#include <new> // For hardware interference size
class String;
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; }
};
#if defined(__cpp_lib_hardware_interference_size)
GODOT_GCC_WARNING_PUSH_AND_IGNORE("-Winterference-size")
static constexpr size_t CACHE_LINE_BYTES = std::hardware_destructive_interference_size;
GODOT_GCC_WARNING_POP
#else
// At a negligible memory cost, we use a conservatively high value.
static constexpr size_t CACHE_LINE_BYTES = 128;
#endif
private:
friend class Main;
ID id = UNASSIGNED_ID;
pthread_t pthread;
static SafeNumeric<uint64_t> id_counter;
static thread_local ID caller_id;
static void *thread_callback(void *p_data);
static void make_main_thread() { caller_id = MAIN_ID; }
static void release_main_thread() { caller_id = id_counter.increment(); }
public:
_FORCE_INLINE_ static void yield() { pthread_yield_np(); }
_FORCE_INLINE_ ID get_id() const { return id; }
// get the ID of the caller thread
_FORCE_INLINE_ static ID get_caller_id() {
return caller_id;
}
// get the ID of the main thread
_FORCE_INLINE_ static ID get_main_id() { return MAIN_ID; }
_FORCE_INLINE_ static bool is_main_thread() { return caller_id == MAIN_ID; }
static Error set_name(const String &p_name);
ID start(Thread::Callback p_callback, void *p_user, const Settings &p_settings = Settings());
bool is_started() const { return id != UNASSIGNED_ID; }
/// Waits until thread is finished, and deallocates it.
void wait_to_finish();
Thread() = default;
~Thread();
};

View file

@ -0,0 +1,35 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
from SCons.Script import Glob
from platform_methods import setup_swift_builder
Import("env")
env_apple_embedded = env.Clone()
# Enable module support
env_apple_embedded.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
# Configure Swift builder
apple_platform = env["APPLE_PLATFORM"]
sdk_path = env["APPLE_SDK_PATH"]
current_path = Dir(".").abspath
bridging_header_filename = "bridging_header_apple_embedded.h"
swift_files = Glob("*.swift")
swift_file_names = list(map(lambda f: f.name, swift_files))
setup_swift_builder(
env_apple_embedded, apple_platform, sdk_path, current_path, bridging_header_filename, swift_file_names
)
# Use bundled Vulkan headers
vulkan_dir = "#thirdparty/vulkan"
env_apple_embedded.Prepend(CPPPATH=[vulkan_dir, vulkan_dir + "/include"])
# Use bundled metal-cpp headers
env_apple_embedded.Prepend(CPPPATH=["#thirdparty/metal-cpp"])
# Driver source files
env_apple_embedded.add_source_files(env_apple_embedded.drivers_sources, "*.mm")
env_apple_embedded.add_source_files(env_apple_embedded.drivers_sources, "*.swift")

View file

@ -0,0 +1,58 @@
/**************************************************************************/
/* app.swift */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
import SwiftUI
import UIKit
struct GodotSwiftUIViewController: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> GDTViewController {
let viewController = GDTViewController()
GDTAppDelegateService.viewController = viewController
return viewController
}
func updateUIViewController(_ uiViewController: GDTViewController, context: Context) {
// NOOP
}
}
@main
struct SwiftUIApp: App {
@UIApplicationDelegateAdaptor(GDTApplicationDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
GodotSwiftUIViewController()
.ignoresSafeArea()
}
}
}

View file

@ -0,0 +1,41 @@
/**************************************************************************/
/* app_delegate_service.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#import <UIKit/UIKit.h>
@class GDTViewController;
@interface GDTAppDelegateService : NSObject <UIApplicationDelegate, UIWindowSceneDelegate>
@property(strong, class, nonatomic) GDTViewController *viewController;
@end

View file

@ -0,0 +1,203 @@
/**************************************************************************/
/* app_delegate_service.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#import "app_delegate_service.h"
#import "godot_view_apple_embedded.h"
#import "godot_view_controller.h"
#import "os_apple_embedded.h"
#include "core/config/project_settings.h"
#include "core/os/main_loop.h"
#include "core/os/os.h"
#import "drivers/coreaudio/audio_driver_coreaudio.h"
#include "main/main.h"
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioServices.h>
int gargc;
char **gargv;
extern int apple_embedded_main(int, char **);
extern void apple_embedded_finish();
@implementation GDTAppDelegateService
enum {
SESSION_CATEGORY_AMBIENT,
SESSION_CATEGORY_MULTI_ROUTE,
SESSION_CATEGORY_PLAY_AND_RECORD,
SESSION_CATEGORY_PLAYBACK,
SESSION_CATEGORY_RECORD,
SESSION_CATEGORY_SOLO_AMBIENT
};
static GDTViewController *mainViewController = nil;
+ (GDTViewController *)viewController {
return mainViewController;
}
+ (void)setViewController:(GDTViewController *)viewController {
mainViewController = viewController;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// TODO: might be required to make an early return, so app wouldn't crash because of timeout.
// TODO: logo screen is not displayed while shaders are compiling
// DummyViewController(Splash/LoadingViewController) -> setup -> GodotViewController
// Fetch the command-line arguments from NSProcessInfo
NSArray *arguments = [[NSProcessInfo processInfo] arguments];
gargc = (int)[arguments count];
gargv = (char **)malloc(sizeof(char *) * gargc);
for (int i = 0; i < gargc; i++) {
NSString *arg = arguments[i];
gargv[i] = strdup([arg UTF8String]);
}
int err = apple_embedded_main(gargc, gargv);
if (err != 0) {
// bail, things did not go very well for us, should probably output a message on screen with our error code...
exit(0);
return NO;
}
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(onAudioInterruption:)
name:AVAudioSessionInterruptionNotification
object:[AVAudioSession sharedInstance]];
int sessionCategorySetting = GLOBAL_GET("audio/general/ios/session_category");
// Initialize with default Ambient category.
AVAudioSessionCategory category = AVAudioSessionCategoryAmbient;
AVAudioSessionCategoryOptions options = 0;
if (GLOBAL_GET("audio/general/ios/mix_with_others")) {
options |= AVAudioSessionCategoryOptionMixWithOthers;
}
if (sessionCategorySetting == SESSION_CATEGORY_MULTI_ROUTE) {
category = AVAudioSessionCategoryMultiRoute;
} else if (sessionCategorySetting == SESSION_CATEGORY_PLAY_AND_RECORD) {
category = AVAudioSessionCategoryPlayAndRecord;
options |= AVAudioSessionCategoryOptionDefaultToSpeaker;
options |= AVAudioSessionCategoryOptionAllowBluetoothA2DP;
options |= AVAudioSessionCategoryOptionAllowAirPlay;
} else if (sessionCategorySetting == SESSION_CATEGORY_PLAYBACK) {
category = AVAudioSessionCategoryPlayback;
} else if (sessionCategorySetting == SESSION_CATEGORY_RECORD) {
category = AVAudioSessionCategoryRecord;
} else if (sessionCategorySetting == SESSION_CATEGORY_SOLO_AMBIENT) {
category = AVAudioSessionCategorySoloAmbient;
}
[[AVAudioSession sharedInstance] setCategory:category withOptions:options error:nil];
return YES;
}
- (void)onAudioInterruption:(NSNotification *)notification {
if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) {
NSLog(@"Audio interruption began");
OS_AppleEmbedded::get_singleton()->on_focus_out();
} else if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]) {
NSLog(@"Audio interruption ended");
OS_AppleEmbedded::get_singleton()->on_focus_in();
}
}
}
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_MEMORY_WARNING);
}
}
- (void)applicationWillTerminate:(UIApplication *)application {
apple_embedded_finish();
}
// When application goes to background (e.g. user switches to another app or presses Home),
// then applicationWillResignActive -> applicationDidEnterBackground are called.
// When user opens the inactive app again,
// applicationWillEnterForeground -> applicationDidBecomeActive are called.
// There are cases when applicationWillResignActive -> applicationDidBecomeActive
// sequence is called without the app going to background. For example, that happens
// if you open the app list without switching to another app or open/close the
// notification panel by swiping from the upper part of the screen.
- (void)sceneDidDisconnect:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) {
OS_AppleEmbedded::get_singleton()->on_focus_out();
}
- (void)sceneWillResignActive:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) {
OS_AppleEmbedded::get_singleton()->on_focus_out();
}
- (void)sceneDidBecomeActive:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) {
OS_AppleEmbedded::get_singleton()->on_focus_in();
}
- (void)sceneDidEnterBackground:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) {
OS_AppleEmbedded::get_singleton()->on_enter_background();
}
- (void)sceneWillEnterForeground:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) {
OS_AppleEmbedded::get_singleton()->on_exit_background();
}
- (void)applicationWillResignActive:(UIApplication *)application {
OS_AppleEmbedded::get_singleton()->on_focus_out();
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
OS_AppleEmbedded::get_singleton()->on_focus_in();
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
OS_AppleEmbedded::get_singleton()->on_enter_background();
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
OS_AppleEmbedded::get_singleton()->on_exit_background();
}
- (void)dealloc {
self.window = nil;
}
@end

View file

@ -0,0 +1,59 @@
/**************************************************************************/
/* apple_embedded.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/object/object.h"
#import <CoreHaptics/CoreHaptics.h>
class AppleEmbedded : public Object {
GDCLASS(AppleEmbedded, Object);
static void _bind_methods();
private:
CHHapticEngine *haptic_engine API_AVAILABLE(ios(13)) = nullptr;
CHHapticEngine *get_haptic_engine_instance() API_AVAILABLE(ios(13));
void start_haptic_engine();
void stop_haptic_engine();
public:
static void alert(const char *p_alert, const char *p_title);
bool supports_haptic_engine();
void vibrate_haptic_engine(float p_duration_seconds, float p_amplitude);
String get_model() const;
String get_rate_url(int p_app_id) const;
AppleEmbedded();
};

View file

@ -0,0 +1,202 @@
/**************************************************************************/
/* apple_embedded.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#import "apple_embedded.h"
#import "app_delegate_service.h"
#import "godot_view_controller.h"
#import <CoreHaptics/CoreHaptics.h>
#import <UIKit/UIKit.h>
#include <sys/sysctl.h>
#include "core/object/class_db.h"
void AppleEmbedded::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_rate_url", "app_id"), &AppleEmbedded::get_rate_url);
ClassDB::bind_method(D_METHOD("supports_haptic_engine"), &AppleEmbedded::supports_haptic_engine);
ClassDB::bind_method(D_METHOD("start_haptic_engine"), &AppleEmbedded::start_haptic_engine);
ClassDB::bind_method(D_METHOD("stop_haptic_engine"), &AppleEmbedded::stop_haptic_engine);
}
bool AppleEmbedded::supports_haptic_engine() {
if (@available(iOS 13, *)) {
id<CHHapticDeviceCapability> capabilities = [CHHapticEngine capabilitiesForHardware];
return capabilities.supportsHaptics;
}
return false;
}
CHHapticEngine *AppleEmbedded::get_haptic_engine_instance() API_AVAILABLE(ios(13)) {
if (haptic_engine == nullptr) {
NSError *error = nullptr;
haptic_engine = [[CHHapticEngine alloc] initAndReturnError:&error];
if (!error) {
[haptic_engine setAutoShutdownEnabled:true];
} else {
haptic_engine = nullptr;
NSLog(@"Could not initialize haptic engine: %@", error);
}
}
return haptic_engine;
}
void AppleEmbedded::vibrate_haptic_engine(float p_duration_seconds, float p_amplitude) API_AVAILABLE(ios(13)) {
if (@available(iOS 13, *)) { // We need the @available check every time to make the compiler happy...
if (supports_haptic_engine()) {
CHHapticEngine *cur_haptic_engine = get_haptic_engine_instance();
if (cur_haptic_engine) {
NSDictionary *hapticDict;
if (p_amplitude < 0) {
hapticDict = @{
CHHapticPatternKeyPattern : @[
@{CHHapticPatternKeyEvent : @{
CHHapticPatternKeyEventType : CHHapticEventTypeHapticContinuous,
CHHapticPatternKeyTime : @(CHHapticTimeImmediate),
CHHapticPatternKeyEventDuration : @(p_duration_seconds),
},
},
],
};
} else {
hapticDict = @{
CHHapticPatternKeyPattern : @[
@{CHHapticPatternKeyEvent : @{
CHHapticPatternKeyEventType : CHHapticEventTypeHapticContinuous,
CHHapticPatternKeyTime : @(CHHapticTimeImmediate),
CHHapticPatternKeyEventDuration : @(p_duration_seconds),
CHHapticPatternKeyEventParameters : @[
@{
CHHapticPatternKeyParameterID : @("HapticIntensity"),
CHHapticPatternKeyParameterValue : @(p_amplitude)
},
],
},
},
],
};
}
NSError *error;
CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithDictionary:hapticDict error:&error];
[[cur_haptic_engine createPlayerWithPattern:pattern error:&error] startAtTime:0 error:&error];
NSLog(@"Could not vibrate using haptic engine: %@", error);
}
return;
}
}
NSLog(@"Haptic engine is not supported");
}
void AppleEmbedded::start_haptic_engine() {
if (@available(iOS 13, *)) {
if (supports_haptic_engine()) {
CHHapticEngine *cur_haptic_engine = get_haptic_engine_instance();
if (cur_haptic_engine) {
[cur_haptic_engine startWithCompletionHandler:^(NSError *returnedError) {
if (returnedError) {
NSLog(@"Could not start haptic engine: %@", returnedError);
}
}];
}
return;
}
}
NSLog(@"Haptic engine is not supported");
}
void AppleEmbedded::stop_haptic_engine() {
if (@available(iOS 13, *)) {
if (supports_haptic_engine()) {
CHHapticEngine *cur_haptic_engine = get_haptic_engine_instance();
if (cur_haptic_engine) {
[cur_haptic_engine stopWithCompletionHandler:^(NSError *returnedError) {
if (returnedError) {
NSLog(@"Could not stop haptic engine: %@", returnedError);
}
}];
}
return;
}
}
NSLog(@"Haptic engine is not supported");
}
void AppleEmbedded::alert(const char *p_alert, const char *p_title) {
NSString *title = [NSString stringWithUTF8String:p_title];
NSString *message = [NSString stringWithUTF8String:p_alert];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *button = [UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleCancel
handler:^(id){
}];
[alert addAction:button];
[GDTAppDelegateService.viewController presentViewController:alert animated:YES completion:nil];
}
String AppleEmbedded::get_model() const {
// [[UIDevice currentDevice] model] only returns "iPad" or "iPhone".
size_t size;
sysctlbyname("hw.machine", nullptr, &size, nullptr, 0);
char *model = (char *)malloc(size);
if (model == nullptr) {
return "";
}
sysctlbyname("hw.machine", model, &size, nullptr, 0);
NSString *platform = [NSString stringWithCString:model encoding:NSUTF8StringEncoding];
free(model);
const char *str = [platform UTF8String];
return String::utf8(str != nullptr ? str : "");
}
String AppleEmbedded::get_rate_url(int p_app_id) const {
String app_url_path = "itms-apps://itunes.apple.com/app/idAPP_ID";
String ret = app_url_path.replace("APP_ID", String::num_int64(p_app_id));
print_verbose(vformat("Returning rate url %s", ret));
return ret;
}
AppleEmbedded::AppleEmbedded() {}

View file

@ -0,0 +1,36 @@
/**************************************************************************/
/* bridging_header_apple_embedded.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#import "app_delegate_service.h"
#import "godot_app_delegate.h"
#import "godot_view_apple_embedded.h"
#import "godot_view_controller.h"

View file

@ -0,0 +1,42 @@
/**************************************************************************/
/* display_layer_apple_embedded.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#import <QuartzCore/CAMetalLayer.h>
@protocol GDTDisplayLayer <NSObject>
- (void)startRenderDisplayLayer;
- (void)stopRenderDisplayLayer;
- (void)initializeDisplayLayer;
- (void)layoutDisplayLayer;
@end

View file

@ -0,0 +1,266 @@
/**************************************************************************/
/* display_server_apple_embedded.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "servers/display/display_server.h"
#if defined(RD_ENABLED)
#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
#include "servers/rendering/rendering_device.h"
#if defined(VULKAN_ENABLED)
#import "rendering_context_driver_vulkan_apple_embedded.h"
#include "drivers/vulkan/godot_vulkan.h"
#endif // VULKAN_ENABLED
#if defined(METAL_ENABLED)
#import "drivers/metal/rendering_context_driver_metal.h"
#endif // METAL_ENABLED
#endif // RD_ENABLED
#if defined(GLES3_ENABLED)
#include "drivers/gles3/rasterizer_gles3.h"
#endif // GLES3_ENABLED
#import <Foundation/Foundation.h>
#import <QuartzCore/CAMetalLayer.h>
class InputEvent;
class NativeMenu;
/// "Embedded" as in "Embedded Device".
class DisplayServerAppleEmbedded : public DisplayServer {
GDSOFTCLASS(DisplayServerAppleEmbedded, DisplayServer);
_THREAD_SAFE_CLASS_
#if defined(RD_ENABLED)
RenderingContextDriver *rendering_context = nullptr;
RenderingDevice *rendering_device = nullptr;
#endif
NativeMenu *native_menu = nullptr;
id tts = nullptr;
DisplayServerEnums::ScreenOrientation screen_orientation;
ObjectID window_attached_instance_id;
Callable window_event_callback;
Callable window_resize_callback;
Callable input_event_callback;
Callable input_text_callback;
Callable system_theme_changed;
int virtual_keyboard_height = 0;
void perform_event(const Ref<InputEvent> &p_event);
void initialize_tts() const;
bool edr_requested = false;
void _update_hdr_output();
float _calculate_current_reference_luminance() const;
protected:
virtual bool _screen_hdr_is_supported() const { return false; }
virtual float _screen_potential_edr_headroom() const { return 1.0f; }
virtual float _screen_current_edr_headroom() const { return 1.0f; }
float hardware_reference_luminance_nits = 100.0f;
DisplayServerAppleEmbedded(const String &p_rendering_driver, DisplayServerEnums::WindowMode p_mode, DisplayServerEnums::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, DisplayServerEnums::Context p_context, int64_t p_parent_window, Error &r_error);
~DisplayServerAppleEmbedded();
public:
String rendering_driver;
static DisplayServerAppleEmbedded *get_singleton();
static Vector<String> get_rendering_drivers_func();
// MARK: - Events
virtual void process_events() override;
virtual void window_set_rect_changed_callback(const Callable &p_callable, DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
virtual void window_set_window_event_callback(const Callable &p_callable, DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
virtual void window_set_input_event_callback(const Callable &p_callable, DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
virtual void window_set_input_text_callback(const Callable &p_callable, DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
virtual void window_set_drop_files_callback(const Callable &p_callable, DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
static void _dispatch_input_events(const Ref<InputEvent> &p_event);
void send_input_event(const Ref<InputEvent> &p_event) const;
void send_input_text(const String &p_text) const;
void send_window_event(DisplayServerEnums::WindowEvent p_event) const;
void _window_callback(const Callable &p_callable, const Variant &p_arg) const;
void emit_system_theme_changed();
// MARK: - Input
// MARK: Touches and Apple Pencil
void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_double_click);
void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y, float p_pressure, Vector2 p_tilt);
void touches_canceled(int p_idx);
// MARK: Keyboard
void key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location);
bool is_keyboard_active() const;
// MARK: Motion
void update_gravity(const Vector3 &p_gravity);
void update_accelerometer(const Vector3 &p_accelerometer);
void update_magnetometer(const Vector3 &p_magnetometer);
void update_gyroscope(const Vector3 &p_gyroscope);
// MARK: -
virtual bool has_feature(DisplayServerEnums::Feature p_feature) const override;
virtual bool tts_is_speaking() const override;
virtual bool tts_is_paused() const override;
virtual TypedArray<Dictionary> tts_get_voices() const override;
virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int64_t p_utterance_id = 0, bool p_interrupt = false) override;
virtual void tts_pause() override;
virtual void tts_resume() override;
virtual void tts_stop() override;
virtual bool is_dark_mode_supported() const override;
virtual bool is_dark_mode() const override;
virtual void set_system_theme_change_callback(const Callable &p_callable) override;
virtual Rect2i get_display_safe_area() const override;
virtual int get_screen_count() const override;
virtual int get_primary_screen() const override;
virtual Point2i screen_get_position(int p_screen = DisplayServerEnums::SCREEN_OF_MAIN_WINDOW) const override;
virtual Size2i screen_get_size(int p_screen = DisplayServerEnums::SCREEN_OF_MAIN_WINDOW) const override;
virtual Rect2i screen_get_usable_rect(int p_screen = DisplayServerEnums::SCREEN_OF_MAIN_WINDOW) const override;
virtual Vector<DisplayServerEnums::WindowID> get_window_list() const override;
virtual DisplayServerEnums::WindowID get_window_at_screen_position(const Point2i &p_position) const override;
virtual int64_t window_get_native_handle(DisplayServerEnums::HandleType p_handle_type, DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual void window_attach_instance_id(ObjectID p_instance, DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
virtual ObjectID window_get_attached_instance_id(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual void window_set_title(const String &p_title, DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
virtual int window_get_current_screen(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual void window_set_current_screen(int p_screen, DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
virtual Point2i window_get_position(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual Point2i window_get_position_with_decorations(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual void window_set_position(const Point2i &p_position, DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
virtual void window_set_transient(DisplayServerEnums::WindowID p_window, DisplayServerEnums::WindowID p_parent) override;
virtual void window_set_max_size(const Size2i p_size, DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
virtual Size2i window_get_max_size(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual void window_set_min_size(const Size2i p_size, DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
virtual Size2i window_get_min_size(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual void window_set_size(const Size2i p_size, DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
virtual Size2i window_get_size(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual Size2i window_get_size_with_decorations(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual void window_set_mode(DisplayServerEnums::WindowMode p_mode, DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
virtual DisplayServerEnums::WindowMode window_get_mode(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual bool window_is_maximize_allowed(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual void window_set_flag(DisplayServerEnums::WindowFlags p_flag, bool p_enabled, DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
virtual bool window_get_flag(DisplayServerEnums::WindowFlags p_flag, DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual void window_request_attention(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
virtual void window_move_to_foreground(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
virtual bool window_is_focused(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual float screen_get_max_scale() const override;
virtual void screen_set_orientation(DisplayServerEnums::ScreenOrientation p_orientation, int p_screen) override;
virtual DisplayServerEnums::ScreenOrientation screen_get_orientation(int p_screen) const override;
virtual bool window_can_draw(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual bool can_any_window_draw() const override;
virtual void window_set_vsync_mode(DisplayServerEnums::VSyncMode p_vsync_mode, DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
virtual DisplayServerEnums::VSyncMode window_get_vsync_mode(DisplayServerEnums::WindowID p_vsync_mode) const override;
virtual bool is_touchscreen_available() const override;
virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, DisplayServerEnums::VirtualKeyboardType p_type, int p_max_length, int p_cursor_start, int p_cursor_end) override;
virtual void virtual_keyboard_hide() override;
void virtual_keyboard_set_height(int height);
virtual int virtual_keyboard_get_height() const override;
virtual bool has_hardware_keyboard() const override;
virtual void clipboard_set(const String &p_text) override;
virtual String clipboard_get() const override;
virtual void screen_set_keep_on(bool p_enable) override;
virtual bool screen_is_kept_on() const override;
// MARK: - HDR / EDR
void current_edr_headroom_changed();
virtual bool window_is_hdr_output_supported(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual void window_request_hdr_output(const bool p_enabled, DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
virtual bool window_is_hdr_output_requested(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual bool window_is_hdr_output_enabled(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual void window_set_hdr_output_reference_luminance(const float p_reference_luminance, DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
virtual float window_get_hdr_output_reference_luminance(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual float window_get_hdr_output_current_reference_luminance(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual void window_set_hdr_output_max_luminance(const float p_max_luminance, DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
virtual float window_get_hdr_output_max_luminance(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual float window_get_hdr_output_current_max_luminance(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual float window_get_output_max_linear_value(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) const override;
void resize_window(CGSize size);
virtual void swap_buffers() override {}
virtual void set_native_icon(const String &p_filename) override;
virtual void set_icon(const Ref<Image> &p_icon) override;
};

View file

@ -0,0 +1,931 @@
/**************************************************************************/
/* display_server_apple_embedded.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#import "display_server_apple_embedded.h"
#import "app_delegate_service.h"
#import "apple_embedded.h"
#import "godot_keyboard_input_view.h"
#import "godot_view_apple_embedded.h"
#import "godot_view_controller.h"
#import "key_mapping_apple_embedded.h"
#import "os_apple_embedded.h"
#import "tts_apple_embedded.h"
#include "core/config/project_settings.h"
#include "core/input/input.h"
#include "core/io/file_access_pack.h"
#include "core/os/os.h"
#include "servers/display/native_menu.h"
#import <GameController/GameController.h>
static const float kDisplayServerIOSAcceleration = 1.f;
DisplayServerAppleEmbedded *DisplayServerAppleEmbedded::get_singleton() {
return (DisplayServerAppleEmbedded *)DisplayServer::get_singleton();
}
DisplayServerAppleEmbedded::DisplayServerAppleEmbedded(const String &p_rendering_driver, DisplayServerEnums::WindowMode p_mode, DisplayServerEnums::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, DisplayServerEnums::Context p_context, int64_t p_parent_window, Error &r_error) {
KeyMappingAppleEmbedded::initialize();
rendering_driver = p_rendering_driver;
// Init TTS
bool tts_enabled = GLOBAL_GET("audio/general/text_to_speech");
if (tts_enabled) {
initialize_tts();
}
native_menu = memnew(NativeMenu);
bool has_made_render_compositor_current = false;
#if defined(RD_ENABLED)
rendering_context = nullptr;
rendering_device = nullptr;
CALayer *layer = nullptr;
union {
#ifdef VULKAN_ENABLED
RenderingContextDriverVulkanAppleEmbedded::WindowPlatformData vulkan;
#endif
#ifdef METAL_ENABLED
GODOT_CLANG_WARNING_PUSH_AND_IGNORE("-Wunguarded-availability")
// Eliminate "RenderingContextDriverMetal is only available on iOS 14.0 or newer".
RenderingContextDriverMetal::WindowPlatformData metal;
GODOT_CLANG_WARNING_POP
#endif
} wpd;
#if defined(VULKAN_ENABLED)
if (rendering_driver == "vulkan") {
layer = [GDTAppDelegateService.viewController.godotView initializeRenderingForDriver:@"vulkan"];
if (!layer) {
ERR_FAIL_MSG("Failed to create iOS Vulkan rendering layer.");
}
wpd.vulkan.layer_ptr = (CAMetalLayer *const *)&layer;
rendering_context = memnew(RenderingContextDriverVulkanAppleEmbedded);
}
#endif
#ifdef METAL_ENABLED
if (rendering_driver == "metal") {
if (@available(iOS 14.0, *)) {
layer = [GDTAppDelegateService.viewController.godotView initializeRenderingForDriver:@"metal"];
wpd.metal.layer = (__bridge CA::MetalLayer *)layer;
rendering_context = memnew(RenderingContextDriverMetal);
} else {
OS::get_singleton()->alert("Metal is only supported on iOS 14.0 and later.");
r_error = ERR_UNAVAILABLE;
return;
}
}
#endif
if (rendering_context) {
if (rendering_context->initialize() != OK) {
memdelete(rendering_context);
rendering_context = nullptr;
#if defined(GLES3_ENABLED)
bool fallback_to_opengl3 = GLOBAL_GET("rendering/rendering_device/fallback_to_opengl3");
if (fallback_to_opengl3 && rendering_driver != "opengl3") {
WARN_PRINT("Your device does not seem to support MoltenVK or Metal, switching to OpenGL 3.");
rendering_driver = "opengl3";
OS::get_singleton()->set_current_rendering_method("gl_compatibility", OS::RENDERING_SOURCE_FALLBACK);
OS::get_singleton()->set_current_rendering_driver_name(rendering_driver, OS::RENDERING_SOURCE_FALLBACK);
} else
#endif
{
ERR_PRINT(vformat("Failed to initialize %s context", rendering_driver));
r_error = ERR_UNAVAILABLE;
return;
}
}
}
if (rendering_context) {
if (rendering_context->window_create(DisplayServerEnums::MAIN_WINDOW_ID, &wpd) != OK) {
ERR_PRINT(vformat("Failed to create %s window.", rendering_driver));
memdelete(rendering_context);
rendering_context = nullptr;
r_error = ERR_UNAVAILABLE;
return;
}
Size2i size = Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_max_scale();
rendering_context->window_set_size(DisplayServerEnums::MAIN_WINDOW_ID, size.width, size.height);
rendering_context->window_set_vsync_mode(DisplayServerEnums::MAIN_WINDOW_ID, p_vsync_mode);
rendering_device = memnew(RenderingDevice);
if (rendering_device->initialize(rendering_context, DisplayServerEnums::MAIN_WINDOW_ID) != OK) {
rendering_device = nullptr;
memdelete(rendering_context);
rendering_context = nullptr;
r_error = ERR_UNAVAILABLE;
return;
}
rendering_device->screen_create(DisplayServerEnums::MAIN_WINDOW_ID);
RendererCompositorRD::make_current();
has_made_render_compositor_current = true;
}
#endif
#if defined(GLES3_ENABLED)
if (rendering_driver == "opengl3") {
CALayer *layer = [GDTAppDelegateService.viewController.godotView initializeRenderingForDriver:@"opengl3"];
if (!layer) {
ERR_FAIL_MSG("Failed to create iOS OpenGLES rendering layer.");
}
RasterizerGLES3::make_current(false);
has_made_render_compositor_current = true;
}
#endif
ERR_FAIL_COND_MSG(!has_made_render_compositor_current, vformat("Failed to make RendererCompositor current for rendering driver %s", rendering_driver));
bool keep_screen_on = bool(GLOBAL_GET("display/window/energy_saving/keep_screen_on"));
screen_set_keep_on(keep_screen_on);
Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
r_error = OK;
}
DisplayServerAppleEmbedded::~DisplayServerAppleEmbedded() {
if (native_menu) {
memdelete(native_menu);
native_menu = nullptr;
}
#if defined(RD_ENABLED)
if (rendering_device) {
rendering_device->screen_free(DisplayServerEnums::MAIN_WINDOW_ID);
memdelete(rendering_device);
rendering_device = nullptr;
}
if (rendering_context) {
rendering_context->window_destroy(DisplayServerEnums::MAIN_WINDOW_ID);
memdelete(rendering_context);
rendering_context = nullptr;
}
#endif
}
Vector<String> DisplayServerAppleEmbedded::get_rendering_drivers_func() {
Vector<String> drivers;
#if defined(VULKAN_ENABLED)
drivers.push_back("vulkan");
#endif
#if defined(METAL_ENABLED)
if (@available(ios 14.0, *)) {
drivers.push_back("metal");
}
#endif
#if defined(GLES3_ENABLED)
drivers.push_back("opengl3");
#endif
return drivers;
}
// MARK: Events
void DisplayServerAppleEmbedded::window_set_rect_changed_callback(const Callable &p_callable, DisplayServerEnums::WindowID p_window) {
window_resize_callback = p_callable;
}
void DisplayServerAppleEmbedded::window_set_window_event_callback(const Callable &p_callable, DisplayServerEnums::WindowID p_window) {
window_event_callback = p_callable;
}
void DisplayServerAppleEmbedded::window_set_input_event_callback(const Callable &p_callable, DisplayServerEnums::WindowID p_window) {
input_event_callback = p_callable;
}
void DisplayServerAppleEmbedded::window_set_input_text_callback(const Callable &p_callable, DisplayServerEnums::WindowID p_window) {
input_text_callback = p_callable;
}
void DisplayServerAppleEmbedded::window_set_drop_files_callback(const Callable &p_callable, DisplayServerEnums::WindowID p_window) {
// Probably not supported for iOS
}
void DisplayServerAppleEmbedded::process_events() {
Input::get_singleton()->flush_buffered_events();
}
void DisplayServerAppleEmbedded::_dispatch_input_events(const Ref<InputEvent> &p_event) {
DisplayServerAppleEmbedded::get_singleton()->send_input_event(p_event);
}
void DisplayServerAppleEmbedded::send_input_event(const Ref<InputEvent> &p_event) const {
_window_callback(input_event_callback, p_event);
}
void DisplayServerAppleEmbedded::send_input_text(const String &p_text) const {
_window_callback(input_text_callback, p_text);
}
void DisplayServerAppleEmbedded::send_window_event(DisplayServerEnums::WindowEvent p_event) const {
_window_callback(window_event_callback, int(p_event));
}
void DisplayServerAppleEmbedded::_window_callback(const Callable &p_callable, const Variant &p_arg) const {
if (p_callable.is_valid()) {
p_callable.call(p_arg);
}
}
// MARK: - Input
// MARK: Touches
void DisplayServerAppleEmbedded::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_double_click) {
Ref<InputEventScreenTouch> ev;
ev.instantiate();
ev->set_index(p_idx);
ev->set_pressed(p_pressed);
ev->set_position(Vector2(p_x, p_y));
ev->set_double_tap(p_double_click);
perform_event(ev);
}
void DisplayServerAppleEmbedded::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y, float p_pressure, Vector2 p_tilt) {
Ref<InputEventScreenDrag> ev;
ev.instantiate();
ev->set_index(p_idx);
ev->set_pressure(p_pressure);
ev->set_tilt(p_tilt);
ev->set_position(Vector2(p_x, p_y));
ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y));
ev->set_relative_screen_position(ev->get_relative());
perform_event(ev);
}
void DisplayServerAppleEmbedded::perform_event(const Ref<InputEvent> &p_event) {
Input *input_singleton = Input::get_singleton();
if (input_singleton == nullptr) {
return;
}
input_singleton->parse_input_event(p_event);
}
void DisplayServerAppleEmbedded::touches_canceled(int p_idx) {
touch_press(p_idx, -1, -1, false, false);
}
// MARK: Keyboard
void DisplayServerAppleEmbedded::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location) {
Ref<InputEventKey> ev;
ev.instantiate();
ev->set_echo(false);
ev->set_pressed(p_pressed);
ev->set_keycode(fix_keycode(p_char, p_key));
if (@available(iOS 13.4, *)) {
if (p_key != Key::SHIFT) {
ev->set_shift_pressed(p_modifier & UIKeyModifierShift);
}
if (p_key != Key::CTRL) {
ev->set_ctrl_pressed(p_modifier & UIKeyModifierControl);
}
if (p_key != Key::ALT) {
ev->set_alt_pressed(p_modifier & UIKeyModifierAlternate);
}
if (p_key != Key::META) {
ev->set_meta_pressed(p_modifier & UIKeyModifierCommand);
}
}
ev->set_key_label(p_unshifted);
ev->set_physical_keycode(p_physical);
ev->set_unicode(fix_unicode(p_char));
ev->set_location(p_location);
perform_event(ev);
}
// MARK: Motion
void DisplayServerAppleEmbedded::update_gravity(const Vector3 &p_gravity) {
Input::get_singleton()->set_gravity(p_gravity);
}
void DisplayServerAppleEmbedded::update_accelerometer(const Vector3 &p_accelerometer) {
Input::get_singleton()->set_accelerometer(p_accelerometer / kDisplayServerIOSAcceleration);
}
void DisplayServerAppleEmbedded::update_magnetometer(const Vector3 &p_magnetometer) {
Input::get_singleton()->set_magnetometer(p_magnetometer);
}
void DisplayServerAppleEmbedded::update_gyroscope(const Vector3 &p_gyroscope) {
Input::get_singleton()->set_gyroscope(p_gyroscope);
}
// MARK: -
bool DisplayServerAppleEmbedded::has_feature(DisplayServerEnums::Feature p_feature) const {
switch (p_feature) {
#ifndef DISABLE_DEPRECATED
case DisplayServerEnums::FEATURE_GLOBAL_MENU: {
return (native_menu && native_menu->has_feature(NativeMenu::FEATURE_GLOBAL_MENU));
} break;
#endif
// case DisplayServerEnums::FEATURE_CURSOR_SHAPE:
// case DisplayServerEnums::FEATURE_CUSTOM_CURSOR_SHAPE:
// case DisplayServerEnums::FEATURE_HIDPI:
// case DisplayServerEnums::FEATURE_ICON:
// case DisplayServerEnums::FEATURE_IME:
// case DisplayServerEnums::FEATURE_MOUSE:
// case DisplayServerEnums::FEATURE_MOUSE_WARP:
// case DisplayServerEnums::FEATURE_NATIVE_DIALOG:
// case DisplayServerEnums::FEATURE_NATIVE_DIALOG_INPUT:
// case DisplayServerEnums::FEATURE_NATIVE_DIALOG_FILE:
// case DisplayServerEnums::FEATURE_NATIVE_DIALOG_FILE_EXTRA:
// case DisplayServerEnums::FEATURE_NATIVE_DIALOG_FILE_MIME:
// case DisplayServerEnums::FEATURE_NATIVE_ICON:
// case DisplayServerEnums::FEATURE_WINDOW_TRANSPARENCY:
case DisplayServerEnums::FEATURE_CLIPBOARD:
case DisplayServerEnums::FEATURE_HDR_OUTPUT:
case DisplayServerEnums::FEATURE_KEEP_SCREEN_ON:
case DisplayServerEnums::FEATURE_ORIENTATION:
case DisplayServerEnums::FEATURE_TOUCHSCREEN:
case DisplayServerEnums::FEATURE_VIRTUAL_KEYBOARD:
case DisplayServerEnums::FEATURE_TEXT_TO_SPEECH:
return true;
default:
return false;
}
}
void DisplayServerAppleEmbedded::initialize_tts() const {
const_cast<DisplayServerAppleEmbedded *>(this)->tts = [[GDTTTS alloc] init];
}
bool DisplayServerAppleEmbedded::tts_is_speaking() const {
if (unlikely(!tts)) {
initialize_tts();
}
ERR_FAIL_NULL_V(tts, false);
return [tts isSpeaking];
}
bool DisplayServerAppleEmbedded::tts_is_paused() const {
if (unlikely(!tts)) {
initialize_tts();
}
ERR_FAIL_NULL_V(tts, false);
return [tts isPaused];
}
TypedArray<Dictionary> DisplayServerAppleEmbedded::tts_get_voices() const {
if (unlikely(!tts)) {
initialize_tts();
}
ERR_FAIL_NULL_V(tts, TypedArray<Dictionary>());
return [tts getVoices];
}
void DisplayServerAppleEmbedded::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int64_t p_utterance_id, bool p_interrupt) {
if (unlikely(!tts)) {
initialize_tts();
}
ERR_FAIL_NULL(tts);
[tts speak:p_text voice:p_voice volume:p_volume pitch:p_pitch rate:p_rate utterance_id:p_utterance_id interrupt:p_interrupt];
}
void DisplayServerAppleEmbedded::tts_pause() {
if (unlikely(!tts)) {
initialize_tts();
}
ERR_FAIL_NULL(tts);
[tts pauseSpeaking];
}
void DisplayServerAppleEmbedded::tts_resume() {
if (unlikely(!tts)) {
initialize_tts();
}
ERR_FAIL_NULL(tts);
[tts resumeSpeaking];
}
void DisplayServerAppleEmbedded::tts_stop() {
if (unlikely(!tts)) {
initialize_tts();
}
ERR_FAIL_NULL(tts);
[tts stopSpeaking];
}
bool DisplayServerAppleEmbedded::is_dark_mode_supported() const {
if (@available(iOS 13.0, *)) {
return true;
} else {
return false;
}
}
bool DisplayServerAppleEmbedded::is_dark_mode() const {
if (@available(iOS 13.0, *)) {
return [UITraitCollection currentTraitCollection].userInterfaceStyle == UIUserInterfaceStyleDark;
} else {
return false;
}
}
void DisplayServerAppleEmbedded::set_system_theme_change_callback(const Callable &p_callable) {
system_theme_changed = p_callable;
}
void DisplayServerAppleEmbedded::emit_system_theme_changed() {
if (system_theme_changed.is_valid()) {
Variant ret;
Callable::CallError ce;
system_theme_changed.callp(nullptr, 0, ret, ce);
if (ce.error != Callable::CallError::CALL_OK) {
ERR_PRINT(vformat("Failed to execute system theme changed callback: %s.", Variant::get_callable_error_text(system_theme_changed, nullptr, 0, ce)));
}
}
}
Rect2i DisplayServerAppleEmbedded::get_display_safe_area() const {
UIEdgeInsets insets = UIEdgeInsetsZero;
UIView *view = GDTAppDelegateService.viewController.godotView;
if ([view respondsToSelector:@selector(safeAreaInsets)]) {
insets = [view safeAreaInsets];
}
float scale = screen_get_scale();
Size2i insets_position = Size2i(insets.left, insets.top) * scale;
Size2i insets_size = Size2i(insets.left + insets.right, insets.top + insets.bottom) * scale;
return Rect2i(screen_get_position() + insets_position, screen_get_size() - insets_size);
}
int DisplayServerAppleEmbedded::get_screen_count() const {
return 1;
}
int DisplayServerAppleEmbedded::get_primary_screen() const {
return 0;
}
Point2i DisplayServerAppleEmbedded::screen_get_position(int p_screen) const {
p_screen = _get_screen_index(p_screen);
int screen_count = get_screen_count();
ERR_FAIL_INDEX_V(p_screen, screen_count, Point2i());
return Point2i(0, 0);
}
Size2i DisplayServerAppleEmbedded::screen_get_size(int p_screen) const {
p_screen = _get_screen_index(p_screen);
int screen_count = get_screen_count();
ERR_FAIL_INDEX_V(p_screen, screen_count, Size2i());
CALayer *layer = GDTAppDelegateService.viewController.godotView.renderingLayer;
if (!layer) {
return Size2i();
}
return Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_scale(p_screen);
}
Rect2i DisplayServerAppleEmbedded::screen_get_usable_rect(int p_screen) const {
p_screen = _get_screen_index(p_screen);
int screen_count = get_screen_count();
ERR_FAIL_INDEX_V(p_screen, screen_count, Rect2i());
return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen));
}
Vector<DisplayServerEnums::WindowID> DisplayServerAppleEmbedded::get_window_list() const {
Vector<DisplayServerEnums::WindowID> list;
list.push_back(DisplayServerEnums::MAIN_WINDOW_ID);
return list;
}
DisplayServerEnums::WindowID DisplayServerAppleEmbedded::get_window_at_screen_position(const Point2i &p_position) const {
return DisplayServerEnums::MAIN_WINDOW_ID;
}
int64_t DisplayServerAppleEmbedded::window_get_native_handle(DisplayServerEnums::HandleType p_handle_type, DisplayServerEnums::WindowID p_window) const {
ERR_FAIL_COND_V(p_window != DisplayServerEnums::MAIN_WINDOW_ID, 0);
switch (p_handle_type) {
case DisplayServerEnums::DISPLAY_HANDLE: {
return 0; // Not supported.
}
case DisplayServerEnums::WINDOW_HANDLE: {
return (int64_t)GDTAppDelegateService.viewController;
}
case DisplayServerEnums::WINDOW_VIEW: {
return (int64_t)GDTAppDelegateService.viewController.godotView;
}
default: {
return 0;
}
}
}
void DisplayServerAppleEmbedded::window_attach_instance_id(ObjectID p_instance, DisplayServerEnums::WindowID p_window) {
window_attached_instance_id = p_instance;
}
ObjectID DisplayServerAppleEmbedded::window_get_attached_instance_id(DisplayServerEnums::WindowID p_window) const {
return window_attached_instance_id;
}
void DisplayServerAppleEmbedded::window_set_title(const String &p_title, DisplayServerEnums::WindowID p_window) {
// Probably not supported for iOS
}
int DisplayServerAppleEmbedded::window_get_current_screen(DisplayServerEnums::WindowID p_window) const {
ERR_FAIL_COND_V(p_window != DisplayServerEnums::MAIN_WINDOW_ID, DisplayServerEnums::INVALID_SCREEN);
return 0;
}
void DisplayServerAppleEmbedded::window_set_current_screen(int p_screen, DisplayServerEnums::WindowID p_window) {
// Probably not supported for iOS
}
Point2i DisplayServerAppleEmbedded::window_get_position(DisplayServerEnums::WindowID p_window) const {
return Point2i();
}
Point2i DisplayServerAppleEmbedded::window_get_position_with_decorations(DisplayServerEnums::WindowID p_window) const {
return Point2i();
}
void DisplayServerAppleEmbedded::window_set_position(const Point2i &p_position, DisplayServerEnums::WindowID p_window) {
// Probably not supported for single window iOS app
}
void DisplayServerAppleEmbedded::window_set_transient(DisplayServerEnums::WindowID p_window, DisplayServerEnums::WindowID p_parent) {
// Probably not supported for iOS
}
void DisplayServerAppleEmbedded::window_set_max_size(const Size2i p_size, DisplayServerEnums::WindowID p_window) {
// Probably not supported for iOS
}
Size2i DisplayServerAppleEmbedded::window_get_max_size(DisplayServerEnums::WindowID p_window) const {
return Size2i();
}
void DisplayServerAppleEmbedded::window_set_min_size(const Size2i p_size, DisplayServerEnums::WindowID p_window) {
// Probably not supported for iOS
}
Size2i DisplayServerAppleEmbedded::window_get_min_size(DisplayServerEnums::WindowID p_window) const {
return Size2i();
}
void DisplayServerAppleEmbedded::window_set_size(const Size2i p_size, DisplayServerEnums::WindowID p_window) {
// Probably not supported for iOS
}
Size2i DisplayServerAppleEmbedded::window_get_size(DisplayServerEnums::WindowID p_window) const {
CGRect viewBounds = GDTAppDelegateService.viewController.view.bounds;
return Size2i(viewBounds.size.width, viewBounds.size.height) * screen_get_max_scale();
}
Size2i DisplayServerAppleEmbedded::window_get_size_with_decorations(DisplayServerEnums::WindowID p_window) const {
return window_get_size(p_window);
}
void DisplayServerAppleEmbedded::window_set_mode(DisplayServerEnums::WindowMode p_mode, DisplayServerEnums::WindowID p_window) {
// Probably not supported for iOS
}
DisplayServerEnums::WindowMode DisplayServerAppleEmbedded::window_get_mode(DisplayServerEnums::WindowID p_window) const {
return DisplayServerEnums::WindowMode::WINDOW_MODE_FULLSCREEN;
}
bool DisplayServerAppleEmbedded::window_is_maximize_allowed(DisplayServerEnums::WindowID p_window) const {
return false;
}
void DisplayServerAppleEmbedded::window_set_flag(DisplayServerEnums::WindowFlags p_flag, bool p_enabled, DisplayServerEnums::WindowID p_window) {
// Probably not supported for iOS
}
bool DisplayServerAppleEmbedded::window_get_flag(DisplayServerEnums::WindowFlags p_flag, DisplayServerEnums::WindowID p_window) const {
return false;
}
void DisplayServerAppleEmbedded::window_request_attention(DisplayServerEnums::WindowID p_window) {
// Probably not supported for iOS
}
void DisplayServerAppleEmbedded::window_move_to_foreground(DisplayServerEnums::WindowID p_window) {
// Probably not supported for iOS
}
bool DisplayServerAppleEmbedded::window_is_focused(DisplayServerEnums::WindowID p_window) const {
return true;
}
float DisplayServerAppleEmbedded::screen_get_max_scale() const {
return screen_get_scale(DisplayServerEnums::SCREEN_OF_MAIN_WINDOW);
}
void DisplayServerAppleEmbedded::screen_set_orientation(DisplayServerEnums::ScreenOrientation p_orientation, int p_screen) {
p_screen = _get_screen_index(p_screen);
int screen_count = get_screen_count();
ERR_FAIL_INDEX(p_screen, screen_count);
screen_orientation = p_orientation;
if (@available(iOS 16.0, *)) {
[GDTAppDelegateService.viewController setNeedsUpdateOfSupportedInterfaceOrientations];
}
#if !defined(VISIONOS_ENABLED)
else {
[UIViewController attemptRotationToDeviceOrientation];
}
#endif
}
DisplayServerEnums::ScreenOrientation DisplayServerAppleEmbedded::screen_get_orientation(int p_screen) const {
p_screen = _get_screen_index(p_screen);
int screen_count = get_screen_count();
ERR_FAIL_INDEX_V(p_screen, screen_count, DisplayServerEnums::SCREEN_LANDSCAPE);
return screen_orientation;
}
bool DisplayServerAppleEmbedded::window_can_draw(DisplayServerEnums::WindowID p_window) const {
return true;
}
bool DisplayServerAppleEmbedded::can_any_window_draw() const {
return true;
}
bool DisplayServerAppleEmbedded::is_touchscreen_available() const {
return true;
}
_FORCE_INLINE_ int _convert_utf32_offset_to_utf16(const String &p_existing_text, int p_pos) {
int limit = p_pos;
for (int i = 0; i < MIN(p_existing_text.length(), p_pos); i++) {
if (p_existing_text[i] > 0xffff) {
limit++;
}
}
return limit;
}
void DisplayServerAppleEmbedded::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, DisplayServerEnums::VirtualKeyboardType p_type, int p_max_length, int p_cursor_start, int p_cursor_end) {
NSString *existingString = [[NSString alloc] initWithUTF8String:p_existing_text.utf8().get_data()];
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault;
GDTAppDelegateService.viewController.keyboardView.textContentType = nil;
switch (p_type) {
case DisplayServerEnums::KEYBOARD_TYPE_DEFAULT: {
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault;
} break;
case DisplayServerEnums::KEYBOARD_TYPE_MULTILINE: {
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault;
} break;
case DisplayServerEnums::KEYBOARD_TYPE_NUMBER: {
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeNumberPad;
} break;
case DisplayServerEnums::KEYBOARD_TYPE_NUMBER_DECIMAL: {
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDecimalPad;
} break;
case DisplayServerEnums::KEYBOARD_TYPE_PHONE: {
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypePhonePad;
GDTAppDelegateService.viewController.keyboardView.textContentType = UITextContentTypeTelephoneNumber;
} break;
case DisplayServerEnums::KEYBOARD_TYPE_EMAIL_ADDRESS: {
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeEmailAddress;
GDTAppDelegateService.viewController.keyboardView.textContentType = UITextContentTypeEmailAddress;
} break;
case DisplayServerEnums::KEYBOARD_TYPE_PASSWORD: {
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault;
GDTAppDelegateService.viewController.keyboardView.textContentType = UITextContentTypePassword;
} break;
case DisplayServerEnums::KEYBOARD_TYPE_URL: {
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeWebSearch;
GDTAppDelegateService.viewController.keyboardView.textContentType = UITextContentTypeURL;
} break;
}
[GDTAppDelegateService.viewController.keyboardView
becomeFirstResponderWithString:existingString
cursorStart:_convert_utf32_offset_to_utf16(p_existing_text, p_cursor_start)
cursorEnd:_convert_utf32_offset_to_utf16(p_existing_text, p_cursor_end)];
}
bool DisplayServerAppleEmbedded::is_keyboard_active() const {
return [GDTAppDelegateService.viewController.keyboardView isFirstResponder];
}
void DisplayServerAppleEmbedded::virtual_keyboard_hide() {
[GDTAppDelegateService.viewController.keyboardView resignFirstResponder];
}
void DisplayServerAppleEmbedded::virtual_keyboard_set_height(int height) {
virtual_keyboard_height = height * screen_get_max_scale();
}
int DisplayServerAppleEmbedded::virtual_keyboard_get_height() const {
return virtual_keyboard_height;
}
bool DisplayServerAppleEmbedded::has_hardware_keyboard() const {
if (@available(iOS 14.0, *)) {
return [GCKeyboard coalescedKeyboard];
} else {
return false;
}
}
void DisplayServerAppleEmbedded::clipboard_set(const String &p_text) {
[UIPasteboard generalPasteboard].string = [NSString stringWithUTF8String:p_text.utf8().get_data()];
}
String DisplayServerAppleEmbedded::clipboard_get() const {
NSString *text = [UIPasteboard generalPasteboard].string;
return String::utf8([text UTF8String]);
}
void DisplayServerAppleEmbedded::screen_set_keep_on(bool p_enable) {
[UIApplication sharedApplication].idleTimerDisabled = p_enable;
}
bool DisplayServerAppleEmbedded::screen_is_kept_on() const {
return [UIApplication sharedApplication].idleTimerDisabled;
}
void DisplayServerAppleEmbedded::resize_window(CGSize viewSize) {
Size2i size = Size2i(viewSize.width, viewSize.height) * screen_get_max_scale();
#if defined(RD_ENABLED)
if (rendering_context) {
rendering_context->window_set_size(DisplayServerEnums::MAIN_WINDOW_ID, size.x, size.y);
}
#endif
Variant resize_rect = Rect2i(Point2i(), size);
_window_callback(window_resize_callback, resize_rect);
}
void DisplayServerAppleEmbedded::window_set_vsync_mode(DisplayServerEnums::VSyncMode p_vsync_mode, DisplayServerEnums::WindowID p_window) {
_THREAD_SAFE_METHOD_
#if defined(RD_ENABLED)
if (rendering_context) {
rendering_context->window_set_vsync_mode(p_window, p_vsync_mode);
}
#endif
}
DisplayServerEnums::VSyncMode DisplayServerAppleEmbedded::window_get_vsync_mode(DisplayServerEnums::WindowID p_window) const {
_THREAD_SAFE_METHOD_
#if defined(RD_ENABLED)
if (rendering_context) {
return rendering_context->window_get_vsync_mode(p_window);
}
#endif
return DisplayServerEnums::VSYNC_ENABLED;
}
// MARK: - HDR / EDR
void DisplayServerAppleEmbedded::_update_hdr_output() {
#ifdef RD_ENABLED
if (!rendering_context) {
return;
}
bool desired = edr_requested && _screen_hdr_is_supported();
if (rendering_context->window_get_hdr_output_enabled(DisplayServerEnums::MAIN_WINDOW_ID) != desired) {
rendering_context->window_set_hdr_output_enabled(DisplayServerEnums::MAIN_WINDOW_ID, desired);
}
float reference_luminance = _calculate_current_reference_luminance();
rendering_context->window_set_hdr_output_reference_luminance(DisplayServerEnums::MAIN_WINDOW_ID, reference_luminance);
float max_luminance = _screen_potential_edr_headroom() * hardware_reference_luminance_nits;
rendering_context->window_set_hdr_output_max_luminance(DisplayServerEnums::MAIN_WINDOW_ID, max_luminance);
#endif
}
void DisplayServerAppleEmbedded::current_edr_headroom_changed() {
_update_hdr_output();
}
bool DisplayServerAppleEmbedded::window_is_hdr_output_supported(DisplayServerEnums::WindowID p_window) const {
#if defined(RD_ENABLED)
if (rendering_device && !rendering_device->has_feature(RenderingDevice::Features::SUPPORTS_HDR_OUTPUT)) {
return false;
}
#endif
return _screen_hdr_is_supported();
}
void DisplayServerAppleEmbedded::window_request_hdr_output(const bool p_enabled, DisplayServerEnums::WindowID p_window) {
#if defined(RD_ENABLED)
ERR_FAIL_COND_MSG(p_enabled && rendering_device && !rendering_device->has_feature(RenderingDevice::Features::SUPPORTS_HDR_OUTPUT), "HDR output is not supported by the rendering device.");
#endif
edr_requested = p_enabled;
_update_hdr_output();
}
bool DisplayServerAppleEmbedded::window_is_hdr_output_requested(DisplayServerEnums::WindowID p_window) const {
return edr_requested;
}
bool DisplayServerAppleEmbedded::window_is_hdr_output_enabled(DisplayServerEnums::WindowID p_window) const {
#if defined(RD_ENABLED)
if (rendering_context) {
return rendering_context->window_get_hdr_output_enabled(p_window);
}
#endif
return false;
}
void DisplayServerAppleEmbedded::window_set_hdr_output_reference_luminance(const float p_reference_luminance, DisplayServerEnums::WindowID p_window) {
ERR_PRINT_ONCE("Manually setting reference white luminance is not supported on Apple devices, as they provide a user-facing brightness setting that directly controls reference white luminance.");
}
float DisplayServerAppleEmbedded::window_get_hdr_output_reference_luminance(DisplayServerEnums::WindowID p_window) const {
return -1.0f; // Always auto-adjusted by the OS on Apple platforms.
}
float DisplayServerAppleEmbedded::_calculate_current_reference_luminance() const {
float potential = _screen_potential_edr_headroom();
float current = _screen_current_edr_headroom();
return potential * hardware_reference_luminance_nits / current;
}
float DisplayServerAppleEmbedded::window_get_hdr_output_current_reference_luminance(DisplayServerEnums::WindowID p_window) const {
#if defined(RD_ENABLED)
if (rendering_context) {
return rendering_context->window_get_hdr_output_reference_luminance(p_window);
}
#endif
return 200.0f;
}
void DisplayServerAppleEmbedded::window_set_hdr_output_max_luminance(const float p_max_luminance, DisplayServerEnums::WindowID p_window) {
ERR_PRINT_ONCE("Manually setting max luminance is not supported on Apple embedded devices as they provide accurate max luminance values for their built-in screens.");
}
float DisplayServerAppleEmbedded::window_get_hdr_output_max_luminance(DisplayServerEnums::WindowID p_window) const {
return -1.0f;
}
float DisplayServerAppleEmbedded::window_get_hdr_output_current_max_luminance(DisplayServerEnums::WindowID p_window) const {
return _screen_potential_edr_headroom() * hardware_reference_luminance_nits;
}
float DisplayServerAppleEmbedded::window_get_output_max_linear_value(DisplayServerEnums::WindowID p_window) const {
#if defined(RD_ENABLED)
if (rendering_context) {
return rendering_context->window_get_output_max_linear_value(p_window);
}
#endif
return 1.0f;
}
void DisplayServerAppleEmbedded::set_native_icon(const String &p_filename) {
// Not supported on Apple embedded platforms.
}
void DisplayServerAppleEmbedded::set_icon(const Ref<Image> &p_icon) {
// Not supported on Apple embedded platforms.
}

View file

@ -0,0 +1,43 @@
/**************************************************************************/
/* godot_app_delegate.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#import <UIKit/UIKit.h>
typedef NSObject<UIApplicationDelegate, UIWindowSceneDelegate> GDTAppDelegateServiceProtocol;
@interface GDTApplicationDelegate : NSObject <UIApplicationDelegate, UIWindowSceneDelegate>
@property(class, readonly, strong) NSArray<GDTAppDelegateServiceProtocol *> *services;
+ (void)addService:(GDTAppDelegateServiceProtocol *)service;
@end

View file

@ -0,0 +1,520 @@
/**************************************************************************/
/* godot_app_delegate.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#import "godot_app_delegate.h"
#import "app_delegate_service.h"
#include "core/typedefs.h"
@implementation GDTApplicationDelegate
static NSMutableArray<GDTAppDelegateServiceProtocol *> *services = nil;
+ (NSArray<GDTAppDelegateServiceProtocol *> *)services {
return services;
}
+ (void)load {
services = [NSMutableArray new];
[services addObject:[GDTAppDelegateService new]];
}
+ (void)addService:(GDTAppDelegateServiceProtocol *)service {
if (!services || !service) {
return;
}
[services addObject:service];
}
// UIApplicationDelegate documentation can be found here: https://developer.apple.com/documentation/uikit/uiapplicationdelegate
// MARK: Window
- (UIWindow *)window {
UIWindow *result = nil;
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
UIWindow *value = [service window];
if (value) {
result = value;
}
}
return result;
}
// MARK: Initializing
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
BOOL result = NO;
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
if ([service application:application willFinishLaunchingWithOptions:launchOptions]) {
result = YES;
}
}
return result;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
BOOL result = NO;
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
if ([service application:application didFinishLaunchingWithOptions:launchOptions]) {
result = YES;
}
}
return result;
}
// MARK: Scene
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) {
UISceneConfiguration *config = [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
config.delegateClass = [GDTApplicationDelegate class];
return config;
}
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) {
}
// MARK: Life-Cycle
- (void)sceneDidDisconnect:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service sceneDidDisconnect:scene];
}
}
- (void)sceneDidBecomeActive:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service sceneDidBecomeActive:scene];
}
}
- (void)sceneWillResignActive:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service sceneWillResignActive:scene];
}
}
- (void)sceneDidEnterBackground:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service sceneDidEnterBackground:scene];
}
}
- (void)sceneWillEnterForeground:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service sceneWillEnterForeground:scene];
}
}
// UIApplication lifecycle has become deprecated in favor of UIScene lifecycle
GODOT_CLANG_WARNING_PUSH_AND_IGNORE("-Wdeprecated-declarations")
- (void)applicationDidBecomeActive:(UIApplication *)application {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationDidBecomeActive:application];
}
}
- (void)applicationWillResignActive:(UIApplication *)application {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationWillResignActive:application];
}
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationDidEnterBackground:application];
}
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationWillEnterForeground:application];
}
}
- (void)applicationWillTerminate:(UIApplication *)application {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationWillTerminate:application];
}
}
// MARK: Environment Changes
- (void)applicationProtectedDataDidBecomeAvailable:(UIApplication *)application {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationProtectedDataDidBecomeAvailable:application];
}
}
- (void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationProtectedDataWillBecomeUnavailable:application];
}
}
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationDidReceiveMemoryWarning:application];
}
}
- (void)applicationSignificantTimeChange:(UIApplication *)application {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationSignificantTimeChange:application];
}
}
// MARK: App State Restoration
- (BOOL)application:(UIApplication *)application shouldSaveSecureApplicationState:(NSCoder *)coder API_AVAILABLE(ios(13.2)) {
BOOL result = NO;
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
if ([service application:application shouldSaveSecureApplicationState:coder]) {
result = YES;
}
}
return result;
}
- (BOOL)application:(UIApplication *)application shouldRestoreSecureApplicationState:(NSCoder *)coder API_AVAILABLE(ios(13.2)) {
BOOL result = NO;
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
if ([service application:application shouldRestoreSecureApplicationState:coder]) {
result = YES;
}
}
return result;
}
- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray<NSString *> *)identifierComponents coder:(NSCoder *)coder {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
UIViewController *controller = [service application:application viewControllerWithRestorationIdentifierPath:identifierComponents coder:coder];
if (controller) {
return controller;
}
}
return nil;
}
- (void)application:(UIApplication *)application willEncodeRestorableStateWithCoder:(NSCoder *)coder {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service application:application willEncodeRestorableStateWithCoder:coder];
}
}
- (void)application:(UIApplication *)application didDecodeRestorableStateWithCoder:(NSCoder *)coder {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service application:application didDecodeRestorableStateWithCoder:coder];
}
}
// MARK: Download Data in Background
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service application:application handleEventsForBackgroundURLSession:identifier completionHandler:completionHandler];
}
completionHandler();
}
// MARK: Remote Notification
// Moved to the iOS Plugin
// MARK: User Activity and Handling Quick Actions
- (BOOL)application:(UIApplication *)application willContinueUserActivityWithType:(NSString *)userActivityType {
BOOL result = NO;
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
if ([service application:application willContinueUserActivityWithType:userActivityType]) {
result = YES;
}
}
return result;
}
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *restorableObjects))restorationHandler {
BOOL result = NO;
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
if ([service application:application continueUserActivity:userActivity restorationHandler:restorationHandler]) {
result = YES;
}
}
return result;
}
- (void)application:(UIApplication *)application didUpdateUserActivity:(NSUserActivity *)userActivity {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service application:application didUpdateUserActivity:userActivity];
}
}
- (void)application:(UIApplication *)application didFailToContinueUserActivityWithType:(NSString *)userActivityType error:(NSError *)error {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service application:application didFailToContinueUserActivityWithType:userActivityType error:error];
}
}
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service application:application performActionForShortcutItem:shortcutItem completionHandler:completionHandler];
}
}
// MARK: WatchKit
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *replyInfo))reply {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service application:application handleWatchKitExtensionRequest:userInfo reply:reply];
}
}
// MARK: HealthKit
- (void)applicationShouldRequestHealthAuthorization:(UIApplication *)application {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service applicationShouldRequestHealthAuthorization:application];
}
}
// MARK: Opening an URL
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
if ([service application:app openURL:url options:options]) {
return YES;
}
}
return NO;
}
// MARK: Disallowing Specified App Extension Types
- (BOOL)application:(UIApplication *)application shouldAllowExtensionPointIdentifier:(UIApplicationExtensionPointIdentifier)extensionPointIdentifier {
BOOL result = NO;
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
if ([service application:application shouldAllowExtensionPointIdentifier:extensionPointIdentifier]) {
result = YES;
}
}
return result;
}
// MARK: SiriKit
- (id)application:(UIApplication *)application handlerForIntent:(INIntent *)intent API_AVAILABLE(ios(14.0)) {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
id result = [service application:application handlerForIntent:intent];
if (result) {
return result;
}
}
return nil;
}
// MARK: CloudKit
- (void)application:(UIApplication *)application userDidAcceptCloudKitShareWithMetadata:(CKShareMetadata *)cloudKitShareMetadata {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
continue;
}
[service application:application userDidAcceptCloudKitShareWithMetadata:cloudKitShareMetadata];
}
}
/* Handled By Info.plist file for now
// MARK: Interface Geometry
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {}
*/
@end
GODOT_CLANG_WARNING_POP

View file

@ -0,0 +1,39 @@
/**************************************************************************/
/* godot_keyboard_input_view.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#import <UIKit/UIKit.h>
@interface GDTKeyboardInputView : UITextView
- (BOOL)becomeFirstResponderWithString:(NSString *)existingString cursorStart:(NSInteger)start cursorEnd:(NSInteger)end;
@end

View file

@ -0,0 +1,201 @@
/**************************************************************************/
/* godot_keyboard_input_view.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#import "godot_keyboard_input_view.h"
#import "display_server_apple_embedded.h"
#import "os_apple_embedded.h"
#include "core/os/keyboard.h"
@interface GDTKeyboardInputView () <UITextViewDelegate>
@property(nonatomic, copy) NSString *previousText;
@property(nonatomic, assign) NSRange previousSelectedRange;
@end
@implementation GDTKeyboardInputView
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self godot_commonInit];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer {
self = [super initWithFrame:frame textContainer:textContainer];
if (self) {
[self godot_commonInit];
}
return self;
}
- (void)godot_commonInit {
self.hidden = YES;
self.delegate = self;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(observeTextChange:)
name:UITextViewTextDidChangeNotification
object:self];
}
- (void)dealloc {
self.delegate = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
// MARK: Keyboard
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (BOOL)becomeFirstResponderWithString:(NSString *)existingString cursorStart:(NSInteger)start cursorEnd:(NSInteger)end {
self.text = existingString;
self.previousText = existingString;
NSInteger safeStartIndex = MAX(start, 0);
NSRange textRange;
// Either a simple cursor or a selection.
if (end > 0) {
textRange = NSMakeRange(safeStartIndex, end - start);
} else {
textRange = NSMakeRange(safeStartIndex, 0);
}
self.selectedRange = textRange;
self.previousSelectedRange = textRange;
return [self becomeFirstResponder];
}
- (BOOL)resignFirstResponder {
self.text = nil;
self.previousText = nil;
return [super resignFirstResponder];
}
// MARK: OS Messages
- (void)deleteText:(NSInteger)charactersToDelete {
for (int i = 0; i < charactersToDelete; i++) {
DisplayServerAppleEmbedded::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, true, KeyLocation::UNSPECIFIED);
DisplayServerAppleEmbedded::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, false, KeyLocation::UNSPECIFIED);
}
}
- (void)enterText:(NSString *)substring {
String characters = String::utf8([substring UTF8String]);
for (int i = 0; i < characters.size(); i++) {
int character = characters[i];
Key key = Key::NONE;
if (character == '\t') { // 0x09
key = Key::TAB;
} else if (character == '\n') { // 0x0A
key = Key::ENTER;
} else if (character == 0x2006) {
key = Key::SPACE;
}
DisplayServerAppleEmbedded::get_singleton()->key(key, character, key, Key::NONE, 0, true, KeyLocation::UNSPECIFIED);
DisplayServerAppleEmbedded::get_singleton()->key(key, character, key, Key::NONE, 0, false, KeyLocation::UNSPECIFIED);
}
}
// MARK: Observer
- (void)observeTextChange:(NSNotification *)notification {
if (notification.object != self) {
return;
}
NSString *substringToDelete = nil;
if (self.previousSelectedRange.length == 0) {
// Get previous text to delete.
substringToDelete = [self.previousText substringToIndex:self.previousSelectedRange.location];
} else {
// If text was previously selected we are sending only one `backspace`. It will remove all text from text input.
[self deleteText:1];
}
NSString *substringToEnter = nil;
if (self.selectedRange.length == 0) {
// If previous cursor had a selection we have to calculate an inserted text.
if (self.previousSelectedRange.length != 0) {
NSInteger rangeEnd = self.selectedRange.location + self.selectedRange.length;
NSInteger rangeStart = MIN(self.previousSelectedRange.location, self.selectedRange.location);
NSInteger rangeLength = MAX(0, rangeEnd - rangeStart);
NSRange calculatedRange;
if (rangeLength >= 0) {
calculatedRange = NSMakeRange(rangeStart, rangeLength);
} else {
calculatedRange = NSMakeRange(rangeStart, 0);
}
substringToEnter = [self.text substringWithRange:calculatedRange];
} else {
substringToEnter = [self.text substringToIndex:self.selectedRange.location];
}
} else {
substringToEnter = [self.text substringWithRange:self.selectedRange];
}
NSInteger skip = 0;
if (substringToDelete != nil) {
for (NSUInteger i = 0; i < MIN([substringToDelete length], [substringToEnter length]); i++) {
if ([substringToDelete characterAtIndex:i] == [substringToEnter characterAtIndex:i]) {
skip++;
} else {
break;
}
}
[self deleteText:[substringToDelete length] - skip]; // Delete changed part of previous text.
}
[self enterText:[substringToEnter substringFromIndex:skip]]; // Enter changed part of new text.
self.previousText = self.text;
self.previousSelectedRange = self.selectedRange;
}
@end

View file

@ -0,0 +1,72 @@
/**************************************************************************/
/* godot_view_apple_embedded.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#import <UIKit/UIKit.h>
class String;
@class GDTView;
@protocol GDTDisplayLayer;
@protocol GDTViewRendererProtocol;
@protocol GDTViewDelegate
- (BOOL)godotViewFinishedSetup:(GDTView *)view;
@end
@interface GDTView : UIView
@property(assign, nonatomic) id<GDTViewRendererProtocol> renderer;
@property(assign, nonatomic) id<GDTViewDelegate> delegate;
@property(assign, readonly, nonatomic) BOOL isActive;
@property(assign, nonatomic) BOOL useCADisplayLink;
@property(strong, readonly, nonatomic) CALayer<GDTDisplayLayer> *renderingLayer;
@property(assign, readonly, nonatomic) BOOL canRender;
@property(assign, nonatomic) float preferredFrameRate;
// Can be extended by subclasses
- (void)godot_commonInit;
// Implemented in subclasses
- (CALayer<GDTDisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName;
- (void)startRendering;
- (void)stopRendering;
@end
// Implemented in subclasses
extern GDTView *GDTViewCreate();

View file

@ -0,0 +1,471 @@
/**************************************************************************/
/* godot_view_apple_embedded.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#import "godot_view_apple_embedded.h"
#import "display_layer_apple_embedded.h"
#import "display_server_apple_embedded.h"
#import "godot_view_renderer.h"
#include "core/config/project_settings.h"
#include "core/os/keyboard.h"
#include "core/string/ustring.h"
#import <CoreMotion/CoreMotion.h>
static const int max_touches = 32;
static const float earth_gravity = 9.80665;
@interface GDTView () {
UITouch *godot_touches[max_touches];
CGFloat last_edr_headroom;
}
@property(assign, nonatomic) BOOL isActive;
// CADisplayLink available on 3.1+ synchronizes the animation timer & drawing with the refresh rate of the display, only supports animation intervals of 1/60 1/30 & 1/15
@property(strong, nonatomic) CADisplayLink *displayLink;
// An animation timer that, when animation is started, will periodically call -drawView at the given rate.
// Only used if CADisplayLink is not
@property(strong, nonatomic) NSTimer *animationTimer;
@property(strong, nonatomic) CALayer<GDTDisplayLayer> *renderingLayer;
@property(strong, nonatomic) CMMotionManager *motionManager;
@property(assign, nonatomic) BOOL delegateDidFinishSetUp;
@end
@implementation GDTView
// Implemented in subclasses
- (CALayer<GDTDisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName {
return nil;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self godot_commonInit];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self godot_commonInit];
}
return self;
}
- (void)dealloc {
[self stopRendering];
self.renderer = nil;
self.delegate = nil;
if (self.renderingLayer) {
[self.renderingLayer removeFromSuperlayer];
self.renderingLayer = nil;
}
if (self.motionManager) {
[self.motionManager stopDeviceMotionUpdates];
self.motionManager = nil;
}
if (self.displayLink) {
[self.displayLink invalidate];
self.displayLink = nil;
}
if (self.animationTimer) {
[self.animationTimer invalidate];
self.animationTimer = nil;
}
}
- (void)godot_commonInit {
self.preferredFrameRate = 60;
self.useCADisplayLink = bool(GLOBAL_DEF("display.AppleEmbedded/use_cadisplaylink", true)) ? YES : NO;
last_edr_headroom = 0.0;
#if !defined(VISIONOS_ENABLED)
self.contentScaleFactor = [UIScreen mainScreen].scale;
#endif
if (@available(iOS 17.0, *)) {
[self registerForTraitChanges:@[ [UITraitUserInterfaceStyle class] ] withTarget:self action:@selector(traitCollectionDidChangeWithView:previousTraitCollection:)];
}
[self initTouches];
self.multipleTouchEnabled = YES;
// Configure and start accelerometer
if (!self.motionManager) {
self.motionManager = [[CMMotionManager alloc] init];
if (self.motionManager.deviceMotionAvailable) {
self.motionManager.deviceMotionUpdateInterval = 1.0 / 70.0;
[self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXMagneticNorthZVertical];
} else {
self.motionManager = nil;
}
}
}
- (void)system_theme_changed {
DisplayServerAppleEmbedded *ds = (DisplayServerAppleEmbedded *)DisplayServer::get_singleton();
if (ds) {
ds->emit_system_theme_changed();
}
}
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
if (@available(iOS 13.0, *)) {
#if !defined(VISIONOS_ENABLED)
[super traitCollectionDidChange:previousTraitCollection];
#endif
[self traitCollectionDidChangeWithView:self
previousTraitCollection:previousTraitCollection];
}
}
- (void)traitCollectionDidChangeWithView:(UIView *)view previousTraitCollection:(UITraitCollection *)previousTraitCollection {
if (@available(iOS 13.0, *)) {
if ([UITraitCollection currentTraitCollection].userInterfaceStyle != previousTraitCollection.userInterfaceStyle) {
[self system_theme_changed];
}
}
}
- (void)stopRendering {
if (!self.isActive) {
return;
}
self.isActive = NO;
print_verbose("Stop animation!");
if (self.useCADisplayLink) {
[self.displayLink invalidate];
self.displayLink = nil;
} else {
[self.animationTimer invalidate];
self.animationTimer = nil;
}
[self clearTouches];
}
- (void)startRendering {
if (self.isActive) {
return;
}
self.isActive = YES;
print_verbose("Start animation!");
if (self.useCADisplayLink) {
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)];
self.displayLink.preferredFramesPerSecond = self.preferredFrameRate;
// Setup DisplayLink in main thread
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
} else {
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / self.preferredFrameRate) target:self selector:@selector(drawView) userInfo:nil repeats:YES];
}
}
- (void)drawView {
if (!self.isActive) {
print_verbose("Draw view not active!");
return;
}
if (self.useCADisplayLink) {
// Pause the CADisplayLink to avoid recursion
[self.displayLink setPaused:YES];
// Process all input events
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource) {
// Continue.
}
// We are good to go, resume the CADisplayLink
[self.displayLink setPaused:NO];
}
[self.renderingLayer startRenderDisplayLayer];
if (!self.renderer) {
return;
}
if ([self.renderer setupView:self]) {
return;
}
if (self.delegate && !self.delegateDidFinishSetUp) {
[self layoutRenderingLayer]; // Trigger DisplayServerVisionOS::resize_window after Main::start()
self.delegateDidFinishSetUp = [self.delegate godotViewFinishedSetup:self];
if (!_delegateDidFinishSetUp) {
return;
}
}
[self handleMotion];
#if !defined(VISIONOS_ENABLED)
if (@available(iOS 16.0, *)) {
CGFloat edr_headroom = UIScreen.mainScreen.currentEDRHeadroom;
if (last_edr_headroom != edr_headroom) {
last_edr_headroom = edr_headroom;
if (DisplayServerAppleEmbedded::get_singleton()) {
DisplayServerAppleEmbedded::get_singleton()->current_edr_headroom_changed();
}
}
}
#endif
[self.renderer renderOnView:self];
[self.renderingLayer stopRenderDisplayLayer];
}
- (BOOL)canRender {
if (self.useCADisplayLink) {
return self.displayLink != nil;
} else {
return self.animationTimer != nil;
}
}
- (void)setPreferredFrameRate:(float)preferredFrameRate {
_preferredFrameRate = preferredFrameRate;
if (self.canRender) {
[self stopRendering];
[self startRendering];
}
}
- (void)layoutSubviews {
[super layoutSubviews];
[self layoutRenderingLayer];
}
- (void)layoutRenderingLayer {
if (self.renderingLayer) {
self.renderingLayer.frame = self.bounds;
[self.renderingLayer layoutDisplayLayer];
if (DisplayServerAppleEmbedded::get_singleton()) {
DisplayServerAppleEmbedded::get_singleton()->resize_window(self.bounds.size);
}
}
}
// MARK: - Input
// MARK: Touches
- (void)initTouches {
for (int i = 0; i < max_touches; i++) {
godot_touches[i] = nullptr;
}
}
- (int)getTouchIDForTouch:(UITouch *)p_touch {
int first = -1;
for (int i = 0; i < max_touches; i++) {
if (first == -1 && godot_touches[i] == nullptr) {
first = i;
continue;
}
if (godot_touches[i] == p_touch) {
return i;
}
}
if (first != -1) {
godot_touches[first] = p_touch;
return first;
}
return -1;
}
- (int)removeTouch:(UITouch *)p_touch {
int remaining = 0;
for (int i = 0; i < max_touches; i++) {
if (godot_touches[i] == nullptr) {
continue;
}
if (godot_touches[i] == p_touch) {
godot_touches[i] = nullptr;
} else {
++remaining;
}
}
return remaining;
}
- (void)clearTouches {
for (int i = 0; i < max_touches; i++) {
godot_touches[i] = nullptr;
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
int tid = [self getTouchIDForTouch:touch];
ERR_FAIL_COND(tid == -1);
CGPoint touchPoint = [touch locationInView:self];
DisplayServerAppleEmbedded::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1);
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
int tid = [self getTouchIDForTouch:touch];
ERR_FAIL_COND(tid == -1);
CGPoint touchPoint = [touch locationInView:self];
CGPoint prev_point = [touch previousLocationInView:self];
CGFloat alt = [touch altitudeAngle];
CGVector azim = [touch azimuthUnitVectorInView:self];
DisplayServerAppleEmbedded::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, [touch force] / [touch maximumPossibleForce], Vector2(azim.dx, azim.dy) * Math::cos(alt));
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
int tid = [self getTouchIDForTouch:touch];
ERR_FAIL_COND(tid == -1);
[self removeTouch:touch];
CGPoint touchPoint = [touch locationInView:self];
DisplayServerAppleEmbedded::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false);
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
int tid = [self getTouchIDForTouch:touch];
ERR_FAIL_COND(tid == -1);
DisplayServerAppleEmbedded::get_singleton()->touches_canceled(tid);
}
[self clearTouches];
}
// MARK: Motion
- (void)handleMotion {
if (!self.motionManager) {
return;
}
// Just using polling approach for now, we can set this up so it sends
// data to us in intervals, might be better. See Apple reference pages
// for more details:
// https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc
// Apple splits our accelerometer date into a gravity and user movement
// component. We add them back together.
CMAcceleration gravity = self.motionManager.deviceMotion.gravity;
CMAcceleration acceleration = self.motionManager.deviceMotion.userAcceleration;
// To be consistent with Android we convert the unit of measurement from g (Earth's gravity)
// to m/s^2.
gravity.x *= earth_gravity;
gravity.y *= earth_gravity;
gravity.z *= earth_gravity;
acceleration.x *= earth_gravity;
acceleration.y *= earth_gravity;
acceleration.z *= earth_gravity;
///@TODO We don't seem to be getting data here, is my device broken or
/// is this code incorrect?
CMMagneticField magnetic = self.motionManager.deviceMotion.magneticField.field;
///@TODO we can access rotationRate as a CMRotationRate variable
///(processed date) or CMGyroData (raw data), have to see what works
/// best
CMRotationRate rotation = self.motionManager.deviceMotion.rotationRate;
// Adjust for screen orientation.
// [[UIDevice currentDevice] orientation] changes even if we've fixed
// our orientation which is not a good thing when you're trying to get
// your user to move the screen in all directions and want consistent
// output
#if defined(VISIONOS_ENABLED)
UIInterfaceOrientation interfaceOrientation = [UIApplication sharedApplication].delegate.window.windowScene.effectiveGeometry.interfaceOrientation;
#else
UIInterfaceOrientation interfaceOrientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
#endif
switch (interfaceOrientation) {
case UIInterfaceOrientationLandscapeLeft: {
DisplayServerAppleEmbedded::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5));
DisplayServerAppleEmbedded::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5));
DisplayServerAppleEmbedded::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5));
DisplayServerAppleEmbedded::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5));
} break;
case UIInterfaceOrientationLandscapeRight: {
DisplayServerAppleEmbedded::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5));
DisplayServerAppleEmbedded::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5));
DisplayServerAppleEmbedded::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5));
DisplayServerAppleEmbedded::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5));
} break;
case UIInterfaceOrientationPortraitUpsideDown: {
DisplayServerAppleEmbedded::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z).rotated(Vector3(0, 0, 1), Math::PI));
DisplayServerAppleEmbedded::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z).rotated(Vector3(0, 0, 1), Math::PI));
DisplayServerAppleEmbedded::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z).rotated(Vector3(0, 0, 1), Math::PI));
DisplayServerAppleEmbedded::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z).rotated(Vector3(0, 0, 1), Math::PI));
} break;
default: { // assume portrait
DisplayServerAppleEmbedded::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z));
DisplayServerAppleEmbedded::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z));
DisplayServerAppleEmbedded::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z));
DisplayServerAppleEmbedded::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z));
} break;
}
}
@end

View file

@ -0,0 +1,43 @@
/**************************************************************************/
/* godot_view_controller.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#import <UIKit/UIKit.h>
@class GDTView;
@class GDTKeyboardInputView;
@interface GDTViewController : UIViewController
@property(nonatomic, readonly, strong) GDTView *godotView;
@property(nonatomic, readonly, strong) GDTKeyboardInputView *keyboardView;
@end

View file

@ -0,0 +1,361 @@
/**************************************************************************/
/* godot_view_controller.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#import "godot_view_controller.h"
#import "display_server_apple_embedded.h"
#import "godot_keyboard_input_view.h"
#import "godot_view_apple_embedded.h"
#import "godot_view_renderer.h"
#import "key_mapping_apple_embedded.h"
#import "os_apple_embedded.h"
#include "core/config/project_settings.h"
#include "servers/camera/camera_server.h"
#import <AVFoundation/AVFoundation.h>
#import <GameController/GameController.h>
@interface GDTViewController () <GDTViewDelegate>
@property(strong, nonatomic) GDTViewRenderer *renderer;
@property(strong, nonatomic) GDTKeyboardInputView *keyboardView;
@property(strong, nonatomic) UIView *godotLoadingOverlay;
@end
@implementation GDTViewController
- (GDTView *)godotView {
return (GDTView *)self.view;
}
- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
[super pressesBegan:presses withEvent:event];
if (!DisplayServerAppleEmbedded::get_singleton() || DisplayServerAppleEmbedded::get_singleton()->is_keyboard_active()) {
return;
}
if (@available(iOS 13.4, *)) {
for (UIPress *press in presses) {
String u32lbl = String::utf8([press.key.charactersIgnoringModifiers UTF8String]);
String u32text = String::utf8([press.key.characters UTF8String]);
Key key = KeyMappingAppleEmbedded::remap_key(press.key.keyCode);
if (press.key.keyCode == 0 && u32text.is_empty() && u32lbl.is_empty()) {
continue;
}
char32_t us = 0;
if (!u32lbl.is_empty() && !u32lbl.begins_with("UIKey")) {
us = u32lbl[0];
}
KeyLocation location = KeyMappingAppleEmbedded::key_location(press.key.keyCode);
if (!u32text.is_empty() && !u32text.begins_with("UIKey")) {
for (int i = 0; i < u32text.length(); i++) {
const char32_t c = u32text[i];
DisplayServerAppleEmbedded::get_singleton()->key(fix_keycode(us, key), c, fix_key_label(us, key), key, press.key.modifierFlags, true, location);
}
} else {
DisplayServerAppleEmbedded::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, true, location);
}
}
}
}
- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
[super pressesEnded:presses withEvent:event];
if (!DisplayServerAppleEmbedded::get_singleton() || DisplayServerAppleEmbedded::get_singleton()->is_keyboard_active()) {
return;
}
if (@available(iOS 13.4, *)) {
for (UIPress *press in presses) {
String u32lbl = String::utf8([press.key.charactersIgnoringModifiers UTF8String]);
Key key = KeyMappingAppleEmbedded::remap_key(press.key.keyCode);
if (press.key.keyCode == 0 && u32lbl.is_empty()) {
continue;
}
char32_t us = 0;
if (!u32lbl.is_empty() && !u32lbl.begins_with("UIKey")) {
us = u32lbl[0];
}
KeyLocation location = KeyMappingAppleEmbedded::key_location(press.key.keyCode);
DisplayServerAppleEmbedded::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, false, location);
}
}
}
- (void)loadView {
GDTView *view = GDTViewCreate();
GDTViewRenderer *renderer = [[GDTViewRenderer alloc] init];
self.renderer = renderer;
self.view = view;
view.renderer = self.renderer;
view.delegate = self;
}
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
[self godot_commonInit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self godot_commonInit];
}
return self;
}
- (void)godot_commonInit {
// Initialize view controller values.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
print_verbose("Did receive memory warning!");
}
- (void)viewDidLoad {
[super viewDidLoad];
[self observeKeyboard];
[self displayLoadingOverlay];
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.godotView startRendering];
}
- (void)viewDidDisappear:(BOOL)animated {
[self.godotView stopRendering];
[super viewDidDisappear:animated];
}
- (void)observeKeyboard {
print_verbose("Setting up keyboard input view.");
self.keyboardView = [GDTKeyboardInputView new];
[self.view addSubview:self.keyboardView];
print_verbose("Adding observer for keyboard show/hide.");
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(keyboardOnScreen:)
name:UIKeyboardDidShowNotification
object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(keyboardHidden:)
name:UIKeyboardDidHideNotification
object:nil];
}
- (void)displayLoadingOverlay {
#if !defined(VISIONOS_ENABLED)
NSBundle *bundle = [NSBundle mainBundle];
NSString *storyboardName = @"Launch Screen";
if ([bundle pathForResource:storyboardName ofType:@"storyboardc"] == nil) {
return;
}
UIStoryboard *launchStoryboard = [UIStoryboard storyboardWithName:storyboardName bundle:bundle];
UIViewController *controller = [launchStoryboard instantiateInitialViewController];
self.godotLoadingOverlay = controller.view;
self.godotLoadingOverlay.frame = self.view.bounds;
self.godotLoadingOverlay.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
[self.view addSubview:self.godotLoadingOverlay];
#endif
}
- (BOOL)godotViewFinishedSetup:(GDTView *)view {
[self.godotLoadingOverlay removeFromSuperview];
self.godotLoadingOverlay = nil;
return YES;
}
- (void)dealloc {
self.keyboardView = nil;
self.renderer = nil;
if (self.godotLoadingOverlay) {
[self.godotLoadingOverlay removeFromSuperview];
self.godotLoadingOverlay = nil;
}
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
// MARK: Orientation
#ifdef IOS_ENABLED
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[coordinator animateAlongsideTransition:nil
completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
// Get the new interface orientation after rotation completes (iOS only)
UIInterfaceOrientation orientation = self.view.window.windowScene.interfaceOrientation;
// Notify camera server of orientation change
CameraServer *camera_server = CameraServer::get_singleton();
if (camera_server) {
camera_server->handle_display_rotation_change((int)orientation);
}
DisplayServer *display_server = DisplayServer::get_singleton();
if (display_server) {
int out = 0;
if (UIInterfaceOrientationIsPortrait(orientation)) {
out = 1;
} else if (UIInterfaceOrientationIsLandscape(orientation)) {
out = 2;
}
display_server->emit_signal("orientation_changed", out);
}
}];
}
#endif
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures {
if (GLOBAL_GET("display/window/ios/suppress_ui_gesture")) {
return UIRectEdgeAll;
} else {
return UIRectEdgeNone;
}
}
- (BOOL)shouldAutorotate {
if (!DisplayServerAppleEmbedded::get_singleton()) {
return NO;
}
switch (DisplayServerAppleEmbedded::get_singleton()->screen_get_orientation(DisplayServerEnums::SCREEN_OF_MAIN_WINDOW)) {
case DisplayServerEnums::SCREEN_SENSOR:
case DisplayServerEnums::SCREEN_SENSOR_LANDSCAPE:
case DisplayServerEnums::SCREEN_SENSOR_PORTRAIT:
return YES;
default:
return NO;
}
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
if (!DisplayServerAppleEmbedded::get_singleton()) {
return UIInterfaceOrientationMaskAll;
}
switch (DisplayServerAppleEmbedded::get_singleton()->screen_get_orientation(DisplayServerEnums::SCREEN_OF_MAIN_WINDOW)) {
case DisplayServerEnums::SCREEN_PORTRAIT:
return UIInterfaceOrientationMaskPortrait;
case DisplayServerEnums::SCREEN_REVERSE_LANDSCAPE:
if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) {
return UIInterfaceOrientationMaskLandscapeLeft;
} else {
return UIInterfaceOrientationMaskLandscapeRight;
}
case DisplayServerEnums::SCREEN_REVERSE_PORTRAIT:
return UIInterfaceOrientationMaskPortraitUpsideDown;
case DisplayServerEnums::SCREEN_SENSOR_LANDSCAPE:
return UIInterfaceOrientationMaskLandscape;
case DisplayServerEnums::SCREEN_SENSOR_PORTRAIT:
return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown;
case DisplayServerEnums::SCREEN_SENSOR:
return UIInterfaceOrientationMaskAll;
case DisplayServerEnums::SCREEN_LANDSCAPE:
if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) {
return UIInterfaceOrientationMaskLandscapeRight;
} else {
return UIInterfaceOrientationMaskLandscapeLeft;
}
}
}
- (BOOL)prefersStatusBarHidden {
if (GLOBAL_GET("display/window/ios/hide_status_bar")) {
return YES;
} else {
return NO;
}
}
- (BOOL)prefersHomeIndicatorAutoHidden {
if (GLOBAL_GET("display/window/ios/hide_home_indicator")) {
return YES;
} else {
return NO;
}
}
// MARK: Keyboard
- (void)keyboardOnScreen:(NSNotification *)notification {
NSDictionary *info = notification.userInfo;
NSValue *value = info[UIKeyboardFrameEndUserInfoKey];
CGRect rawFrame = [value CGRectValue];
CGRect keyboardFrame = [self.view convertRect:rawFrame fromView:nil];
if (DisplayServerAppleEmbedded::get_singleton()) {
DisplayServerAppleEmbedded::get_singleton()->virtual_keyboard_set_height(keyboardFrame.size.height);
}
}
- (void)keyboardHidden:(NSNotification *)notification {
if (DisplayServerAppleEmbedded::get_singleton()) {
DisplayServerAppleEmbedded::get_singleton()->virtual_keyboard_set_height(0);
}
}
@end

View file

@ -0,0 +1,46 @@
/**************************************************************************/
/* godot_view_renderer.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#import <UIKit/UIKit.h>
@protocol GDTViewRendererProtocol <NSObject>
@property(assign, readonly, nonatomic) BOOL hasFinishedSetup;
- (BOOL)setupView:(UIView *)view;
- (void)renderOnView:(UIView *)view;
@end
@interface GDTViewRenderer : NSObject <GDTViewRendererProtocol>
@end

View file

@ -0,0 +1,120 @@
/**************************************************************************/
/* godot_view_renderer.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#import "godot_view_renderer.h"
#import "display_server_apple_embedded.h"
#import "os_apple_embedded.h"
#include "core/config/project_settings.h"
#include "core/os/keyboard.h"
#include "core/os/os.h"
#include "main/main.h"
#include "servers/audio/audio_server.h"
#import <AudioToolbox/AudioServices.h>
#import <CoreMotion/CoreMotion.h>
#import <GameController/GameController.h>
#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>
@interface GDTViewRenderer ()
@property(assign, nonatomic) BOOL hasFinishedProjectDataSetup;
@property(assign, nonatomic) BOOL hasStartedMain;
@property(assign, nonatomic) BOOL hasFinishedSetup;
@end
@implementation GDTViewRenderer
- (BOOL)setupView:(UIView *)view {
if (self.hasFinishedSetup) {
return NO;
}
if (!OS::get_singleton()) {
exit(0);
}
if (!self.hasFinishedProjectDataSetup) {
[self setupProjectData];
return YES;
}
if (!self.hasStartedMain) {
self.hasStartedMain = YES;
OS_AppleEmbedded::get_singleton()->start();
return YES;
}
self.hasFinishedSetup = YES;
return NO;
}
- (void)setupProjectData {
self.hasFinishedProjectDataSetup = YES;
Main::setup2();
// this might be necessary before here
NSDictionary *dict = [[NSBundle mainBundle] infoDictionary];
for (NSString *key in dict) {
NSObject *value = [dict objectForKey:key];
String ukey = String::utf8([key UTF8String]);
// we need a NSObject to Variant conversor
if ([value isKindOfClass:[NSString class]]) {
NSString *str = (NSString *)value;
String uval = String::utf8([str UTF8String]);
ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval);
} else if ([value isKindOfClass:[NSNumber class]]) {
NSNumber *n = (NSNumber *)value;
double dval = [n doubleValue];
ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval);
}
// do stuff
}
}
- (void)renderOnView:(UIView *)view {
if (!OS_AppleEmbedded::get_singleton()) {
return;
}
OS_AppleEmbedded::get_singleton()->iterate();
}
@end

View file

@ -0,0 +1,44 @@
/**************************************************************************/
/* key_mapping_apple_embedded.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/os/keyboard.h"
#import <UIKit/UIKit.h>
class KeyMappingAppleEmbedded {
KeyMappingAppleEmbedded() {}
public:
static void initialize();
static Key remap_key(CFIndex p_keycode);
static KeyLocation key_location(CFIndex p_keycode);
};

View file

@ -0,0 +1,206 @@
/**************************************************************************/
/* key_mapping_apple_embedded.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#import "key_mapping_apple_embedded.h"
#include "core/templates/hash_map.h"
struct HashMapHasherKeys {
static _FORCE_INLINE_ uint32_t hash(const Key p_key) { return hash_fmix32(static_cast<uint32_t>(p_key)); }
static _FORCE_INLINE_ uint32_t hash(const CFIndex p_key) { return hash_fmix32(p_key); }
};
HashMap<CFIndex, Key, HashMapHasherKeys> keyusage_map;
HashMap<CFIndex, KeyLocation, HashMapHasherKeys> location_map;
void KeyMappingAppleEmbedded::initialize() {
if (@available(iOS 13.4, *)) {
keyusage_map[UIKeyboardHIDUsageKeyboardA] = Key::A;
keyusage_map[UIKeyboardHIDUsageKeyboardB] = Key::B;
keyusage_map[UIKeyboardHIDUsageKeyboardC] = Key::C;
keyusage_map[UIKeyboardHIDUsageKeyboardD] = Key::D;
keyusage_map[UIKeyboardHIDUsageKeyboardE] = Key::E;
keyusage_map[UIKeyboardHIDUsageKeyboardF] = Key::F;
keyusage_map[UIKeyboardHIDUsageKeyboardG] = Key::G;
keyusage_map[UIKeyboardHIDUsageKeyboardH] = Key::H;
keyusage_map[UIKeyboardHIDUsageKeyboardI] = Key::I;
keyusage_map[UIKeyboardHIDUsageKeyboardJ] = Key::J;
keyusage_map[UIKeyboardHIDUsageKeyboardK] = Key::K;
keyusage_map[UIKeyboardHIDUsageKeyboardL] = Key::L;
keyusage_map[UIKeyboardHIDUsageKeyboardM] = Key::M;
keyusage_map[UIKeyboardHIDUsageKeyboardN] = Key::N;
keyusage_map[UIKeyboardHIDUsageKeyboardO] = Key::O;
keyusage_map[UIKeyboardHIDUsageKeyboardP] = Key::P;
keyusage_map[UIKeyboardHIDUsageKeyboardQ] = Key::Q;
keyusage_map[UIKeyboardHIDUsageKeyboardR] = Key::R;
keyusage_map[UIKeyboardHIDUsageKeyboardS] = Key::S;
keyusage_map[UIKeyboardHIDUsageKeyboardT] = Key::T;
keyusage_map[UIKeyboardHIDUsageKeyboardU] = Key::U;
keyusage_map[UIKeyboardHIDUsageKeyboardV] = Key::V;
keyusage_map[UIKeyboardHIDUsageKeyboardW] = Key::W;
keyusage_map[UIKeyboardHIDUsageKeyboardX] = Key::X;
keyusage_map[UIKeyboardHIDUsageKeyboardY] = Key::Y;
keyusage_map[UIKeyboardHIDUsageKeyboardZ] = Key::Z;
keyusage_map[UIKeyboardHIDUsageKeyboard0] = Key::KEY_0;
keyusage_map[UIKeyboardHIDUsageKeyboard1] = Key::KEY_1;
keyusage_map[UIKeyboardHIDUsageKeyboard2] = Key::KEY_2;
keyusage_map[UIKeyboardHIDUsageKeyboard3] = Key::KEY_3;
keyusage_map[UIKeyboardHIDUsageKeyboard4] = Key::KEY_4;
keyusage_map[UIKeyboardHIDUsageKeyboard5] = Key::KEY_5;
keyusage_map[UIKeyboardHIDUsageKeyboard6] = Key::KEY_6;
keyusage_map[UIKeyboardHIDUsageKeyboard7] = Key::KEY_7;
keyusage_map[UIKeyboardHIDUsageKeyboard8] = Key::KEY_8;
keyusage_map[UIKeyboardHIDUsageKeyboard9] = Key::KEY_9;
keyusage_map[UIKeyboardHIDUsageKeyboardBackslash] = Key::BACKSLASH;
keyusage_map[UIKeyboardHIDUsageKeyboardCloseBracket] = Key::BRACKETRIGHT;
keyusage_map[UIKeyboardHIDUsageKeyboardComma] = Key::COMMA;
keyusage_map[UIKeyboardHIDUsageKeyboardEqualSign] = Key::EQUAL;
keyusage_map[UIKeyboardHIDUsageKeyboardHyphen] = Key::MINUS;
keyusage_map[UIKeyboardHIDUsageKeyboardNonUSBackslash] = Key::SECTION;
keyusage_map[UIKeyboardHIDUsageKeyboardNonUSPound] = Key::ASCIITILDE;
keyusage_map[UIKeyboardHIDUsageKeyboardOpenBracket] = Key::BRACKETLEFT;
keyusage_map[UIKeyboardHIDUsageKeyboardPeriod] = Key::PERIOD;
keyusage_map[UIKeyboardHIDUsageKeyboardQuote] = Key::QUOTEDBL;
keyusage_map[UIKeyboardHIDUsageKeyboardSemicolon] = Key::SEMICOLON;
keyusage_map[UIKeyboardHIDUsageKeyboardSeparator] = Key::SECTION;
keyusage_map[UIKeyboardHIDUsageKeyboardSlash] = Key::SLASH;
keyusage_map[UIKeyboardHIDUsageKeyboardSpacebar] = Key::SPACE;
keyusage_map[UIKeyboardHIDUsageKeyboardCapsLock] = Key::CAPSLOCK;
keyusage_map[UIKeyboardHIDUsageKeyboardLeftAlt] = Key::ALT;
keyusage_map[UIKeyboardHIDUsageKeyboardLeftControl] = Key::CTRL;
keyusage_map[UIKeyboardHIDUsageKeyboardLeftShift] = Key::SHIFT;
keyusage_map[UIKeyboardHIDUsageKeyboardRightAlt] = Key::ALT;
keyusage_map[UIKeyboardHIDUsageKeyboardRightControl] = Key::CTRL;
keyusage_map[UIKeyboardHIDUsageKeyboardRightShift] = Key::SHIFT;
keyusage_map[UIKeyboardHIDUsageKeyboardScrollLock] = Key::SCROLLLOCK;
keyusage_map[UIKeyboardHIDUsageKeyboardLeftArrow] = Key::LEFT;
keyusage_map[UIKeyboardHIDUsageKeyboardRightArrow] = Key::RIGHT;
keyusage_map[UIKeyboardHIDUsageKeyboardUpArrow] = Key::UP;
keyusage_map[UIKeyboardHIDUsageKeyboardDownArrow] = Key::DOWN;
keyusage_map[UIKeyboardHIDUsageKeyboardPageUp] = Key::PAGEUP;
keyusage_map[UIKeyboardHIDUsageKeyboardPageDown] = Key::PAGEDOWN;
keyusage_map[UIKeyboardHIDUsageKeyboardHome] = Key::HOME;
keyusage_map[UIKeyboardHIDUsageKeyboardEnd] = Key::END;
keyusage_map[UIKeyboardHIDUsageKeyboardDeleteForward] = Key::KEY_DELETE;
keyusage_map[UIKeyboardHIDUsageKeyboardDeleteOrBackspace] = Key::BACKSPACE;
keyusage_map[UIKeyboardHIDUsageKeyboardEscape] = Key::ESCAPE;
keyusage_map[UIKeyboardHIDUsageKeyboardInsert] = Key::INSERT;
keyusage_map[UIKeyboardHIDUsageKeyboardReturn] = Key::ENTER;
keyusage_map[UIKeyboardHIDUsageKeyboardTab] = Key::TAB;
keyusage_map[UIKeyboardHIDUsageKeyboardF1] = Key::F1;
keyusage_map[UIKeyboardHIDUsageKeyboardF2] = Key::F2;
keyusage_map[UIKeyboardHIDUsageKeyboardF3] = Key::F3;
keyusage_map[UIKeyboardHIDUsageKeyboardF4] = Key::F4;
keyusage_map[UIKeyboardHIDUsageKeyboardF5] = Key::F5;
keyusage_map[UIKeyboardHIDUsageKeyboardF6] = Key::F6;
keyusage_map[UIKeyboardHIDUsageKeyboardF7] = Key::F7;
keyusage_map[UIKeyboardHIDUsageKeyboardF8] = Key::F8;
keyusage_map[UIKeyboardHIDUsageKeyboardF9] = Key::F9;
keyusage_map[UIKeyboardHIDUsageKeyboardF10] = Key::F10;
keyusage_map[UIKeyboardHIDUsageKeyboardF11] = Key::F11;
keyusage_map[UIKeyboardHIDUsageKeyboardF12] = Key::F12;
keyusage_map[UIKeyboardHIDUsageKeyboardF13] = Key::F13;
keyusage_map[UIKeyboardHIDUsageKeyboardF14] = Key::F14;
keyusage_map[UIKeyboardHIDUsageKeyboardF15] = Key::F15;
keyusage_map[UIKeyboardHIDUsageKeyboardF16] = Key::F16;
keyusage_map[UIKeyboardHIDUsageKeyboardF17] = Key::F17;
keyusage_map[UIKeyboardHIDUsageKeyboardF18] = Key::F18;
keyusage_map[UIKeyboardHIDUsageKeyboardF19] = Key::F19;
keyusage_map[UIKeyboardHIDUsageKeyboardF20] = Key::F20;
keyusage_map[UIKeyboardHIDUsageKeyboardF21] = Key::F21;
keyusage_map[UIKeyboardHIDUsageKeyboardF22] = Key::F22;
keyusage_map[UIKeyboardHIDUsageKeyboardF23] = Key::F23;
keyusage_map[UIKeyboardHIDUsageKeyboardF24] = Key::F24;
keyusage_map[UIKeyboardHIDUsageKeypad0] = Key::KP_0;
keyusage_map[UIKeyboardHIDUsageKeypad1] = Key::KP_1;
keyusage_map[UIKeyboardHIDUsageKeypad2] = Key::KP_2;
keyusage_map[UIKeyboardHIDUsageKeypad3] = Key::KP_3;
keyusage_map[UIKeyboardHIDUsageKeypad4] = Key::KP_4;
keyusage_map[UIKeyboardHIDUsageKeypad5] = Key::KP_5;
keyusage_map[UIKeyboardHIDUsageKeypad6] = Key::KP_6;
keyusage_map[UIKeyboardHIDUsageKeypad7] = Key::KP_7;
keyusage_map[UIKeyboardHIDUsageKeypad8] = Key::KP_8;
keyusage_map[UIKeyboardHIDUsageKeypad9] = Key::KP_9;
keyusage_map[UIKeyboardHIDUsageKeypadAsterisk] = Key::KP_MULTIPLY;
keyusage_map[UIKeyboardHIDUsageKeyboardGraveAccentAndTilde] = Key::BAR;
keyusage_map[UIKeyboardHIDUsageKeypadEnter] = Key::KP_ENTER;
keyusage_map[UIKeyboardHIDUsageKeypadHyphen] = Key::KP_SUBTRACT;
keyusage_map[UIKeyboardHIDUsageKeypadNumLock] = Key::NUMLOCK;
keyusage_map[UIKeyboardHIDUsageKeypadPeriod] = Key::KP_PERIOD;
keyusage_map[UIKeyboardHIDUsageKeypadPlus] = Key::KP_ADD;
keyusage_map[UIKeyboardHIDUsageKeypadSlash] = Key::KP_DIVIDE;
keyusage_map[UIKeyboardHIDUsageKeyboardPause] = Key::PAUSE;
keyusage_map[UIKeyboardHIDUsageKeyboardStop] = Key::STOP;
keyusage_map[UIKeyboardHIDUsageKeyboardMute] = Key::VOLUMEMUTE;
keyusage_map[UIKeyboardHIDUsageKeyboardVolumeUp] = Key::VOLUMEUP;
keyusage_map[UIKeyboardHIDUsageKeyboardVolumeDown] = Key::VOLUMEDOWN;
keyusage_map[UIKeyboardHIDUsageKeyboardFind] = Key::SEARCH;
keyusage_map[UIKeyboardHIDUsageKeyboardHelp] = Key::HELP;
keyusage_map[UIKeyboardHIDUsageKeyboardLeftGUI] = Key::META;
keyusage_map[UIKeyboardHIDUsageKeyboardRightGUI] = Key::META;
keyusage_map[UIKeyboardHIDUsageKeyboardMenu] = Key::MENU;
keyusage_map[UIKeyboardHIDUsageKeyboardPrintScreen] = Key::PRINT;
keyusage_map[UIKeyboardHIDUsageKeyboardReturnOrEnter] = Key::ENTER;
keyusage_map[UIKeyboardHIDUsageKeyboardSysReqOrAttention] = Key::SYSREQ;
keyusage_map[0x01AE] = Key::KEYBOARD; // On-screen keyboard key on smart connector keyboard.
keyusage_map[0x029D] = Key::GLOBE; // "Globe" key on smart connector / Mac keyboard.
keyusage_map[UIKeyboardHIDUsageKeyboardLANG1] = Key::JIS_EISU;
keyusage_map[UIKeyboardHIDUsageKeyboardLANG2] = Key::JIS_KANA;
location_map[UIKeyboardHIDUsageKeyboardLeftAlt] = KeyLocation::LEFT;
location_map[UIKeyboardHIDUsageKeyboardRightAlt] = KeyLocation::RIGHT;
location_map[UIKeyboardHIDUsageKeyboardLeftControl] = KeyLocation::LEFT;
location_map[UIKeyboardHIDUsageKeyboardRightControl] = KeyLocation::RIGHT;
location_map[UIKeyboardHIDUsageKeyboardLeftShift] = KeyLocation::LEFT;
location_map[UIKeyboardHIDUsageKeyboardRightShift] = KeyLocation::RIGHT;
location_map[UIKeyboardHIDUsageKeyboardLeftGUI] = KeyLocation::LEFT;
location_map[UIKeyboardHIDUsageKeyboardRightGUI] = KeyLocation::RIGHT;
}
}
Key KeyMappingAppleEmbedded::remap_key(CFIndex p_keycode) {
if (@available(iOS 13.4, *)) {
const Key *key = keyusage_map.getptr(p_keycode);
if (key) {
return *key;
}
}
return Key::NONE;
}
KeyLocation KeyMappingAppleEmbedded::key_location(CFIndex p_keycode) {
if (@available(iOS 13.4, *)) {
const KeyLocation *location = location_map.getptr(p_keycode);
if (location) {
return *location;
}
}
return KeyLocation::UNSPECIFIED;
}

View file

@ -0,0 +1,35 @@
/**************************************************************************/
/* main_utilities.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
void change_to_launch_dir(char **p_args);
int process_args(int p_argc, char **p_args, char **r_args);

View file

@ -0,0 +1,81 @@
/**************************************************************************/
/* main_utilities.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "core/string/ustring.h"
#import <UIKit/UIKit.h>
#include <unistd.h>
#include <cstdio>
void change_to_launch_dir(char **p_args) {
size_t len = strlen(p_args[0]);
while (len--) {
if (p_args[0][len] == '/') {
break;
}
}
if (len >= 0) {
char path[512];
memcpy(path, p_args[0], len > sizeof(path) ? sizeof(path) : len);
path[len] = 0;
chdir(path);
}
}
int add_cmdline(int p_argc, char **p_args) {
NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"];
if (!arr) {
return p_argc;
}
for (NSUInteger i = 0; i < [arr count]; i++) {
NSString *str = [arr objectAtIndex:i];
if (!str) {
continue;
}
p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
}
p_args[p_argc] = nullptr;
return p_argc;
}
int process_args(int p_argc, char **p_args, char **r_args) {
for (int i = 0; i < p_argc; i++) {
r_args[i] = p_args[i];
}
r_args[p_argc] = nullptr;
p_argc = add_cmdline(p_argc, r_args);
return p_argc;
}

View file

@ -0,0 +1,160 @@
/**************************************************************************/
/* os_apple_embedded.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifdef APPLE_EMBEDDED_ENABLED
#import "apple_embedded.h"
#import "drivers/coreaudio/audio_driver_coreaudio.h"
#include "drivers/unix/os_unix.h"
#include "servers/audio/audio_server.h"
#include "servers/rendering/renderer_compositor.h"
#if defined(RD_ENABLED)
#include "servers/rendering/rendering_device.h"
#if defined(VULKAN_ENABLED)
#import "rendering_context_driver_vulkan_apple_embedded.h"
#endif
#endif
class JoypadSDL;
class OS_AppleEmbedded : public OS_Unix {
private:
static HashMap<String, void *> dynamic_symbol_lookup_table;
friend void register_dynamic_symbol(char *name, void *address);
AudioDriverCoreAudio audio_driver;
AppleEmbedded *apple_embedded = nullptr;
#ifdef SDL_ENABLED
JoypadSDL *joypad_sdl = nullptr;
#endif
MainLoop *main_loop = nullptr;
virtual void initialize_core() override;
virtual void initialize() override;
virtual void initialize_joypads() override;
virtual void set_main_loop(MainLoop *p_main_loop) override;
virtual MainLoop *get_main_loop() const override;
virtual void delete_main_loop() override;
virtual void finalize() override;
bool is_focused = false;
CGFloat _weight_to_ct(int p_weight) const;
CGFloat _stretch_to_ct(int p_stretch) const;
String _get_default_fontname(const String &p_font_name) const;
static _FORCE_INLINE_ String get_framework_executable(const String &p_path);
void deinitialize_modules();
mutable String remote_fs_dir;
public:
static OS_AppleEmbedded *get_singleton();
OS_AppleEmbedded();
~OS_AppleEmbedded();
void initialize_modules();
bool iterate();
void start();
virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
virtual Vector<String> get_system_fonts() const override;
virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override;
virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override;
virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override;
virtual Error close_dynamic_library(void *p_library_handle) override;
virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional = false) override;
virtual String get_distribution_name() const override;
virtual String get_version() const override;
virtual String get_model_name() const override;
virtual Error shell_open(const String &p_uri) override;
virtual String get_user_data_dir(const String &p_user_dir) const override;
virtual String get_cache_path() const override;
virtual String get_temp_path() const override;
virtual String get_resource_dir() const override;
virtual String get_bundle_resource_dir() const override;
virtual String get_locale() const override;
virtual String get_unique_id() const override;
virtual String get_processor_name() const override;
virtual void vibrate_handheld(int p_duration_ms = 500, float p_amplitude = -1.0) override;
virtual bool _check_internal_feature_support(const String &p_feature) override;
virtual Error setup_remote_filesystem(const String &p_server_host, int p_port, const String &p_password, String &r_project_path) override;
void on_focus_out();
void on_focus_in();
void on_enter_background();
void on_exit_background();
virtual Rect2 calculate_boot_screen_rect(const Size2 &p_window_size, const Size2 &p_imgrect_size) const override;
virtual bool request_permission(const String &p_name) override;
virtual Vector<String> get_granted_permissions() const override;
virtual String get_platform_string(PlatformString p_platform_string) const override {
switch (p_platform_string) {
case OS::PlatformString::PLATFORM_STRING_FILE_MANAGER_OPEN:
return ETR("Open in Files");
case OS::PlatformString::PLATFORM_STRING_FILE_MANAGER_SHOW:
return ETR("Show in Files");
default:
return OS::get_platform_string(p_platform_string);
}
}
};
#endif // APPLE_EMBEDDED_ENABLED

View file

@ -0,0 +1,865 @@
/**************************************************************************/
/* os_apple_embedded.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#import "os_apple_embedded.h"
#ifdef APPLE_EMBEDDED_ENABLED
#import "app_delegate_service.h"
#import "display_server_apple_embedded.h"
#import "godot_view_apple_embedded.h"
#import "godot_view_controller.h"
#include "core/config/engine.h"
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
#include "core/io/file_access.h"
#include "core/os/main_loop.h"
#include "core/os/os.h"
#include "core/profiling/profiling.h"
#import "drivers/apple/os_log_logger.h"
#ifdef SDL_ENABLED
#include "drivers/sdl/joypad_sdl.h"
#endif
#include "main/main.h"
#import <AVFoundation/AVFAudio.h>
#import <AudioToolbox/AudioServices.h>
#import <CoreText/CoreText.h>
#import <UIKit/UIKit.h>
#import <dlfcn.h>
#include <sys/sysctl.h>
#include <iterator>
#if defined(RD_ENABLED)
#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
#import <QuartzCore/CAMetalLayer.h>
#if defined(VULKAN_ENABLED)
#include "drivers/vulkan/godot_vulkan.h"
#endif // VULKAN_ENABLED
#endif
// Initialization order between compilation units is not guaranteed,
// so we use this as a hack to ensure certain code is called before
// everything else, but after all units are initialized.
typedef void (*init_callback)();
static init_callback *apple_init_callbacks = nullptr;
static int apple_embedded_platform_init_callbacks_count = 0;
static int apple_embedded_platform_init_callbacks_capacity = 0;
HashMap<String, void *> OS_AppleEmbedded::dynamic_symbol_lookup_table;
void add_apple_embedded_platform_init_callback(init_callback cb) {
if (apple_embedded_platform_init_callbacks_count == apple_embedded_platform_init_callbacks_capacity) {
void *new_ptr = realloc(apple_init_callbacks, sizeof(cb) * (apple_embedded_platform_init_callbacks_capacity + 32));
if (new_ptr) {
apple_init_callbacks = (init_callback *)(new_ptr);
apple_embedded_platform_init_callbacks_capacity += 32;
} else {
ERR_FAIL_MSG("Unable to allocate memory for extension callbacks.");
}
}
apple_init_callbacks[apple_embedded_platform_init_callbacks_count++] = cb;
}
void register_dynamic_symbol(char *name, void *address) {
OS_AppleEmbedded::dynamic_symbol_lookup_table[String(name)] = address;
}
Rect2 fit_keep_aspect_centered(const Vector2 &p_container, const Vector2 &p_rect) {
real_t available_ratio = p_container.width / p_container.height;
real_t fit_ratio = p_rect.width / p_rect.height;
Rect2 result;
if (fit_ratio < available_ratio) {
// Fit height - we'll have horizontal gaps
result.size.height = p_container.height;
result.size.width = p_container.height * fit_ratio;
result.position.y = 0;
result.position.x = (p_container.width - result.size.width) * 0.5f;
} else {
// Fit width - we'll have vertical gaps
result.size.width = p_container.width;
result.size.height = p_container.width / fit_ratio;
result.position.x = 0;
result.position.y = (p_container.height - result.size.height) * 0.5f;
}
return result;
}
Rect2 fit_keep_aspect_covered(const Vector2 &p_container, const Vector2 &p_rect) {
real_t available_ratio = p_container.width / p_container.height;
real_t fit_ratio = p_rect.width / p_rect.height;
Rect2 result;
if (fit_ratio < available_ratio) {
// Need to scale up to fit width, and crop height
result.size.width = p_container.width;
result.size.height = p_container.width / fit_ratio;
result.position.x = 0;
result.position.y = (p_container.height - result.size.height) * 0.5f;
} else {
// Need to scale up to fit height, and crop width
result.size.width = p_container.height * fit_ratio;
result.size.height = p_container.height;
result.position.x = (p_container.width - result.size.width) * 0.5f;
result.position.y = 0;
}
return result;
}
OS_AppleEmbedded *OS_AppleEmbedded::get_singleton() {
return (OS_AppleEmbedded *)OS::get_singleton();
}
OS_AppleEmbedded::OS_AppleEmbedded() {
for (int i = 0; i < apple_embedded_platform_init_callbacks_count; ++i) {
apple_init_callbacks[i]();
}
free(apple_init_callbacks);
apple_init_callbacks = nullptr;
apple_embedded_platform_init_callbacks_count = 0;
apple_embedded_platform_init_callbacks_capacity = 0;
main_loop = nullptr;
Vector<Logger *> loggers;
loggers.push_back(memnew(OsLogLogger(NSBundle.mainBundle.bundleIdentifier.UTF8String)));
_set_logger(memnew(CompositeLogger(loggers)));
AudioDriverManager::add_driver(&audio_driver);
}
OS_AppleEmbedded::~OS_AppleEmbedded() {}
void OS_AppleEmbedded::alert(const String &p_alert, const String &p_title) {
const CharString utf8_alert = p_alert.utf8();
const CharString utf8_title = p_title.utf8();
AppleEmbedded::alert(utf8_alert.get_data(), utf8_title.get_data());
}
void OS_AppleEmbedded::initialize_core() {
OS_Unix::initialize_core();
}
void OS_AppleEmbedded::initialize() {
initialize_core();
}
void OS_AppleEmbedded::initialize_joypads() {
#ifdef SDL_ENABLED
joypad_sdl = memnew(JoypadSDL());
if (joypad_sdl->initialize() != OK) {
ERR_PRINT("Couldn't initialize SDL joypad input driver.");
memdelete(joypad_sdl);
joypad_sdl = nullptr;
}
#endif
}
void OS_AppleEmbedded::initialize_modules() {
apple_embedded = memnew(AppleEmbedded);
Engine::get_singleton()->add_singleton(Engine::Singleton("AppleEmbedded", apple_embedded));
}
void OS_AppleEmbedded::deinitialize_modules() {
#ifdef SDL_ENABLED
if (joypad_sdl) {
memdelete(joypad_sdl);
}
#endif
if (apple_embedded) {
memdelete(apple_embedded);
}
}
void OS_AppleEmbedded::set_main_loop(MainLoop *p_main_loop) {
main_loop = p_main_loop;
}
MainLoop *OS_AppleEmbedded::get_main_loop() const {
return main_loop;
}
void OS_AppleEmbedded::delete_main_loop() {
if (main_loop) {
main_loop->finalize();
memdelete(main_loop);
}
main_loop = nullptr;
}
bool OS_AppleEmbedded::iterate() {
if (!main_loop) {
return true;
}
GodotProfileFrameMark;
GodotProfileZone("OS_AppleEmbedded::iterate");
if (DisplayServer::get_singleton()) {
DisplayServer::get_singleton()->process_events();
}
#ifdef SDL_ENABLED
if (joypad_sdl) {
joypad_sdl->process_events();
}
#endif
return Main::iteration();
}
void OS_AppleEmbedded::start() {
if (Main::start() == EXIT_SUCCESS) {
main_loop->initialize();
}
}
void OS_AppleEmbedded::finalize() {
deinitialize_modules();
// Already gets called
//delete_main_loop();
}
// MARK: Dynamic Libraries
_FORCE_INLINE_ String OS_AppleEmbedded::get_framework_executable(const String &p_path) {
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
// Read framework bundle to get executable name.
NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())];
NSBundle *bundle = [NSBundle bundleWithURL:url];
if (bundle) {
String exe_path = String::utf8([[bundle executablePath] UTF8String]);
if (da->file_exists(exe_path)) {
return exe_path;
}
}
// Try default executable name (invalid framework).
if (da->dir_exists(p_path) && da->file_exists(p_path.path_join(p_path.get_file().get_basename()))) {
return p_path.path_join(p_path.get_file().get_basename());
}
// Not a framework, try loading as .dylib.
return p_path;
}
Error OS_AppleEmbedded::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) {
if (p_path.length() == 0) {
// Static xcframework.
p_library_handle = RTLD_SELF;
if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
*p_data->r_resolved_path = p_path;
}
return OK;
}
String path = get_framework_executable(p_path);
if (!FileAccess::exists(path)) {
// Load .dylib or framework from within the executable path.
path = get_framework_executable(get_executable_path().get_base_dir().path_join(p_path.get_file()));
}
if (!FileAccess::exists(path)) {
// Load .dylib converted to framework from within the executable path.
path = get_framework_executable(get_executable_path().get_base_dir().path_join(p_path.get_file().get_basename() + ".framework"));
}
if (!FileAccess::exists(path)) {
// Load .dylib from within the executable path.
path = get_framework_executable(get_executable_path().get_base_dir().path_join(p_path.get_file().get_basename() + ".dylib"));
}
if (!FileAccess::exists(path)) {
// Load .dylib or framework from a standard iOS location.
path = get_framework_executable(get_executable_path().get_base_dir().path_join("Frameworks").path_join(p_path.get_file()));
}
if (!FileAccess::exists(path)) {
// Load .dylib converted to framework from a standard iOS location.
path = get_framework_executable(get_executable_path().get_base_dir().path_join("Frameworks").path_join(p_path.get_file().get_basename() + ".framework"));
}
if (!FileAccess::exists(path)) {
// Load .dylib from a standard iOS location.
path = get_framework_executable(get_executable_path().get_base_dir().path_join("Frameworks").path_join(p_path.get_file().get_basename() + ".dylib"));
}
if (!FileAccess::exists(path) && (p_path.ends_with(".a") || p_path.ends_with(".xcframework"))) {
path = String(); // Try loading static library.
} else {
ERR_FAIL_COND_V(!FileAccess::exists(path), ERR_FILE_NOT_FOUND);
}
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
*p_data->r_resolved_path = path;
}
return OK;
}
Error OS_AppleEmbedded::close_dynamic_library(void *p_library_handle) {
if (p_library_handle == RTLD_SELF) {
return OK;
}
return OS_Unix::close_dynamic_library(p_library_handle);
}
Error OS_AppleEmbedded::get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional) {
if (p_library_handle == RTLD_SELF) {
void **ptr = OS_AppleEmbedded::dynamic_symbol_lookup_table.getptr(p_name);
if (ptr) {
p_symbol_handle = *ptr;
return OK;
}
}
return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional);
}
String OS_AppleEmbedded::get_distribution_name() const {
return get_name();
}
String OS_AppleEmbedded::get_version() const {
NSOperatingSystemVersion ver = [NSProcessInfo processInfo].operatingSystemVersion;
return vformat("%d.%d.%d", (int64_t)ver.majorVersion, (int64_t)ver.minorVersion, (int64_t)ver.patchVersion);
}
String OS_AppleEmbedded::get_model_name() const {
String model = apple_embedded->get_model();
if (model != "") {
return model;
}
return OS_Unix::get_model_name();
}
Error OS_AppleEmbedded::shell_open(const String &p_uri) {
NSString *urlPath = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()];
NSURL *url = [NSURL URLWithString:urlPath];
if (![[UIApplication sharedApplication] canOpenURL:url]) {
return ERR_CANT_OPEN;
}
print_verbose(vformat("Opening URL %s", p_uri));
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
return OK;
}
String OS_AppleEmbedded::get_user_data_dir(const String &p_user_dir) const {
static String ret;
if (ret.is_empty()) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
if (paths && [paths count] >= 1) {
ret.append_utf8([[paths firstObject] UTF8String]);
}
}
return ret;
}
String OS_AppleEmbedded::get_cache_path() const {
static String ret;
if (ret.is_empty()) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
if (paths && [paths count] >= 1) {
ret.append_utf8([[paths firstObject] UTF8String]);
}
}
return ret;
}
String OS_AppleEmbedded::get_temp_path() const {
static String ret;
if (ret.is_empty()) {
NSURL *url = [NSURL fileURLWithPath:NSTemporaryDirectory()
isDirectory:YES];
if (url) {
ret = String::utf8([url.path UTF8String]);
ret = ret.trim_prefix("file://");
}
}
return ret;
}
String OS_AppleEmbedded::get_resource_dir() const {
#ifdef TOOLS_ENABLED
return OS_Unix::get_resource_dir();
#else
if (remote_fs_dir.is_empty()) {
return OS_Unix::get_resource_dir();
} else {
return remote_fs_dir;
}
#endif
}
String OS_AppleEmbedded::get_bundle_resource_dir() const {
NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"];
if (!str) {
return OS_Unix::get_bundle_resource_dir();
} else {
return String::utf8([str cStringUsingEncoding:NSUTF8StringEncoding]);
}
}
String OS_AppleEmbedded::get_locale() const {
NSString *preferredLanguage = [NSLocale preferredLanguages].firstObject;
if (preferredLanguage) {
return String::utf8([preferredLanguage UTF8String]).replace_char('-', '_');
}
NSString *localeIdentifier = [[NSLocale currentLocale] localeIdentifier];
return String::utf8([localeIdentifier UTF8String]).replace_char('-', '_');
}
String OS_AppleEmbedded::get_unique_id() const {
NSString *uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
return String::utf8([uuid UTF8String]);
}
struct _ModelInfo {
Vector<String> model;
String soc;
};
static const _ModelInfo _models[] = {
{ { "iPhone1,1", "iPhone1,2", "iPod1,1" }, "Samsung S5L8900" },
{ { "iPod2,1" }, "Samsung S5L8720" },
{ { "iPhone2,1" }, "Samsung S5L8920" },
{ { "iPod3,1" }, "Samsung S5L8922" },
{ { "iPhone3,1", "iPhone3,2", "iPhone3,3", "iPad1,1", "iPad1,2", "iPod4,1", "AppleTV2,1" }, "Apple A4" },
{ { "iPhone4,1", "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4", "iPad2,5", "iPad2,6", "iPad2,7", "iPod5,1", "AppleTV3,1", "AppleTV3,2" }, "Apple A5" },
{ { "iPad3,1", "iPad3,2", "iPad3,3" }, "Apple A5X" },
{ { "iPhone5,1", "iPhone5,2", "iPhone5,3", "iPhone5,4" }, "Apple A6" },
{ { "iPad3,4", "iPad3,5", "iPad3,6" }, "Apple A6X" },
{ { "iPhone6,1", "iPhone6,2", "iPad4,1", "iPad4,2", "iPad4,3", "iPad4,4", "iPad4,5", "iPad4,6", "iPad4,7", "iPad4,8", "iPad4,9" }, "Apple A7" },
{ { "iPhone7,1", "iPhone7,2", "iPad5,1", "iPad5,2", "iPod7,1", "AppleTV5,3" }, "Apple A8" },
{ { "iPad5,3", "iPad5,4" }, "Apple A8X" },
{ { "iPhone8,1", "iPhone8,2", "iPhone8,4", "iPad6,11", "iPad6,12" }, "Apple A9" },
{ { "iPad6,3", "iPad6,4", "iPad6,7", "iPad6,8" }, "Apple A9X" },
{ { "iPhone9,1", "iPhone9,2", "iPhone9,3", "iPhone9,4", "iPad7,5", "iPad7,6", "iPad7,11", "iPad7,12", "iPod9,1" }, "Apple A10 Fusion" },
{ { "iPad7,1", "iPad7,2", "iPad7,3", "iPad7,4", "AppleTV6,2" }, "Apple A10X Fusion" },
{ { "iPhone10,1", "iPhone10,2", "iPhone10,3", "iPhone10,4", "iPhone10,5", "iPhone10,6" }, "Apple A11 Bionic" },
{ { "iPhone11,2", "iPhone11,4", "iPhone11,6", "iPhone11,8", "iPad11,1", "iPad11,2", "iPad11,3", "iPad11,4", "iPad11,6", "iPad11,7", "AppleTV11,1" }, "Apple A12 Bionic" },
{ { "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4", "iPad8,5", "iPad8,6", "iPad8,7", "iPad8,8" }, "Apple A12X Bionic" },
{ { "iPad8,9", "iPad8,10", "iPad8,11", "iPad8,12" }, "Apple A12Z Bionic" },
{ { "iPhone12,1", "iPhone12,3", "iPhone12,5", "iPhone12,8", "iPad12,1", "iPad12,2" }, "Apple A13 Bionic" },
{ { "iPhone13,1", "iPhone13,2", "iPhone13,3", "iPhone13,4", "iPad13,1", "iPad13,2", "iPad13,18", "iPad13,19" }, "Apple A14 Bionic" },
{ { "iPad13,4", "iPad13,5", "iPad13,6", "iPad13,7", "iPad13,8", "iPad13,9", "iPad13,10", "iPad13,11", "iPad13,16", "iPad13,17" }, "Apple M1" },
{ { "iPhone14,2", "iPhone14,3", "iPhone14,4", "iPhone14,5", "iPhone14,6", "iPhone14,7", "iPhone14,8", "iPad14,1", "iPad14,2", "AppleTV14,1" }, "Apple A15 Bionic" },
{ { "iPhone15,2", "iPhone15,3", "iPhone15,4", "iPhone15,5", "iPad15,7", "iPad15,8" }, "Apple A16 Bionic" },
{ { "iPad14,3", "iPad14,4", "iPad14,5", "iPad14,6", "iPad14,8", "iPad14,9", "iPad14,10", "iPad14,11", "RealityDevice14,1" }, "Apple M2" },
{ { "iPhone16,1", "iPhone16,2", "iPad16,1", "iPad16,2" }, "Apple A17 Pro" },
{ { "iPad15,3", "iPad15,4", "iPad15,5", "iPad15,6" }, "Apple M3" },
{ { "iPad16,3", "iPad16,4", "iPad16,5", "iPad16,6" }, "Apple M4" },
{ { "iPad17,1", "iPad17,2", "iPad17,3", "iPad17,4", "RealityDevice17,1" }, "Apple M5" },
{ { "iPhone17,3", "iPhone17,4", "iPhone17,5" }, "Apple A18" },
{ { "iPhone17,1", "iPhone17,2" }, "Apple A18 Pro" },
{ { "iPhone18,3" }, "Apple A19" },
{ { "iPhone18,1", "iPhone18,2", "iPhone18,4" }, "Apple A19 Pro" },
};
String OS_AppleEmbedded::get_processor_name() const {
#if defined(IOS_SIMULATOR) || defined(VISIONOS_SIMULATOR)
return "Simulator";
#else
if (apple_embedded) {
String model = apple_embedded->get_model();
for (unsigned int i = 0; i < std::size(_models); i++) {
for (const String &m : _models[i].model) {
if (model.contains(m)) {
return _models[i].soc;
}
}
}
}
#endif
return OS::get_processor_name();
}
Vector<String> OS_AppleEmbedded::get_system_fonts() const {
HashSet<String> font_names;
CFArrayRef fonts = CTFontManagerCopyAvailableFontFamilyNames();
if (fonts) {
for (CFIndex i = 0; i < CFArrayGetCount(fonts); i++) {
CFStringRef cf_name = (CFStringRef)CFArrayGetValueAtIndex(fonts, i);
if (cf_name && (CFStringGetLength(cf_name) > 0) && (CFStringCompare(cf_name, CFSTR("LastResort"), kCFCompareCaseInsensitive) != kCFCompareEqualTo) && (CFStringGetCharacterAtIndex(cf_name, 0) != '.')) {
NSString *ns_name = (__bridge NSString *)cf_name;
font_names.insert(String::utf8([ns_name UTF8String]));
}
}
CFRelease(fonts);
}
Vector<String> ret;
for (const String &E : font_names) {
ret.push_back(E);
}
return ret;
}
String OS_AppleEmbedded::_get_default_fontname(const String &p_font_name) const {
String font_name = p_font_name;
if (font_name.to_lower() == "sans-serif") {
font_name = "Helvetica";
} else if (font_name.to_lower() == "serif") {
font_name = "Times";
} else if (font_name.to_lower() == "monospace") {
font_name = "Courier";
} else if (font_name.to_lower() == "fantasy") {
font_name = "Papyrus";
} else if (font_name.to_lower() == "cursive") {
font_name = "Apple Chancery";
};
return font_name;
}
CGFloat OS_AppleEmbedded::_weight_to_ct(int p_weight) const {
if (p_weight < 150) {
return -0.80;
} else if (p_weight < 250) {
return -0.60;
} else if (p_weight < 350) {
return -0.40;
} else if (p_weight < 450) {
return 0.0;
} else if (p_weight < 550) {
return 0.23;
} else if (p_weight < 650) {
return 0.30;
} else if (p_weight < 750) {
return 0.40;
} else if (p_weight < 850) {
return 0.56;
} else if (p_weight < 925) {
return 0.62;
} else {
return 1.00;
}
}
CGFloat OS_AppleEmbedded::_stretch_to_ct(int p_stretch) const {
if (p_stretch < 56) {
return -0.5;
} else if (p_stretch < 69) {
return -0.37;
} else if (p_stretch < 81) {
return -0.25;
} else if (p_stretch < 93) {
return -0.13;
} else if (p_stretch < 106) {
return 0.0;
} else if (p_stretch < 137) {
return 0.13;
} else if (p_stretch < 144) {
return 0.25;
} else if (p_stretch < 162) {
return 0.37;
} else {
return 0.5;
}
}
Vector<String> OS_AppleEmbedded::get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale, const String &p_script, int p_weight, int p_stretch, bool p_italic) const {
Vector<String> ret;
String font_name = _get_default_fontname(p_font_name);
CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name.utf8().get_data(), kCFStringEncodingUTF8);
CTFontSymbolicTraits traits = 0;
if (p_weight >= 700) {
traits |= kCTFontBoldTrait;
}
if (p_italic) {
traits |= kCTFontItalicTrait;
}
if (p_stretch < 100) {
traits |= kCTFontCondensedTrait;
} else if (p_stretch > 100) {
traits |= kCTFontExpandedTrait;
}
CFNumberRef sym_traits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &traits);
CFMutableDictionaryRef traits_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
CFDictionaryAddValue(traits_dict, kCTFontSymbolicTrait, sym_traits);
CGFloat weight = _weight_to_ct(p_weight);
CFNumberRef font_weight = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &weight);
CFDictionaryAddValue(traits_dict, kCTFontWeightTrait, font_weight);
CGFloat stretch = _stretch_to_ct(p_stretch);
CFNumberRef font_stretch = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &stretch);
CFDictionaryAddValue(traits_dict, kCTFontWidthTrait, font_stretch);
CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, name);
CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits_dict);
CTFontDescriptorRef font = CTFontDescriptorCreateWithAttributes(attributes);
if (font) {
CTFontRef family = CTFontCreateWithFontDescriptor(font, 0, nullptr);
if (family) {
CFStringRef string = CFStringCreateWithCString(kCFAllocatorDefault, p_text.utf8().get_data(), kCFStringEncodingUTF8);
CFRange range = CFRangeMake(0, CFStringGetLength(string));
CTFontRef fallback_family = CTFontCreateForString(family, string, range);
if (fallback_family) {
CTFontDescriptorRef fallback_font = CTFontCopyFontDescriptor(fallback_family);
if (fallback_font) {
CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(fallback_font, kCTFontURLAttribute);
if (url) {
NSString *font_path = [NSString stringWithString:[(__bridge NSURL *)url path]];
ret.push_back(String::utf8([font_path UTF8String]));
CFRelease(url);
}
CFRelease(fallback_font);
}
CFRelease(fallback_family);
}
CFRelease(string);
CFRelease(family);
}
CFRelease(font);
}
CFRelease(attributes);
CFRelease(traits_dict);
CFRelease(sym_traits);
CFRelease(font_stretch);
CFRelease(font_weight);
CFRelease(name);
return ret;
}
String OS_AppleEmbedded::get_system_font_path(const String &p_font_name, int p_weight, int p_stretch, bool p_italic) const {
String ret;
String font_name = _get_default_fontname(p_font_name);
CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name.utf8().get_data(), kCFStringEncodingUTF8);
CTFontSymbolicTraits traits = 0;
if (p_weight >= 700) {
traits |= kCTFontBoldTrait;
}
if (p_italic) {
traits |= kCTFontItalicTrait;
}
if (p_stretch < 100) {
traits |= kCTFontCondensedTrait;
} else if (p_stretch > 100) {
traits |= kCTFontExpandedTrait;
}
CFNumberRef sym_traits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &traits);
CFMutableDictionaryRef traits_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
CFDictionaryAddValue(traits_dict, kCTFontSymbolicTrait, sym_traits);
CGFloat weight = _weight_to_ct(p_weight);
CFNumberRef font_weight = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &weight);
CFDictionaryAddValue(traits_dict, kCTFontWeightTrait, font_weight);
CGFloat stretch = _stretch_to_ct(p_stretch);
CFNumberRef font_stretch = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &stretch);
CFDictionaryAddValue(traits_dict, kCTFontWidthTrait, font_stretch);
CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, name);
CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits_dict);
CTFontDescriptorRef font = CTFontDescriptorCreateWithAttributes(attributes);
if (font) {
CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(font, kCTFontURLAttribute);
if (url) {
NSString *font_path = [NSString stringWithString:[(__bridge NSURL *)url path]];
ret = String::utf8([font_path UTF8String]);
CFRelease(url);
}
CFRelease(font);
}
CFRelease(attributes);
CFRelease(traits_dict);
CFRelease(sym_traits);
CFRelease(font_stretch);
CFRelease(font_weight);
CFRelease(name);
return ret;
}
void OS_AppleEmbedded::vibrate_handheld(int p_duration_ms, float p_amplitude) {
if (apple_embedded->supports_haptic_engine()) {
if (p_amplitude > 0.0) {
p_amplitude = CLAMP(p_amplitude, 0.0, 1.0);
}
apple_embedded->vibrate_haptic_engine((float)p_duration_ms / 1000.f, p_amplitude);
} else {
// iOS <13 does not support duration for vibration
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
}
}
bool OS_AppleEmbedded::_check_internal_feature_support(const String &p_feature) {
if (p_feature == "system_fonts") {
return true;
}
if (p_feature == "mobile") {
return true;
}
return false;
}
Error OS_AppleEmbedded::setup_remote_filesystem(const String &p_server_host, int p_port, const String &p_password, String &r_project_path) {
r_project_path = OS::get_user_data_dir();
Error err = OS_Unix::setup_remote_filesystem(p_server_host, p_port, p_password, r_project_path);
if (err == OK) {
remote_fs_dir = r_project_path;
}
return err;
}
void OS_AppleEmbedded::on_focus_out() {
if (is_focused) {
is_focused = false;
if (DisplayServerAppleEmbedded::get_singleton()) {
DisplayServerAppleEmbedded::get_singleton()->send_window_event(DisplayServerEnums::WINDOW_EVENT_FOCUS_OUT);
}
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);
}
[GDTAppDelegateService.viewController.godotView stopRendering];
audio_driver.stop();
}
}
void OS_AppleEmbedded::on_focus_in() {
if (!is_focused) {
is_focused = true;
if (DisplayServerAppleEmbedded::get_singleton()) {
DisplayServerAppleEmbedded::get_singleton()->send_window_event(DisplayServerEnums::WINDOW_EVENT_FOCUS_IN);
}
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN);
}
[GDTAppDelegateService.viewController.godotView startRendering];
audio_driver.start();
}
}
void OS_AppleEmbedded::on_enter_background() {
// Do not check for is_focused, because on_focus_out will always be fired first by applicationWillResignActive.
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_PAUSED);
}
on_focus_out();
}
void OS_AppleEmbedded::on_exit_background() {
if (!is_focused) {
on_focus_in();
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_RESUMED);
}
}
}
Rect2 OS_AppleEmbedded::calculate_boot_screen_rect(const Size2 &p_window_size, const Size2 &p_imgrect_size) const {
String scalemodestr = GLOBAL_GET("ios/launch_screen_image_mode");
if (scalemodestr == "scaleAspectFit") {
return fit_keep_aspect_centered(p_window_size, p_imgrect_size);
} else if (scalemodestr == "scaleAspectFill") {
return fit_keep_aspect_covered(p_window_size, p_imgrect_size);
} else if (scalemodestr == "scaleToFill") {
return Rect2(Point2(), p_window_size);
} else if (scalemodestr == "center") {
return OS_Unix::calculate_boot_screen_rect(p_window_size, p_imgrect_size);
} else {
WARN_PRINT(vformat("Boot screen scale mode mismatch between iOS and Godot: %s not supported", scalemodestr));
return OS_Unix::calculate_boot_screen_rect(p_window_size, p_imgrect_size);
}
}
bool OS_AppleEmbedded::request_permission(const String &p_name) {
if (p_name == "appleembedded.permission.AUDIO_RECORD") {
if (@available(iOS 17.0, *)) {
AVAudioApplicationRecordPermission permission = [AVAudioApplication sharedInstance].recordPermission;
if (permission == AVAudioApplicationRecordPermissionGranted) {
// Permission already granted, you can start recording.
return true;
} else if (permission == AVAudioApplicationRecordPermissionDenied) {
// Permission denied, or not yet granted.
return false;
} else {
// Request the permission, but for now return false as documented.
[AVAudioApplication requestRecordPermissionWithCompletionHandler:^(BOOL granted) {
get_main_loop()->emit_signal(SNAME("on_request_permissions_result"), p_name, granted);
}];
}
}
}
return false;
}
Vector<String> OS_AppleEmbedded::get_granted_permissions() const {
Vector<String> ret;
if (@available(iOS 17.0, *)) {
if ([AVAudioApplication sharedInstance].recordPermission == AVAudioApplicationRecordPermissionGranted) {
ret.push_back("appleembedded.permission.AUDIO_RECORD");
}
}
return ret;
}
#endif // APPLE_EMBEDDED_ENABLED

View file

@ -0,0 +1,44 @@
/**************************************************************************/
/* platform_config.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include <alloca.h>
#define PLATFORM_THREAD_OVERRIDE
#define PTHREAD_RENAME_SELF
#define _weakify(var) __weak typeof(var) GDWeak_##var = var;
#define _strongify(var) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
__strong typeof(var) var = GDWeak_##var; \
_Pragma("clang diagnostic pop")

View file

@ -0,0 +1,55 @@
/**************************************************************************/
/* rendering_context_driver_vulkan_apple_embedded.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifdef VULKAN_ENABLED
#include "drivers/vulkan/rendering_context_driver_vulkan.h"
#import <QuartzCore/CAMetalLayer.h>
class RenderingContextDriverVulkanAppleEmbedded : public RenderingContextDriverVulkan {
private:
virtual const char *_get_platform_surface_extension() const override final;
protected:
SurfaceID surface_create(const void *p_platform_data) override final;
public:
struct WindowPlatformData {
CAMetalLayer *const *layer_ptr;
};
RenderingContextDriverVulkanAppleEmbedded();
~RenderingContextDriverVulkanAppleEmbedded();
};
#endif // VULKAN_ENABLED

View file

@ -0,0 +1,69 @@
/**************************************************************************/
/* rendering_context_driver_vulkan_apple_embedded.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#import "rendering_context_driver_vulkan_apple_embedded.h"
#ifdef VULKAN_ENABLED
#ifdef USE_VOLK
#include <volk.h>
#else
#include <vulkan/vulkan_metal.h>
#endif
const char *RenderingContextDriverVulkanAppleEmbedded::_get_platform_surface_extension() const {
return VK_EXT_METAL_SURFACE_EXTENSION_NAME;
}
RenderingContextDriver::SurfaceID RenderingContextDriverVulkanAppleEmbedded::surface_create(const void *p_platform_data) {
const WindowPlatformData *wpd = (const WindowPlatformData *)(p_platform_data);
VkMetalSurfaceCreateInfoEXT create_info = {};
create_info.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
create_info.pLayer = *wpd->layer_ptr;
VkSurfaceKHR vk_surface = VK_NULL_HANDLE;
VkResult err = vkCreateMetalSurfaceEXT(instance_get(), &create_info, get_allocation_callbacks(VK_OBJECT_TYPE_SURFACE_KHR), &vk_surface);
ERR_FAIL_COND_V(err != VK_SUCCESS, SurfaceID());
Surface *surface = memnew(Surface);
surface->vk_surface = vk_surface;
return SurfaceID(surface);
}
RenderingContextDriverVulkanAppleEmbedded::RenderingContextDriverVulkanAppleEmbedded() {
// Does nothing.
}
RenderingContextDriverVulkanAppleEmbedded::~RenderingContextDriverVulkanAppleEmbedded() {
// Does nothing.
}
#endif // VULKAN_ENABLED

View file

@ -0,0 +1,61 @@
/**************************************************************************/
/* tts_apple_embedded.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/string/ustring.h"
#include "core/templates/hash_map.h"
#include "core/templates/list.h"
#include "core/variant/array.h"
#if __has_include(<AVFAudio/AVSpeechSynthesis.h>)
#import <AVFAudio/AVSpeechSynthesis.h>
#else
#import <AVFoundation/AVFoundation.h>
#endif
struct TTSUtterance;
@interface GDTTTS : NSObject <AVSpeechSynthesizerDelegate> {
bool speaking;
HashMap<id, int64_t> ids;
AVSpeechSynthesizer *av_synth;
List<TTSUtterance> queue;
}
- (void)pauseSpeaking;
- (void)resumeSpeaking;
- (void)stopSpeaking;
- (bool)isSpeaking;
- (bool)isPaused;
- (void)speak:(const String &)text voice:(const String &)voice volume:(int)volume pitch:(float)pitch rate:(float)rate utterance_id:(int64_t)utterance_id interrupt:(bool)interrupt;
- (Array)getVoices;
@end

View file

@ -0,0 +1,165 @@
/**************************************************************************/
/* tts_apple_embedded.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#import "tts_apple_embedded.h"
#include "servers/display/display_server.h"
@implementation GDTTTS
- (id)init {
self = [super init];
self->speaking = false;
self->av_synth = [[AVSpeechSynthesizer alloc] init];
[self->av_synth setDelegate:self];
print_verbose("Text-to-Speech: AVSpeechSynthesizer initialized.");
return self;
}
- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth willSpeakRangeOfSpeechString:(NSRange)characterRange utterance:(AVSpeechUtterance *)utterance {
NSString *string = [utterance speechString];
// Convert from UTF-16 to UTF-32 position.
int pos = 0;
for (NSUInteger i = 0; i < MIN(characterRange.location, string.length); i++) {
unichar c = [string characterAtIndex:i];
if ((c & 0xfffffc00) == 0xd800) {
i++;
}
pos++;
}
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServerEnums::TTS_UTTERANCE_BOUNDARY, ids[utterance], pos);
}
- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth didCancelSpeechUtterance:(AVSpeechUtterance *)utterance {
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServerEnums::TTS_UTTERANCE_CANCELED, ids[utterance]);
ids.erase(utterance);
speaking = false;
[self update];
}
- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth didFinishSpeechUtterance:(AVSpeechUtterance *)utterance {
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServerEnums::TTS_UTTERANCE_ENDED, ids[utterance]);
ids.erase(utterance);
speaking = false;
[self update];
}
- (void)update {
if (!speaking && queue.size() > 0) {
TTSUtterance &message = queue.front()->get();
AVSpeechUtterance *new_utterance = [[AVSpeechUtterance alloc] initWithString:[NSString stringWithUTF8String:message.text.utf8().get_data()]];
[new_utterance setVoice:[AVSpeechSynthesisVoice voiceWithIdentifier:[NSString stringWithUTF8String:message.voice.utf8().get_data()]]];
if (message.rate > 1.f) {
[new_utterance setRate:Math::remap(message.rate, 1.f, 10.f, AVSpeechUtteranceDefaultSpeechRate, AVSpeechUtteranceMaximumSpeechRate)];
} else if (message.rate < 1.f) {
[new_utterance setRate:Math::remap(message.rate, 0.1f, 1.f, AVSpeechUtteranceMinimumSpeechRate, AVSpeechUtteranceDefaultSpeechRate)];
}
[new_utterance setPitchMultiplier:message.pitch];
[new_utterance setVolume:(Math::remap(message.volume, 0.f, 100.f, 0.f, 1.f))];
ids[new_utterance] = message.id;
[av_synth speakUtterance:new_utterance];
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServerEnums::TTS_UTTERANCE_STARTED, message.id);
queue.pop_front();
speaking = true;
}
}
- (void)pauseSpeaking {
[av_synth pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate];
}
- (void)resumeSpeaking {
[av_synth continueSpeaking];
}
- (void)stopSpeaking {
for (TTSUtterance &message : queue) {
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServerEnums::TTS_UTTERANCE_CANCELED, message.id);
}
queue.clear();
[av_synth stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];
speaking = false;
}
- (bool)isSpeaking {
return speaking || (queue.size() > 0);
}
- (bool)isPaused {
return [av_synth isPaused];
}
- (void)speak:(const String &)text voice:(const String &)voice volume:(int)volume pitch:(float)pitch rate:(float)rate utterance_id:(int64_t)utterance_id interrupt:(bool)interrupt {
if (interrupt) {
[self stopSpeaking];
}
if (text.is_empty()) {
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServerEnums::TTS_UTTERANCE_CANCELED, utterance_id);
return;
}
TTSUtterance message;
message.text = text;
message.voice = voice;
message.volume = CLAMP(volume, 0, 100);
message.pitch = CLAMP(pitch, 0.f, 2.f);
message.rate = CLAMP(rate, 0.1f, 10.f);
message.id = utterance_id;
queue.push_back(message);
if ([self isPaused]) {
[self resumeSpeaking];
} else {
[self update];
}
}
- (Array)getVoices {
Array list;
for (AVSpeechSynthesisVoice *voice in [AVSpeechSynthesisVoice speechVoices]) {
NSString *voiceIdentifierString = [voice identifier];
NSString *voiceLocaleIdentifier = [voice language];
NSString *voiceName = [voice name];
Dictionary voice_d;
voice_d["name"] = String::utf8([voiceName UTF8String]);
voice_d["id"] = String::utf8([voiceIdentifierString UTF8String]);
voice_d["language"] = String::utf8([voiceLocaleIdentifier UTF8String]);
list.push_back(voice_d);
}
return list;
}
@end

View file

@ -0,0 +1,45 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
env_backtrace = env.Clone()
# Thirdparty source files
thirdparty_obj = []
thirdparty_dir = "#thirdparty/libbacktrace/"
thirdparty_sources = [
"atomic.c",
"dwarf.c",
"fileline.c",
"posix.c",
"print.c",
"sort.c",
"state.c",
"backtrace.c",
"simple.c",
"pecoff.c",
"read.c",
"alloc.c",
]
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
env_backtrace.Prepend(CPPPATH=[thirdparty_dir])
env_thirdparty = env_backtrace.Clone()
env_thirdparty.disable_warnings()
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
env.drivers_sources += thirdparty_obj
# Godot source files
driver_obj = []
env_backtrace.add_source_files(driver_obj, "*.cpp")
env.drivers_sources += driver_obj
# Needed to force rebuilding the driver files when the thirdparty library is updated.
env.Depends(driver_obj, thirdparty_obj)

View file

@ -0,0 +1,7 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
# Driver source files
env.add_source_files(env.drivers_sources, "*.mm")

View file

@ -0,0 +1,127 @@
/**************************************************************************/
/* audio_driver_coreaudio.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifdef COREAUDIO_ENABLED
#include "servers/audio/audio_server.h"
#import <AudioUnit/AudioUnit.h>
#ifdef MACOS_ENABLED
#import <CoreAudio/AudioHardware.h>
#else
#import <AVFoundation/AVFoundation.h>
#endif
class AudioDriverCoreAudio : public AudioDriver {
AudioComponentInstance audio_unit = nullptr;
AudioComponentInstance input_unit = nullptr;
bool active = false;
Mutex mutex;
String output_device_name = "Default";
String input_device_name = "Default";
int mix_rate = 0;
int capture_mix_rate = 0;
unsigned int channels = 2;
unsigned int capture_channels = 2;
unsigned int buffer_frames = 0;
unsigned int capture_buffer_frames = 0;
Vector<int32_t> samples_in;
unsigned int buffer_size = 0;
#ifdef MACOS_ENABLED
PackedStringArray _get_device_list(bool capture = false);
void _set_device(const String &output_device, bool capture = false);
static OSStatus input_device_address_cb(AudioObjectID inObjectID,
UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses,
void *inClientData);
static OSStatus output_device_address_cb(AudioObjectID inObjectID,
UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses,
void *inClientData);
#endif
static OSStatus output_callback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList *ioData);
static OSStatus input_callback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList *ioData);
Error init_input_device();
void finish_input_device();
public:
virtual const char *get_name() const override {
return "CoreAudio";
}
virtual Error init() override;
virtual void start() override;
virtual int get_mix_rate() const override;
virtual int get_input_mix_rate() const override;
virtual SpeakerMode get_speaker_mode() const override;
virtual void lock() override;
virtual void unlock() override;
virtual void finish() override;
#ifdef MACOS_ENABLED
virtual PackedStringArray get_output_device_list() override;
virtual String get_output_device() override;
virtual void set_output_device(const String &p_name) override;
virtual PackedStringArray get_input_device_list() override;
virtual String get_input_device() override;
virtual void set_input_device(const String &p_name) override;
#endif
virtual Error input_start() override;
virtual Error input_stop() override;
bool try_lock();
void stop();
AudioDriverCoreAudio();
~AudioDriverCoreAudio() {}
};
#endif // COREAUDIO_ENABLED

View file

@ -0,0 +1,730 @@
/**************************************************************************/
/* audio_driver_coreaudio.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#import "audio_driver_coreaudio.h"
#ifdef COREAUDIO_ENABLED
#include "core/config/engine.h"
#include "core/config/project_settings.h"
#include "core/math/math_funcs_binary.h"
#include "core/os/os.h"
#define kOutputBus 0
#define kInputBus 1
#ifdef MACOS_ENABLED
OSStatus AudioDriverCoreAudio::input_device_address_cb(AudioObjectID inObjectID,
UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses,
void *inClientData) {
AudioDriverCoreAudio *driver = static_cast<AudioDriverCoreAudio *>(inClientData);
// If our selected input device is the Default, call set_input_device to update the
// kAudioOutputUnitProperty_CurrentDevice property
if (driver->input_device_name == "Default") {
driver->set_input_device("Default");
}
return noErr;
}
OSStatus AudioDriverCoreAudio::output_device_address_cb(AudioObjectID inObjectID,
UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses,
void *inClientData) {
AudioDriverCoreAudio *driver = static_cast<AudioDriverCoreAudio *>(inClientData);
// If our selected output device is the Default call set_output_device to update the
// kAudioOutputUnitProperty_CurrentDevice property
if (driver->output_device_name == "Default") {
driver->set_output_device("Default");
}
return noErr;
}
// Switch to kAudioObjectPropertyElementMain everywhere to remove deprecated warnings.
#if (TARGET_OS_OSX && __MAC_OS_X_VERSION_MAX_ALLOWED < 120000) || (TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED < 150000)
#define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster
#endif
#endif
Error AudioDriverCoreAudio::init() {
AudioComponentDescription desc;
memset(&desc, 0, sizeof(desc));
desc.componentType = kAudioUnitType_Output;
#ifdef MACOS_ENABLED
desc.componentSubType = kAudioUnitSubType_HALOutput;
#else
desc.componentSubType = kAudioUnitSubType_RemoteIO;
#endif
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
AudioComponent comp = AudioComponentFindNext(nullptr, &desc);
ERR_FAIL_NULL_V(comp, FAILED);
OSStatus result = AudioComponentInstanceNew(comp, &audio_unit);
ERR_FAIL_COND_V(result != noErr, FAILED);
#ifdef MACOS_ENABLED
AudioObjectPropertyAddress prop;
prop.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
prop.mScope = kAudioObjectPropertyScopeGlobal;
prop.mElement = kAudioObjectPropertyElementMain;
result = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &prop, &output_device_address_cb, this);
ERR_FAIL_COND_V(result != noErr, FAILED);
#endif
AudioStreamBasicDescription strdesc;
memset(&strdesc, 0, sizeof(strdesc));
UInt32 size = sizeof(strdesc);
result = AudioUnitGetProperty(audio_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kOutputBus, &strdesc, &size);
ERR_FAIL_COND_V(result != noErr, FAILED);
switch (strdesc.mChannelsPerFrame) {
case 2: // Stereo
case 4: // Surround 3.1
case 6: // Surround 5.1
case 8: // Surround 7.1
channels = strdesc.mChannelsPerFrame;
break;
default:
// Unknown number of channels, default to stereo
channels = 2;
break;
}
#ifdef MACOS_ENABLED
AudioDeviceID device_id;
UInt32 dev_id_size = sizeof(AudioDeviceID);
AudioObjectPropertyAddress property_dev_id = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &property_dev_id, 0, nullptr, &dev_id_size, &device_id);
ERR_FAIL_COND_V(result != noErr, FAILED);
double hw_mix_rate;
UInt32 hw_mix_rate_size = sizeof(hw_mix_rate);
AudioObjectPropertyAddress property_sr = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain };
result = AudioObjectGetPropertyData(device_id, &property_sr, 0, nullptr, &hw_mix_rate_size, &hw_mix_rate);
ERR_FAIL_COND_V(result != noErr, FAILED);
#else
double hw_mix_rate = [AVAudioSession sharedInstance].sampleRate;
#endif
mix_rate = hw_mix_rate;
memset(&strdesc, 0, sizeof(strdesc));
strdesc.mFormatID = kAudioFormatLinearPCM;
strdesc.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
strdesc.mChannelsPerFrame = channels;
strdesc.mSampleRate = mix_rate;
strdesc.mFramesPerPacket = 1;
strdesc.mBitsPerChannel = 16;
strdesc.mBytesPerFrame = strdesc.mBitsPerChannel * strdesc.mChannelsPerFrame / 8;
strdesc.mBytesPerPacket = strdesc.mBytesPerFrame * strdesc.mFramesPerPacket;
result = AudioUnitSetProperty(audio_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &strdesc, sizeof(strdesc));
ERR_FAIL_COND_V(result != noErr, FAILED);
uint32_t latency = Engine::get_singleton()->get_audio_output_latency();
// Sample rate is independent of channels (ref: https://stackoverflow.com/questions/11048825/audio-sample-frequency-rely-on-channels)
buffer_frames = Math::closest_power_of_2(latency * (uint32_t)mix_rate / (uint32_t)1000);
#ifdef MACOS_ENABLED
result = AudioUnitSetProperty(audio_unit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global, kOutputBus, &buffer_frames, sizeof(UInt32));
ERR_FAIL_COND_V(result != noErr, FAILED);
#endif
unsigned int buffer_size = buffer_frames * channels;
samples_in.resize(buffer_size);
print_verbose("CoreAudio: detected " + itos(channels) + " channels");
print_verbose("CoreAudio: output sampling rate: " + itos(mix_rate) + " Hz");
print_verbose("CoreAudio: output audio buffer frames: " + itos(buffer_frames) + " calculated latency: " + itos(buffer_frames * 1000 / mix_rate) + "ms");
AURenderCallbackStruct callback;
memset(&callback, 0, sizeof(AURenderCallbackStruct));
callback.inputProc = &AudioDriverCoreAudio::output_callback;
callback.inputProcRefCon = this;
result = AudioUnitSetProperty(audio_unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, kOutputBus, &callback, sizeof(callback));
ERR_FAIL_COND_V(result != noErr, FAILED);
result = AudioUnitInitialize(audio_unit);
ERR_FAIL_COND_V(result != noErr, FAILED);
if (GLOBAL_GET("audio/driver/enable_input")) {
return init_input_device();
}
return OK;
}
OSStatus AudioDriverCoreAudio::output_callback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList *ioData) {
AudioDriverCoreAudio *ad = static_cast<AudioDriverCoreAudio *>(inRefCon);
if (!ad->active || !ad->try_lock()) {
for (unsigned int i = 0; i < ioData->mNumberBuffers; i++) {
AudioBuffer *abuf = &ioData->mBuffers[i];
memset(abuf->mData, 0, abuf->mDataByteSize);
}
return 0;
}
ad->start_counting_ticks();
for (unsigned int i = 0; i < ioData->mNumberBuffers; i++) {
AudioBuffer *abuf = &ioData->mBuffers[i];
unsigned int frames_left = inNumberFrames;
int16_t *out = (int16_t *)abuf->mData;
while (frames_left) {
unsigned int frames = MIN(frames_left, ad->buffer_frames);
ad->audio_server_process(frames, ad->samples_in.ptrw());
for (unsigned int j = 0; j < frames * ad->channels; j++) {
out[j] = ad->samples_in[j] >> 16;
}
frames_left -= frames;
out += frames * ad->channels;
}
}
ad->stop_counting_ticks();
ad->unlock();
return 0;
}
OSStatus AudioDriverCoreAudio::input_callback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList *ioData) {
AudioDriverCoreAudio *ad = static_cast<AudioDriverCoreAudio *>(inRefCon);
if (!ad->active) {
return 0;
}
ad->lock();
ad->start_counting_ticks();
AudioBufferList bufferList;
bufferList.mNumberBuffers = 1;
bufferList.mBuffers[0].mData = nullptr;
bufferList.mBuffers[0].mNumberChannels = ad->capture_channels;
bufferList.mBuffers[0].mDataByteSize = ad->buffer_size * sizeof(int16_t);
OSStatus result = AudioUnitRender(ad->input_unit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &bufferList);
if (result == noErr) {
int16_t *data = (int16_t *)bufferList.mBuffers[0].mData;
for (unsigned int i = 0; i < inNumberFrames * ad->capture_channels; i++) {
int32_t sample = data[i] << 16;
ad->input_buffer_write(sample);
if (ad->capture_channels == 1) {
// In case input device is single channel convert it to Stereo
ad->input_buffer_write(sample);
}
}
} else {
ERR_PRINT("AudioUnitRender failed, code: " + itos(result));
}
ad->stop_counting_ticks();
ad->unlock();
return result;
}
void AudioDriverCoreAudio::start() {
if (!active && audio_unit != nullptr) {
OSStatus result = AudioOutputUnitStart(audio_unit);
if (result != noErr) {
ERR_PRINT("AudioOutputUnitStart failed, code: " + itos(result));
} else {
active = true;
}
}
}
void AudioDriverCoreAudio::stop() {
if (active) {
OSStatus result = AudioOutputUnitStop(audio_unit);
if (result != noErr) {
ERR_PRINT("AudioOutputUnitStop failed, code: " + itos(result));
} else {
active = false;
}
}
}
int AudioDriverCoreAudio::get_mix_rate() const {
return mix_rate;
}
int AudioDriverCoreAudio::get_input_mix_rate() const {
return capture_mix_rate;
}
AudioDriver::SpeakerMode AudioDriverCoreAudio::get_speaker_mode() const {
return get_speaker_mode_by_total_channels(channels);
}
void AudioDriverCoreAudio::lock() {
mutex.lock();
}
void AudioDriverCoreAudio::unlock() {
mutex.unlock();
}
bool AudioDriverCoreAudio::try_lock() {
return mutex.try_lock();
}
void AudioDriverCoreAudio::finish() {
finish_input_device();
if (audio_unit) {
OSStatus result;
lock();
AURenderCallbackStruct callback;
memset(&callback, 0, sizeof(AURenderCallbackStruct));
result = AudioUnitSetProperty(audio_unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, kOutputBus, &callback, sizeof(callback));
if (result != noErr) {
ERR_PRINT("AudioUnitSetProperty failed");
}
if (active) {
result = AudioOutputUnitStop(audio_unit);
if (result != noErr) {
ERR_PRINT("AudioOutputUnitStop failed");
}
active = false;
}
result = AudioUnitUninitialize(audio_unit);
if (result != noErr) {
ERR_PRINT("AudioUnitUninitialize failed");
}
#ifdef MACOS_ENABLED
AudioObjectPropertyAddress prop;
prop.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
prop.mScope = kAudioObjectPropertyScopeGlobal;
prop.mElement = kAudioObjectPropertyElementMain;
result = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &prop, &output_device_address_cb, this);
if (result != noErr) {
ERR_PRINT("AudioObjectRemovePropertyListener failed");
}
#endif
result = AudioComponentInstanceDispose(audio_unit);
if (result != noErr) {
ERR_PRINT("AudioComponentInstanceDispose failed");
}
audio_unit = nullptr;
unlock();
}
}
Error AudioDriverCoreAudio::init_input_device() {
AudioComponentDescription desc;
memset(&desc, 0, sizeof(desc));
desc.componentType = kAudioUnitType_Output;
#ifdef MACOS_ENABLED
desc.componentSubType = kAudioUnitSubType_HALOutput;
#else
desc.componentSubType = kAudioUnitSubType_RemoteIO;
#endif
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
AudioComponent comp = AudioComponentFindNext(nullptr, &desc);
ERR_FAIL_NULL_V(comp, FAILED);
OSStatus result = AudioComponentInstanceNew(comp, &input_unit);
ERR_FAIL_COND_V(result != noErr, FAILED);
#ifdef MACOS_ENABLED
AudioObjectPropertyAddress prop;
prop.mSelector = kAudioHardwarePropertyDefaultInputDevice;
prop.mScope = kAudioObjectPropertyScopeGlobal;
prop.mElement = kAudioObjectPropertyElementMain;
result = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &prop, &input_device_address_cb, this);
ERR_FAIL_COND_V(result != noErr, FAILED);
#endif
UInt32 flag = 1;
result = AudioUnitSetProperty(input_unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag));
ERR_FAIL_COND_V(result != noErr, FAILED);
#ifdef MACOS_ENABLED
flag = 0;
result = AudioUnitSetProperty(input_unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag));
ERR_FAIL_COND_V(result != noErr, FAILED);
#endif
UInt32 size;
#ifdef MACOS_ENABLED
AudioDeviceID device_id;
size = sizeof(AudioDeviceID);
AudioObjectPropertyAddress property = { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain };
result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &property, 0, nullptr, &size, &device_id);
ERR_FAIL_COND_V(result != noErr, FAILED);
result = AudioUnitSetProperty(input_unit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &device_id, sizeof(AudioDeviceID));
ERR_FAIL_COND_V(result != noErr, FAILED);
#endif
AudioStreamBasicDescription strdesc;
memset(&strdesc, 0, sizeof(strdesc));
size = sizeof(strdesc);
result = AudioUnitGetProperty(input_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &strdesc, &size);
ERR_FAIL_COND_V(result != noErr, FAILED);
switch (strdesc.mChannelsPerFrame) {
case 1: // Mono
capture_channels = 1;
break;
case 2: // Stereo
capture_channels = 2;
break;
default:
// Unknown number of channels, default to stereo
capture_channels = 2;
break;
}
#ifdef MACOS_ENABLED
double hw_mix_rate;
UInt32 hw_mix_rate_size = sizeof(hw_mix_rate);
AudioObjectPropertyAddress property_sr = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain };
result = AudioObjectGetPropertyData(device_id, &property_sr, 0, nullptr, &hw_mix_rate_size, &hw_mix_rate);
ERR_FAIL_COND_V(result != noErr, FAILED);
#else
double hw_mix_rate = [AVAudioSession sharedInstance].sampleRate;
#endif
capture_mix_rate = hw_mix_rate;
memset(&strdesc, 0, sizeof(strdesc));
strdesc.mFormatID = kAudioFormatLinearPCM;
strdesc.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
strdesc.mChannelsPerFrame = capture_channels;
strdesc.mSampleRate = capture_mix_rate;
strdesc.mFramesPerPacket = 1;
strdesc.mBitsPerChannel = 16;
strdesc.mBytesPerFrame = strdesc.mBitsPerChannel * strdesc.mChannelsPerFrame / 8;
strdesc.mBytesPerPacket = strdesc.mBytesPerFrame * strdesc.mFramesPerPacket;
result = AudioUnitSetProperty(input_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &strdesc, sizeof(strdesc));
ERR_FAIL_COND_V(result != noErr, FAILED);
uint32_t latency = Engine::get_singleton()->get_audio_output_latency();
// Sample rate is independent of channels (ref: https://stackoverflow.com/questions/11048825/audio-sample-frequency-rely-on-channels)
capture_buffer_frames = Math::closest_power_of_2(latency * (uint32_t)capture_mix_rate / (uint32_t)1000);
buffer_size = capture_buffer_frames * capture_channels;
AURenderCallbackStruct callback;
memset(&callback, 0, sizeof(AURenderCallbackStruct));
callback.inputProc = &AudioDriverCoreAudio::input_callback;
callback.inputProcRefCon = this;
result = AudioUnitSetProperty(input_unit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, kInputBus, &callback, sizeof(callback));
ERR_FAIL_COND_V(result != noErr, FAILED);
result = AudioUnitInitialize(input_unit);
ERR_FAIL_COND_V(result != noErr, FAILED);
print_verbose("CoreAudio: input sampling rate: " + itos(capture_mix_rate) + " Hz");
print_verbose("CoreAudio: input audio buffer frames: " + itos(capture_buffer_frames) + " calculated latency: " + itos(capture_buffer_frames * 1000 / capture_mix_rate) + "ms");
return OK;
}
void AudioDriverCoreAudio::finish_input_device() {
if (input_unit) {
lock();
AURenderCallbackStruct callback;
memset(&callback, 0, sizeof(AURenderCallbackStruct));
OSStatus result = AudioUnitSetProperty(input_unit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 0, &callback, sizeof(callback));
if (result != noErr) {
ERR_PRINT("AudioUnitSetProperty failed");
}
result = AudioUnitUninitialize(input_unit);
if (result != noErr) {
ERR_PRINT("AudioUnitUninitialize failed");
}
#ifdef MACOS_ENABLED
AudioObjectPropertyAddress prop;
prop.mSelector = kAudioHardwarePropertyDefaultInputDevice;
prop.mScope = kAudioObjectPropertyScopeGlobal;
prop.mElement = kAudioObjectPropertyElementMain;
result = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &prop, &input_device_address_cb, this);
if (result != noErr) {
ERR_PRINT("AudioObjectRemovePropertyListener failed");
}
#endif
result = AudioComponentInstanceDispose(input_unit);
if (result != noErr) {
ERR_PRINT("AudioComponentInstanceDispose failed");
}
input_unit = nullptr;
unlock();
}
}
Error AudioDriverCoreAudio::input_start() {
ERR_FAIL_NULL_V(input_unit, FAILED);
input_buffer_init(capture_buffer_frames);
OSStatus result = AudioOutputUnitStart(input_unit);
if (result != noErr) {
ERR_PRINT("AudioOutputUnitStart failed, code: " + itos(result));
}
return OK;
}
Error AudioDriverCoreAudio::input_stop() {
if (input_unit) {
OSStatus result = AudioOutputUnitStop(input_unit);
if (result != noErr) {
ERR_PRINT("AudioOutputUnitStop failed, code: " + itos(result));
}
}
return OK;
}
#ifdef MACOS_ENABLED
PackedStringArray AudioDriverCoreAudio::_get_device_list(bool input) {
PackedStringArray list;
list.push_back("Default");
AudioObjectPropertyAddress prop;
prop.mSelector = kAudioHardwarePropertyDevices;
prop.mScope = kAudioObjectPropertyScopeGlobal;
prop.mElement = kAudioObjectPropertyElementMain;
UInt32 size = 0;
AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &prop, 0, nullptr, &size);
AudioDeviceID *audioDevices = (AudioDeviceID *)memalloc(size);
ERR_FAIL_NULL_V_MSG(audioDevices, list, "Out of memory.");
AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop, 0, nullptr, &size, audioDevices);
UInt32 deviceCount = size / sizeof(AudioDeviceID);
for (UInt32 i = 0; i < deviceCount; i++) {
prop.mScope = input ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput;
prop.mSelector = kAudioDevicePropertyStreamConfiguration;
AudioObjectGetPropertyDataSize(audioDevices[i], &prop, 0, nullptr, &size);
AudioBufferList *bufferList = (AudioBufferList *)memalloc(size);
ERR_FAIL_NULL_V_MSG(bufferList, list, "Out of memory.");
AudioObjectGetPropertyData(audioDevices[i], &prop, 0, nullptr, &size, bufferList);
UInt32 channelCount = 0;
for (UInt32 j = 0; j < bufferList->mNumberBuffers; j++) {
channelCount += bufferList->mBuffers[j].mNumberChannels;
}
memfree(bufferList);
if (channelCount >= 1) {
CFStringRef cfname;
size = sizeof(CFStringRef);
prop.mSelector = kAudioObjectPropertyName;
AudioObjectGetPropertyData(audioDevices[i], &prop, 0, nullptr, &size, &cfname);
CFIndex length = CFStringGetLength(cfname);
CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
char *buffer = (char *)memalloc(maxSize);
ERR_FAIL_NULL_V_MSG(buffer, list, "Out of memory.");
if (CFStringGetCString(cfname, buffer, maxSize, kCFStringEncodingUTF8)) {
// Append the ID to the name in case we have devices with duplicate name
list.push_back(String::utf8(buffer) + " (" + itos(audioDevices[i]) + ")");
}
memfree(buffer);
}
}
memfree(audioDevices);
return list;
}
void AudioDriverCoreAudio::_set_device(const String &output_device, bool input) {
AudioDeviceID device_id;
bool found = false;
if (output_device != "Default") {
AudioObjectPropertyAddress prop;
prop.mSelector = kAudioHardwarePropertyDevices;
prop.mScope = kAudioObjectPropertyScopeGlobal;
prop.mElement = kAudioObjectPropertyElementMain;
UInt32 size = 0;
AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &prop, 0, nullptr, &size);
AudioDeviceID *audioDevices = (AudioDeviceID *)memalloc(size);
ERR_FAIL_NULL_MSG(audioDevices, "Out of memory.");
AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop, 0, nullptr, &size, audioDevices);
UInt32 deviceCount = size / sizeof(AudioDeviceID);
for (UInt32 i = 0; i < deviceCount && !found; i++) {
prop.mScope = input ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput;
prop.mSelector = kAudioDevicePropertyStreamConfiguration;
AudioObjectGetPropertyDataSize(audioDevices[i], &prop, 0, nullptr, &size);
AudioBufferList *bufferList = (AudioBufferList *)memalloc(size);
ERR_FAIL_NULL_MSG(bufferList, "Out of memory.");
AudioObjectGetPropertyData(audioDevices[i], &prop, 0, nullptr, &size, bufferList);
UInt32 channelCount = 0;
for (UInt32 j = 0; j < bufferList->mNumberBuffers; j++) {
channelCount += bufferList->mBuffers[j].mNumberChannels;
}
memfree(bufferList);
if (channelCount >= 1) {
CFStringRef cfname;
size = sizeof(CFStringRef);
prop.mSelector = kAudioObjectPropertyName;
AudioObjectGetPropertyData(audioDevices[i], &prop, 0, nullptr, &size, &cfname);
CFIndex length = CFStringGetLength(cfname);
CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
char *buffer = (char *)memalloc(maxSize);
ERR_FAIL_NULL_MSG(buffer, "Out of memory.");
if (CFStringGetCString(cfname, buffer, maxSize, kCFStringEncodingUTF8)) {
String name = String::utf8(buffer) + " (" + itos(audioDevices[i]) + ")";
if (name == output_device) {
device_id = audioDevices[i];
found = true;
}
}
memfree(buffer);
}
}
memfree(audioDevices);
}
if (!found) {
// If we haven't found the desired device get the system default one
UInt32 size = sizeof(AudioDeviceID);
UInt32 elem = input ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice;
AudioObjectPropertyAddress property = { elem, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain };
OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &property, 0, nullptr, &size, &device_id);
ERR_FAIL_COND(result != noErr);
found = true;
}
if (found) {
OSStatus result = AudioUnitSetProperty(input ? input_unit : audio_unit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &device_id, sizeof(AudioDeviceID));
ERR_FAIL_COND(result != noErr);
if (input) {
// Reset audio input to keep synchronization.
input_position = 0;
input_size = 0;
}
}
}
PackedStringArray AudioDriverCoreAudio::get_output_device_list() {
return _get_device_list();
}
String AudioDriverCoreAudio::get_output_device() {
return output_device_name;
}
void AudioDriverCoreAudio::set_output_device(const String &p_name) {
output_device_name = p_name;
if (active) {
_set_device(output_device_name);
}
}
PackedStringArray AudioDriverCoreAudio::get_input_device_list() {
return _get_device_list(true);
}
String AudioDriverCoreAudio::get_input_device() {
return input_device_name;
}
void AudioDriverCoreAudio::set_input_device(const String &p_name) {
input_device_name = p_name;
if (active) {
_set_device(input_device_name, true);
}
}
#endif
AudioDriverCoreAudio::AudioDriverCoreAudio() {
samples_in.clear();
}
#endif // COREAUDIO_ENABLED

View file

@ -0,0 +1,7 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
# Driver source files
env.add_source_files(env.drivers_sources, "*.mm")

View file

@ -0,0 +1,68 @@
/**************************************************************************/
/* midi_driver_coremidi.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifdef COREMIDI_ENABLED
#include "core/os/midi_driver.h"
#include "core/os/mutex.h"
#include "core/templates/vector.h"
#import <CoreMIDI/CoreMIDI.h>
#include <cstdio>
class MIDIDriverCoreMidi : public MIDIDriver {
MIDIClientRef client = 0;
MIDIPortRef port_in;
struct InputConnection {
InputConnection() = default;
InputConnection(int p_device_index, MIDIEndpointRef p_source);
Parser parser;
MIDIEndpointRef source;
};
Vector<InputConnection *> connected_sources;
static Mutex mutex;
static bool core_midi_closed;
static void read(const MIDIPacketList *packet_list, void *read_proc_ref_con, void *src_conn_ref_con);
public:
virtual Error open() override;
virtual void close() override;
MIDIDriverCoreMidi() = default;
virtual ~MIDIDriverCoreMidi();
};
#endif // COREMIDI_ENABLED

View file

@ -0,0 +1,132 @@
/**************************************************************************/
/* midi_driver_coremidi.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#import "midi_driver_coremidi.h"
#ifdef COREMIDI_ENABLED
#include "core/string/print_string.h"
#import <CoreServices/CoreServices.h>
Mutex MIDIDriverCoreMidi::mutex;
bool MIDIDriverCoreMidi::core_midi_closed = false;
MIDIDriverCoreMidi::InputConnection::InputConnection(int p_device_index, MIDIEndpointRef p_source) :
parser(p_device_index), source(p_source) {}
void MIDIDriverCoreMidi::read(const MIDIPacketList *packet_list, void *read_proc_ref_con, void *src_conn_ref_con) {
MutexLock lock(mutex);
if (!core_midi_closed) {
InputConnection *source = static_cast<InputConnection *>(src_conn_ref_con);
const MIDIPacket *packet = packet_list->packet;
for (UInt32 packet_index = 0; packet_index < packet_list->numPackets; packet_index++) {
for (UInt16 data_index = 0; data_index < packet->length; data_index++) {
source->parser.parse_fragment(packet->data[data_index]);
}
packet = MIDIPacketNext(packet);
}
}
}
Error MIDIDriverCoreMidi::open() {
ERR_FAIL_COND_V_MSG(client || core_midi_closed, FAILED,
"MIDIDriverCoreMidi cannot be reopened.");
CFStringRef name = CFStringCreateWithCString(nullptr, "Godot", kCFStringEncodingASCII);
OSStatus result = MIDIClientCreate(name, nullptr, nullptr, &client);
CFRelease(name);
if (result != noErr) {
ERR_PRINT("MIDIClientCreate failed, code: " + itos(result));
return ERR_CANT_OPEN;
}
result = MIDIInputPortCreate(client, CFSTR("Godot Input"), MIDIDriverCoreMidi::read, (void *)this, &port_in);
if (result != noErr) {
ERR_PRINT("MIDIInputPortCreate failed, code: " + itos(result));
return ERR_CANT_OPEN;
}
int source_count = MIDIGetNumberOfSources();
int connection_index = 0;
for (int i = 0; i < source_count; i++) {
MIDIEndpointRef source = MIDIGetSource(i);
if (source) {
InputConnection *conn = memnew(InputConnection(connection_index, source));
const OSStatus res = MIDIPortConnectSource(port_in, source, static_cast<void *>(conn));
if (res != noErr) {
memdelete(conn);
} else {
connected_sources.push_back(conn);
CFStringRef nameRef = nullptr;
char name[256];
MIDIObjectGetStringProperty(source, kMIDIPropertyDisplayName, &nameRef);
CFStringGetCString(nameRef, name, sizeof(name), kCFStringEncodingUTF8);
CFRelease(nameRef);
connected_input_names.push_back(name);
connection_index++; // Contiguous index for successfully connected inputs.
}
}
}
return OK;
}
void MIDIDriverCoreMidi::close() {
mutex.lock();
core_midi_closed = true;
mutex.unlock();
for (InputConnection *conn : connected_sources) {
MIDIPortDisconnectSource(port_in, conn->source);
memdelete(conn);
}
connected_sources.clear();
connected_input_names.clear();
if (port_in != 0) {
MIDIPortDispose(port_in);
port_in = 0;
}
if (client != 0) {
MIDIClientDispose(client);
client = 0;
}
}
MIDIDriverCoreMidi::~MIDIDriverCoreMidi() {
close();
}
#endif // COREMIDI_ENABLED

188
engine/drivers/d3d12/SCsub Normal file
View file

@ -0,0 +1,188 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
import os
import re
import sys
from pathlib import Path
import methods
from methods import print_error
# Sync with `misc/scripts/install_d3d12_sdk_windows.py` when updating Mesa.
# Check for latest version: https://github.com/godotengine/godot-nir-static/releases/latest
req_version_major = 25
req_version_minor = 3
Import("env")
env_d3d12_rdd = env.Clone()
# Agility SDK.
if env["agility_sdk_path"] != "" and os.path.exists(env["agility_sdk_path"]):
env_d3d12_rdd.Append(CPPDEFINES=["AGILITY_SDK_ENABLED"])
if env["agility_sdk_multiarch"]:
env_d3d12_rdd.Append(CPPDEFINES=["AGILITY_SDK_MULTIARCH_ENABLED"])
# PIX.
if env["use_pix"]:
env_d3d12_rdd.Append(CPPDEFINES=["PIX_ENABLED"])
env_d3d12_rdd.Append(CPPPATH=[env["pix_path"] + "/Include"])
# Direct composition.
if "dcomp" in env.get("supported", []):
env_d3d12_rdd.Append(CPPDEFINES=["DCOMP_ENABLED"])
env.Append(CPPDEFINES=["DCOMP_ENABLED"]) # Used in header included in platform.
# Mesa (SPIR-V to DXIL functionality).
mesa_libs = env["mesa_libs"]
if env.msvc and os.path.exists(env["mesa_libs"] + "-" + env["arch"] + "-msvc"):
mesa_libs = env["mesa_libs"] + "-" + env["arch"] + "-msvc"
elif env["use_llvm"] and os.path.exists(env["mesa_libs"] + "-" + env["arch"] + "-llvm"):
mesa_libs = env["mesa_libs"] + "-" + env["arch"] + "-llvm"
elif not env["use_llvm"] and os.path.exists(env["mesa_libs"] + "-" + env["arch"] + "-gcc"):
mesa_libs = env["mesa_libs"] + "-" + env["arch"] + "-gcc"
mesa_dir = (mesa_libs + "/godot-mesa").replace("\\", "/")
mesa_gen_dir = (mesa_libs + "/godot-mesa/generated").replace("\\", "/")
mesa_absdir = Dir(mesa_dir).abspath
mesa_gen_absdir = Dir(mesa_dir + "/generated").abspath
custom_build_steps = [
[
"src/compiler",
"glsl/ir_expression_operation.py enum > %s/ir_expression_operation.h",
"ir_expression_operation.h",
],
["src/compiler/nir", "nir_builder_opcodes_h.py > %s/nir_builder_opcodes.h", "nir_builder_opcodes.h"],
["src/compiler/nir", "nir_constant_expressions.py > %s/nir_constant_expressions.c", "nir_constant_expressions.c"],
["src/compiler/nir", "nir_intrinsics_h.py --outdir %s", "nir_intrinsics.h"],
["src/compiler/nir", "nir_intrinsics_c.py --outdir %s", "nir_intrinsics.c"],
["src/compiler/nir", "nir_intrinsics_indices_h.py --outdir %s", "nir_intrinsics_indices.h"],
["src/compiler/nir", "nir_opcodes_h.py > %s/nir_opcodes.h", "nir_opcodes.h"],
["src/compiler/nir", "nir_opcodes_c.py > %s/nir_opcodes.c", "nir_opcodes.c"],
["src/compiler/nir", "nir_opt_algebraic.py > %s/nir_opt_algebraic.c", "nir_opt_algebraic.c"],
["src/compiler/spirv", "vtn_generator_ids_h.py spir-v.xml %s/vtn_generator_ids.h", "vtn_generator_ids.h"],
[
"src/microsoft/compiler",
"dxil_nir_algebraic.py -p ../../../src/compiler/nir > %s/dxil_nir_algebraic.c",
"dxil_nir_algebraic.c",
],
["src/util", "format_srgb.py > %s/format_srgb.c", "format_srgb.c"],
["src/util/format", "u_format_table.py u_format.csv --header > %s/u_format_pack.h", "u_format_pack.h"],
["src/util/format", "u_format_table.py u_format.csv > %s/u_format_table.c", "u_format_table.c"],
]
mesa_gen_include_paths = [mesa_gen_dir + "/src"]
# See update_mesa.sh for explanation.
for v in custom_build_steps:
subdir = v[0]
cmd = v[1]
gen_filename = v[2]
in_dir = str(Path(mesa_absdir + "/" + subdir))
out_full_path = mesa_dir + "/generated/" + subdir
out_file_full_path = out_full_path + "/" + gen_filename
if gen_filename.endswith(".h"):
mesa_gen_include_paths += [out_full_path.replace("\\", "/")]
mesa_private_inc_paths = [v[0] for v in os.walk(mesa_absdir)]
mesa_private_inc_paths = [v.replace(mesa_absdir, mesa_dir) for v in mesa_private_inc_paths]
mesa_private_inc_paths = [v.replace("\\", "/") for v in mesa_private_inc_paths]
# Avoid build results depending on if generated files already exist.
mesa_private_inc_paths = [v for v in mesa_private_inc_paths if not v.startswith(mesa_gen_dir)]
mesa_private_inc_paths.sort()
# Include the list of the generated ones now, so out-of-the-box sources can include generated headers.
mesa_private_inc_paths += mesa_gen_include_paths
# We have to blacklist some because we are bindly adding every Mesa directory
# to the include path and in some cases that causes the wrong header to be included.
mesa_blacklist_inc_paths = [
"src/c11",
]
mesa_blacklist_inc_paths = [mesa_dir + "/" + v for v in mesa_blacklist_inc_paths]
mesa_private_inc_paths = [v for v in mesa_private_inc_paths if v not in mesa_blacklist_inc_paths]
# Added by ourselves.
extra_defines = [
"WINDOWS_NO_FUTEX",
]
mesa_ver = Path(mesa_absdir + "/VERSION.info")
if not mesa_ver.is_file():
mesa_ver = Path(mesa_absdir + "/VERSION")
match = re.match(r"([0-9]*).([0-9]*).([0-9]*)-?([0-9.+]*)", mesa_ver.read_text().strip())
if (
match is None
or int(match.group(1)) != req_version_major
or (int(match.group(1)) == req_version_major and int(match.group(2)) < req_version_minor)
):
print_error(
"The Direct3D 12 rendering driver dependencies are outdated or missing.\n"
f"You can re-install them by running `python {os.path.join('misc', 'scripts', 'install_d3d12_sdk_windows.py')}`.\n"
)
sys.exit(255)
# These defines are inspired by the Meson build scripts in the original repo.
extra_defines += [
"__STDC_CONSTANT_MACROS",
"__STDC_FORMAT_MACROS",
"__STDC_LIMIT_MACROS",
("PACKAGE_VERSION", '\\"' + mesa_ver.read_text().strip() + '\\"'),
("PACKAGE_BUGREPORT", '\\"https://gitlab.freedesktop.org/mesa/mesa/-/issues\\"'),
"PIPE_SUBSYSTEM_WINDOWS_USER",
("_Static_assert", "static_assert"),
"HAVE_STRUCT_TIMESPEC",
]
if env.msvc:
extra_defines += [
"_USE_MATH_DEFINES",
"VC_EXTRALEAN",
"_CRT_SECURE_NO_WARNINGS",
"_CRT_SECURE_NO_DEPRECATE",
"_SCL_SECURE_NO_WARNINGS",
"_SCL_SECURE_NO_DEPRECATE",
"_ALLOW_KEYWORD_MACROS",
"NOMINMAX",
]
else:
extra_defines += [
# Match current version used by MinGW. MSVC and Direct3D 12 headers use 500.
("__REQUIRED_RPCNDR_H_VERSION__", 475),
]
if methods.using_gcc(env) and methods.get_compiler_version(env)["major"] < 13:
# `region` & `endregion` not recognized as valid pragmas.
env_d3d12_rdd.Append(CCFLAGS=["-Wno-unknown-pragmas"])
# This is needed since rendering_device_d3d12.cpp needs to include some Mesa internals.
env_d3d12_rdd.Prepend(CPPPATH=mesa_private_inc_paths)
# For the same reason as above, the defines must be the same as in the 3rd-party code itself.
env_d3d12_rdd.Append(CPPDEFINES=extra_defines)
# Thirdparty.
thirdparty_obj = []
env_thirdparty = env_d3d12_rdd.Clone()
env_thirdparty.disable_warnings()
env_thirdparty.Prepend(
CPPPATH=[
"#thirdparty/directx_headers/include/directx",
"#thirdparty/directx_headers/include/dxguids",
"#thirdparty/d3d12ma",
]
)
env_thirdparty.add_source_files(thirdparty_obj, "#thirdparty/d3d12ma/D3D12MemAlloc.cpp")
env.drivers_sources += thirdparty_obj
# Godot source files.
driver_obj = []
env_d3d12_rdd.add_source_files(driver_obj, "*.cpp")
env.drivers_sources += driver_obj
# Needed to force rebuilding the driver files when the thirdparty code is updated.
env.Depends(driver_obj, thirdparty_obj)

View file

@ -0,0 +1,61 @@
/**************************************************************************/
/* d3d12_godot_nir_bridge.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include <cstdint>
#ifdef __cplusplus
extern "C" {
#endif
// This one leaves room for potentially extremely copious bindings in a set.
static const uint32_t GODOT_NIR_DESCRIPTOR_SET_MULTIPLIER = 100000000;
// This one leaves room for potentially big sized arrays.
static const uint32_t GODOT_NIR_BINDING_MULTIPLIER = 100000;
static const uint64_t GODOT_NIR_SC_SENTINEL_MAGIC = 0x45678900; // This must be as big as to be VBR-ed as a 32 bits number.
static const uint64_t GODOT_NIR_SC_SENTINEL_MAGIC_MASK = 0xffffffffffffff00;
static const uint64_t GODOT_NIR_SC_SENTINEL_ID_MASK = 0x00000000000000ff;
typedef struct GodotNirCallbacks {
void *data;
void (*report_resource)(uint32_t p_register, uint32_t p_space, uint32_t p_dxil_type, void *p_data);
void (*report_sc_bit_offset_fn)(uint32_t p_sc_id, uint64_t p_bit_offset, void *p_data);
void (*report_bitcode_bit_offset_fn)(uint64_t p_bit_offset, void *p_data);
} GodotNirCallbacks;
extern void *godot_nir_malloc(size_t p_size);
extern void *godot_nir_realloc(void *p_block, size_t p_size);
extern void godot_nir_free(void *p_block);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,45 @@
/**************************************************************************/
/* d3d12_hooks.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "d3d12_hooks.h"
D3D12Hooks *D3D12Hooks::singleton = nullptr;
D3D12Hooks::D3D12Hooks() {
if (singleton == nullptr) {
singleton = this;
}
}
D3D12Hooks::~D3D12Hooks() {
if (singleton == this) {
singleton = nullptr;
}
}

View file

@ -0,0 +1,48 @@
/**************************************************************************/
/* d3d12_hooks.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "rendering_device_driver_d3d12.h"
class D3D12Hooks {
private:
static D3D12Hooks *singleton;
public:
D3D12Hooks();
virtual ~D3D12Hooks();
virtual D3D_FEATURE_LEVEL get_feature_level() const = 0;
virtual LUID get_adapter_luid() const = 0;
virtual void set_device(ID3D12Device *p_device) = 0;
virtual void set_command_queue(ID3D12CommandQueue *p_queue) = 0;
virtual void cleanup_device() = 0;
static D3D12Hooks *get_singleton() { return singleton; }
};

View file

@ -0,0 +1,209 @@
/**************************************************************************/
/* dxil_hash.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
// Based on the patched public domain implementation released by Microsoft here:
// https://github.com/microsoft/hlsl-specs/blob/main/proposals/infra/INF-0004-validator-hashing.md
#include "dxil_hash.h"
#include <memory.h>
#define S11 7
#define S12 12
#define S13 17
#define S14 22
#define S21 5
#define S22 9
#define S23 14
#define S24 20
#define S31 4
#define S32 11
#define S33 16
#define S34 23
#define S41 6
#define S42 10
#define S43 15
#define S44 21
static const BYTE padding[64] = {
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
static void FF(UINT &a, UINT b, UINT c, UINT d, UINT x, UINT8 s, UINT ac) {
a += ((b & c) | (~b & d)) + x + ac;
a = ((a << s) | (a >> (32 - s))) + b;
}
static void GG(UINT &a, UINT b, UINT c, UINT d, UINT x, UINT8 s, UINT ac) {
a += ((b & d) | (c & ~d)) + x + ac;
a = ((a << s) | (a >> (32 - s))) + b;
}
static void HH(UINT &a, UINT b, UINT c, UINT d, UINT x, UINT8 s, UINT ac) {
a += (b ^ c ^ d) + x + ac;
a = ((a << s) | (a >> (32 - s))) + b;
}
static void II(UINT &a, UINT b, UINT c, UINT d, UINT x, UINT8 s, UINT ac) {
a += (c ^ (b | ~d)) + x + ac;
a = ((a << s) | (a >> (32 - s))) + b;
}
void compute_dxil_hash(const BYTE *pData, UINT byteCount, BYTE *pOutHash) {
UINT leftOver = byteCount & 0x3f;
UINT padAmount;
bool bTwoRowsPadding = false;
if (leftOver < 56) {
padAmount = 56 - leftOver;
} else {
padAmount = 120 - leftOver;
bTwoRowsPadding = true;
}
UINT padAmountPlusSize = padAmount + 8;
UINT state[4] = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 };
UINT N = (byteCount + padAmountPlusSize) >> 6;
UINT offset = 0;
UINT NextEndState = bTwoRowsPadding ? N - 2 : N - 1;
const BYTE *pCurrData = pData;
for (UINT i = 0; i < N; i++, offset += 64, pCurrData += 64) {
UINT x[16] = {};
const UINT *pX;
if (i == NextEndState) {
if (!bTwoRowsPadding && i == N - 1) {
UINT remainder = byteCount - offset;
x[0] = byteCount << 3;
memcpy((BYTE *)x + 4, pCurrData, remainder);
memcpy((BYTE *)x + 4 + remainder, padding, padAmount);
x[15] = 1 | (byteCount << 1);
} else if (bTwoRowsPadding) {
if (i == N - 2) {
UINT remainder = byteCount - offset;
memcpy(x, pCurrData, remainder);
memcpy((BYTE *)x + remainder, padding, padAmount - 56);
NextEndState = N - 1;
} else if (i == N - 1) {
x[0] = byteCount << 3;
memcpy((BYTE *)x + 4, padding + padAmount - 56, 56);
x[15] = 1 | (byteCount << 1);
}
}
pX = x;
} else {
pX = (const UINT *)pCurrData;
}
UINT a = state[0];
UINT b = state[1];
UINT c = state[2];
UINT d = state[3];
/* Round 1 */
FF(a, b, c, d, pX[0], S11, 0xd76aa478); /* 1 */
FF(d, a, b, c, pX[1], S12, 0xe8c7b756); /* 2 */
FF(c, d, a, b, pX[2], S13, 0x242070db); /* 3 */
FF(b, c, d, a, pX[3], S14, 0xc1bdceee); /* 4 */
FF(a, b, c, d, pX[4], S11, 0xf57c0faf); /* 5 */
FF(d, a, b, c, pX[5], S12, 0x4787c62a); /* 6 */
FF(c, d, a, b, pX[6], S13, 0xa8304613); /* 7 */
FF(b, c, d, a, pX[7], S14, 0xfd469501); /* 8 */
FF(a, b, c, d, pX[8], S11, 0x698098d8); /* 9 */
FF(d, a, b, c, pX[9], S12, 0x8b44f7af); /* 10 */
FF(c, d, a, b, pX[10], S13, 0xffff5bb1); /* 11 */
FF(b, c, d, a, pX[11], S14, 0x895cd7be); /* 12 */
FF(a, b, c, d, pX[12], S11, 0x6b901122); /* 13 */
FF(d, a, b, c, pX[13], S12, 0xfd987193); /* 14 */
FF(c, d, a, b, pX[14], S13, 0xa679438e); /* 15 */
FF(b, c, d, a, pX[15], S14, 0x49b40821); /* 16 */
/* Round 2 */
GG(a, b, c, d, pX[1], S21, 0xf61e2562); /* 17 */
GG(d, a, b, c, pX[6], S22, 0xc040b340); /* 18 */
GG(c, d, a, b, pX[11], S23, 0x265e5a51); /* 19 */
GG(b, c, d, a, pX[0], S24, 0xe9b6c7aa); /* 20 */
GG(a, b, c, d, pX[5], S21, 0xd62f105d); /* 21 */
GG(d, a, b, c, pX[10], S22, 0x2441453); /* 22 */
GG(c, d, a, b, pX[15], S23, 0xd8a1e681); /* 23 */
GG(b, c, d, a, pX[4], S24, 0xe7d3fbc8); /* 24 */
GG(a, b, c, d, pX[9], S21, 0x21e1cde6); /* 25 */
GG(d, a, b, c, pX[14], S22, 0xc33707d6); /* 26 */
GG(c, d, a, b, pX[3], S23, 0xf4d50d87); /* 27 */
GG(b, c, d, a, pX[8], S24, 0x455a14ed); /* 28 */
GG(a, b, c, d, pX[13], S21, 0xa9e3e905); /* 29 */
GG(d, a, b, c, pX[2], S22, 0xfcefa3f8); /* 30 */
GG(c, d, a, b, pX[7], S23, 0x676f02d9); /* 31 */
GG(b, c, d, a, pX[12], S24, 0x8d2a4c8a); /* 32 */
/* Round 3 */
HH(a, b, c, d, pX[5], S31, 0xfffa3942); /* 33 */
HH(d, a, b, c, pX[8], S32, 0x8771f681); /* 34 */
HH(c, d, a, b, pX[11], S33, 0x6d9d6122); /* 35 */
HH(b, c, d, a, pX[14], S34, 0xfde5380c); /* 36 */
HH(a, b, c, d, pX[1], S31, 0xa4beea44); /* 37 */
HH(d, a, b, c, pX[4], S32, 0x4bdecfa9); /* 38 */
HH(c, d, a, b, pX[7], S33, 0xf6bb4b60); /* 39 */
HH(b, c, d, a, pX[10], S34, 0xbebfbc70); /* 40 */
HH(a, b, c, d, pX[13], S31, 0x289b7ec6); /* 41 */
HH(d, a, b, c, pX[0], S32, 0xeaa127fa); /* 42 */
HH(c, d, a, b, pX[3], S33, 0xd4ef3085); /* 43 */
HH(b, c, d, a, pX[6], S34, 0x4881d05); /* 44 */
HH(a, b, c, d, pX[9], S31, 0xd9d4d039); /* 45 */
HH(d, a, b, c, pX[12], S32, 0xe6db99e5); /* 46 */
HH(c, d, a, b, pX[15], S33, 0x1fa27cf8); /* 47 */
HH(b, c, d, a, pX[2], S34, 0xc4ac5665); /* 48 */
/* Round 4 */
II(a, b, c, d, pX[0], S41, 0xf4292244); /* 49 */
II(d, a, b, c, pX[7], S42, 0x432aff97); /* 50 */
II(c, d, a, b, pX[14], S43, 0xab9423a7); /* 51 */
II(b, c, d, a, pX[5], S44, 0xfc93a039); /* 52 */
II(a, b, c, d, pX[12], S41, 0x655b59c3); /* 53 */
II(d, a, b, c, pX[3], S42, 0x8f0ccc92); /* 54 */
II(c, d, a, b, pX[10], S43, 0xffeff47d); /* 55 */
II(b, c, d, a, pX[1], S44, 0x85845dd1); /* 56 */
II(a, b, c, d, pX[8], S41, 0x6fa87e4f); /* 57 */
II(d, a, b, c, pX[15], S42, 0xfe2ce6e0); /* 58 */
II(c, d, a, b, pX[6], S43, 0xa3014314); /* 59 */
II(b, c, d, a, pX[13], S44, 0x4e0811a1); /* 60 */
II(a, b, c, d, pX[4], S41, 0xf7537e82); /* 61 */
II(d, a, b, c, pX[11], S42, 0xbd3af235); /* 62 */
II(c, d, a, b, pX[2], S43, 0x2ad7d2bb); /* 63 */
II(b, c, d, a, pX[9], S44, 0xeb86d391); /* 64 */
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
}
memcpy(pOutHash, state, 16);
}

View file

@ -0,0 +1,36 @@
/**************************************************************************/
/* dxil_hash.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
void compute_dxil_hash(const BYTE *pData, UINT byteCount, BYTE *pOutHash);

View file

@ -0,0 +1,405 @@
/**************************************************************************/
/* rendering_context_driver_d3d12.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "rendering_context_driver_d3d12.h"
#include "d3d12_hooks.h"
#include "core/config/engine.h"
#include "core/config/project_settings.h"
#include "core/string/ustring.h"
#include "core/templates/local_vector.h"
#include "core/version.h"
GODOT_GCC_WARNING_PUSH
GODOT_GCC_WARNING_IGNORE("-Wmissing-field-initializers")
GODOT_GCC_WARNING_IGNORE("-Wnon-virtual-dtor")
GODOT_GCC_WARNING_IGNORE("-Wshadow")
GODOT_GCC_WARNING_IGNORE("-Wswitch")
GODOT_CLANG_WARNING_PUSH
GODOT_CLANG_WARNING_IGNORE("-Wmissing-field-initializers")
GODOT_CLANG_WARNING_IGNORE("-Wnon-virtual-dtor")
GODOT_CLANG_WARNING_IGNORE("-Wstring-plus-int")
GODOT_CLANG_WARNING_IGNORE("-Wswitch")
#include <dxcapi.h>
#include <dxgi1_6.h>
GODOT_GCC_WARNING_POP
GODOT_CLANG_WARNING_POP
#if !defined(_MSC_VER)
#include <guiddef.h>
#include <thirdparty/directx_headers/include/dxguids/dxguids.h>
#endif
using Microsoft::WRL::ComPtr;
// Note: symbols are not available in MinGW and old MSVC import libraries.
// GUID values from https://github.com/microsoft/DirectX-Headers/blob/7a9f4d06911d30eecb56a4956dab29dcca2709ed/include/directx/d3d12.idl#L5877-L5881
const GUID CLSID_D3D12DebugGodot = { 0xf2352aeb, 0xdd84, 0x49fe, { 0xb9, 0x7b, 0xa9, 0xdc, 0xfd, 0xcc, 0x1b, 0x4f } };
const GUID CLSID_D3D12SDKConfigurationGodot = { 0x7cda6aca, 0xa03e, 0x49c8, { 0x94, 0x58, 0x03, 0x34, 0xd2, 0x0e, 0x07, 0xce } };
#ifdef PIX_ENABLED
#if defined(__GNUC__)
#define _MSC_VER 1800
#endif
#define USE_PIX
#include <WinPixEventRuntime/pix3.h>
#if defined(__GNUC__)
#undef _MSC_VER
#endif
#endif
RenderingContextDriverD3D12::RenderingContextDriverD3D12() {}
RenderingContextDriverD3D12::~RenderingContextDriverD3D12() {
// Let's release manually everything that may still be holding
// onto the DLLs before freeing them.
device_factory.Reset();
dxgi_factory.Reset();
if (lib_d3d12) {
FreeLibrary(lib_d3d12);
}
if (lib_dxgi) {
FreeLibrary(lib_dxgi);
}
#ifdef DCOMP_ENABLED
if (lib_dcomp) {
FreeLibrary(lib_dcomp);
}
#endif
}
Error RenderingContextDriverD3D12::_init_device_factory() {
uint32_t agility_sdk_version = GLOBAL_GET("rendering/rendering_device/d3d12/agility_sdk_version");
String agility_sdk_path = String(".\\") + Engine::get_singleton()->get_architecture_name();
lib_d3d12 = LoadLibraryW(L"D3D12.dll");
ERR_FAIL_NULL_V(lib_d3d12, ERR_CANT_CREATE);
lib_dxgi = LoadLibraryW(L"DXGI.dll");
ERR_FAIL_NULL_V(lib_dxgi, ERR_CANT_CREATE);
#ifdef DCOMP_ENABLED
lib_dcomp = LoadLibraryW(L"Dcomp.dll");
ERR_FAIL_NULL_V(lib_dcomp, ERR_CANT_CREATE);
#endif
// Note: symbol is not available in MinGW import library.
PFN_D3D12_GET_INTERFACE d3d_D3D12GetInterface = (PFN_D3D12_GET_INTERFACE)(void *)GetProcAddress(lib_d3d12, "D3D12GetInterface");
if (!d3d_D3D12GetInterface) {
return OK; // Fallback to the system loader.
}
ComPtr<ID3D12SDKConfiguration1> sdk_config;
HRESULT hr = d3d_D3D12GetInterface(CLSID_D3D12SDKConfigurationGodot, IID_PPV_ARGS(sdk_config.GetAddressOf()));
if (SUCCEEDED(hr)) {
hr = sdk_config->CreateDeviceFactory(agility_sdk_version, agility_sdk_path.ascii().get_data(), IID_PPV_ARGS(device_factory.GetAddressOf()));
if (FAILED(hr)) {
sdk_config->CreateDeviceFactory(agility_sdk_version, ".\\", IID_PPV_ARGS(device_factory.GetAddressOf()));
}
// If both calls failed, device factory is going to be nullptr, and D3D12CreateDevice is going to be used as fallback.
}
return OK;
}
Error RenderingContextDriverD3D12::_initialize_debug_layers() {
ComPtr<ID3D12Debug> debug_controller;
HRESULT res;
if (device_factory) {
res = device_factory->GetConfigurationInterface(CLSID_D3D12DebugGodot, IID_PPV_ARGS(&debug_controller));
} else {
PFN_D3D12_GET_DEBUG_INTERFACE d3d_D3D12GetDebugInterface = (PFN_D3D12_GET_DEBUG_INTERFACE)(void *)GetProcAddress(lib_d3d12, "D3D12GetDebugInterface");
ERR_FAIL_NULL_V(d3d_D3D12GetDebugInterface, ERR_CANT_CREATE);
res = d3d_D3D12GetDebugInterface(IID_PPV_ARGS(&debug_controller));
}
ERR_FAIL_COND_V(!SUCCEEDED(res), ERR_QUERY_FAILED);
debug_controller->EnableDebugLayer();
return OK;
}
Error RenderingContextDriverD3D12::_create_dxgi_factory() {
const UINT dxgi_factory_flags = use_validation_layers() ? DXGI_CREATE_FACTORY_DEBUG : 0;
typedef HRESULT(WINAPI * PFN_DXGI_CREATE_DXGI_FACTORY2)(UINT, REFIID, void **);
PFN_DXGI_CREATE_DXGI_FACTORY2 dxgi_CreateDXGIFactory2 = (PFN_DXGI_CREATE_DXGI_FACTORY2)(void *)GetProcAddress(lib_dxgi, "CreateDXGIFactory2");
ERR_FAIL_NULL_V(dxgi_CreateDXGIFactory2, ERR_CANT_CREATE);
HRESULT res = dxgi_CreateDXGIFactory2(dxgi_factory_flags, IID_PPV_ARGS(&dxgi_factory));
ERR_FAIL_COND_V(!SUCCEEDED(res), ERR_CANT_CREATE);
return OK;
}
Error RenderingContextDriverD3D12::_initialize_devices() {
// Create the initial DXGI factory.
Error err = _create_dxgi_factory();
ERR_FAIL_COND_V(err != OK, err);
HRESULT res;
// Enumerate all possible adapters.
LocalVector<IDXGIAdapter1 *> adapters;
IDXGIAdapter1 *adapter = nullptr;
do {
adapter = create_adapter(adapters.size());
if (adapter != nullptr) {
adapters.push_back(adapter);
}
} while (adapter != nullptr);
ERR_FAIL_COND_V_MSG(adapters.is_empty(), ERR_CANT_CREATE, "Adapters enumeration reported zero accessible devices.");
// Fill the device descriptions with the adapters.
driver_devices.resize(adapters.size());
for (uint32_t i = 0; i < adapters.size(); ++i) {
DXGI_ADAPTER_DESC1 desc = {};
adapters[i]->GetDesc1(&desc);
Device &device = driver_devices[i];
device.name = desc.Description;
device.vendor = desc.VendorId;
device.workarounds = Workarounds();
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) {
device.type = DEVICE_TYPE_CPU;
} else {
const bool has_dedicated_vram = desc.DedicatedVideoMemory > 0;
device.type = has_dedicated_vram ? DEVICE_TYPE_DISCRETE_GPU : DEVICE_TYPE_INTEGRATED_GPU;
}
}
// Release all created adapters.
for (uint32_t i = 0; i < adapters.size(); ++i) {
adapters[i]->Release();
}
ComPtr<IDXGIFactory5> factory_5;
dxgi_factory.As(&factory_5);
if (factory_5 != nullptr) {
// The type is important as in general, sizeof(bool) != sizeof(BOOL).
BOOL feature_supported = FALSE;
res = factory_5->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &feature_supported, sizeof(feature_supported));
if (SUCCEEDED(res)) {
tearing_supported = feature_supported;
} else {
ERR_PRINT("CheckFeatureSupport failed with error " + vformat("0x%08ux", (uint64_t)res) + ".");
}
}
return OK;
}
bool RenderingContextDriverD3D12::use_validation_layers() const {
return Engine::get_singleton()->is_validation_layers_enabled();
}
Error RenderingContextDriverD3D12::initialize() {
Error err = _init_device_factory();
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
if (use_validation_layers()) {
err = _initialize_debug_layers();
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
}
err = _initialize_devices();
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
return OK;
}
const RenderingContextDriver::Device &RenderingContextDriverD3D12::device_get(uint32_t p_device_index) const {
DEV_ASSERT(p_device_index < driver_devices.size());
return driver_devices[p_device_index];
}
uint32_t RenderingContextDriverD3D12::device_get_count() const {
return driver_devices.size();
}
bool RenderingContextDriverD3D12::device_supports_present(uint32_t p_device_index, SurfaceID p_surface) const {
// All devices should support presenting to any surface.
return true;
}
RenderingDeviceDriver *RenderingContextDriverD3D12::driver_create() {
return memnew(RenderingDeviceDriverD3D12(this));
}
void RenderingContextDriverD3D12::driver_free(RenderingDeviceDriver *p_driver) {
memdelete(p_driver);
}
RenderingContextDriver::SurfaceID RenderingContextDriverD3D12::surface_create(const void *p_platform_data) {
const WindowPlatformData *wpd = (const WindowPlatformData *)(p_platform_data);
Surface *surface = memnew(Surface);
surface->hwnd = wpd->window;
return SurfaceID(surface);
}
void RenderingContextDriverD3D12::surface_set_size(SurfaceID p_surface, uint32_t p_width, uint32_t p_height) {
Surface *surface = (Surface *)(p_surface);
surface->width = p_width;
surface->height = p_height;
surface->needs_resize = true;
}
void RenderingContextDriverD3D12::surface_set_vsync_mode(SurfaceID p_surface, DisplayServerEnums::VSyncMode p_vsync_mode) {
Surface *surface = (Surface *)(p_surface);
surface->vsync_mode = p_vsync_mode;
surface->needs_resize = true;
}
DisplayServerEnums::VSyncMode RenderingContextDriverD3D12::surface_get_vsync_mode(SurfaceID p_surface) const {
Surface *surface = (Surface *)(p_surface);
return surface->vsync_mode;
}
void RenderingContextDriverD3D12::surface_set_hdr_output_enabled(SurfaceID p_surface, bool p_enabled) {
Surface *surface = (Surface *)(p_surface);
surface->hdr_output = p_enabled;
surface->needs_resize = true;
}
bool RenderingContextDriverD3D12::surface_get_hdr_output_enabled(SurfaceID p_surface) const {
Surface *surface = (Surface *)(p_surface);
return surface->hdr_output;
}
void RenderingContextDriverD3D12::surface_set_hdr_output_reference_luminance(SurfaceID p_surface, float p_reference_luminance) {
Surface *surface = (Surface *)(p_surface);
surface->hdr_reference_luminance = p_reference_luminance;
}
float RenderingContextDriverD3D12::surface_get_hdr_output_reference_luminance(SurfaceID p_surface) const {
Surface *surface = (Surface *)(p_surface);
return surface->hdr_reference_luminance;
}
void RenderingContextDriverD3D12::surface_set_hdr_output_max_luminance(SurfaceID p_surface, float p_max_luminance) {
Surface *surface = (Surface *)(p_surface);
surface->hdr_max_luminance = p_max_luminance;
}
float RenderingContextDriverD3D12::surface_get_hdr_output_max_luminance(SurfaceID p_surface) const {
Surface *surface = (Surface *)(p_surface);
return surface->hdr_max_luminance;
}
void RenderingContextDriverD3D12::surface_set_hdr_output_linear_luminance_scale(SurfaceID p_surface, float p_linear_luminance_scale) {
Surface *surface = (Surface *)(p_surface);
surface->hdr_linear_luminance_scale = p_linear_luminance_scale;
}
float RenderingContextDriverD3D12::surface_get_hdr_output_linear_luminance_scale(SurfaceID p_surface) const {
Surface *surface = (Surface *)(p_surface);
return surface->hdr_linear_luminance_scale;
}
float RenderingContextDriverD3D12::surface_get_hdr_output_max_value(SurfaceID p_surface) const {
Surface *surface = (Surface *)(p_surface);
return MAX(surface->hdr_max_luminance / MAX(surface->hdr_reference_luminance, 1.0f), 1.0f);
}
uint32_t RenderingContextDriverD3D12::surface_get_width(SurfaceID p_surface) const {
Surface *surface = (Surface *)(p_surface);
return surface->width;
}
uint32_t RenderingContextDriverD3D12::surface_get_height(SurfaceID p_surface) const {
Surface *surface = (Surface *)(p_surface);
return surface->height;
}
void RenderingContextDriverD3D12::surface_set_needs_resize(SurfaceID p_surface, bool p_needs_resize) {
Surface *surface = (Surface *)(p_surface);
surface->needs_resize = p_needs_resize;
}
bool RenderingContextDriverD3D12::surface_get_needs_resize(SurfaceID p_surface) const {
Surface *surface = (Surface *)(p_surface);
return surface->needs_resize;
}
void RenderingContextDriverD3D12::surface_destroy(SurfaceID p_surface) {
Surface *surface = (Surface *)(p_surface);
memdelete(surface);
}
bool RenderingContextDriverD3D12::is_debug_utils_enabled() const {
#ifdef PIX_ENABLED
return true;
#else
return false;
#endif
}
IDXGIAdapter1 *RenderingContextDriverD3D12::create_adapter(uint32_t p_adapter_index) const {
ComPtr<IDXGIFactory6> factory_6;
dxgi_factory.As(&factory_6);
// TODO: Use IDXCoreAdapterList, which gives more comprehensive information.
IDXGIAdapter1 *adapter = nullptr;
if (factory_6) {
if (factory_6->EnumAdapterByGpuPreference(p_adapter_index, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, IID_PPV_ARGS(&adapter)) == DXGI_ERROR_NOT_FOUND) {
return nullptr;
}
} else {
if (dxgi_factory->EnumAdapters1(p_adapter_index, &adapter) == DXGI_ERROR_NOT_FOUND) {
return nullptr;
}
}
return adapter;
}
ID3D12DeviceFactory *RenderingContextDriverD3D12::device_factory_get() const {
return device_factory.Get();
}
IDXGIFactory2 *RenderingContextDriverD3D12::dxgi_factory_get() {
// Check if the factory is still current. It can become invalid if displays change
if (dxgi_factory && !dxgi_factory->IsCurrent()) {
dxgi_factory.Reset();
_create_dxgi_factory();
}
return dxgi_factory.Get();
}
bool RenderingContextDriverD3D12::get_tearing_supported() const {
return tearing_supported;
}

View file

@ -0,0 +1,155 @@
/**************************************************************************/
/* rendering_context_driver_d3d12.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/os/mutex.h"
#include "core/string/ustring.h"
#include "core/templates/rid_owner.h"
#include "rendering_device_driver_d3d12.h"
#include "servers/display/display_server_enums.h"
#include "servers/rendering/rendering_context_driver.h"
#if !defined(_MSC_VER) && !defined(__REQUIRED_RPCNDR_H_VERSION__)
// Match current version used by MinGW, MSVC and Direct3D 12 headers use 500.
#define __REQUIRED_RPCNDR_H_VERSION__ 475
#endif // !defined(_MSC_VER) && !defined(__REQUIRED_RPCNDR_H_VERSION__)
GODOT_GCC_WARNING_PUSH
GODOT_GCC_WARNING_IGNORE("-Wimplicit-fallthrough")
GODOT_GCC_WARNING_IGNORE("-Wmissing-field-initializers")
GODOT_GCC_WARNING_IGNORE("-Wnon-virtual-dtor")
GODOT_GCC_WARNING_IGNORE("-Wshadow")
GODOT_GCC_WARNING_IGNORE("-Wswitch")
GODOT_CLANG_WARNING_PUSH
GODOT_CLANG_WARNING_IGNORE("-Wimplicit-fallthrough")
GODOT_CLANG_WARNING_IGNORE("-Wmissing-field-initializers")
GODOT_CLANG_WARNING_IGNORE("-Wnon-virtual-dtor")
GODOT_CLANG_WARNING_IGNORE("-Wstring-plus-int")
GODOT_CLANG_WARNING_IGNORE("-Wswitch")
#include <thirdparty/directx_headers/include/directx/d3dx12.h>
GODOT_GCC_WARNING_POP
GODOT_CLANG_WARNING_POP
#if defined(AS)
#undef AS
#endif
#ifdef DCOMP_ENABLED
#include <dcomp.h>
#endif
#include <wrl/client.h>
#define ARRAY_SIZE(a) std_size(a)
class RenderingContextDriverD3D12 : public RenderingContextDriver {
Microsoft::WRL::ComPtr<ID3D12DeviceFactory> device_factory;
Microsoft::WRL::ComPtr<IDXGIFactory2> dxgi_factory;
TightLocalVector<Device> driver_devices;
bool tearing_supported = false;
Error _init_device_factory();
Error _initialize_debug_layers();
Error _create_dxgi_factory();
Error _initialize_devices();
public:
virtual Error initialize() override;
virtual const Device &device_get(uint32_t p_device_index) const override;
virtual uint32_t device_get_count() const override;
virtual bool device_supports_present(uint32_t p_device_index, SurfaceID p_surface) const override;
virtual RenderingDeviceDriver *driver_create() override;
virtual void driver_free(RenderingDeviceDriver *p_driver) override;
virtual SurfaceID surface_create(const void *p_platform_data) override;
virtual void surface_set_size(SurfaceID p_surface, uint32_t p_width, uint32_t p_height) override;
virtual void surface_set_vsync_mode(SurfaceID p_surface, DisplayServerEnums::VSyncMode p_vsync_mode) override;
virtual DisplayServerEnums::VSyncMode surface_get_vsync_mode(SurfaceID p_surface) const override;
virtual void surface_set_hdr_output_enabled(SurfaceID p_surface, bool p_enabled) override;
virtual bool surface_get_hdr_output_enabled(SurfaceID p_surface) const override;
virtual void surface_set_hdr_output_reference_luminance(SurfaceID p_surface, float p_reference_luminance) override;
virtual float surface_get_hdr_output_reference_luminance(SurfaceID p_surface) const override;
virtual void surface_set_hdr_output_max_luminance(SurfaceID p_surface, float p_max_luminance) override;
virtual float surface_get_hdr_output_max_luminance(SurfaceID p_surface) const override;
virtual void surface_set_hdr_output_linear_luminance_scale(SurfaceID p_surface, float p_linear_luminance_scale) override;
virtual float surface_get_hdr_output_linear_luminance_scale(SurfaceID p_surface) const override;
virtual float surface_get_hdr_output_max_value(SurfaceID p_surface) const override;
virtual uint32_t surface_get_width(SurfaceID p_surface) const override;
virtual uint32_t surface_get_height(SurfaceID p_surface) const override;
virtual void surface_set_needs_resize(SurfaceID p_surface, bool p_needs_resize) override;
virtual bool surface_get_needs_resize(SurfaceID p_surface) const override;
virtual void surface_destroy(SurfaceID p_surface) override;
virtual bool is_debug_utils_enabled() const override;
// Platform-specific data for the Windows embedded in this driver.
struct WindowPlatformData {
HWND window;
};
// D3D12-only methods.
struct Surface {
HWND hwnd = nullptr;
uint32_t width = 0;
uint32_t height = 0;
DisplayServerEnums::VSyncMode vsync_mode = DisplayServerEnums::VSYNC_ENABLED;
bool needs_resize = false;
bool hdr_output = false;
// BT.2408 recommendation of 203 nits for HDR Reference White, rounded to 200
// to be a more pleasant player-facing value. This value is used by Steam
// Deck and other Windows emulation that does not provide an SDRWhiteLevel.
float hdr_reference_luminance = 200.0f;
float hdr_max_luminance = 1000.0f;
float hdr_linear_luminance_scale = 80.0f;
#ifdef DCOMP_ENABLED
Microsoft::WRL::ComPtr<IDCompositionDevice> composition_device;
Microsoft::WRL::ComPtr<IDCompositionTarget> composition_target;
Microsoft::WRL::ComPtr<IDCompositionVisual> composition_visual;
#endif
};
HMODULE lib_d3d12 = nullptr;
HMODULE lib_dxgi = nullptr;
#ifdef DCOMP_ENABLED
HMODULE lib_dcomp = nullptr;
#endif
IDXGIAdapter1 *create_adapter(uint32_t p_adapter_index) const;
ID3D12DeviceFactory *device_factory_get() const;
IDXGIFactory2 *dxgi_factory_get();
bool get_tearing_supported() const;
bool use_validation_layers() const;
RenderingContextDriverD3D12();
virtual ~RenderingContextDriverD3D12() override;
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,981 @@
/**************************************************************************/
/* rendering_device_driver_d3d12.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/templates/a_hash_map.h"
#include "core/templates/hash_map.h"
#include "core/templates/paged_allocator.h"
#include "core/templates/rb_map.h"
#include "core/templates/self_list.h"
#include "rendering_shader_container_d3d12.h"
#include "servers/rendering/rendering_device_driver.h"
#if !defined(_MSC_VER) && !defined(__REQUIRED_RPCNDR_H_VERSION__)
// Match current version used by MinGW, MSVC and Direct3D 12 headers use 500.
#define __REQUIRED_RPCNDR_H_VERSION__ 475
#endif // !defined(_MSC_VER) && !defined(__REQUIRED_RPCNDR_H_VERSION__)
GODOT_GCC_WARNING_PUSH
GODOT_GCC_WARNING_IGNORE("-Wimplicit-fallthrough")
GODOT_GCC_WARNING_IGNORE("-Wmissing-field-initializers")
GODOT_GCC_WARNING_IGNORE("-Wnon-virtual-dtor")
GODOT_GCC_WARNING_IGNORE("-Wshadow")
GODOT_GCC_WARNING_IGNORE("-Wswitch")
GODOT_CLANG_WARNING_PUSH
GODOT_CLANG_WARNING_IGNORE("-Wimplicit-fallthrough")
GODOT_CLANG_WARNING_IGNORE("-Wmissing-field-initializers")
GODOT_CLANG_WARNING_IGNORE("-Wnon-virtual-dtor")
GODOT_CLANG_WARNING_IGNORE("-Wstring-plus-int")
GODOT_CLANG_WARNING_IGNORE("-Wswitch")
#include <thirdparty/directx_headers/include/directx/d3dx12.h>
GODOT_GCC_WARNING_POP
GODOT_CLANG_WARNING_POP
#include <wrl/client.h>
#ifdef DEV_ENABLED
#define CUSTOM_INFO_QUEUE_ENABLED 0
#endif
class RenderingContextDriverD3D12;
namespace D3D12MA {
class Allocation;
class Allocator;
class VirtualBlock;
}; // namespace D3D12MA
struct IDXGIAdapter;
struct IDXGISwapChain3;
// Design principles:
// - D3D12 structs are zero-initialized and fields not requiring a non-zero value are omitted (except in cases where expresivity reasons apply).
class RenderingDeviceDriverD3D12 : public RenderingDeviceDriver {
/*****************/
/**** GENERIC ****/
/*****************/
struct D3D12Format {
DXGI_FORMAT family = DXGI_FORMAT_UNKNOWN;
DXGI_FORMAT general_format = DXGI_FORMAT_UNKNOWN;
UINT swizzle = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
DXGI_FORMAT dsv_format = DXGI_FORMAT_UNKNOWN;
};
static const D3D12Format RD_TO_D3D12_FORMAT[RDD::DATA_FORMAT_MAX];
static const DXGI_COLOR_SPACE_TYPE RD_TO_DXGI_COLOR_SPACE_TYPE[RDD::COLOR_SPACE_MAX];
struct DeviceLimits {
uint64_t max_srvs_per_shader_stage = 0;
uint64_t max_cbvs_per_shader_stage = 0;
uint64_t max_samplers_across_all_stages = 0;
uint64_t max_uavs_across_all_stages = 0;
uint64_t timestamp_frequency = 0;
};
struct SubgroupCapabilities {
uint32_t size = 0;
bool wave_ops_supported = false;
uint32_t supported_stages_flags_rd() const;
uint32_t supported_operations_flags_rd() const;
};
struct ShaderCapabilities {
D3D_SHADER_MODEL shader_model = (D3D_SHADER_MODEL)0;
bool native_16bit_ops = false;
};
struct FormatCapabilities {
bool relaxed_casting_supported = false;
};
struct BarrierCapabilities {
bool enhanced_barriers_supported = false;
};
struct MiscFeaturesSupport {
bool depth_bounds_supported = false;
};
struct SamplerCapabilities {
bool aniso_filter_with_point_mip_supported = false;
};
RenderingContextDriverD3D12 *context_driver = nullptr;
RenderingContextDriver::Device context_device;
Microsoft::WRL::ComPtr<IDXGIAdapter> adapter;
Microsoft::WRL::ComPtr<ID3D12Device> device;
DeviceLimits device_limits;
RDD::Capabilities device_capabilities;
uint32_t feature_level = 0; // Major * 10 + minor.
SubgroupCapabilities subgroup_capabilities;
RDD::MultiviewCapabilities multiview_capabilities;
FragmentShadingRateCapabilities fsr_capabilities;
FragmentDensityMapCapabilities fdm_capabilities;
ShaderCapabilities shader_capabilities;
FormatCapabilities format_capabilities;
BarrierCapabilities barrier_capabilities;
MiscFeaturesSupport misc_features_support;
SamplerCapabilities sampler_capabilities;
RenderingShaderContainerFormatD3D12 shader_container_format;
String pipeline_cache_id;
D3D12_HEAP_TYPE dynamic_persistent_upload_heap = D3D12_HEAP_TYPE_UPLOAD;
struct DescriptorHeap {
struct Allocation {
uint64_t virtual_alloc_handle = {}; // This is the handle value in "D3D12MA::VirtualAllocation".
D3D12_CPU_DESCRIPTOR_HANDLE cpu_handle = {};
D3D12_GPU_DESCRIPTOR_HANDLE gpu_handle = {};
};
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> heap;
D3D12_CPU_DESCRIPTOR_HANDLE cpu_handle = {};
D3D12_GPU_DESCRIPTOR_HANDLE gpu_handle = {};
uint32_t increment_size = 0;
Microsoft::WRL::ComPtr<D3D12MA::VirtualBlock> virtual_block;
Error initialize(ID3D12Device *p_device, D3D12_DESCRIPTOR_HEAP_TYPE p_type, uint32_t p_num_descriptors, bool p_shader_visible);
Error allocate(uint32_t p_descriptor_count, Allocation &r_allocation);
void free(const Allocation &p_allocation);
};
// Some IHVs do not allow creating descriptor heaps beyond a certain limit, so they must be pooled.
struct CPUDescriptorHeapPool {
struct Allocation : DescriptorHeap::Allocation {
uint32_t heap_index = UINT_MAX;
};
BinaryMutex mutex;
LocalVector<DescriptorHeap> heaps;
D3D12_DESCRIPTOR_HEAP_TYPE type = {};
uint32_t increment_size = 0;
void initialize(ID3D12Device *p_device, D3D12_DESCRIPTOR_HEAP_TYPE p_type);
Error allocate(uint32_t p_descriptor_count, ID3D12Device *p_device, Allocation &r_allocation);
void free(const Allocation &p_allocation);
};
DescriptorHeap resource_descriptor_heap;
DescriptorHeap sampler_descriptor_heap;
CPUDescriptorHeapPool resource_descriptor_heap_pool;
CPUDescriptorHeapPool rtv_descriptor_heap_pool;
CPUDescriptorHeapPool dsv_descriptor_heap_pool;
CPUDescriptorHeapPool::Allocation null_rtv_alloc;
struct {
Microsoft::WRL::ComPtr<ID3D12CommandSignature> draw;
Microsoft::WRL::ComPtr<ID3D12CommandSignature> draw_indexed;
Microsoft::WRL::ComPtr<ID3D12CommandSignature> dispatch;
} indirect_cmd_signatures;
static void STDMETHODCALLTYPE _debug_message_func(D3D12_MESSAGE_CATEGORY p_category, D3D12_MESSAGE_SEVERITY p_severity, D3D12_MESSAGE_ID p_id, LPCSTR p_description, void *p_context);
void _set_object_name(ID3D12Object *p_object, String p_object_name);
Error _initialize_device();
Error _check_capabilities();
Error _get_device_limits();
Error _initialize_allocator();
Error _initialize_frames(uint32_t p_frame_count);
Error _initialize_command_signatures();
public:
Error initialize(uint32_t p_device_index, uint32_t p_frame_count) override final;
private:
/****************/
/**** MEMORY ****/
/****************/
Microsoft::WRL::ComPtr<D3D12MA::Allocator> allocator;
/******************/
/**** RESOURCE ****/
/******************/
struct ResourceInfo {
struct States {
// As many subresources as mipmaps * layers; planes (for depth-stencil) are tracked together.
TightLocalVector<D3D12_RESOURCE_STATES> subresource_states; // Used only if not a view.
uint32_t last_batch_with_uav_barrier = 0;
};
ID3D12Resource *resource = nullptr; // Non-null even if not owned.
struct {
Microsoft::WRL::ComPtr<ID3D12Resource> resource;
Microsoft::WRL::ComPtr<D3D12MA::Allocation> allocation;
States states;
} owner_info; // All empty if the resource is not owned.
States *states_ptr = nullptr; // Own or from another if it doesn't own the D3D12 resource.
};
struct BarrierRequest {
static const uint32_t MAX_GROUPS = 4;
// Maybe this is too much data to have it locally. Benchmarking may reveal that
// cache would be used better by having a maximum of local subresource masks and beyond
// that have an allocated vector with the rest.
static const uint32_t MAX_SUBRESOURCES = 4096;
ID3D12Resource *dx_resource = nullptr;
uint8_t subres_mask_qwords = 0;
uint8_t planes = 0;
struct Group {
D3D12_RESOURCE_STATES states = {};
static_assert(MAX_SUBRESOURCES % 64 == 0);
uint64_t subres_mask[MAX_SUBRESOURCES / 64] = {};
} groups[MAX_GROUPS];
uint8_t groups_count = 0;
static const D3D12_RESOURCE_STATES DELETED_GROUP = D3D12_RESOURCE_STATES(0xFFFFFFFFU);
};
struct CommandBufferInfo;
void _resource_transition_batch(CommandBufferInfo *p_command_buffer, ResourceInfo *p_resource, uint32_t p_subresource, uint32_t p_num_planes, D3D12_RESOURCE_STATES p_new_state);
void _resource_transitions_flush(CommandBufferInfo *p_command_buffer);
/*****************/
/**** BUFFERS ****/
/*****************/
struct BufferInfo : public ResourceInfo {
D3D12_GPU_VIRTUAL_ADDRESS gpu_virtual_address = {};
DataFormat texel_format = DATA_FORMAT_MAX;
uint64_t size = 0;
struct {
bool is_dynamic : 1; // Only used for tracking (e.g. Vulkan needs these checks).
} flags = {};
bool is_dynamic() const { return flags.is_dynamic; }
};
struct BufferDynamicInfo : BufferInfo {
uint32_t frame_idx = UINT32_MAX;
uint8_t *persistent_ptr = nullptr;
#ifdef DEBUG_ENABLED
// For tracking that a persistent buffer isn't mapped twice in the same frame.
uint64_t last_frame_mapped = 0;
#endif
};
public:
virtual BufferID buffer_create(uint64_t p_size, BitField<BufferUsageBits> p_usage, MemoryAllocationType p_allocation_type, uint64_t p_frames_drawn) override final;
virtual bool buffer_set_texel_format(BufferID p_buffer, DataFormat p_format) override final;
virtual void buffer_free(BufferID p_buffer) override final;
virtual uint64_t buffer_get_allocation_size(BufferID p_buffer) override final;
virtual uint8_t *buffer_map(BufferID p_buffer) override final;
virtual void buffer_unmap(BufferID p_buffer) override final;
virtual uint8_t *buffer_persistent_map_advance(BufferID p_buffer, uint64_t p_frames_drawn) override final;
virtual uint64_t buffer_get_dynamic_offsets(Span<BufferID> p_buffers) override final;
virtual uint64_t buffer_get_device_address(BufferID p_buffer) override final;
/*****************/
/**** TEXTURE ****/
/*****************/
private:
struct TextureInfo : public ResourceInfo {
DataFormat format = DATA_FORMAT_MAX;
CD3DX12_RESOURCE_DESC desc = {};
uint32_t base_layer = 0;
uint32_t layers = 0;
uint32_t base_mip = 0;
uint32_t mipmaps = 0;
struct {
D3D12_SHADER_RESOURCE_VIEW_DESC srv;
D3D12_UNORDERED_ACCESS_VIEW_DESC uav;
} view_descs = {};
TextureInfo *main_texture = nullptr;
#ifdef DEBUG_ENABLED
bool created_from_extension = false;
#endif
};
HashMap<DXGI_FORMAT, uint32_t> format_sample_counts_mask_cache;
Mutex format_sample_counts_mask_cache_mutex;
uint32_t _find_max_common_supported_sample_count(VectorView<DXGI_FORMAT> p_formats);
UINT _compute_component_mapping(const TextureView &p_view);
UINT _compute_plane_slice(DataFormat p_format, BitField<TextureAspectBits> p_aspect_bits);
UINT _compute_plane_slice(DataFormat p_format, TextureAspect p_aspect);
UINT _compute_subresource_from_layers(TextureInfo *p_texture, const TextureSubresourceLayers &p_layers, uint32_t p_layer_offset);
void _discard_texture_subresources(const TextureInfo *p_tex_info, const CommandBufferInfo *p_cmd_buf_info);
protected:
virtual bool _unordered_access_supported_by_format(DataFormat p_format);
public:
virtual TextureID texture_create(const TextureFormat &p_format, const TextureView &p_view) override final;
virtual TextureID texture_create_from_extension(uint64_t p_native_texture, TextureType p_type, DataFormat p_format, uint32_t p_array_layers, bool p_depth_stencil, uint32_t p_mipmaps) override final;
virtual TextureID texture_create_shared(TextureID p_original_texture, const TextureView &p_view) override final;
virtual TextureID texture_create_shared_from_slice(TextureID p_original_texture, const TextureView &p_view, TextureSliceType p_slice_type, uint32_t p_layer, uint32_t p_layers, uint32_t p_mipmap, uint32_t p_mipmaps) override final;
virtual void texture_free(TextureID p_texture) override final;
virtual uint64_t texture_get_allocation_size(TextureID p_texture) override final;
virtual void texture_get_copyable_layout(TextureID p_texture, const TextureSubresource &p_subresource, TextureCopyableLayout *r_layout) override final;
virtual Vector<uint8_t> texture_get_data(TextureID p_texture, uint32_t p_layer) override final;
virtual BitField<TextureUsageBits> texture_get_usages_supported_by_format(DataFormat p_format, bool p_cpu_readable) override final;
virtual bool texture_can_make_shared_with_format(TextureID p_texture, DataFormat p_format, bool &r_raw_reinterpretation) override final;
private:
TextureID _texture_create_shared_from_slice(TextureID p_original_texture, const TextureView &p_view, TextureSliceType p_slice_type, uint32_t p_layer, uint32_t p_layers, uint32_t p_mipmap, uint32_t p_mipmaps);
public:
/*****************/
/**** SAMPLER ****/
/*****************/
private:
LocalVector<D3D12_SAMPLER_DESC> samplers;
struct SamplerDescriptorHeapAllocation : DescriptorHeap::Allocation {
uint32_t key = 0;
uint32_t use_count = 1;
};
RBMap<uint32_t, SamplerDescriptorHeapAllocation> sampler_descriptor_heap_allocations;
public:
virtual SamplerID sampler_create(const SamplerState &p_state) final override;
virtual void sampler_free(SamplerID p_sampler) final override;
virtual bool sampler_is_format_supported_for_filter(DataFormat p_format, SamplerFilter p_filter) override final;
/**********************/
/**** VERTEX ARRAY ****/
/**********************/
private:
struct VertexFormatInfo {
TightLocalVector<D3D12_INPUT_ELEMENT_DESC> input_elem_descs;
TightLocalVector<UINT> vertex_buffer_strides;
};
public:
virtual VertexFormatID vertex_format_create(Span<VertexAttribute> p_vertex_attribs, const VertexAttributeBindingsMap &p_vertex_bindings) override final;
virtual void vertex_format_free(VertexFormatID p_vertex_format) override final;
/******************/
/**** BARRIERS ****/
/******************/
virtual void command_pipeline_barrier(
CommandBufferID p_cmd_buffer,
BitField<PipelineStageBits> p_src_stages,
BitField<PipelineStageBits> p_dst_stages,
VectorView<RDD::MemoryAccessBarrier> p_memory_barriers,
VectorView<RDD::BufferBarrier> p_buffer_barriers,
VectorView<RDD::TextureBarrier> p_texture_barriers,
VectorView<AccelerationStructureBarrier> p_acceleration_structure_barriers) override final;
private:
/****************/
/**** FENCES ****/
/****************/
struct FenceInfo {
Microsoft::WRL::ComPtr<ID3D12Fence> d3d_fence = nullptr;
HANDLE event_handle = nullptr;
UINT64 fence_value = 0;
};
public:
virtual FenceID fence_create() override;
virtual Error fence_wait(FenceID p_fence) override;
virtual void fence_free(FenceID p_fence) override;
private:
/********************/
/**** SEMAPHORES ****/
/********************/
struct SemaphoreInfo {
Microsoft::WRL::ComPtr<ID3D12Fence> d3d_fence = nullptr;
UINT64 fence_value = 0;
};
virtual SemaphoreID semaphore_create() override;
virtual void semaphore_free(SemaphoreID p_semaphore) override;
/******************/
/**** COMMANDS ****/
/******************/
// ----- QUEUE FAMILY -----
virtual CommandQueueFamilyID command_queue_family_get(BitField<CommandQueueFamilyBits> p_cmd_queue_family_bits, RenderingContextDriver::SurfaceID p_surface = 0) override;
private:
// ----- QUEUE -----
struct CommandQueueInfo {
Microsoft::WRL::ComPtr<ID3D12CommandQueue> d3d_queue;
};
public:
virtual CommandQueueID command_queue_create(CommandQueueFamilyID p_cmd_queue_family, bool p_identify_as_main_queue = false) override;
virtual Error command_queue_execute_and_present(CommandQueueID p_cmd_queue, VectorView<SemaphoreID> p_wait_semaphores, VectorView<CommandBufferID> p_cmd_buffers, VectorView<SemaphoreID> p_cmd_semaphores, FenceID p_cmd_fence, VectorView<SwapChainID> p_swap_chains) override;
virtual void command_queue_free(CommandQueueID p_cmd_queue) override;
private:
// ----- POOL -----
struct CommandPoolInfo {
CommandQueueFamilyID queue_family;
CommandBufferType buffer_type = COMMAND_BUFFER_TYPE_PRIMARY;
// Since there are no command pools in D3D12, we need to track the command buffers created by this pool
// so that we can free them when the pool is freed.
SelfList<CommandBufferInfo>::List command_buffers;
};
public:
virtual CommandPoolID command_pool_create(CommandQueueFamilyID p_cmd_queue_family, CommandBufferType p_cmd_buffer_type) override final;
virtual bool command_pool_reset(CommandPoolID p_cmd_pool) override final;
virtual void command_pool_free(CommandPoolID p_cmd_pool) override final;
// ----- BUFFER -----
private:
// Belongs to RENDERING-SUBPASS, but needed here.
struct FramebufferInfo;
struct RenderPassInfo;
struct RenderPassState {
struct AttachmentLayout {
struct AspectLayout {
TextureLayout cur_layout = TEXTURE_LAYOUT_UNDEFINED;
TextureLayout expected_layout = TEXTURE_LAYOUT_UNDEFINED;
};
AspectLayout aspect_layouts[TEXTURE_ASPECT_MAX];
};
uint32_t current_subpass = UINT32_MAX;
const FramebufferInfo *fb_info = nullptr;
const RenderPassInfo *pass_info = nullptr;
CD3DX12_RECT region_rect = {};
bool region_is_all = false;
LocalVector<AttachmentLayout> attachment_layouts;
const VertexFormatInfo *vf_info = nullptr;
D3D12_VERTEX_BUFFER_VIEW vertex_buffer_views[8] = {};
uint32_t vertex_buffer_count = 0;
};
struct DynParams {
D3D12_PRIMITIVE_TOPOLOGY primitive_topology = {};
Color blend_constant;
float depth_bounds_min = 0.0f;
float depth_bounds_max = 1.0f;
uint32_t stencil_reference = 0;
};
// Leveraging knowledge of actual usage and D3D12 specifics (namely, command lists from the same allocator
// can't be freely begun and ended), an allocator per list works better.
struct CommandBufferInfo {
// Store a self list reference to be used by the command pool.
SelfList<CommandBufferInfo> command_buffer_info_elem{ this };
Microsoft::WRL::ComPtr<ID3D12CommandAllocator> cmd_allocator;
Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList> cmd_list;
Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList1> cmd_list_1;
Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList5> cmd_list_5;
Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList7> cmd_list_7;
ID3D12PipelineState *graphics_pso = nullptr;
ID3D12PipelineState *compute_pso = nullptr;
DynParams dyn_params;
bool pending_dyn_params = true;
uint32_t graphics_root_signature_crc = 0;
uint32_t compute_root_signature_crc = 0;
RenderPassState render_pass_state;
bool descriptor_heaps_set = false;
HashMap<ResourceInfo::States *, BarrierRequest> res_barriers_requests;
LocalVector<D3D12_RESOURCE_BARRIER> res_barriers;
uint32_t res_barriers_count = 0;
uint32_t res_barriers_batch = 0;
CPUDescriptorHeapPool::Allocation uav_alloc;
CPUDescriptorHeapPool::Allocation rtv_alloc;
CPUDescriptorHeapPool::Allocation dsv_alloc;
};
public:
virtual CommandBufferID command_buffer_create(CommandPoolID p_cmd_pool) override final;
virtual bool command_buffer_begin(CommandBufferID p_cmd_buffer) override final;
virtual bool command_buffer_begin_secondary(CommandBufferID p_cmd_buffer, RenderPassID p_render_pass, uint32_t p_subpass, FramebufferID p_framebuffer) override final;
virtual void command_buffer_end(CommandBufferID p_cmd_buffer) override final;
virtual void command_buffer_execute_secondary(CommandBufferID p_cmd_buffer, VectorView<CommandBufferID> p_secondary_cmd_buffers) override final;
private:
/********************/
/**** SWAP CHAIN ****/
/********************/
struct SwapChain {
Microsoft::WRL::ComPtr<IDXGISwapChain3> d3d_swap_chain;
RenderingContextDriver::SurfaceID surface = RenderingContextDriver::SurfaceID();
UINT present_flags = 0;
UINT sync_interval = 1;
UINT creation_flags = 0;
RenderPassID render_pass;
TightLocalVector<ID3D12Resource *> render_targets;
TightLocalVector<TextureInfo> render_targets_info;
TightLocalVector<FramebufferID> framebuffers;
RDD::DataFormat data_format = DATA_FORMAT_MAX;
RDD::ColorSpace color_space = COLOR_SPACE_MAX;
};
void _swap_chain_release(SwapChain *p_swap_chain);
void _swap_chain_release_buffers(SwapChain *p_swap_chain);
RenderPassID _swap_chain_create_render_pass(RDD::DataFormat p_format);
void _determine_swap_chain_format(SwapChain *p_swap_chain, DataFormat &r_format, ColorSpace &r_color_space);
public:
virtual SwapChainID swap_chain_create(RenderingContextDriver::SurfaceID p_surface) override;
virtual Error swap_chain_resize(CommandQueueID p_cmd_queue, SwapChainID p_swap_chain, uint32_t p_desired_framebuffer_count) override;
virtual FramebufferID swap_chain_acquire_framebuffer(CommandQueueID p_cmd_queue, SwapChainID p_swap_chain, bool &r_resize_required) override;
virtual RenderPassID swap_chain_get_render_pass(SwapChainID p_swap_chain) override;
virtual DataFormat swap_chain_get_format(SwapChainID p_swap_chain) override;
virtual ColorSpace swap_chain_get_color_space(SwapChainID p_swap_chain) override;
virtual void swap_chain_free(SwapChainID p_swap_chain) override;
/*********************/
/**** FRAMEBUFFER ****/
/*********************/
private:
struct FramebufferInfo {
bool is_screen = false;
Size2i size;
TightLocalVector<uint32_t> attachments_handle_inds; // RTV heap index for color; DSV heap index for DSV.
CPUDescriptorHeapPool::Allocation rtv_alloc;
CPUDescriptorHeapPool::Allocation dsv_alloc; // Used only for depth-stencil attachments.
TightLocalVector<TextureID> attachments; // Color and depth-stencil. Used if not screen.
TextureID vrs_attachment;
};
D3D12_RENDER_TARGET_VIEW_DESC _make_rtv_for_texture(const TextureInfo *p_texture_info, uint32_t p_mipmap_offset, uint32_t p_layer_offset, uint32_t p_layers, bool p_add_bases = true);
D3D12_UNORDERED_ACCESS_VIEW_DESC _make_ranged_uav_for_texture(const TextureInfo *p_texture_info, uint32_t p_mipmap_offset, uint32_t p_layer_offset, uint32_t p_layers, bool p_add_bases = true);
D3D12_DEPTH_STENCIL_VIEW_DESC _make_dsv_for_texture(const TextureInfo *p_texture_info, uint32_t p_mipmap_offset, uint32_t p_layer_offset, uint32_t p_layers, bool p_add_bases = true);
FramebufferID _framebuffer_create(RenderPassID p_render_pass, VectorView<TextureID> p_attachments, uint32_t p_width, uint32_t p_height, bool p_is_screen);
public:
virtual FramebufferID framebuffer_create(RenderPassID p_render_pass, VectorView<TextureID> p_attachments, uint32_t p_width, uint32_t p_height) override final;
virtual void framebuffer_free(FramebufferID p_framebuffer) override final;
/****************/
/**** SHADER ****/
/****************/
private:
static const uint32_t ROOT_SIGNATURE_SIZE = 256;
static const uint32_t PUSH_CONSTANT_SIZE = 128; // Mimicking Vulkan.
enum {
// We can only aim to set a maximum here, since depending on the shader
// there may be more or less root signature free for descriptor tables.
// Therefore, we'll have to rely on the final check at runtime, when building
// the root signature structure for a given shader.
// To be precise, these may be present or not, and their size vary statically:
// - Push constant (we'll assume this is always present to avoid reserving much
// more space for descriptor sets than needed for almost any imaginable case,
// given that most shader templates feature push constants).
// - NIR-DXIL runtime data.
MAX_UNIFORM_SETS = (ROOT_SIGNATURE_SIZE - PUSH_CONSTANT_SIZE) / sizeof(uint32_t),
};
struct ShaderInfo {
uint32_t dxil_push_constant_size = 0;
uint32_t nir_runtime_data_root_param_idx = UINT32_MAX;
PipelineType pipeline_type = PIPELINE_TYPE_RASTERIZATION;
struct UniformBindingInfo {
uint32_t stages = 0; // Actual shader stages using the uniform (0 if totally optimized out).
ResourceClass res_class = RES_CLASS_INVALID;
UniformType type = UNIFORM_TYPE_MAX;
uint32_t length = UINT32_MAX;
bool writable = false;
uint32_t resource_descriptor_offset = UINT32_MAX;
uint32_t sampler_descriptor_offset = UINT32_MAX;
uint32_t root_param_idx = UINT32_MAX;
};
struct UniformSet {
TightLocalVector<UniformBindingInfo> bindings;
uint32_t resource_root_param_idx = UINT32_MAX;
uint32_t resource_descriptor_count = 0;
uint32_t sampler_root_param_idx = UINT32_MAX;
uint32_t sampler_descriptor_count = 0;
};
TightLocalVector<UniformSet> sets;
struct SpecializationConstant {
uint32_t constant_id = UINT32_MAX;
uint32_t int_value = UINT32_MAX;
uint64_t stages_bit_offsets[D3D12_BITCODE_OFFSETS_NUM_STAGES] = {};
};
TightLocalVector<SpecializationConstant> specialization_constants;
uint32_t spirv_specialization_constants_ids_mask = 0;
HashMap<ShaderStage, Vector<uint8_t>> stages_bytecode;
Microsoft::WRL::ComPtr<ID3D12RootSignature> root_signature;
Microsoft::WRL::ComPtr<ID3D12RootSignatureDeserializer> root_signature_deserializer;
const D3D12_ROOT_SIGNATURE_DESC *root_signature_desc = nullptr; // Owned by the deserializer.
uint32_t root_signature_crc = 0;
};
bool _shader_apply_specialization_constants(
const ShaderInfo *p_shader_info,
VectorView<PipelineSpecializationConstant> p_specialization_constants,
HashMap<ShaderStage, Vector<uint8_t>> &r_final_stages_bytecode);
public:
virtual ShaderID shader_create_from_container(const Ref<RenderingShaderContainer> &p_shader_container, const Vector<ImmutableSampler> &p_immutable_samplers) override final;
virtual uint32_t shader_get_layout_hash(ShaderID p_shader) override final;
virtual void shader_free(ShaderID p_shader) override final;
virtual void shader_destroy_modules(ShaderID p_shader) override final;
/*********************/
/**** UNIFORM SET ****/
/*********************/
private:
struct UniformSetInfo {
DescriptorHeap::Allocation resource_descriptor_heap_alloc;
SamplerDescriptorHeapAllocation *sampler_descriptor_heap_alloc = nullptr;
struct DynamicBuffer {
BufferDynamicInfo const *info = nullptr;
uint32_t binding = UINT_MAX;
};
TightLocalVector<DynamicBuffer> dynamic_buffers;
struct StateRequirement {
ResourceInfo *resource = nullptr;
bool is_buffer = false;
D3D12_RESOURCE_STATES states = {};
uint64_t shader_uniform_idx_mask = 0;
};
TightLocalVector<StateRequirement> resource_states;
};
public:
virtual UniformSetID uniform_set_create(VectorView<BoundUniform> p_uniforms, ShaderID p_shader, uint32_t p_set_index, int p_linear_pool_index) override final;
virtual void uniform_set_free(UniformSetID p_uniform_set) override final;
virtual uint32_t uniform_sets_get_dynamic_offsets(VectorView<UniformSetID> p_uniform_sets, ShaderID p_shader, uint32_t p_first_set_index, uint32_t p_set_count) const override final;
// ----- COMMANDS -----
virtual void command_uniform_set_prepare_for_use(CommandBufferID p_cmd_buffer, UniformSetID p_uniform_set, ShaderID p_shader, uint32_t p_set_index) override final;
private:
void _command_check_descriptor_sets(CommandBufferID p_cmd_buffer);
DescriptorHeap::Allocation _command_allocate_per_frame_descriptor();
public:
/******************/
/**** TRANSFER ****/
/******************/
virtual void command_clear_buffer(CommandBufferID p_cmd_buffer, BufferID p_buffer, uint64_t p_offset, uint64_t p_size) override final;
virtual void command_copy_buffer(CommandBufferID p_cmd_buffer, BufferID p_src_buffer, BufferID p_dst_buffer, VectorView<BufferCopyRegion> p_regions) override final;
virtual void command_copy_texture(CommandBufferID p_cmd_buffer, TextureID p_src_texture, TextureLayout p_src_texture_layout, TextureID p_dst_texture, TextureLayout p_dst_texture_layout, VectorView<TextureCopyRegion> p_regions) override final;
virtual void command_resolve_texture(CommandBufferID p_cmd_buffer, TextureID p_src_texture, TextureLayout p_src_texture_layout, uint32_t p_src_layer, uint32_t p_src_mipmap, TextureID p_dst_texture, TextureLayout p_dst_texture_layout, uint32_t p_dst_layer, uint32_t p_dst_mipmap) override final;
virtual void command_clear_color_texture(CommandBufferID p_cmd_buffer, TextureID p_texture, TextureLayout p_texture_layout, const Color &p_color, const TextureSubresourceRange &p_subresources) override final;
virtual void command_clear_depth_stencil_texture(CommandBufferID p_cmd_buffer, TextureID p_texture, TextureLayout p_texture_layout, float p_depth, uint8_t p_stencil, const TextureSubresourceRange &p_subresources) override final;
public:
virtual void command_copy_buffer_to_texture(CommandBufferID p_cmd_buffer, BufferID p_src_buffer, TextureID p_dst_texture, TextureLayout p_dst_texture_layout, VectorView<BufferTextureCopyRegion> p_regions) override final;
virtual void command_copy_texture_to_buffer(CommandBufferID p_cmd_buffer, TextureID p_src_texture, TextureLayout p_src_texture_layout, BufferID p_dst_buffer, VectorView<BufferTextureCopyRegion> p_regions) override final;
/******************/
/**** PIPELINE ****/
/******************/
struct RenderPipelineInfo {
const VertexFormatInfo *vf_info = nullptr;
DynParams dyn_params;
};
struct PipelineInfo {
Microsoft::WRL::ComPtr<ID3D12PipelineState> pso;
const ShaderInfo *shader_info = nullptr;
RenderPipelineInfo render_info;
};
virtual void pipeline_free(PipelineID p_pipeline) override final;
public:
// ----- BINDING -----
virtual void command_bind_push_constants(CommandBufferID p_cmd_buffer, ShaderID p_shader, uint32_t p_dst_first_index, VectorView<uint32_t> p_data) override final;
// ----- CACHE -----
virtual bool pipeline_cache_create(const Vector<uint8_t> &p_data) override final;
virtual void pipeline_cache_free() override final;
virtual size_t pipeline_cache_query_size() override final;
virtual Vector<uint8_t> pipeline_cache_serialize() override final;
/*******************/
/**** RENDERING ****/
/*******************/
// ----- SUBPASS -----
private:
struct RenderPassInfo {
TightLocalVector<Attachment> attachments;
TightLocalVector<Subpass> subpasses;
uint32_t view_count = 0;
uint32_t max_supported_sample_count = 0;
};
public:
virtual RenderPassID render_pass_create(VectorView<Attachment> p_attachments, VectorView<Subpass> p_subpasses, VectorView<SubpassDependency> p_subpass_dependencies, uint32_t p_view_count, AttachmentReference p_fragment_density_map_attachment) override final;
virtual void render_pass_free(RenderPassID p_render_pass) override final;
// ----- COMMANDS -----
virtual void command_begin_render_pass(CommandBufferID p_cmd_buffer, RenderPassID p_render_pass, FramebufferID p_framebuffer, CommandBufferType p_cmd_buffer_type, const Rect2i &p_rect, VectorView<RenderPassClearValue> p_clear_values) override final;
private:
void _render_pass_enhanced_barriers_flush(CommandBufferID p_cmd_buffer);
void _end_render_pass(CommandBufferID p_cmd_buffer);
public:
virtual void command_end_render_pass(CommandBufferID p_cmd_buffer) override final;
virtual void command_next_render_subpass(CommandBufferID p_cmd_buffer, CommandBufferType p_cmd_buffer_type) override final;
virtual void command_render_set_viewport(CommandBufferID p_cmd_buffer, VectorView<Rect2i> p_viewports) override final;
virtual void command_render_set_scissor(CommandBufferID p_cmd_buffer, VectorView<Rect2i> p_scissors) override final;
virtual void command_render_clear_attachments(CommandBufferID p_cmd_buffer, VectorView<AttachmentClear> p_attachment_clears, VectorView<Rect2i> p_rects) override final;
// Binding.
virtual void command_bind_render_pipeline(CommandBufferID p_cmd_buffer, PipelineID p_pipeline) override final;
virtual void command_bind_render_uniform_sets(CommandBufferID p_cmd_buffer, VectorView<UniformSetID> p_uniform_sets, ShaderID p_shader, uint32_t p_first_set_index, uint32_t p_set_count, uint32_t p_dynamic_offsets) override final;
// Drawing.
virtual void command_render_draw(CommandBufferID p_cmd_buffer, uint32_t p_vertex_count, uint32_t p_instance_count, uint32_t p_base_vertex, uint32_t p_first_instance) override final;
virtual void command_render_draw_indexed(CommandBufferID p_cmd_buffer, uint32_t p_index_count, uint32_t p_instance_count, uint32_t p_first_index, int32_t p_vertex_offset, uint32_t p_first_instance) override final;
virtual void command_render_draw_indexed_indirect(CommandBufferID p_cmd_buffer, BufferID p_indirect_buffer, uint64_t p_offset, uint32_t p_draw_count, uint32_t p_stride) override final;
virtual void command_render_draw_indexed_indirect_count(CommandBufferID p_cmd_buffer, BufferID p_indirect_buffer, uint64_t p_offset, BufferID p_count_buffer, uint64_t p_count_buffer_offset, uint32_t p_max_draw_count, uint32_t p_stride) override final;
virtual void command_render_draw_indirect(CommandBufferID p_cmd_buffer, BufferID p_indirect_buffer, uint64_t p_offset, uint32_t p_draw_count, uint32_t p_stride) override final;
virtual void command_render_draw_indirect_count(CommandBufferID p_cmd_buffer, BufferID p_indirect_buffer, uint64_t p_offset, BufferID p_count_buffer, uint64_t p_count_buffer_offset, uint32_t p_max_draw_count, uint32_t p_stride) override final;
// Buffer binding.
virtual void command_render_bind_vertex_buffers(CommandBufferID p_cmd_buffer, uint32_t p_binding_count, const BufferID *p_buffers, const uint64_t *p_offsets, uint64_t p_dynamic_offsets) override final;
virtual void command_render_bind_index_buffer(CommandBufferID p_cmd_buffer, BufferID p_buffer, IndexBufferFormat p_format, uint64_t p_offset) override final;
private:
void _bind_vertex_buffers(CommandBufferInfo *p_cmd_buf_info);
public:
// Dynamic state.
virtual void command_render_set_blend_constants(CommandBufferID p_cmd_buffer, const Color &p_constants) override final;
virtual void command_render_set_line_width(CommandBufferID p_cmd_buffer, float p_width) override final;
// ----- PIPELINE -----
public:
virtual PipelineID render_pipeline_create(
ShaderID p_shader,
VertexFormatID p_vertex_format,
RenderPrimitive p_render_primitive,
PipelineRasterizationState p_rasterization_state,
PipelineMultisampleState p_multisample_state,
PipelineDepthStencilState p_depth_stencil_state,
PipelineColorBlendState p_blend_state,
VectorView<int32_t> p_color_attachments,
BitField<PipelineDynamicStateFlags> p_dynamic_state,
RenderPassID p_render_pass,
uint32_t p_render_subpass,
VectorView<PipelineSpecializationConstant> p_specialization_constants) override final;
/*****************/
/**** COMPUTE ****/
/*****************/
// ----- COMMANDS -----
// Binding.
virtual void command_bind_compute_pipeline(CommandBufferID p_cmd_buffer, PipelineID p_pipeline) override final;
virtual void command_bind_compute_uniform_sets(CommandBufferID p_cmd_buffer, VectorView<UniformSetID> p_uniform_sets, ShaderID p_shader, uint32_t p_first_set_index, uint32_t p_set_count, uint32_t p_dynamic_offsets) override final;
// Dispatching.
virtual void command_compute_dispatch(CommandBufferID p_cmd_buffer, uint32_t p_x_groups, uint32_t p_y_groups, uint32_t p_z_groups) override final;
virtual void command_compute_dispatch_indirect(CommandBufferID p_cmd_buffer, BufferID p_indirect_buffer, uint64_t p_offset) override final;
// ----- PIPELINE -----
virtual PipelineID compute_pipeline_create(ShaderID p_shader, VectorView<PipelineSpecializationConstant> p_specialization_constants) override final;
/********************/
/**** RAYTRACING ****/
/********************/
// ---- ACCELERATION STRUCTURES ----
virtual AccelerationStructureID blas_create(BufferID p_vertex_buffer, uint64_t p_vertex_offset, VertexFormatID p_vertex_format, uint32_t p_vertex_count, uint32_t p_position_attribute_location, BufferID p_index_buffer, IndexBufferFormat p_index_format, uint64_t p_index_offset, uint32_t p_index_count, BitField<AccelerationStructureGeometryBits> p_geometry_bits) override final;
virtual uint32_t tlas_instances_buffer_get_size_bytes(uint32_t p_instance_count) override final;
virtual void tlas_instances_buffer_fill(BufferID p_instances_buffer, VectorView<AccelerationStructureID> p_blases, VectorView<Transform3D> p_transforms) override final;
virtual AccelerationStructureID tlas_create(BufferID p_instances_buffer) override final;
virtual void acceleration_structure_free(AccelerationStructureID p_acceleration_structure) override final;
virtual uint32_t acceleration_structure_get_scratch_size_bytes(AccelerationStructureID p_acceleration_structure) override final;
// ----- PIPELINE -----
virtual RaytracingPipelineID raytracing_pipeline_create(ShaderID p_shader, VectorView<PipelineSpecializationConstant> p_specialization_constants) override final;
virtual void raytracing_pipeline_free(RaytracingPipelineID p_pipeline) override final;
// ----- COMMANDS -----
virtual void command_build_acceleration_structure(CommandBufferID p_cmd_buffer, AccelerationStructureID p_acceleration_structure, BufferID p_scratch_buffer) override final;
virtual void command_bind_raytracing_pipeline(CommandBufferID p_cmd_buffer, RaytracingPipelineID p_pipeline) override final;
virtual void command_bind_raytracing_uniform_set(CommandBufferID p_cmd_buffer, UniformSetID p_uniform_set, ShaderID p_shader, uint32_t p_set_index) override final;
virtual void command_trace_rays(CommandBufferID p_cmd_buffer, uint32_t p_width, uint32_t p_height) override final;
/*****************/
/**** QUERIES ****/
/*****************/
// ----- TIMESTAMP -----
private:
struct TimestampQueryPoolInfo {
Microsoft::WRL::ComPtr<ID3D12QueryHeap> query_heap;
uint32_t query_count = 0;
Microsoft::WRL::ComPtr<D3D12MA::Allocation> results_buffer_allocation;
};
public:
// Basic.
virtual QueryPoolID timestamp_query_pool_create(uint32_t p_query_count) override final;
virtual void timestamp_query_pool_free(QueryPoolID p_pool_id) override final;
virtual void timestamp_query_pool_get_results(QueryPoolID p_pool_id, uint32_t p_query_count, uint64_t *r_results) override final;
virtual uint64_t timestamp_query_result_to_time(uint64_t p_result) override final;
// Commands.
virtual void command_timestamp_query_pool_reset(CommandBufferID p_cmd_buffer, QueryPoolID p_pool_id, uint32_t p_query_count) override final;
virtual void command_timestamp_write(CommandBufferID p_cmd_buffer, QueryPoolID p_pool_id, uint32_t p_index) override final;
/****************/
/**** LABELS ****/
/****************/
virtual void command_begin_label(CommandBufferID p_cmd_buffer, const char *p_label_name, const Color &p_color) override final;
virtual void command_end_label(CommandBufferID p_cmd_buffer) override final;
/****************/
/**** DEBUG *****/
/****************/
virtual void command_insert_breadcrumb(CommandBufferID p_cmd_buffer, uint32_t p_data) override final;
/********************/
/**** SUBMISSION ****/
/********************/
private:
struct FrameInfo {
LocalVector<DescriptorHeap::Allocation> descriptor_allocations;
uint32_t descriptor_allocation_count = 0;
};
TightLocalVector<FrameInfo> frames;
uint32_t frame_idx = 0;
uint32_t frames_drawn = 0;
bool segment_begun = false;
HashMap<uint64_t, bool> has_comp_alpha;
public:
virtual void begin_segment(uint32_t p_frame_index, uint32_t p_frames_drawn) override final;
virtual void end_segment() override final;
/**************/
/**** MISC ****/
/**************/
virtual void set_object_name(ObjectType p_type, ID p_driver_id, const String &p_name) override final;
virtual uint64_t get_resource_native_handle(DriverResource p_type, ID p_driver_id) override final;
virtual uint64_t get_total_memory_used() override final;
virtual uint64_t get_lazily_memory_used() override final;
virtual uint64_t limit_get(Limit p_limit) override final;
virtual uint64_t api_trait_get(ApiTrait p_trait) override final;
virtual bool has_feature(Features p_feature) override final;
virtual const MultiviewCapabilities &get_multiview_capabilities() override final;
virtual const FragmentShadingRateCapabilities &get_fragment_shading_rate_capabilities() override final;
virtual const FragmentDensityMapCapabilities &get_fragment_density_map_capabilities() override final;
virtual String get_api_name() const override final;
virtual String get_api_version() const override final;
virtual String get_pipeline_cache_uuid() const override final;
virtual const Capabilities &get_capabilities() const override final;
virtual const RenderingShaderContainerFormat &get_shader_container_format() const override final;
virtual bool is_composite_alpha_supported(CommandQueueID p_queue) const override final;
static bool is_in_developer_mode();
private:
/*********************/
/**** BOOKKEEPING ****/
/*********************/
using VersatileResource = VersatileResourceTemplate<
BufferInfo,
TextureInfo,
TextureInfo,
TextureInfo,
VertexFormatInfo,
CommandBufferInfo,
FramebufferInfo,
ShaderInfo,
UniformSetInfo,
RenderPassInfo,
TimestampQueryPoolInfo>;
PagedAllocator<VersatileResource, true> resources_allocator;
/******************/
public:
RenderingDeviceDriverD3D12(RenderingContextDriverD3D12 *p_context_driver);
virtual ~RenderingDeviceDriverD3D12();
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,185 @@
/**************************************************************************/
/* rendering_shader_container_d3d12.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "servers/rendering/rendering_shader_container.h"
#define NIR_ENABLED 1
#ifdef SHADER_BAKER_RUNTIME_ENABLED
#undef NIR_ENABLED
#endif
#include "d3d12_godot_nir_bridge.h"
#define D3D12_BITCODE_OFFSETS_NUM_STAGES 3
#if NIR_ENABLED
struct nir_shader;
struct nir_shader_compiler_options;
#endif
enum RootSignatureLocationType {
RS_LOC_TYPE_RESOURCE,
RS_LOC_TYPE_SAMPLER,
};
enum ResourceClass {
RES_CLASS_INVALID,
RES_CLASS_CBV,
RES_CLASS_SRV,
RES_CLASS_UAV,
};
struct RenderingDXIL {
static uint32_t patch_specialization_constant(
RenderingDeviceCommons::PipelineSpecializationConstantType p_type,
const void *p_value,
const uint64_t (&p_stages_bit_offsets)[D3D12_BITCODE_OFFSETS_NUM_STAGES],
HashMap<RenderingDeviceCommons::ShaderStage, Vector<uint8_t>> &r_stages_bytecodes,
bool p_is_first_patch);
static void sign_bytecode(RenderingDeviceCommons::ShaderStage p_stage, Vector<uint8_t> &r_dxil_blob);
};
class RenderingShaderContainerD3D12 : public RenderingShaderContainer {
GDSOFTCLASS(RenderingShaderContainerD3D12, RenderingShaderContainer);
public:
static constexpr uint32_t REQUIRED_SHADER_MODEL = 0x62; // D3D_SHADER_MODEL_6_2
static constexpr uint32_t ROOT_CONSTANT_REGISTER = GODOT_NIR_DESCRIPTOR_SET_MULTIPLIER * (RenderingDeviceCommons::MAX_UNIFORM_SETS + 1);
static constexpr uint32_t RUNTIME_DATA_REGISTER = GODOT_NIR_DESCRIPTOR_SET_MULTIPLIER * (RenderingDeviceCommons::MAX_UNIFORM_SETS + 2);
static constexpr uint32_t FORMAT_VERSION = 1;
static constexpr uint32_t SHADER_STAGES_BIT_OFFSET_INDICES[RenderingDeviceCommons::SHADER_STAGE_MAX] = {
0, // SHADER_STAGE_VERTEX
1, // SHADER_STAGE_FRAGMENT
UINT32_MAX, // SHADER_STAGE_TESSELATION_CONTROL
UINT32_MAX, // SHADER_STAGE_TESSELATION_EVALUATION
2, // SHADER_STAGE_COMPUTE
};
struct ReflectionBindingSetDataD3D12 {
uint32_t resource_root_param_idx = UINT32_MAX;
uint32_t resource_descriptor_count = 0;
uint32_t sampler_root_param_idx = UINT32_MAX;
uint32_t sampler_descriptor_count = 0;
};
struct ReflectionBindingDataD3D12 {
uint32_t resource_class = 0;
uint32_t has_sampler = 0;
uint32_t dxil_stages = 0;
uint32_t resource_descriptor_offset = UINT32_MAX;
uint32_t sampler_descriptor_offset = UINT32_MAX;
uint32_t root_param_idx = UINT32_MAX; // Root descriptor only.
};
struct ReflectionSpecializationDataD3D12 {
uint64_t stages_bit_offsets[D3D12_BITCODE_OFFSETS_NUM_STAGES] = {};
};
protected:
struct ReflectionDataD3D12 {
uint32_t spirv_specialization_constants_ids_mask = 0;
uint32_t dxil_push_constant_stages = 0;
uint32_t nir_runtime_data_root_param_idx = 0;
};
struct ContainerFooterD3D12 {
uint32_t root_signature_length = 0;
uint32_t root_signature_crc = 0;
};
void *lib_d3d12 = nullptr;
ReflectionDataD3D12 reflection_data_d3d12;
Vector<ReflectionBindingSetDataD3D12> reflection_binding_set_data_d3d12;
Vector<ReflectionBindingDataD3D12> reflection_binding_set_uniforms_data_d3d12;
Vector<ReflectionSpecializationDataD3D12> reflection_specialization_data_d3d12;
Vector<uint8_t> root_signature_bytes;
uint32_t root_signature_crc = 0;
#if NIR_ENABLED
bool _convert_spirv_to_nir(Span<ReflectShaderStage> p_spirv, const nir_shader_compiler_options *p_compiler_options, HashMap<int, nir_shader *> &r_stages_nir_shaders, Vector<RenderingDeviceCommons::ShaderStage> &r_stages, BitField<RenderingDeviceCommons::ShaderStage> &r_stages_processed);
bool _convert_nir_to_dxil(const HashMap<int, nir_shader *> &p_stages_nir_shaders, BitField<RenderingDeviceCommons::ShaderStage> p_stages_processed, HashMap<RenderingDeviceCommons::ShaderStage, Vector<uint8_t>> &r_dxil_blobs);
bool _convert_spirv_to_dxil(Span<ReflectShaderStage> p_spirv, HashMap<RenderingDeviceCommons::ShaderStage, Vector<uint8_t>> &r_dxil_blobs, Vector<RenderingDeviceCommons::ShaderStage> &r_stages, BitField<RenderingDeviceCommons::ShaderStage> &r_stages_processed);
bool _generate_root_signature(BitField<RenderingDeviceCommons::ShaderStage> p_stages_processed);
// GodotNirCallbacks.
static void _nir_report_resource(uint32_t p_register, uint32_t p_space, uint32_t p_dxil_type, void *p_data);
static void _nir_report_sc_bit_offset(uint32_t p_sc_id, uint64_t p_bit_offset, void *p_data);
static void _nir_report_bitcode_bit_offset(uint64_t p_bit_offset, void *p_data);
#endif
// RenderingShaderContainer overrides.
virtual uint32_t _format() const override;
virtual uint32_t _format_version() const override;
virtual uint32_t _from_bytes_reflection_extra_data(const uint8_t *p_bytes) override;
virtual uint32_t _from_bytes_reflection_binding_uniform_extra_data_start(const uint8_t *p_bytes) override;
virtual uint32_t _from_bytes_reflection_binding_uniform_extra_data(const uint8_t *p_bytes, uint32_t p_index) override;
virtual uint32_t _from_bytes_reflection_specialization_extra_data_start(const uint8_t *p_bytes) override;
virtual uint32_t _from_bytes_reflection_specialization_extra_data(const uint8_t *p_bytes, uint32_t p_index) override;
virtual uint32_t _from_bytes_footer_extra_data(const uint8_t *p_bytes) override;
virtual uint32_t _to_bytes_reflection_extra_data(uint8_t *p_bytes) const override;
virtual uint32_t _to_bytes_reflection_binding_uniform_extra_data(uint8_t *p_bytes, uint32_t p_index) const override;
virtual uint32_t _to_bytes_reflection_specialization_extra_data(uint8_t *p_bytes, uint32_t p_index) const override;
virtual uint32_t _to_bytes_footer_extra_data(uint8_t *p_bytes) const override;
virtual void _set_from_shader_reflection_post(const ReflectShader &p_shader) override;
virtual bool _set_code_from_spirv(const ReflectShader &p_shader) override;
public:
struct ShaderReflectionD3D12 {
uint32_t spirv_specialization_constants_ids_mask = 0;
uint32_t dxil_push_constant_stages = 0;
uint32_t nir_runtime_data_root_param_idx = 0;
Vector<ReflectionBindingSetDataD3D12> reflection_binding_sets_d3d12;
Vector<Vector<ReflectionBindingDataD3D12>> reflection_binding_set_uniforms_d3d12;
Vector<ReflectionSpecializationDataD3D12> reflection_specialization_data_d3d12;
Vector<uint8_t> root_signature_bytes;
uint32_t root_signature_crc = 0;
};
RenderingShaderContainerD3D12();
RenderingShaderContainerD3D12(void *p_lib_d3d12);
ShaderReflectionD3D12 get_shader_reflection_d3d12() const;
};
class RenderingShaderContainerFormatD3D12 : public RenderingShaderContainerFormat {
protected:
void *lib_d3d12 = nullptr;
public:
void set_lib_d3d12(void *p_lib_d3d12);
virtual Ref<RenderingShaderContainer> create_container() const override;
virtual ShaderLanguageVersion get_shader_language_version() const override;
virtual ShaderSpirvVersion get_shader_spirv_version() const override;
RenderingShaderContainerFormatD3D12();
virtual ~RenderingShaderContainerFormatD3D12();
};

7
engine/drivers/egl/SCsub Normal file
View file

@ -0,0 +1,7 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
# Godot source files
env.add_source_files(env.drivers_sources, "*.cpp")

View file

@ -0,0 +1,544 @@
/**************************************************************************/
/* egl_manager.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "egl_manager.h"
#include "core/config/engine.h"
#include "core/crypto/crypto_core.h"
#include "core/io/dir_access.h"
#include "core/io/file_access.h"
#include "core/os/os.h"
#ifdef WINDOWS_ENABLED
#include "drivers/gles3/rasterizer_gles3.h"
#endif
#ifdef EGL_ENABLED
#if defined(EGL_STATIC)
#define GLAD_EGL_VERSION_1_5 true
#ifdef EGL_EXT_platform_base
#define GLAD_EGL_EXT_platform_base 1
#endif
#define KHRONOS_STATIC 1
extern "C" EGLAPI void EGLAPIENTRY eglSetBlobCacheFuncsANDROID(EGLDisplay dpy, EGLSetBlobFuncANDROID set, EGLGetBlobFuncANDROID get);
extern "C" EGLAPI EGLDisplay EGLAPIENTRY eglGetPlatformDisplayEXT(EGLenum platform, void *native_display, const EGLint *attrib_list);
#undef KHRONOS_STATIC
#endif // defined(EGL_STATIC)
#ifndef EGL_EXT_platform_base
#define GLAD_EGL_EXT_platform_base 0
#endif
#ifdef WINDOWS_ENABLED
// Unofficial ANGLE extension: EGL_ANGLE_surface_orientation
#ifndef EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE
#define EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE 0x33A7
#define EGL_SURFACE_ORIENTATION_ANGLE 0x33A8
#define EGL_SURFACE_ORIENTATION_INVERT_X_ANGLE 0x0001
#define EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE 0x0002
#endif
#endif
// Creates and caches a GLDisplay. Returns -1 on error.
int EGLManager::_get_gldisplay_id(void *p_display) {
// Look for a cached GLDisplay.
for (unsigned int i = 0; i < displays.size(); i++) {
if (displays[i].display == p_display) {
return i;
}
}
// We didn't find any, so we'll have to create one, along with its own
// EGLDisplay and EGLContext.
GLDisplay new_gldisplay;
new_gldisplay.display = p_display;
if (GLAD_EGL_VERSION_1_5) {
Vector<EGLAttrib> attribs = _get_platform_display_attributes();
new_gldisplay.egl_display = eglGetPlatformDisplay(_get_platform_extension_enum(), new_gldisplay.display, (attribs.size() > 0) ? attribs.ptr() : nullptr);
} else if (GLAD_EGL_EXT_platform_base) {
#ifdef EGL_EXT_platform_base
// eglGetPlatformDisplayEXT wants its attributes as EGLint, so we'll truncate
// what we already have. It's a bit naughty but I'm really not sure what else
// we could do here.
Vector<EGLint> attribs;
for (const EGLAttrib &attrib : _get_platform_display_attributes()) {
attribs.push_back((EGLint)attrib);
}
new_gldisplay.egl_display = eglGetPlatformDisplayEXT(_get_platform_extension_enum(), new_gldisplay.display, (attribs.size() > 0) ? attribs.ptr() : nullptr);
#endif // EGL_EXT_platform_base
} else {
NativeDisplayType *native_display_type = (NativeDisplayType *)new_gldisplay.display;
new_gldisplay.egl_display = eglGetDisplay(*native_display_type);
}
ERR_FAIL_COND_V(eglGetError() != EGL_SUCCESS, -1);
ERR_FAIL_COND_V_MSG(new_gldisplay.egl_display == EGL_NO_DISPLAY, -1, "Can't create an EGL display.");
if (!eglInitialize(new_gldisplay.egl_display, nullptr, nullptr)) {
ERR_FAIL_V_MSG(-1, "Can't initialize an EGL display.");
}
if (!eglBindAPI(_get_platform_api_enum())) {
ERR_FAIL_V_MSG(-1, "OpenGL not supported.");
}
Error err = _gldisplay_create_context(new_gldisplay);
if (err != OK) {
eglTerminate(new_gldisplay.egl_display);
ERR_FAIL_V(-1);
}
#ifdef EGL_ANDROID_blob_cache
#if defined(EGL_STATIC)
bool has_blob_cache = true;
#else
bool has_blob_cache = (eglSetBlobCacheFuncsANDROID != nullptr);
#endif
if (has_blob_cache && !shader_cache_dir.is_empty()) {
eglSetBlobCacheFuncsANDROID(new_gldisplay.egl_display, &EGLManager::_set_cache, &EGLManager::_get_cache);
}
#endif
#ifdef WINDOWS_ENABLED
String client_extensions_string = eglQueryString(new_gldisplay.egl_display, EGL_EXTENSIONS);
if (eglGetError() == EGL_SUCCESS) {
Vector<String> egl_extensions = client_extensions_string.split(" ");
if (egl_extensions.has("EGL_ANGLE_surface_orientation")) {
new_gldisplay.has_EGL_ANGLE_surface_orientation = true;
print_verbose("EGL: EGL_ANGLE_surface_orientation is supported.");
}
}
#endif
displays.push_back(new_gldisplay);
// Return the new GLDisplay's ID.
return displays.size() - 1;
}
#ifdef EGL_ANDROID_blob_cache
String EGLManager::shader_cache_dir;
void EGLManager::_set_cache(const void *p_key, EGLsizeiANDROID p_key_size, const void *p_value, EGLsizeiANDROID p_value_size) {
String name = CryptoCore::b64_encode_str((const uint8_t *)p_key, p_key_size).replace_char('/', '_');
String path = shader_cache_dir.path_join(name) + ".cache";
Error err = OK;
Ref<FileAccess> file = FileAccess::open(path, FileAccess::WRITE, &err);
if (err != OK) {
return;
}
file->store_buffer((const uint8_t *)p_value, p_value_size);
}
EGLsizeiANDROID EGLManager::_get_cache(const void *p_key, EGLsizeiANDROID p_key_size, void *p_value, EGLsizeiANDROID p_value_size) {
String name = CryptoCore::b64_encode_str((const uint8_t *)p_key, p_key_size).replace_char('/', '_');
String path = shader_cache_dir.path_join(name) + ".cache";
Error err = OK;
Ref<FileAccess> file = FileAccess::open(path, FileAccess::READ, &err);
if (err != OK) {
return 0;
}
EGLsizeiANDROID len = file->get_length();
if (len <= p_value_size) {
file->get_buffer((uint8_t *)p_value, len);
}
return len;
}
#endif
Error EGLManager::_gldisplay_create_context(GLDisplay &p_gldisplay) {
EGLint attribs[] = {
EGL_RED_SIZE,
1,
EGL_BLUE_SIZE,
1,
EGL_GREEN_SIZE,
1,
EGL_DEPTH_SIZE,
24,
EGL_NONE,
};
EGLint attribs_layered[] = {
EGL_RED_SIZE,
8,
EGL_GREEN_SIZE,
8,
EGL_GREEN_SIZE,
8,
EGL_ALPHA_SIZE,
8,
EGL_DEPTH_SIZE,
24,
EGL_NONE,
};
EGLint config_count = 0;
if (OS::get_singleton()->is_layered_allowed()) {
eglChooseConfig(p_gldisplay.egl_display, attribs_layered, &p_gldisplay.egl_config, 1, &config_count);
} else {
eglChooseConfig(p_gldisplay.egl_display, attribs, &p_gldisplay.egl_config, 1, &config_count);
}
ERR_FAIL_COND_V(eglGetError() != EGL_SUCCESS, ERR_BUG);
ERR_FAIL_COND_V(config_count == 0, ERR_UNCONFIGURED);
Vector<EGLint> context_attribs = _get_platform_context_attribs();
p_gldisplay.egl_context = eglCreateContext(p_gldisplay.egl_display, p_gldisplay.egl_config, EGL_NO_CONTEXT, (context_attribs.size() > 0) ? context_attribs.ptr() : nullptr);
ERR_FAIL_COND_V_MSG(p_gldisplay.egl_context == EGL_NO_CONTEXT, ERR_CANT_CREATE, vformat("Can't create an EGL context. Error code: %d", eglGetError()));
return OK;
}
Error EGLManager::open_display(void *p_display) {
int gldisplay_id = _get_gldisplay_id(p_display);
if (gldisplay_id < 0) {
return ERR_CANT_CREATE;
} else {
return OK;
}
}
int EGLManager::display_get_native_visual_id(void *p_display) {
int gldisplay_id = _get_gldisplay_id(p_display);
ERR_FAIL_COND_V(gldisplay_id < 0, ERR_CANT_CREATE);
GLDisplay gldisplay = displays[gldisplay_id];
EGLint native_visual_id = -1;
if (!eglGetConfigAttrib(gldisplay.egl_display, gldisplay.egl_config, EGL_NATIVE_VISUAL_ID, &native_visual_id)) {
ERR_FAIL_V(-1);
}
return native_visual_id;
}
Error EGLManager::window_create(DisplayServerEnums::WindowID p_window_id, void *p_display, void *p_native_window, int p_width, int p_height) {
int gldisplay_id = _get_gldisplay_id(p_display);
ERR_FAIL_COND_V(gldisplay_id < 0, ERR_CANT_CREATE);
GLDisplay &gldisplay = displays[gldisplay_id];
// In order to ensure a fast lookup, make sure we got enough elements in the
// windows local vector to use the window id as an index.
if (p_window_id >= (int)windows.size()) {
windows.resize(p_window_id + 1);
}
GLWindow &glwindow = windows[p_window_id];
glwindow.gldisplay_id = gldisplay_id;
Vector<EGLAttrib> egl_attribs;
#ifdef WINDOWS_ENABLED
if (gldisplay.has_EGL_ANGLE_surface_orientation) {
EGLint optimal_orientation;
if (eglGetConfigAttrib(gldisplay.egl_display, gldisplay.egl_config, EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE, &optimal_orientation)) {
// We only need to support inverting Y for optimizing ANGLE on D3D11.
if (optimal_orientation & EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE && !(optimal_orientation & EGL_SURFACE_ORIENTATION_INVERT_X_ANGLE)) {
egl_attribs.push_back(EGL_SURFACE_ORIENTATION_ANGLE);
egl_attribs.push_back(EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE);
}
} else {
ERR_PRINT(vformat("Failed to get EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE, error: 0x%08X", eglGetError()));
}
}
if (!egl_attribs.is_empty()) {
egl_attribs.push_back(EGL_NONE);
}
#endif
if (GLAD_EGL_VERSION_1_5) {
glwindow.egl_surface = eglCreatePlatformWindowSurface(gldisplay.egl_display, gldisplay.egl_config, p_native_window, egl_attribs.ptr());
} else {
EGLNativeWindowType *native_window_type = (EGLNativeWindowType *)p_native_window;
glwindow.egl_surface = eglCreateWindowSurface(gldisplay.egl_display, gldisplay.egl_config, *native_window_type, nullptr);
}
if (glwindow.egl_surface == EGL_NO_SURFACE) {
return ERR_CANT_CREATE;
}
glwindow.initialized = true;
#ifdef WINDOWS_ENABLED
if (gldisplay.has_EGL_ANGLE_surface_orientation) {
EGLint orientation;
if (eglQuerySurface(gldisplay.egl_display, glwindow.egl_surface, EGL_SURFACE_ORIENTATION_ANGLE, &orientation)) {
if (orientation & EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE && !(orientation & EGL_SURFACE_ORIENTATION_INVERT_X_ANGLE)) {
glwindow.flipped_y = true;
print_verbose("EGL: Using optimal surface orientation: Invert Y");
}
} else {
ERR_PRINT(vformat("Failed to get EGL_SURFACE_ORIENTATION_ANGLE, error: 0x%08X", eglGetError()));
}
}
#endif
window_make_current(p_window_id);
return OK;
}
void EGLManager::window_destroy(DisplayServerEnums::WindowID p_window_id) {
ERR_FAIL_INDEX(p_window_id, (int)windows.size());
GLWindow &glwindow = windows[p_window_id];
if (!glwindow.initialized) {
return;
}
glwindow.initialized = false;
ERR_FAIL_INDEX(glwindow.gldisplay_id, (int)displays.size());
GLDisplay &gldisplay = displays[glwindow.gldisplay_id];
if (glwindow.egl_surface != EGL_NO_SURFACE) {
eglDestroySurface(gldisplay.egl_display, glwindow.egl_surface);
glwindow.egl_surface = nullptr;
}
}
void EGLManager::release_current() {
if (!current_window) {
return;
}
GLDisplay &current_display = displays[current_window->gldisplay_id];
eglMakeCurrent(current_display.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
void EGLManager::swap_buffers() {
if (!current_window) {
return;
}
if (!current_window->initialized) {
WARN_PRINT("Current OpenGL window is uninitialized!");
return;
}
GLDisplay &current_display = displays[current_window->gldisplay_id];
eglSwapBuffers(current_display.egl_display, current_window->egl_surface);
}
void EGLManager::window_make_current(DisplayServerEnums::WindowID p_window_id) {
if (p_window_id == DisplayServerEnums::INVALID_WINDOW_ID) {
return;
}
GLWindow &glwindow = windows[p_window_id];
if (&glwindow == current_window || !glwindow.initialized) {
return;
}
current_window = &glwindow;
GLDisplay &current_display = displays[current_window->gldisplay_id];
eglMakeCurrent(current_display.egl_display, current_window->egl_surface, current_window->egl_surface, current_display.egl_context);
#ifdef WINDOWS_ENABLED
RasterizerGLES3::set_screen_flipped_y(glwindow.flipped_y);
#endif
}
void EGLManager::set_use_vsync(bool p_use) {
// We need an active window to get a display to set the vsync.
if (!current_window) {
return;
}
GLDisplay &disp = displays[current_window->gldisplay_id];
int swap_interval = p_use ? 1 : 0;
if (!eglSwapInterval(disp.egl_display, swap_interval)) {
WARN_PRINT("Could not set V-Sync mode.");
}
use_vsync = p_use;
}
bool EGLManager::is_using_vsync() const {
return use_vsync;
}
EGLContext EGLManager::get_context(DisplayServerEnums::WindowID p_window_id) {
GLWindow &glwindow = windows[p_window_id];
if (!glwindow.initialized) {
return EGL_NO_CONTEXT;
}
GLDisplay &display = displays[glwindow.gldisplay_id];
return display.egl_context;
}
EGLDisplay EGLManager::get_display(DisplayServerEnums::WindowID p_window_id) {
GLWindow &glwindow = windows[p_window_id];
if (!glwindow.initialized) {
return EGL_NO_CONTEXT;
}
GLDisplay &display = displays[glwindow.gldisplay_id];
return display.egl_display;
}
EGLConfig EGLManager::get_config(DisplayServerEnums::WindowID p_window_id) {
GLWindow &glwindow = windows[p_window_id];
if (!glwindow.initialized) {
return nullptr;
}
GLDisplay &display = displays[glwindow.gldisplay_id];
return display.egl_config;
}
Error EGLManager::initialize(void *p_native_display) {
#if defined(GLAD_ENABLED) && !defined(EGL_STATIC)
// Loading EGL with a new display gets us just the bare minimum API. We'll then
// have to temporarily get a proper display and reload EGL once again to
// initialize everything else.
if (!gladLoaderLoadEGL(EGL_NO_DISPLAY)) {
ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "Can't load EGL dynamic library.");
}
EGLDisplay tmp_display = EGL_NO_DISPLAY;
if (GLAD_EGL_EXT_platform_base) {
#ifdef EGL_EXT_platform_base
// eglGetPlatformDisplayEXT wants its attributes as EGLint.
Vector<EGLint> attribs;
for (const EGLAttrib &attrib : _get_platform_display_attributes()) {
attribs.push_back((EGLint)attrib);
}
tmp_display = eglGetPlatformDisplayEXT(_get_platform_extension_enum(), p_native_display, attribs.ptr());
#endif // EGL_EXT_platform_base
} else {
WARN_PRINT("EGL: EGL_EXT_platform_base not found during init, using default platform.");
EGLNativeDisplayType *native_display_type = (EGLNativeDisplayType *)p_native_display;
tmp_display = eglGetDisplay(*native_display_type);
}
if (tmp_display == EGL_NO_DISPLAY) {
eglTerminate(tmp_display);
ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "Can't get a valid initial EGL display.");
}
eglInitialize(tmp_display, nullptr, nullptr);
int version = gladLoaderLoadEGL(tmp_display);
if (!version) {
eglTerminate(tmp_display);
ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "Can't load EGL dynamic library.");
}
int major = GLAD_VERSION_MAJOR(version);
int minor = GLAD_VERSION_MINOR(version);
print_verbose(vformat("Loaded EGL %d.%d", major, minor));
ERR_FAIL_COND_V_MSG(!GLAD_EGL_VERSION_1_4, ERR_UNAVAILABLE, vformat("EGL version is too old! %d.%d < 1.4", major, minor));
eglTerminate(tmp_display);
#endif
#ifdef EGL_ANDROID_blob_cache
shader_cache_dir = Engine::get_singleton()->get_shader_cache_path();
if (shader_cache_dir.is_empty()) {
shader_cache_dir = "user://";
}
Error err = OK;
Ref<DirAccess> da = DirAccess::open(shader_cache_dir);
if (da.is_null()) {
ERR_PRINT("EGL: Can't create shader cache folder, no shader caching will happen: " + shader_cache_dir);
shader_cache_dir = String();
} else {
err = da->change_dir(String("shader_cache").path_join("EGL"));
if (err != OK) {
err = da->make_dir_recursive(String("shader_cache").path_join("EGL"));
}
if (err != OK) {
ERR_PRINT("EGL: Can't create shader cache folder, no shader caching will happen: " + shader_cache_dir);
shader_cache_dir = String();
} else {
shader_cache_dir = shader_cache_dir.path_join(String("shader_cache").path_join("EGL"));
}
}
#endif
String client_extensions_string = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
// If the above method fails, we don't support client extensions, so there's nothing to check.
if (eglGetError() == EGL_SUCCESS) {
const char *platform = _get_platform_extension_name();
if (!client_extensions_string.split(" ").has(platform)) {
ERR_FAIL_V_MSG(ERR_UNAVAILABLE, vformat("EGL platform extension \"%s\" not found.", platform));
}
}
return OK;
}
EGLManager::EGLManager() {
}
EGLManager::~EGLManager() {
for (unsigned int i = 0; i < displays.size(); i++) {
eglTerminate(displays[i].egl_display);
}
}
#endif // EGL_ENABLED

View file

@ -0,0 +1,120 @@
/**************************************************************************/
/* egl_manager.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifdef EGL_ENABLED
// These must come first to avoid windows.h mess.
#include "platform_gl.h"
#include "core/templates/local_vector.h"
#include "servers/display/display_server_enums.h"
class EGLManager {
private:
// An EGL-side representation of a display with its own rendering
// context.
struct GLDisplay {
void *display = nullptr;
EGLDisplay egl_display = EGL_NO_DISPLAY;
EGLContext egl_context = EGL_NO_CONTEXT;
EGLConfig egl_config = nullptr;
#ifdef WINDOWS_ENABLED
bool has_EGL_ANGLE_surface_orientation = false;
#endif
};
// EGL specific window data.
struct GLWindow {
bool initialized = false;
#ifdef WINDOWS_ENABLED
bool flipped_y = false;
#endif
// An handle to the GLDisplay associated with this window.
int gldisplay_id = -1;
EGLSurface egl_surface = EGL_NO_SURFACE;
};
LocalVector<GLDisplay> displays;
LocalVector<GLWindow> windows;
GLWindow *current_window = nullptr;
// On EGL the default swap interval is 1 and thus vsync is on by default.
bool use_vsync = true;
virtual const char *_get_platform_extension_name() const = 0;
virtual EGLenum _get_platform_extension_enum() const = 0;
virtual EGLenum _get_platform_api_enum() const = 0;
virtual Vector<EGLAttrib> _get_platform_display_attributes() const = 0;
virtual Vector<EGLint> _get_platform_context_attribs() const = 0;
#ifdef EGL_ANDROID_blob_cache
static String shader_cache_dir;
static void _set_cache(const void *p_key, EGLsizeiANDROID p_key_size, const void *p_value, EGLsizeiANDROID p_value_size);
static EGLsizeiANDROID _get_cache(const void *p_key, EGLsizeiANDROID p_key_size, void *p_value, EGLsizeiANDROID p_value_size);
#endif
int _get_gldisplay_id(void *p_display);
Error _gldisplay_create_context(GLDisplay &p_gldisplay);
public:
int display_get_native_visual_id(void *p_display);
Error open_display(void *p_display);
Error window_create(DisplayServerEnums::WindowID p_window_id, void *p_display, void *p_native_window, int p_width, int p_height);
void window_destroy(DisplayServerEnums::WindowID p_window_id);
void release_current();
void swap_buffers();
void window_make_current(DisplayServerEnums::WindowID p_window_id);
void set_use_vsync(bool p_use);
bool is_using_vsync() const;
EGLContext get_context(DisplayServerEnums::WindowID p_window_id);
EGLDisplay get_display(DisplayServerEnums::WindowID p_window_id);
EGLConfig get_config(DisplayServerEnums::WindowID p_window_id);
Error initialize(void *p_native_display = nullptr);
EGLManager();
virtual ~EGLManager();
};
#endif // EGL_ENABLED

View file

@ -0,0 +1,26 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
if env["platform"] in ["macos", "windows", "linuxbsd"]:
# Thirdparty source files
thirdparty_dir = "#thirdparty/glad/"
thirdparty_sources = ["gl.c"]
if not env.get("angle_libs"):
thirdparty_sources += ["egl.c"]
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
env.Prepend(CPPPATH=[thirdparty_dir])
env.Append(CPPDEFINES=["GLAD_ENABLED"])
env.Append(CPPDEFINES=["EGL_ENABLED"])
env_thirdparty = env.Clone()
env_thirdparty.disable_warnings()
env_thirdparty.add_source_files(env.drivers_sources, thirdparty_sources)
# Godot source files
env.add_source_files(env.drivers_sources, "*.cpp")

View file

@ -0,0 +1,11 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
env.add_source_files(env.drivers_sources, "*.cpp")
SConscript("shaders/SCsub")
SConscript("storage/SCsub")
SConscript("effects/SCsub")
SConscript("environment/SCsub")

View file

@ -0,0 +1,6 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
env.add_source_files(env.drivers_sources, "*.cpp")

View file

@ -0,0 +1,336 @@
/**************************************************************************/
/* copy_effects.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "copy_effects.h"
#ifdef GLES3_ENABLED
#include "drivers/gles3/storage/texture_storage.h"
using namespace GLES3;
CopyEffects *CopyEffects::singleton = nullptr;
CopyEffects *CopyEffects::get_singleton() {
return singleton;
}
CopyEffects::CopyEffects() {
singleton = this;
copy.shader.initialize();
copy.shader_version = copy.shader.version_create();
copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_DEFAULT);
{ // Screen Triangle.
glGenBuffers(1, &screen_triangle);
glBindBuffer(GL_ARRAY_BUFFER, screen_triangle);
const float qv[6] = {
-1.0f,
-1.0f,
3.0f,
-1.0f,
-1.0f,
3.0f,
};
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6, qv, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind
glGenVertexArrays(1, &screen_triangle_array);
glBindVertexArray(screen_triangle_array);
glBindBuffer(GL_ARRAY_BUFFER, screen_triangle);
glVertexAttribPointer(RSE::ARRAY_VERTEX, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, nullptr);
glEnableVertexAttribArray(RSE::ARRAY_VERTEX);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind
}
{ // Screen Quad
glGenBuffers(1, &quad);
glBindBuffer(GL_ARRAY_BUFFER, quad);
const float qv[12] = {
-1.0f,
-1.0f,
1.0f,
-1.0f,
1.0f,
1.0f,
-1.0f,
-1.0f,
1.0f,
1.0f,
-1.0f,
1.0f,
};
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 12, qv, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind
glGenVertexArrays(1, &quad_array);
glBindVertexArray(quad_array);
glBindBuffer(GL_ARRAY_BUFFER, quad);
glVertexAttribPointer(RSE::ARRAY_VERTEX, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, nullptr);
glEnableVertexAttribArray(RSE::ARRAY_VERTEX);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind
}
}
CopyEffects::~CopyEffects() {
singleton = nullptr;
glDeleteBuffers(1, &screen_triangle);
glDeleteVertexArrays(1, &screen_triangle_array);
glDeleteBuffers(1, &quad);
glDeleteVertexArrays(1, &quad_array);
copy.shader.version_free(copy.shader_version);
}
void CopyEffects::copy_to_rect(const Rect2 &p_rect, bool p_linear_to_srgb) {
uint64_t specializations = p_linear_to_srgb ? CopyShaderGLES3::CONVERT_LINEAR_TO_SRGB : 0;
bool success = copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_COPY_SECTION, specializations);
if (!success) {
return;
}
copy.shader.version_set_uniform(CopyShaderGLES3::COPY_SECTION, p_rect.position.x, p_rect.position.y, p_rect.size.x, p_rect.size.y, copy.shader_version, CopyShaderGLES3::MODE_COPY_SECTION, specializations);
draw_screen_quad();
}
void CopyEffects::copy_to_rect_3d(const Rect2 &p_rect, float p_layer, int p_type, float p_lod, bool p_linear_to_srgb) {
ERR_FAIL_COND(p_type != Texture::TYPE_LAYERED && p_type != Texture::TYPE_3D);
CopyShaderGLES3::ShaderVariant variant = p_type == Texture::TYPE_LAYERED
? CopyShaderGLES3::MODE_COPY_SECTION_2D_ARRAY
: CopyShaderGLES3::MODE_COPY_SECTION_3D;
uint64_t specializations = p_linear_to_srgb ? CopyShaderGLES3::CONVERT_LINEAR_TO_SRGB : 0;
bool success = copy.shader.version_bind_shader(copy.shader_version, variant, specializations);
if (!success) {
return;
}
copy.shader.version_set_uniform(CopyShaderGLES3::COPY_SECTION, p_rect.position.x, p_rect.position.y, p_rect.size.x, p_rect.size.y, copy.shader_version, variant, specializations);
copy.shader.version_set_uniform(CopyShaderGLES3::LAYER, p_layer, copy.shader_version, variant, specializations);
copy.shader.version_set_uniform(CopyShaderGLES3::LOD, p_lod, copy.shader_version, variant, specializations);
draw_screen_quad();
}
void CopyEffects::copy_with_lens_distortion(const Rect2 &p_rect, float p_layer, const Vector2 &p_eye_center, float p_k1, float p_k2, float p_upscale, float p_aspect_ration, bool p_linear_to_srgb) {
CopyShaderGLES3::ShaderVariant variant = CopyShaderGLES3::MODE_LENS_DISTORTION;
uint64_t specializations = p_linear_to_srgb ? CopyShaderGLES3::CONVERT_LINEAR_TO_SRGB : 0;
bool success = copy.shader.version_bind_shader(copy.shader_version, variant, specializations);
if (!success) {
return;
}
copy.shader.version_set_uniform(CopyShaderGLES3::COPY_SECTION, p_rect.position.x, p_rect.position.y, p_rect.size.x, p_rect.size.y, copy.shader_version, variant, specializations);
copy.shader.version_set_uniform(CopyShaderGLES3::LAYER, p_layer, copy.shader_version, variant, specializations);
copy.shader.version_set_uniform(CopyShaderGLES3::LOD, 0.0, copy.shader_version, variant, specializations);
copy.shader.version_set_uniform(CopyShaderGLES3::EYE_CENTER, p_eye_center.x, p_eye_center.y, copy.shader_version, variant, specializations);
copy.shader.version_set_uniform(CopyShaderGLES3::K1, p_k1, copy.shader_version, variant, specializations);
copy.shader.version_set_uniform(CopyShaderGLES3::K2, p_k1, copy.shader_version, variant, specializations);
copy.shader.version_set_uniform(CopyShaderGLES3::UPSCALE, p_upscale, copy.shader_version, variant, specializations);
copy.shader.version_set_uniform(CopyShaderGLES3::ASPECT_RATIO, p_aspect_ration, copy.shader_version, variant, specializations);
draw_screen_quad();
}
void CopyEffects::copy_to_and_from_rect(const Rect2 &p_rect) {
bool success = copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_COPY_SECTION_SOURCE);
if (!success) {
return;
}
copy.shader.version_set_uniform(CopyShaderGLES3::COPY_SECTION, p_rect.position.x, p_rect.position.y, p_rect.size.x, p_rect.size.y, copy.shader_version, CopyShaderGLES3::MODE_COPY_SECTION_SOURCE);
copy.shader.version_set_uniform(CopyShaderGLES3::SOURCE_SECTION, p_rect.position.x, p_rect.position.y, p_rect.size.x, p_rect.size.y, copy.shader_version, CopyShaderGLES3::MODE_COPY_SECTION_SOURCE);
draw_screen_quad();
}
void CopyEffects::copy_screen(float p_multiply) {
bool success = copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_SCREEN);
if (!success) {
return;
}
copy.shader.version_set_uniform(CopyShaderGLES3::MULTIPLY, p_multiply, copy.shader_version, CopyShaderGLES3::MODE_SCREEN);
draw_screen_triangle();
}
void CopyEffects::copy_cube_to_rect(const Rect2 &p_rect) {
bool success = copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_CUBE_TO_OCTAHEDRAL);
if (!success) {
return;
}
copy.shader.version_set_uniform(CopyShaderGLES3::COPY_SECTION, p_rect.position.x, p_rect.position.y, p_rect.size.x, p_rect.size.y, copy.shader_version, CopyShaderGLES3::MODE_CUBE_TO_OCTAHEDRAL);
draw_screen_quad();
}
void CopyEffects::copy_cube_to_panorama(float p_mip_level) {
bool success = copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_CUBE_TO_PANORAMA);
if (!success) {
return;
}
copy.shader.version_set_uniform(CopyShaderGLES3::MIP_LEVEL, p_mip_level, copy.shader_version, CopyShaderGLES3::MODE_CUBE_TO_PANORAMA);
draw_screen_quad();
}
// Intended for efficiently mipmapping textures.
void CopyEffects::bilinear_blur(GLuint p_source_texture, int p_mipmap_count, const Rect2i &p_region) {
GLuint framebuffers[2];
glGenFramebuffers(2, framebuffers);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffers[0]);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, p_source_texture, 0);
Rect2i source_region = p_region;
Rect2i dest_region = p_region;
for (int i = 1; i < p_mipmap_count; i++) {
dest_region.position.x >>= 1;
dest_region.position.y >>= 1;
dest_region.size = Size2i(dest_region.size.x >> 1, dest_region.size.y >> 1).maxi(1);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffers[i % 2]);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, p_source_texture, i);
glBlitFramebuffer(source_region.position.x, source_region.position.y, source_region.position.x + source_region.size.x, source_region.position.y + source_region.size.y,
dest_region.position.x, dest_region.position.y, dest_region.position.x + dest_region.size.x, dest_region.position.y + dest_region.size.y, GL_COLOR_BUFFER_BIT, GL_LINEAR);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffers[i % 2]);
source_region = dest_region;
}
glBindFramebuffer(GL_READ_FRAMEBUFFER, GLES3::TextureStorage::system_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, GLES3::TextureStorage::system_fbo);
glDeleteFramebuffers(2, framebuffers);
}
// Intended for approximating a gaussian blur. Used for 2D backbuffer mipmaps. Slightly less efficient than bilinear_blur().
void CopyEffects::gaussian_blur(GLuint p_source_texture, int p_mipmap_count, const Rect2i &p_region, const Size2i &p_size) {
GLuint framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, p_source_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
Size2i base_size = p_size;
Rect2i source_region = p_region;
Rect2i dest_region = p_region;
Size2 float_size = Size2(p_size);
Rect2 normalized_source_region = Rect2(p_region);
normalized_source_region.position = normalized_source_region.position / float_size;
normalized_source_region.size = normalized_source_region.size / float_size;
Rect2 normalized_dest_region = Rect2(p_region);
for (int i = 1; i < p_mipmap_count; i++) {
dest_region.position.x >>= 1;
dest_region.position.y >>= 1;
dest_region.size = Size2i(dest_region.size.x >> 1, dest_region.size.y >> 1).maxi(1);
base_size.x >>= 1;
base_size.y >>= 1;
glBindTexture(GL_TEXTURE_2D, p_source_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, i - 1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, i);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, p_source_texture, i);
#ifdef DEV_ENABLED
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
WARN_PRINT("Could not bind Gaussian blur framebuffer, status: " + GLES3::TextureStorage::get_singleton()->get_framebuffer_error(status));
}
#endif
glViewport(0, 0, base_size.x, base_size.y);
bool success = copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_GAUSSIAN_BLUR);
if (!success) {
return;
}
float_size = Size2(base_size);
normalized_dest_region.position = Size2(dest_region.position) / float_size;
normalized_dest_region.size = Size2(dest_region.size) / float_size;
copy.shader.version_set_uniform(CopyShaderGLES3::COPY_SECTION, normalized_dest_region.position.x, normalized_dest_region.position.y, normalized_dest_region.size.x, normalized_dest_region.size.y, copy.shader_version, CopyShaderGLES3::MODE_GAUSSIAN_BLUR);
copy.shader.version_set_uniform(CopyShaderGLES3::SOURCE_SECTION, normalized_source_region.position.x, normalized_source_region.position.y, normalized_source_region.size.x, normalized_source_region.size.y, copy.shader_version, CopyShaderGLES3::MODE_GAUSSIAN_BLUR);
copy.shader.version_set_uniform(CopyShaderGLES3::PIXEL_SIZE, 1.0 / float_size.x, 1.0 / float_size.y, copy.shader_version, CopyShaderGLES3::MODE_GAUSSIAN_BLUR);
draw_screen_quad();
source_region = dest_region;
normalized_source_region = normalized_dest_region;
}
glBindFramebuffer(GL_FRAMEBUFFER, GLES3::TextureStorage::system_fbo);
glDeleteFramebuffers(1, &framebuffer);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, p_mipmap_count - 1);
glBindTexture(GL_TEXTURE_2D, 0);
glViewport(0, 0, p_size.x, p_size.y);
}
void CopyEffects::set_color(const Color &p_color, const Rect2i &p_region) {
bool success = copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_SIMPLE_COLOR);
if (!success) {
return;
}
copy.shader.version_set_uniform(CopyShaderGLES3::COPY_SECTION, p_region.position.x, p_region.position.y, p_region.size.x, p_region.size.y, copy.shader_version, CopyShaderGLES3::MODE_SIMPLE_COLOR);
copy.shader.version_set_uniform(CopyShaderGLES3::COLOR_IN, p_color, copy.shader_version, CopyShaderGLES3::MODE_SIMPLE_COLOR);
draw_screen_quad();
}
void CopyEffects::draw_screen_triangle() {
glBindVertexArray(screen_triangle_array);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
}
void CopyEffects::draw_screen_quad() {
glBindVertexArray(quad_array);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
}
#endif // GLES3_ENABLED

View file

@ -0,0 +1,79 @@
/**************************************************************************/
/* copy_effects.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifdef GLES3_ENABLED
#include "drivers/gles3/shaders/effects/copy.glsl.gen.h"
namespace GLES3 {
class CopyEffects {
private:
struct Copy {
CopyShaderGLES3 shader;
RID shader_version;
} copy;
static CopyEffects *singleton;
// Use for full-screen effects. Slightly more efficient than screen_quad as this eliminates pixel overdraw along the diagonal.
GLuint screen_triangle = 0;
GLuint screen_triangle_array = 0;
// Use for rect-based effects.
GLuint quad = 0;
GLuint quad_array = 0;
public:
static CopyEffects *get_singleton();
CopyEffects();
~CopyEffects();
// These functions assume that a framebuffer and texture are bound already. They only manage the shader, uniforms, and vertex array.
void copy_to_rect(const Rect2 &p_rect, bool p_linear_to_srgb = false);
void copy_to_rect_3d(const Rect2 &p_rect, float p_layer, int p_type, float p_lod = 0.0f, bool p_linear_to_srgb = false);
void copy_with_lens_distortion(const Rect2 &p_rect, float p_layer, const Vector2 &p_eye_center, float p_k1, float p_k2, float p_upscale, float p_aspect_ration, bool p_linear_to_srgb = false);
void copy_to_and_from_rect(const Rect2 &p_rect);
void copy_screen(float p_multiply = 1.0);
void copy_cube_to_rect(const Rect2 &p_rect);
void copy_cube_to_panorama(float p_mip_level);
void bilinear_blur(GLuint p_source_texture, int p_mipmap_count, const Rect2i &p_region);
void gaussian_blur(GLuint p_source_texture, int p_mipmap_count, const Rect2i &p_region, const Size2i &p_size);
void set_color(const Color &p_color, const Rect2i &p_region);
void draw_screen_triangle();
void draw_screen_quad();
};
} //namespace GLES3
#endif // GLES3_ENABLED

View file

@ -0,0 +1,213 @@
/**************************************************************************/
/* cubemap_filter.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "cubemap_filter.h"
#ifdef GLES3_ENABLED
#include "core/config/project_settings.h"
#include "drivers/gles3/storage/texture_storage.h"
using namespace GLES3;
CubemapFilter *CubemapFilter::singleton = nullptr;
CubemapFilter::CubemapFilter() {
singleton = this;
// Use a factor 4 larger for the compatibility renderer to make up for the fact
// That we don't use an array texture. We will reduce samples on low roughness
// to compensate.
ggx_samples = 4 * uint32_t(GLOBAL_GET("rendering/reflections/sky_reflections/ggx_samples"));
{
String defines;
defines += "\n#define MAX_SAMPLE_COUNT " + itos(ggx_samples) + "\n";
cubemap_filter.shader.initialize(defines);
cubemap_filter.shader_version = cubemap_filter.shader.version_create();
}
{ // Screen Triangle.
glGenBuffers(1, &screen_triangle);
glBindBuffer(GL_ARRAY_BUFFER, screen_triangle);
const float qv[6] = {
-1.0f,
-1.0f,
-1.0f,
3.0f,
3.0f,
-1.0f,
};
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6, qv, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind
glGenVertexArrays(1, &screen_triangle_array);
glBindVertexArray(screen_triangle_array);
glBindBuffer(GL_ARRAY_BUFFER, screen_triangle);
glVertexAttribPointer(RSE::ARRAY_VERTEX, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, nullptr);
glEnableVertexAttribArray(RSE::ARRAY_VERTEX);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind
}
}
CubemapFilter::~CubemapFilter() {
glDeleteBuffers(1, &screen_triangle);
glDeleteVertexArrays(1, &screen_triangle_array);
cubemap_filter.shader.version_free(cubemap_filter.shader_version);
singleton = nullptr;
}
// Helper functions for IBL filtering
Vector3 importance_sample_GGX(Vector2 xi, float roughness4) {
// Compute distribution direction
float phi = 2.0 * Math::PI * xi.x;
float cos_theta = std::sqrt((1.0 - xi.y) / (1.0 + (roughness4 - 1.0) * xi.y));
float sin_theta = std::sqrt(1.0 - cos_theta * cos_theta);
// Convert to spherical direction
Vector3 half_vector;
half_vector.x = sin_theta * std::cos(phi);
half_vector.y = sin_theta * std::sin(phi);
half_vector.z = cos_theta;
return half_vector;
}
float distribution_GGX(float NdotH, float roughness4) {
float NdotH2 = NdotH * NdotH;
float denom = (NdotH2 * (roughness4 - 1.0) + 1.0);
denom = Math::PI * denom * denom;
return roughness4 / denom;
}
float radical_inverse_vdC(uint32_t bits) {
bits = (bits << 16) | (bits >> 16);
bits = ((bits & 0x55555555) << 1) | ((bits & 0xAAAAAAAA) >> 1);
bits = ((bits & 0x33333333) << 2) | ((bits & 0xCCCCCCCC) >> 2);
bits = ((bits & 0x0F0F0F0F) << 4) | ((bits & 0xF0F0F0F0) >> 4);
bits = ((bits & 0x00FF00FF) << 8) | ((bits & 0xFF00FF00) >> 8);
return float(bits) * 2.3283064365386963e-10;
}
Vector2 hammersley(uint32_t i, uint32_t N) {
return Vector2(float(i) / float(N), radical_inverse_vdC(i));
}
void CubemapFilter::filter_radiance(GLuint p_source_cubemap, GLuint p_dest_cubemap, GLuint p_dest_framebuffer, int p_source_size, int p_mipmap_count, int p_layer) {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, p_source_cubemap);
glBindFramebuffer(GL_FRAMEBUFFER, p_dest_framebuffer);
CubemapFilterShaderGLES3::ShaderVariant mode = CubemapFilterShaderGLES3::MODE_DEFAULT;
if (p_layer == 0) {
glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
// Copy over base layer without filtering.
mode = CubemapFilterShaderGLES3::MODE_COPY;
}
int size = p_source_size >> p_layer;
glViewport(0, 0, size, size);
glBindVertexArray(screen_triangle_array);
bool success = cubemap_filter.shader.version_bind_shader(cubemap_filter.shader_version, mode);
if (!success) {
return;
}
if (p_layer > 0) {
const uint32_t sample_counts[5] = { 1, ggx_samples / 16, ggx_samples / 8, ggx_samples / 4, ggx_samples };
uint32_t sample_count = sample_counts[MIN(4, p_layer)];
float roughness = float(p_layer) / (p_mipmap_count - 1);
roughness *= roughness; // Convert to non-perceptual roughness.
float roughness4 = roughness * roughness;
roughness4 *= roughness4;
float solid_angle_texel = 4.0 * Math::PI / float(6 * size * size);
LocalVector<float> sample_directions;
sample_directions.resize(4 * sample_count);
uint32_t index = 0;
float weight = 0.0;
for (uint32_t i = 0; i < sample_count; i++) {
Vector2 xi = hammersley(i, sample_count);
Vector3 dir = importance_sample_GGX(xi, roughness4);
Vector3 light_vec = (2.0 * dir.z * dir - Vector3(0.0, 0.0, 1.0));
if (light_vec.z <= 0.0) {
continue;
}
sample_directions[index * 4] = light_vec.x;
sample_directions[index * 4 + 1] = light_vec.y;
sample_directions[index * 4 + 2] = light_vec.z;
float D = distribution_GGX(dir.z, roughness4);
float pdf = D * dir.z / (4.0 * dir.z) + 0.0001;
float solid_angle_sample = 1.0 / (float(sample_count) * pdf + 0.0001);
float mip_level = MAX(0.5 * std::log2(solid_angle_sample / solid_angle_texel) + float(MAX(1, p_layer - 3)), 1.0);
sample_directions[index * 4 + 3] = mip_level;
weight += light_vec.z;
index++;
}
glUniform4fv(cubemap_filter.shader.version_get_uniform(CubemapFilterShaderGLES3::SAMPLE_DIRECTIONS_MIP, cubemap_filter.shader_version, mode), sample_count, sample_directions.ptr());
cubemap_filter.shader.version_set_uniform(CubemapFilterShaderGLES3::WEIGHT, weight, cubemap_filter.shader_version, mode);
cubemap_filter.shader.version_set_uniform(CubemapFilterShaderGLES3::SAMPLE_COUNT, index, cubemap_filter.shader_version, mode);
}
for (int i = 0; i < 6; i++) {
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, p_dest_cubemap, p_layer);
#ifdef DEBUG_ENABLED
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
WARN_PRINT("Could not bind sky radiance face: " + itos(i) + ", status: " + GLES3::TextureStorage::get_singleton()->get_framebuffer_error(status));
}
#endif
cubemap_filter.shader.version_set_uniform(CubemapFilterShaderGLES3::FACE_ID, i, cubemap_filter.shader_version, mode);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
glBindVertexArray(0);
glBindFramebuffer(GL_FRAMEBUFFER, GLES3::TextureStorage::system_fbo);
}
#endif // GLES3_ENABLED

View file

@ -0,0 +1,67 @@
/**************************************************************************/
/* cubemap_filter.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifdef GLES3_ENABLED
#include "drivers/gles3/shaders/effects/cubemap_filter.glsl.gen.h"
namespace GLES3 {
class CubemapFilter {
private:
struct CMF {
CubemapFilterShaderGLES3 shader;
RID shader_version;
} cubemap_filter;
static CubemapFilter *singleton;
// Use for full-screen effects. Slightly more efficient than screen_quad as this eliminates pixel overdraw along the diagonal.
GLuint screen_triangle = 0;
GLuint screen_triangle_array = 0;
uint32_t ggx_samples = 128;
public:
static CubemapFilter *get_singleton() {
return singleton;
}
CubemapFilter();
~CubemapFilter();
void filter_radiance(GLuint p_source_cubemap, GLuint p_dest_cubemap, GLuint p_dest_framebuffer, int p_source_size, int p_mipmap_count, int p_layer);
};
} //namespace GLES3
#endif // GLES3_ENABLED

View file

@ -0,0 +1,125 @@
/**************************************************************************/
/* feed_effects.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "feed_effects.h"
#ifdef GLES3_ENABLED
#include "core/os/os.h"
#include "servers/rendering/rendering_server_enums.h"
using namespace GLES3;
FeedEffects *FeedEffects::singleton = nullptr;
FeedEffects *FeedEffects::get_singleton() {
return singleton;
}
FeedEffects::FeedEffects() {
singleton = this;
feed.shader.initialize();
feed.shader_version = feed.shader.version_create();
feed.shader.version_bind_shader(feed.shader_version, FeedShaderGLES3::MODE_DEFAULT);
{ // Screen Triangle.
glGenBuffers(1, &screen_triangle);
glBindBuffer(GL_ARRAY_BUFFER, screen_triangle);
const float qv[6] = {
-1.0f,
-1.0f,
3.0f,
-1.0f,
-1.0f,
3.0f,
};
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6, qv, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind
glGenVertexArrays(1, &screen_triangle_array);
glBindVertexArray(screen_triangle_array);
glBindBuffer(GL_ARRAY_BUFFER, screen_triangle);
glVertexAttribPointer(RSE::ARRAY_VERTEX, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, nullptr);
glEnableVertexAttribArray(RSE::ARRAY_VERTEX);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind
}
}
FeedEffects::~FeedEffects() {
singleton = nullptr;
glDeleteBuffers(1, &screen_triangle);
glDeleteVertexArrays(1, &screen_triangle_array);
feed.shader.version_free(feed.shader_version);
}
Transform3D transform3D_from_mat4(const float *p_mat4) {
Transform3D res;
res.basis.rows[0][0] = p_mat4[0];
res.basis.rows[1][0] = p_mat4[1];
res.basis.rows[2][0] = p_mat4[2];
// p_mat4[3] = 0;
res.basis.rows[0][1] = p_mat4[4];
res.basis.rows[1][1] = p_mat4[5];
res.basis.rows[2][1] = p_mat4[6];
// p_mat4[7] = 0;
res.basis.rows[0][2] = p_mat4[8];
res.basis.rows[1][2] = p_mat4[9];
res.basis.rows[2][2] = p_mat4[10];
// p_mat4[11] = 0;
res.origin.x = p_mat4[12];
res.origin.y = p_mat4[13];
res.origin.z = p_mat4[14];
// p_mat4[15] = 1;
return res;
}
void FeedEffects::draw() {
bool success = feed.shader.version_bind_shader(feed.shader_version, FeedShaderGLES3::MODE_DEFAULT, FeedShaderGLES3::USE_EXTERNAL_SAMPLER);
if (!success) {
OS::get_singleton()->print("Godot : FeedShaderGLES3 Could not bind version_bind_shader USE_EXTERNAL_SAMPLER");
return;
}
draw_screen_triangle();
}
void FeedEffects::draw_screen_triangle() {
glBindVertexArray(screen_triangle_array);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
}
#endif // GLES3_ENABLED

View file

@ -0,0 +1,65 @@
/**************************************************************************/
/* feed_effects.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifdef GLES3_ENABLED
#include "drivers/gles3/shaders/feed.glsl.gen.h"
namespace GLES3 {
class FeedEffects {
private:
struct Feed {
FeedShaderGLES3 shader;
RID shader_version;
} feed;
static FeedEffects *singleton;
GLuint screen_triangle = 0;
GLuint screen_triangle_array = 0;
public:
static FeedEffects *get_singleton();
FeedEffects();
~FeedEffects();
void draw();
private:
void draw_screen_triangle();
};
} // namespace GLES3
#endif // GLES3_ENABLED

View file

@ -0,0 +1,174 @@
/**************************************************************************/
/* glow.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "glow.h"
#ifdef GLES3_ENABLED
#include "drivers/gles3/storage/texture_storage.h"
using namespace GLES3;
Glow *Glow::singleton = nullptr;
Glow *Glow::get_singleton() {
return singleton;
}
Glow::Glow() {
singleton = this;
glow.shader.initialize();
glow.shader_version = glow.shader.version_create();
{ // Screen Triangle.
glGenBuffers(1, &screen_triangle);
glBindBuffer(GL_ARRAY_BUFFER, screen_triangle);
const float qv[6] = {
-1.0f,
-1.0f,
3.0f,
-1.0f,
-1.0f,
3.0f,
};
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6, qv, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind
glGenVertexArrays(1, &screen_triangle_array);
glBindVertexArray(screen_triangle_array);
glBindBuffer(GL_ARRAY_BUFFER, screen_triangle);
glVertexAttribPointer(RSE::ARRAY_VERTEX, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, nullptr);
glEnableVertexAttribArray(RSE::ARRAY_VERTEX);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind
}
}
Glow::~Glow() {
glDeleteBuffers(1, &screen_triangle);
glDeleteVertexArrays(1, &screen_triangle_array);
glow.shader.version_free(glow.shader_version);
singleton = nullptr;
}
void Glow::_draw_screen_triangle() {
glBindVertexArray(screen_triangle_array);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
}
void Glow::process_glow(GLuint p_source_color, Size2i p_size, const Glow::Level *p_glow_buffers, uint32_t p_view, bool p_use_multiview) {
ERR_FAIL_COND(p_source_color == 0);
ERR_FAIL_COND(p_glow_buffers[3].color == 0);
// Reset some OpenGL state...
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
// Start with our filter pass
{
glBindFramebuffer(GL_FRAMEBUFFER, p_glow_buffers[0].fbo);
glViewport(0, 0, p_glow_buffers[0].size.x, p_glow_buffers[0].size.y);
glActiveTexture(GL_TEXTURE0);
glBindTexture(p_use_multiview ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D, p_source_color);
uint64_t specialization = p_use_multiview ? GlowShaderGLES3::USE_MULTIVIEW : 0;
bool success = glow.shader.version_bind_shader(glow.shader_version, GlowShaderGLES3::MODE_FILTER, specialization);
if (!success) {
return;
}
glow.shader.version_set_uniform(GlowShaderGLES3::PIXEL_SIZE, 1.0 / p_glow_buffers[0].size.x, 1.0 / p_glow_buffers[0].size.y, glow.shader_version, GlowShaderGLES3::MODE_FILTER, specialization);
glow.shader.version_set_uniform(GlowShaderGLES3::VIEW, float(p_view), glow.shader_version, GlowShaderGLES3::MODE_FILTER, specialization);
glow.shader.version_set_uniform(GlowShaderGLES3::LUMINANCE_MULTIPLIER, luminance_multiplier, glow.shader_version, GlowShaderGLES3::MODE_FILTER, specialization);
glow.shader.version_set_uniform(GlowShaderGLES3::GLOW_BLOOM, glow_bloom, glow.shader_version, GlowShaderGLES3::MODE_FILTER, specialization);
glow.shader.version_set_uniform(GlowShaderGLES3::GLOW_HDR_THRESHOLD, glow_hdr_bleed_threshold, glow.shader_version, GlowShaderGLES3::MODE_FILTER, specialization);
glow.shader.version_set_uniform(GlowShaderGLES3::GLOW_HDR_SCALE, glow_hdr_bleed_scale, glow.shader_version, GlowShaderGLES3::MODE_FILTER, specialization);
glow.shader.version_set_uniform(GlowShaderGLES3::GLOW_LUMINANCE_CAP, glow_hdr_luminance_cap, glow.shader_version, GlowShaderGLES3::MODE_FILTER, specialization);
_draw_screen_triangle();
}
// Continue with downsampling
{
bool success = glow.shader.version_bind_shader(glow.shader_version, GlowShaderGLES3::MODE_DOWNSAMPLE, 0);
if (!success) {
return;
}
for (int i = 1; i < 4; i++) {
glBindFramebuffer(GL_FRAMEBUFFER, p_glow_buffers[i].fbo);
glViewport(0, 0, p_glow_buffers[i].size.x, p_glow_buffers[i].size.y);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, p_glow_buffers[i - 1].color);
glow.shader.version_set_uniform(GlowShaderGLES3::PIXEL_SIZE, 1.0 / p_glow_buffers[i].size.x, 1.0 / p_glow_buffers[i].size.y, glow.shader_version, GlowShaderGLES3::MODE_DOWNSAMPLE);
_draw_screen_triangle();
}
}
// Now upsample
{
bool success = glow.shader.version_bind_shader(glow.shader_version, GlowShaderGLES3::MODE_UPSAMPLE, 0);
if (!success) {
return;
}
for (int i = 2; i >= 0; i--) {
glBindFramebuffer(GL_FRAMEBUFFER, p_glow_buffers[i].fbo);
glViewport(0, 0, p_glow_buffers[i].size.x, p_glow_buffers[i].size.y);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, p_glow_buffers[i + 1].color);
glow.shader.version_set_uniform(GlowShaderGLES3::PIXEL_SIZE, 1.0 / p_glow_buffers[i].size.x, 1.0 / p_glow_buffers[i].size.y, glow.shader_version, GlowShaderGLES3::MODE_UPSAMPLE);
_draw_screen_triangle();
}
}
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glUseProgram(0);
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, GLES3::TextureStorage::system_fbo);
}
#endif // GLES3_ENABLED

View file

@ -0,0 +1,86 @@
/**************************************************************************/
/* glow.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifdef GLES3_ENABLED
#include "drivers/gles3/shaders/effects/glow.glsl.gen.h"
namespace GLES3 {
class Glow {
private:
static Glow *singleton;
struct GlowShader {
GlowShaderGLES3 shader;
RID shader_version;
} glow;
float luminance_multiplier = 1.0;
float glow_intensity = 1.0;
float glow_bloom = 0.0;
float glow_hdr_bleed_threshold = 1.0;
float glow_hdr_bleed_scale = 2.0;
float glow_hdr_luminance_cap = 12.0;
// Use for full-screen effects. Slightly more efficient than screen_quad as this eliminates pixel overdraw along the diagonal.
GLuint screen_triangle = 0;
GLuint screen_triangle_array = 0;
void _draw_screen_triangle();
public:
struct Level {
Size2i size;
GLuint color = 0;
GLuint fbo = 0;
};
static Glow *get_singleton();
Glow();
~Glow();
void set_intensity(float p_value) { glow_intensity = p_value; }
void set_luminance_multiplier(float p_luminance_multiplier) { luminance_multiplier = p_luminance_multiplier; }
void set_glow_bloom(float p_bloom) { glow_bloom = p_bloom; }
void set_glow_hdr_bleed_threshold(float p_threshold) { glow_hdr_bleed_threshold = p_threshold; }
void set_glow_hdr_bleed_scale(float p_scale) { glow_hdr_bleed_scale = p_scale; }
void set_glow_hdr_luminance_cap(float p_cap) { glow_hdr_luminance_cap = p_cap; }
void process_glow(GLuint p_source_color, Size2i p_size, const Level *p_glow_buffers, uint32_t p_view = 0, bool p_use_multiview = false);
};
} //namespace GLES3
#endif // GLES3_ENABLED

View file

@ -0,0 +1,190 @@
/**************************************************************************/
/* post_effects.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "post_effects.h"
#ifdef GLES3_ENABLED
#include "drivers/gles3/storage/texture_storage.h"
using namespace GLES3;
PostEffects *PostEffects::singleton = nullptr;
PostEffects *PostEffects::get_singleton() {
return singleton;
}
PostEffects::PostEffects() {
singleton = this;
post.shader.initialize();
post.shader_version = post.shader.version_create();
post.shader.version_bind_shader(post.shader_version, PostShaderGLES3::MODE_DEFAULT);
{ // Screen Triangle.
glGenBuffers(1, &screen_triangle);
glBindBuffer(GL_ARRAY_BUFFER, screen_triangle);
const float qv[6] = {
-1.0f,
-1.0f,
3.0f,
-1.0f,
-1.0f,
3.0f,
};
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6, qv, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind
glGenVertexArrays(1, &screen_triangle_array);
glBindVertexArray(screen_triangle_array);
glBindBuffer(GL_ARRAY_BUFFER, screen_triangle);
glVertexAttribPointer(RSE::ARRAY_VERTEX, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, nullptr);
glEnableVertexAttribArray(RSE::ARRAY_VERTEX);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind
}
}
PostEffects::~PostEffects() {
singleton = nullptr;
glDeleteBuffers(1, &screen_triangle);
glDeleteVertexArrays(1, &screen_triangle_array);
post.shader.version_free(post.shader_version);
}
void PostEffects::_draw_screen_triangle() {
glBindVertexArray(screen_triangle_array);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
}
void PostEffects::post_copy(
GLuint p_dest_framebuffer, Size2i p_dest_size, GLuint p_source_color,
GLuint p_source_depth, bool p_ssao_enabled, int p_ssao_quality_level, float p_ssao_strength, float p_ssao_radius,
Size2i p_source_size, float p_luminance_multiplier, const Glow::Level *p_glow_buffers, float p_glow_intensity,
float p_srgb_white, uint32_t p_view, bool p_use_multiview, uint64_t p_spec_constants) {
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glDisable(GL_BLEND);
glBindFramebuffer(GL_FRAMEBUFFER, p_dest_framebuffer);
glViewport(0, 0, p_dest_size.x, p_dest_size.y);
PostShaderGLES3::ShaderVariant mode = PostShaderGLES3::MODE_DEFAULT;
uint64_t flags = p_spec_constants;
if (p_use_multiview) {
flags |= PostShaderGLES3::USE_MULTIVIEW;
}
if (p_glow_buffers != nullptr) {
flags |= PostShaderGLES3::USE_GLOW;
}
if (p_ssao_enabled) {
if (p_ssao_quality_level == RSE::ENV_SSAO_QUALITY_VERY_LOW) {
flags |= PostShaderGLES3::USE_SSAO_ABYSS;
} else if (p_ssao_quality_level == RSE::ENV_SSAO_QUALITY_LOW) {
flags |= PostShaderGLES3::USE_SSAO_LOW;
} else if (p_ssao_quality_level == RSE::ENV_SSAO_QUALITY_HIGH) {
flags |= PostShaderGLES3::USE_SSAO_HIGH;
} else if (p_ssao_quality_level == RSE::ENV_SSAO_QUALITY_ULTRA) {
flags |= PostShaderGLES3::USE_SSAO_MEGA;
} else {
flags |= PostShaderGLES3::USE_SSAO_MED;
}
}
if (p_luminance_multiplier != 1.0) {
flags |= PostShaderGLES3::USE_LUMINANCE_MULTIPLIER;
}
bool success = post.shader.version_bind_shader(post.shader_version, mode, flags);
if (!success) {
return;
}
GLenum texture_target = p_use_multiview ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D;
glActiveTexture(GL_TEXTURE0);
glBindTexture(texture_target, p_source_color);
glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
if (p_ssao_enabled) {
glActiveTexture(GL_TEXTURE3);
glBindTexture(texture_target, p_source_depth);
glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Thanks to mrjustaguy!
glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
post.shader.version_set_uniform(PostShaderGLES3::SSAO_INTENSITY, p_ssao_strength, post.shader_version, mode, flags);
post.shader.version_set_uniform(PostShaderGLES3::SSAO_RADIUS_FRAC, p_ssao_radius, post.shader_version, mode, flags);
post.shader.version_set_uniform(PostShaderGLES3::SSAO_PRN_UV, // This converts the UV coordinate into a pseudo-random number.
p_source_size.x * 1.087f * ((1.0f + sqrt(5.0f)) / 2.0f),
p_source_size.y * 1.087f * ((9.0f + sqrt(221.0f)) / 10.0f),
post.shader_version, mode, flags);
}
if (p_glow_buffers != nullptr) {
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, p_glow_buffers[0].color);
post.shader.version_set_uniform(PostShaderGLES3::PIXEL_SIZE, 1.0 / p_source_size.x, 1.0 / p_source_size.y, post.shader_version, mode, flags);
post.shader.version_set_uniform(PostShaderGLES3::GLOW_INTENSITY, p_glow_intensity, post.shader_version, mode, flags);
post.shader.version_set_uniform(PostShaderGLES3::SRGB_WHITE, p_srgb_white, post.shader_version, mode, flags);
}
post.shader.version_set_uniform(PostShaderGLES3::VIEW, float(p_view), post.shader_version, mode, flags);
post.shader.version_set_uniform(PostShaderGLES3::LUMINANCE_MULTIPLIER, p_luminance_multiplier, post.shader_version, mode, flags);
_draw_screen_triangle();
// Reset state
if (p_glow_buffers != nullptr) {
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, 0);
}
if (p_ssao_enabled) {
glActiveTexture(GL_TEXTURE3);
glBindTexture(texture_target, 0);
}
// Return back to nearest
glActiveTexture(GL_TEXTURE0);
glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glBindTexture(texture_target, 0);
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glUseProgram(0);
glBindFramebuffer(GL_FRAMEBUFFER, GLES3::TextureStorage::system_fbo);
}
#endif // GLES3_ENABLED

View file

@ -0,0 +1,69 @@
/**************************************************************************/
/* post_effects.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifdef GLES3_ENABLED
#include "drivers/gles3/effects/glow.h"
#include "drivers/gles3/shaders/effects/post.glsl.gen.h"
namespace GLES3 {
class PostEffects {
private:
struct Post {
PostShaderGLES3 shader;
RID shader_version;
} post;
static PostEffects *singleton;
// Use for full-screen effects. Slightly more efficient than screen_quad as this eliminates pixel overdraw along the diagonal.
GLuint screen_triangle = 0;
GLuint screen_triangle_array = 0;
void _draw_screen_triangle();
public:
static PostEffects *get_singleton();
PostEffects();
~PostEffects();
void post_copy(GLuint p_dest_framebuffer, Size2i p_dest_size, GLuint p_source_color,
GLuint p_source_depth, bool p_ssao_enabled, int p_ssao_quality_level, float p_ssao_strength, float p_ssao_radius,
Size2i p_source_size, float p_luminance_multiplier, const Glow::Level *p_glow_buffers, float p_glow_intensity,
float p_srgb_white, uint32_t p_view = 0, bool p_use_multiview = false, uint64_t p_spec_constants = 0);
};
} //namespace GLES3
#endif // GLES3_ENABLED

View file

@ -0,0 +1,6 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
env.add_source_files(env.drivers_sources, "*.cpp")

View file

@ -0,0 +1,69 @@
/**************************************************************************/
/* fog.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "fog.h"
#ifdef GLES3_ENABLED
#include "core/math/aabb.h"
#include "core/templates/rid.h"
using namespace GLES3;
/* FOG */
RID Fog::fog_volume_allocate() {
return RID();
}
void Fog::fog_volume_initialize(RID p_rid) {
}
void Fog::fog_volume_free(RID p_rid) {
}
void Fog::fog_volume_set_shape(RID p_fog_volume, RSE::FogVolumeShape p_shape) {
}
void Fog::fog_volume_set_size(RID p_fog_volume, const Vector3 &p_size) {
}
void Fog::fog_volume_set_material(RID p_fog_volume, RID p_material) {
}
AABB Fog::fog_volume_get_aabb(RID p_fog_volume) const {
return AABB();
}
RSE::FogVolumeShape Fog::fog_volume_get_shape(RID p_fog_volume) const {
return RSE::FOG_VOLUME_SHAPE_BOX;
}
#endif // GLES3_ENABLED

View file

@ -0,0 +1,56 @@
/**************************************************************************/
/* fog.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifdef GLES3_ENABLED
#include "servers/rendering/environment/renderer_fog.h"
namespace GLES3 {
class Fog : public RendererFog {
public:
/* FOG VOLUMES */
virtual RID fog_volume_allocate() override;
virtual void fog_volume_initialize(RID p_rid) override;
virtual void fog_volume_free(RID p_rid) override;
virtual void fog_volume_set_shape(RID p_fog_volume, RSE::FogVolumeShape p_shape) override;
virtual void fog_volume_set_size(RID p_fog_volume, const Vector3 &p_size) override;
virtual void fog_volume_set_material(RID p_fog_volume, RID p_material) override;
virtual AABB fog_volume_get_aabb(RID p_fog_volume) const override;
virtual RSE::FogVolumeShape fog_volume_get_shape(RID p_fog_volume) const override;
};
} // namespace GLES3
#endif // GLES3_ENABLED

View file

@ -0,0 +1,149 @@
/**************************************************************************/
/* gi.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "gi.h"
#ifdef GLES3_ENABLED
#include "core/math/aabb.h"
#include "core/math/transform_3d.h"
#include "core/math/vector3i.h"
#include "core/templates/rid.h"
#include "core/templates/vector.h"
using namespace GLES3;
/* VOXEL GI API */
RID GI::voxel_gi_allocate() {
return RID();
}
void GI::voxel_gi_free(RID p_rid) {
}
void GI::voxel_gi_initialize(RID p_rid) {
}
void GI::voxel_gi_allocate_data(RID p_voxel_gi, const Transform3D &p_to_cell_xform, const AABB &p_aabb, const Vector3i &p_octree_size, const Vector<uint8_t> &p_octree_cells, const Vector<uint8_t> &p_data_cells, const Vector<uint8_t> &p_distance_field, const Vector<int> &p_level_counts) {
}
AABB GI::voxel_gi_get_bounds(RID p_voxel_gi) const {
return AABB();
}
Vector3i GI::voxel_gi_get_octree_size(RID p_voxel_gi) const {
return Vector3i();
}
Vector<uint8_t> GI::voxel_gi_get_octree_cells(RID p_voxel_gi) const {
return Vector<uint8_t>();
}
Vector<uint8_t> GI::voxel_gi_get_data_cells(RID p_voxel_gi) const {
return Vector<uint8_t>();
}
Vector<uint8_t> GI::voxel_gi_get_distance_field(RID p_voxel_gi) const {
return Vector<uint8_t>();
}
Vector<int> GI::voxel_gi_get_level_counts(RID p_voxel_gi) const {
return Vector<int>();
}
Transform3D GI::voxel_gi_get_to_cell_xform(RID p_voxel_gi) const {
return Transform3D();
}
void GI::voxel_gi_set_dynamic_range(RID p_voxel_gi, float p_range) {
}
float GI::voxel_gi_get_dynamic_range(RID p_voxel_gi) const {
return 0;
}
void GI::voxel_gi_set_propagation(RID p_voxel_gi, float p_range) {
}
float GI::voxel_gi_get_propagation(RID p_voxel_gi) const {
return 0;
}
void GI::voxel_gi_set_energy(RID p_voxel_gi, float p_range) {
}
float GI::voxel_gi_get_energy(RID p_voxel_gi) const {
return 0.0;
}
void GI::voxel_gi_set_baked_exposure_normalization(RID p_voxel_gi, float p_baked_exposure) {
}
float GI::voxel_gi_get_baked_exposure_normalization(RID p_voxel_gi) const {
return 1.0;
}
void GI::voxel_gi_set_bias(RID p_voxel_gi, float p_range) {
}
float GI::voxel_gi_get_bias(RID p_voxel_gi) const {
return 0.0;
}
void GI::voxel_gi_set_normal_bias(RID p_voxel_gi, float p_range) {
}
float GI::voxel_gi_get_normal_bias(RID p_voxel_gi) const {
return 0.0;
}
void GI::voxel_gi_set_interior(RID p_voxel_gi, bool p_enable) {
}
bool GI::voxel_gi_is_interior(RID p_voxel_gi) const {
return false;
}
void GI::voxel_gi_set_use_two_bounces(RID p_voxel_gi, bool p_enable) {
}
bool GI::voxel_gi_is_using_two_bounces(RID p_voxel_gi) const {
return false;
}
uint32_t GI::voxel_gi_get_version(RID p_voxel_gi) const {
return 0;
}
void GI::sdfgi_reset() {
}
#endif // GLES3_ENABLED

View file

@ -0,0 +1,88 @@
/**************************************************************************/
/* gi.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifdef GLES3_ENABLED
#include "servers/rendering/environment/renderer_gi.h"
namespace GLES3 {
class GI : public RendererGI {
public:
/* VOXEL GI API */
virtual RID voxel_gi_allocate() override;
virtual void voxel_gi_free(RID p_rid) override;
virtual void voxel_gi_initialize(RID p_rid) override;
virtual void voxel_gi_allocate_data(RID p_voxel_gi, const Transform3D &p_to_cell_xform, const AABB &p_aabb, const Vector3i &p_octree_size, const Vector<uint8_t> &p_octree_cells, const Vector<uint8_t> &p_data_cells, const Vector<uint8_t> &p_distance_field, const Vector<int> &p_level_counts) override;
virtual AABB voxel_gi_get_bounds(RID p_voxel_gi) const override;
virtual Vector3i voxel_gi_get_octree_size(RID p_voxel_gi) const override;
virtual Vector<uint8_t> voxel_gi_get_octree_cells(RID p_voxel_gi) const override;
virtual Vector<uint8_t> voxel_gi_get_data_cells(RID p_voxel_gi) const override;
virtual Vector<uint8_t> voxel_gi_get_distance_field(RID p_voxel_gi) const override;
virtual Vector<int> voxel_gi_get_level_counts(RID p_voxel_gi) const override;
virtual Transform3D voxel_gi_get_to_cell_xform(RID p_voxel_gi) const override;
virtual void voxel_gi_set_dynamic_range(RID p_voxel_gi, float p_range) override;
virtual float voxel_gi_get_dynamic_range(RID p_voxel_gi) const override;
virtual void voxel_gi_set_propagation(RID p_voxel_gi, float p_range) override;
virtual float voxel_gi_get_propagation(RID p_voxel_gi) const override;
virtual void voxel_gi_set_energy(RID p_voxel_gi, float p_range) override;
virtual float voxel_gi_get_energy(RID p_voxel_gi) const override;
virtual void voxel_gi_set_baked_exposure_normalization(RID p_voxel_gi, float p_baked_exposure) override;
virtual float voxel_gi_get_baked_exposure_normalization(RID p_voxel_gi) const override;
virtual void voxel_gi_set_bias(RID p_voxel_gi, float p_range) override;
virtual float voxel_gi_get_bias(RID p_voxel_gi) const override;
virtual void voxel_gi_set_normal_bias(RID p_voxel_gi, float p_range) override;
virtual float voxel_gi_get_normal_bias(RID p_voxel_gi) const override;
virtual void voxel_gi_set_interior(RID p_voxel_gi, bool p_enable) override;
virtual bool voxel_gi_is_interior(RID p_voxel_gi) const override;
virtual void voxel_gi_set_use_two_bounces(RID p_voxel_gi, bool p_enable) override;
virtual bool voxel_gi_is_using_two_bounces(RID p_voxel_gi) const override;
virtual uint32_t voxel_gi_get_version(RID p_voxel_gi) const override;
virtual void sdfgi_reset() override;
};
}; // namespace GLES3
#endif // GLES3_ENABLED

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,384 @@
/**************************************************************************/
/* rasterizer_canvas_gles3.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifdef GLES3_ENABLED
#include "drivers/gles3/shaders/canvas.glsl.gen.h"
#include "drivers/gles3/shaders/canvas_occlusion.glsl.gen.h"
#include "drivers/gles3/storage/material_storage.h"
#include "servers/rendering/renderer_canvas_render.h"
#include "servers/rendering/rendering_server_enums.h"
class RasterizerCanvasGLES3 : public RendererCanvasRender {
static RasterizerCanvasGLES3 *singleton;
_FORCE_INLINE_ void _update_transform_2d_to_mat2x4(const Transform2D &p_transform, float *p_mat2x4);
_FORCE_INLINE_ void _update_transform_2d_to_mat2x3(const Transform2D &p_transform, float *p_mat2x3);
_FORCE_INLINE_ void _update_transform_2d_to_mat4(const Transform2D &p_transform, float *p_mat4);
_FORCE_INLINE_ void _update_transform_to_mat4(const Transform3D &p_transform, float *p_mat4);
enum {
INSTANCE_FLAGS_LIGHT_COUNT_SHIFT = 0, // 4 bits for light count.
INSTANCE_FLAGS_CLIP_RECT_UV = (1 << 4),
INSTANCE_FLAGS_TRANSPOSE_RECT = (1 << 5),
INSTANCE_FLAGS_USE_MSDF = (1 << 6),
INSTANCE_FLAGS_USE_LCD = (1 << 7),
INSTANCE_FLAGS_NINEPACH_DRAW_CENTER = (1 << 8),
INSTANCE_FLAGS_NINEPATCH_H_MODE_SHIFT = 9,
INSTANCE_FLAGS_NINEPATCH_V_MODE_SHIFT = 11,
INSTANCE_FLAGS_SHADOW_MASKED_SHIFT = 13, // 16 bits.
};
enum {
BATCH_FLAGS_INSTANCING_MASK = 0x7F,
BATCH_FLAGS_INSTANCING_HAS_COLORS = (1 << 7),
BATCH_FLAGS_INSTANCING_HAS_CUSTOM_DATA = (1 << 8),
BATCH_FLAGS_DEFAULT_NORMAL_MAP_USED = (1 << 9),
BATCH_FLAGS_DEFAULT_SPECULAR_MAP_USED = (1 << 10),
};
enum {
LIGHT_FLAGS_TEXTURE_MASK = 0xFFFF,
LIGHT_FLAGS_BLEND_SHIFT = 16,
LIGHT_FLAGS_BLEND_MASK = (3 << 16),
LIGHT_FLAGS_BLEND_MODE_ADD = (0 << 16),
LIGHT_FLAGS_BLEND_MODE_SUB = (1 << 16),
LIGHT_FLAGS_BLEND_MODE_MIX = (2 << 16),
LIGHT_FLAGS_BLEND_MODE_MASK = (3 << 16),
LIGHT_FLAGS_HAS_SHADOW = (1 << 20),
LIGHT_FLAGS_FILTER_SHIFT = 22
};
enum {
MAX_RENDER_ITEMS = 256 * 1024,
MAX_LIGHT_TEXTURES = 1024,
MAX_LIGHTS_PER_ITEM = 16,
DEFAULT_MAX_LIGHTS_PER_RENDER = 256,
};
/******************/
/**** LIGHTING ****/
/******************/
struct CanvasLight {
RID texture;
struct {
bool enabled = false;
float z_far;
float y_offset;
Transform2D directional_xform;
} shadow;
};
RID_Owner<CanvasLight> canvas_light_owner;
struct OccluderPolygon {
RSE::CanvasOccluderPolygonCullMode cull_mode = RSE::CANVAS_OCCLUDER_POLYGON_CULL_DISABLED;
int line_point_count = 0;
GLuint vertex_buffer = 0;
GLuint vertex_array = 0;
GLuint index_buffer = 0;
int sdf_point_count = 0;
int sdf_index_count = 0;
GLuint sdf_vertex_buffer = 0;
GLuint sdf_vertex_array = 0;
GLuint sdf_index_buffer = 0;
bool sdf_is_lines = false;
};
RID_Owner<OccluderPolygon> occluder_polygon_owner;
void _update_shadow_atlas();
struct {
CanvasOcclusionShaderGLES3 shader;
RID shader_version;
} shadow_render;
struct LightUniform {
float matrix[8]; //light to texture coordinate matrix
float shadow_matrix[8]; //light to shadow coordinate matrix
float color[4];
uint8_t shadow_color[4];
uint32_t flags; //index to light texture
float shadow_pixel_size;
float height;
float position[2];
float shadow_z_far_inv;
float shadow_y_ofs;
float atlas_rect[4];
};
static_assert(sizeof(LightUniform) % 16 == 0, "2D light UBO size must be a multiple of 16 bytes");
public:
enum {
BASE_UNIFORM_LOCATION = 0,
GLOBAL_UNIFORM_LOCATION = 1,
LIGHT_UNIFORM_LOCATION = 2,
INSTANCE_UNIFORM_LOCATION = 3,
MATERIAL_UNIFORM_LOCATION = 4,
};
struct StateBuffer {
float canvas_transform[16];
float screen_transform[16];
float canvas_normal_transform[16];
float canvas_modulate[4];
float screen_pixel_size[2];
float time;
uint32_t use_pixel_snap;
float sdf_to_tex[4];
float sdf_to_screen[2];
float screen_to_sdf[2];
uint32_t directional_light_count;
float tex_to_sdf;
uint32_t pad1;
uint32_t pad2;
};
static_assert(sizeof(StateBuffer) % 16 == 0, "2D state UBO size must be a multiple of 16 bytes");
struct PolygonBuffers {
GLuint vertex_buffer = 0;
GLuint vertex_array = 0;
GLuint index_buffer = 0;
int count = 0;
bool color_disabled = false;
Color color = Color(1.0, 1.0, 1.0, 1.0);
};
struct {
HashMap<PolygonID, PolygonBuffers> polygons;
PolygonID last_id = 0;
} polygon_buffers;
RendererCanvasRender::PolygonID request_polygon(const Vector<int> &p_indices, const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs = Vector<Point2>(), const Vector<int> &p_bones = Vector<int>(), const Vector<float> &p_weights = Vector<float>(), int p_count = -1) override;
void free_polygon(PolygonID p_polygon) override;
struct InstanceData {
float world[6];
float color_texture_pixel_size[2];
union {
//rect
struct {
float modulation[4];
union {
float msdf[4];
float ninepatch_margins[4];
};
float dst_rect[4];
float src_rect[4];
float pad[2];
};
//primitive
struct {
float points[6]; // vec2 points[3]
float uvs[6]; // vec2 points[3]
uint32_t colors[6]; // colors encoded as half
};
};
uint32_t flags;
uint32_t instance_uniforms_ofs;
uint32_t lights[4];
};
static_assert(sizeof(InstanceData) == 128, "2D instance data struct size must be 128 bytes");
struct Data {
GLuint canvas_quad_vertices;
GLuint canvas_quad_array;
GLuint indexed_quad_buffer;
GLuint indexed_quad_array;
GLuint particle_quad_vertices;
GLuint particle_quad_array;
GLuint ninepatch_vertices;
GLuint ninepatch_elements;
RID canvas_shader_default_version;
uint32_t max_lights_per_render = 256;
uint32_t max_lights_per_item = 16;
uint32_t max_instances_per_buffer = 16384;
uint32_t max_instance_buffer_size = 16384 * 128;
} data;
struct Batch {
// Position in the UBO measured in bytes
uint32_t start = 0;
uint32_t instance_count = 0;
uint32_t instance_buffer_index = 0;
RID tex;
RSE::CanvasItemTextureFilter filter = RSE::CANVAS_ITEM_TEXTURE_FILTER_MAX;
RSE::CanvasItemTextureRepeat repeat = RSE::CANVAS_ITEM_TEXTURE_REPEAT_MAX;
GLES3::CanvasShaderData::BlendMode blend_mode = GLES3::CanvasShaderData::BLEND_MODE_MIX;
Color blend_color = Color(1.0, 1.0, 1.0, 1.0);
Item *clip = nullptr;
RID material;
GLES3::CanvasMaterialData *material_data = nullptr;
uint64_t vertex_input_mask = RSE::ARRAY_FORMAT_VERTEX | RSE::ARRAY_FORMAT_COLOR | RSE::ARRAY_FORMAT_TEX_UV;
uint64_t specialization = 0;
const Item::Command *command = nullptr;
Item::Command::Type command_type = Item::Command::TYPE_ANIMATION_SLICE; // Can default to any type that doesn't form a batch.
uint32_t primitive_points = 0;
uint32_t flags = 0;
uint32_t specular_shininess = 0.0;
bool lights_disabled = false;
};
// DataBuffer contains our per-frame data. I.e. the resources that are updated each frame.
// We track them and ensure that they don't get reused until at least 2 frames have passed
// to avoid the GPU stalling to wait for a resource to become available.
struct DataBuffer {
Vector<GLuint> instance_buffers;
GLuint light_ubo = 0;
GLuint state_ubo = 0;
uint64_t last_frame_used = -3;
GLsync fence = GLsync();
};
struct State {
LocalVector<DataBuffer> canvas_instance_data_buffers;
LocalVector<Batch> canvas_instance_batches;
uint32_t current_data_buffer_index = 0;
uint32_t current_instance_buffer_index = 0;
uint32_t current_batch_index = 0;
uint32_t last_item_index = 0;
InstanceData *instance_data_array = nullptr;
LightUniform *light_uniforms = nullptr;
GLuint shadow_texture = 0;
GLuint shadow_depth_buffer = 0;
GLuint shadow_fb = 0;
int shadow_texture_size = 2048;
bool using_directional_lights = false;
RID current_tex;
RSE::CanvasItemTextureFilter current_filter_mode = RSE::CANVAS_ITEM_TEXTURE_FILTER_MAX;
RSE::CanvasItemTextureRepeat current_repeat_mode = RSE::CANVAS_ITEM_TEXTURE_REPEAT_MAX;
bool transparent_render_target = false;
double time = 0.0;
RSE::CanvasItemTextureFilter default_filter = RSE::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT;
RSE::CanvasItemTextureRepeat default_repeat = RSE::CANVAS_ITEM_TEXTURE_REPEAT_DEFAULT;
} state;
Item *items[MAX_RENDER_ITEMS];
RID default_canvas_texture;
RID default_canvas_group_material;
RID default_canvas_group_shader;
RID default_clip_children_material;
RID default_clip_children_shader;
typedef void Texture;
void canvas_begin(RID p_to_render_target, bool p_to_backbuffer, bool p_backbuffer_has_mipmaps);
//virtual void draw_window_margins(int *black_margin, RID *black_image) override;
void draw_lens_distortion_rect(const Rect2 &p_rect, float p_k1, float p_k2, const Vector2 &p_eye_center, float p_oversample);
void reset_canvas();
RID light_create() override;
void light_set_texture(RID p_rid, RID p_texture) override;
void light_set_use_shadow(RID p_rid, bool p_enable) override;
void light_update_shadow(RID p_rid, int p_shadow_index, const Transform2D &p_light_xform, int p_light_mask, float p_near, float p_far, LightOccluderInstance *p_occluders, const Rect2 &p_light_rect) override;
void light_update_directional_shadow(RID p_rid, int p_shadow_index, const Transform2D &p_light_xform, int p_light_mask, float p_cull_distance, const Rect2 &p_clip_rect, LightOccluderInstance *p_occluders) override;
void render_sdf(RID p_render_target, LightOccluderInstance *p_occluders) override;
RID occluder_polygon_create() override;
void occluder_polygon_set_shape(RID p_occluder, const Vector<Vector2> &p_points, bool p_closed) override;
void occluder_polygon_set_cull_mode(RID p_occluder, RSE::CanvasOccluderPolygonCullMode p_mode) override;
void set_shadow_texture_size(int p_size) override;
bool free(RID p_rid) override;
void update() override;
void _bind_canvas_texture(RID p_texture, RSE::CanvasItemTextureFilter p_base_filter, RSE::CanvasItemTextureRepeat p_base_repeat);
void _prepare_canvas_texture(RID p_texture, RSE::CanvasItemTextureFilter p_base_filter, RSE::CanvasItemTextureRepeat p_base_repeat, uint32_t &r_index, Size2 &r_texpixel_size);
void canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_list, const Transform2D &p_canvas_transform, RSE::CanvasItemTextureFilter p_default_filter, RSE::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used, RenderingServerTypes::RenderInfo *r_render_info = nullptr) override;
void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer = false, RenderingServerTypes::RenderInfo *r_render_info = nullptr, bool p_backbuffer_has_mipmaps = false);
void _record_item_commands(const Item *p_item, RID p_render_target, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_break_batch, bool &r_sdf_used, const Point2 &p_repeat_offset);
void _render_batch(Light *p_lights, uint32_t p_index, RenderingServerTypes::RenderInfo *r_render_info = nullptr);
bool _bind_material(GLES3::CanvasMaterialData *p_material_data, CanvasShaderGLES3::ShaderVariant p_variant, uint64_t p_specialization);
void _new_batch(bool &r_batch_broken);
void _add_to_batch(uint32_t &r_index, bool &r_batch_broken);
void _allocate_instance_data_buffer();
void _allocate_instance_buffer();
void _enable_attributes(uint32_t p_start, bool p_primitive, uint32_t p_rate = 1);
void set_time(double p_time);
virtual void set_debug_redraw(bool p_enabled, double p_time, const Color &p_color) override {
if (p_enabled) {
WARN_PRINT_ONCE("Debug CanvasItem Redraw is not available yet when using the Compatibility renderer.");
}
}
virtual uint32_t get_pipeline_compilations(RSE::PipelineSource p_source) override { return 0; }
static RasterizerCanvasGLES3 *get_singleton();
RasterizerCanvasGLES3();
~RasterizerCanvasGLES3();
};
#endif // GLES3_ENABLED

View file

@ -0,0 +1,506 @@
/**************************************************************************/
/* rasterizer_gles3.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "rasterizer_gles3.h"
#ifdef GLES3_ENABLED
#include "core/config/engine.h"
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
#include "core/io/image.h"
#include "core/os/os.h"
#include "drivers/gles3/rasterizer_util_gles3.h"
#include "servers/display/display_server.h"
#include "servers/rendering/rendering_server.h"
#include "servers/rendering/rendering_server_types.h"
#define _EXT_DEBUG_OUTPUT_SYNCHRONOUS_ARB 0x8242
#define _EXT_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_ARB 0x8243
#define _EXT_DEBUG_CALLBACK_FUNCTION_ARB 0x8244
#define _EXT_DEBUG_CALLBACK_USER_PARAM_ARB 0x8245
#define _EXT_DEBUG_SOURCE_API_ARB 0x8246
#define _EXT_DEBUG_SOURCE_WINDOW_SYSTEM_ARB 0x8247
#define _EXT_DEBUG_SOURCE_SHADER_COMPILER_ARB 0x8248
#define _EXT_DEBUG_SOURCE_THIRD_PARTY_ARB 0x8249
#define _EXT_DEBUG_SOURCE_APPLICATION_ARB 0x824A
#define _EXT_DEBUG_SOURCE_OTHER_ARB 0x824B
#define _EXT_DEBUG_TYPE_ERROR_ARB 0x824C
#define _EXT_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB 0x824D
#define _EXT_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB 0x824E
#define _EXT_DEBUG_TYPE_PORTABILITY_ARB 0x824F
#define _EXT_DEBUG_TYPE_PERFORMANCE_ARB 0x8250
#define _EXT_DEBUG_TYPE_OTHER_ARB 0x8251
#define _EXT_DEBUG_TYPE_MARKER_ARB 0x8268
#define _EXT_MAX_DEBUG_MESSAGE_LENGTH_ARB 0x9143
#define _EXT_MAX_DEBUG_LOGGED_MESSAGES_ARB 0x9144
#define _EXT_DEBUG_LOGGED_MESSAGES_ARB 0x9145
#define _EXT_DEBUG_SEVERITY_HIGH_ARB 0x9146
#define _EXT_DEBUG_SEVERITY_MEDIUM_ARB 0x9147
#define _EXT_DEBUG_SEVERITY_LOW_ARB 0x9148
#define _EXT_DEBUG_OUTPUT 0x92E0
#ifndef GL_FRAMEBUFFER_SRGB
#define GL_FRAMEBUFFER_SRGB 0x8DB9
#endif
#ifndef GLAPIENTRY
#if defined(WINDOWS_ENABLED)
#define GLAPIENTRY APIENTRY
#else
#define GLAPIENTRY
#endif
#endif
#if !defined(IOS_ENABLED) && !defined(WEB_ENABLED)
// We include EGL below to get debug callback on GLES2 platforms,
// but EGL is not available on iOS or the web.
#define CAN_DEBUG
#endif
#include "platform_gl.h"
#if defined(MINGW_ENABLED) || defined(_MSC_VER)
#define strcpy strcpy_s
#endif
#ifdef WINDOWS_ENABLED
bool RasterizerGLES3::screen_flipped_y = false;
#endif
void RasterizerGLES3::begin_frame(double frame_step) {
frame++;
delta = frame_step;
time_total += frame_step;
double time_roll_over = GLOBAL_GET_CACHED(double, "rendering/limits/time/time_rollover_secs");
time_total = Math::fmod(time_total, time_roll_over);
canvas->set_time(time_total);
scene->set_time(time_total, frame_step);
GLES3::Utilities *utils = GLES3::Utilities::get_singleton();
utils->_capture_timestamps_begin();
//scene->iteration();
}
void RasterizerGLES3::end_frame(bool p_swap_buffers) {
GLES3::Utilities *utils = GLES3::Utilities::get_singleton();
utils->capture_timestamps_end();
}
void RasterizerGLES3::gl_end_frame(bool p_swap_buffers) {
if (p_swap_buffers) {
DisplayServer::get_singleton()->swap_buffers();
} else {
glFinish();
}
}
#ifdef CAN_DEBUG
static void GLAPIENTRY _gl_debug_print(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const GLvoid *userParam) {
// These are ultimately annoying, so removing for now.
if (type == _EXT_DEBUG_TYPE_OTHER_ARB || type == _EXT_DEBUG_TYPE_PERFORMANCE_ARB || type == _EXT_DEBUG_TYPE_MARKER_ARB) {
return;
}
char debSource[256], debType[256], debSev[256];
if (source == _EXT_DEBUG_SOURCE_API_ARB) {
strcpy(debSource, "OpenGL");
} else if (source == _EXT_DEBUG_SOURCE_WINDOW_SYSTEM_ARB) {
strcpy(debSource, "Windows");
} else if (source == _EXT_DEBUG_SOURCE_SHADER_COMPILER_ARB) {
strcpy(debSource, "Shader Compiler");
} else if (source == _EXT_DEBUG_SOURCE_THIRD_PARTY_ARB) {
strcpy(debSource, "Third Party");
} else if (source == _EXT_DEBUG_SOURCE_APPLICATION_ARB) {
strcpy(debSource, "Application");
} else if (source == _EXT_DEBUG_SOURCE_OTHER_ARB) {
strcpy(debSource, "Other");
} else {
ERR_FAIL_MSG(vformat("GL ERROR: Invalid or unhandled source '%d' in debug callback.", source));
}
if (type == _EXT_DEBUG_TYPE_ERROR_ARB) {
strcpy(debType, "Error");
} else if (type == _EXT_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB) {
strcpy(debType, "Deprecated behavior");
} else if (type == _EXT_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB) {
strcpy(debType, "Undefined behavior");
} else if (type == _EXT_DEBUG_TYPE_PORTABILITY_ARB) {
strcpy(debType, "Portability");
} else {
ERR_FAIL_MSG(vformat("GL ERROR: Invalid or unhandled type '%d' in debug callback.", type));
}
if (severity == _EXT_DEBUG_SEVERITY_HIGH_ARB) {
strcpy(debSev, "High");
} else if (severity == _EXT_DEBUG_SEVERITY_MEDIUM_ARB) {
strcpy(debSev, "Medium");
} else if (severity == _EXT_DEBUG_SEVERITY_LOW_ARB) {
strcpy(debSev, "Low");
} else {
ERR_FAIL_MSG(vformat("GL ERROR: Invalid or unhandled severity '%d' in debug callback.", severity));
}
String output = String() + "GL ERROR: Source: " + debSource + "\tType: " + debType + "\tID: " + itos(id) + "\tSeverity: " + debSev + "\tMessage: " + message;
ERR_PRINT(output);
}
#endif
typedef void(GLAPIENTRY *DEBUGPROCARB)(GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei length,
const char *message,
const void *userParam);
typedef void(GLAPIENTRY *DebugMessageCallbackARB)(DEBUGPROCARB callback, const void *userParam);
void RasterizerGLES3::initialize() {
Engine::get_singleton()->print_header(vformat("OpenGL API %s - Compatibility - Using Device: %s - %s", RS::get_singleton()->get_video_adapter_api_version(), RS::get_singleton()->get_video_adapter_vendor(), RS::get_singleton()->get_video_adapter_name()));
}
void RasterizerGLES3::finalize() {
// Has to be a separate call due to TextureStorage & MaterialStorage needing to interact for TexBlit Shaders
texture_storage->_tex_blit_shader_free();
memdelete(scene);
memdelete(canvas);
memdelete(gi);
memdelete(fog);
memdelete(post_effects);
memdelete(glow);
memdelete(cubemap_filter);
memdelete(copy_effects);
memdelete(feed_effects);
memdelete(light_storage);
memdelete(particles_storage);
memdelete(mesh_storage);
memdelete(material_storage);
memdelete(texture_storage);
memdelete(utilities);
memdelete(config);
}
void RasterizerGLES3::make_current(bool p_gles_over_gl) {
RasterizerUtilGLES3::set_gles_over_gl(p_gles_over_gl);
OS::get_singleton()->set_gles_over_gl(p_gles_over_gl);
_create_func = _create_current;
low_end = true;
}
RasterizerGLES3 *RasterizerGLES3::singleton = nullptr;
#ifdef EGL_ENABLED
void *_egl_load_function_wrapper(const char *p_name) {
return (void *)eglGetProcAddress(p_name);
}
#endif
RasterizerGLES3::RasterizerGLES3() {
singleton = this;
#ifdef GLAD_ENABLED
bool glad_loaded = false;
#ifdef EGL_ENABLED
// There should be a more flexible system for getting the GL pointer, as
// different DisplayServers can have different ways. We can just use the GLAD
// version global to see if it loaded for now though, otherwise we fall back to
// the generic loader below.
#if defined(EGL_STATIC)
bool has_egl = true;
#else
bool has_egl = (eglGetProcAddress != nullptr);
#endif
if (RasterizerUtilGLES3::is_gles_over_gl()) {
if (has_egl && !glad_loaded && gladLoadGL((GLADloadfunc)&_egl_load_function_wrapper)) {
glad_loaded = true;
}
} else {
if (has_egl && !glad_loaded && gladLoadGLES2((GLADloadfunc)&_egl_load_function_wrapper)) {
glad_loaded = true;
}
}
#endif // EGL_ENABLED
if (RasterizerUtilGLES3::is_gles_over_gl()) {
if (!glad_loaded && gladLoaderLoadGL()) {
glad_loaded = true;
}
} else {
if (!glad_loaded && gladLoaderLoadGLES2()) {
glad_loaded = true;
}
}
// FIXME this is an early return from a constructor. Any other code using this instance will crash or the finalizer will crash, because none of
// the members of this instance are initialized, so this just makes debugging harder. It should either crash here intentionally,
// or we need to actually test for this situation before constructing this.
ERR_FAIL_COND_MSG(!glad_loaded, "Error initializing GLAD.");
if (RasterizerUtilGLES3::is_gles_over_gl()) {
if (OS::get_singleton()->is_stdout_verbose()) {
if (GLAD_GL_ARB_debug_output) {
glEnable(_EXT_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
glDebugMessageCallbackARB((GLDEBUGPROCARB)_gl_debug_print, nullptr);
glEnable(_EXT_DEBUG_OUTPUT);
} else {
print_line("OpenGL debugging not supported!");
}
}
}
#endif // GLAD_ENABLED
// For debugging
#ifdef CAN_DEBUG
#ifdef GL_API_ENABLED
if (RasterizerUtilGLES3::is_gles_over_gl()) {
if (OS::get_singleton()->is_stdout_verbose() && GLAD_GL_ARB_debug_output) {
glDebugMessageControlARB(_EXT_DEBUG_SOURCE_API_ARB, _EXT_DEBUG_TYPE_ERROR_ARB, _EXT_DEBUG_SEVERITY_HIGH_ARB, 0, nullptr, GL_TRUE);
glDebugMessageControlARB(_EXT_DEBUG_SOURCE_API_ARB, _EXT_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB, _EXT_DEBUG_SEVERITY_HIGH_ARB, 0, nullptr, GL_TRUE);
glDebugMessageControlARB(_EXT_DEBUG_SOURCE_API_ARB, _EXT_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB, _EXT_DEBUG_SEVERITY_HIGH_ARB, 0, nullptr, GL_TRUE);
glDebugMessageControlARB(_EXT_DEBUG_SOURCE_API_ARB, _EXT_DEBUG_TYPE_PORTABILITY_ARB, _EXT_DEBUG_SEVERITY_HIGH_ARB, 0, nullptr, GL_TRUE);
glDebugMessageControlARB(_EXT_DEBUG_SOURCE_API_ARB, _EXT_DEBUG_TYPE_PERFORMANCE_ARB, _EXT_DEBUG_SEVERITY_HIGH_ARB, 0, nullptr, GL_TRUE);
glDebugMessageControlARB(_EXT_DEBUG_SOURCE_API_ARB, _EXT_DEBUG_TYPE_OTHER_ARB, _EXT_DEBUG_SEVERITY_HIGH_ARB, 0, nullptr, GL_TRUE);
}
}
#endif // GL_API_ENABLED
#ifdef GLES_API_ENABLED
if (!RasterizerUtilGLES3::is_gles_over_gl()) {
if (OS::get_singleton()->is_stdout_verbose()) {
DebugMessageCallbackARB callback = (DebugMessageCallbackARB)eglGetProcAddress("glDebugMessageCallback");
if (!callback) {
callback = (DebugMessageCallbackARB)eglGetProcAddress("glDebugMessageCallbackKHR");
}
if (callback) {
print_line("godot: ENABLING GL DEBUG");
glEnable(_EXT_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
callback((DEBUGPROCARB)_gl_debug_print, nullptr);
glEnable(_EXT_DEBUG_OUTPUT);
}
}
}
#endif // GLES_API_ENABLED
#endif // CAN_DEBUG
{
String shader_cache_dir = Engine::get_singleton()->get_shader_cache_path();
if (shader_cache_dir.is_empty()) {
shader_cache_dir = "user://";
}
Ref<DirAccess> da = DirAccess::open(shader_cache_dir);
if (da.is_null()) {
ERR_PRINT("Can't create shader cache folder, no shader caching will happen: " + shader_cache_dir);
} else {
Error err = da->change_dir("shader_cache");
if (err != OK) {
err = da->make_dir("shader_cache");
}
if (err != OK) {
ERR_PRINT("Can't create shader cache folder, no shader caching will happen: " + shader_cache_dir);
} else {
shader_cache_dir = shader_cache_dir.path_join("shader_cache");
bool shader_cache_enabled = GLOBAL_GET("rendering/shader_compiler/shader_cache/enabled");
if (!Engine::get_singleton()->is_editor_hint() && !shader_cache_enabled) {
shader_cache_dir = String(); //disable only if not editor
}
if (!shader_cache_dir.is_empty()) {
ShaderGLES3::set_shader_cache_dir(shader_cache_dir);
}
}
}
}
// OpenGL needs to be initialized before initializing the Rasterizers
config = memnew(GLES3::Config);
utilities = memnew(GLES3::Utilities);
texture_storage = memnew(GLES3::TextureStorage);
material_storage = memnew(GLES3::MaterialStorage);
mesh_storage = memnew(GLES3::MeshStorage);
particles_storage = memnew(GLES3::ParticlesStorage);
light_storage = memnew(GLES3::LightStorage);
copy_effects = memnew(GLES3::CopyEffects);
cubemap_filter = memnew(GLES3::CubemapFilter);
glow = memnew(GLES3::Glow);
post_effects = memnew(GLES3::PostEffects);
feed_effects = memnew(GLES3::FeedEffects);
gi = memnew(GLES3::GI);
fog = memnew(GLES3::Fog);
canvas = memnew(RasterizerCanvasGLES3());
scene = memnew(RasterizerSceneGLES3());
// Has to be a separate call due to TextureStorage & MaterialStorage needing to interact for TexBlit Shaders
texture_storage->_tex_blit_shader_initialize();
// Disable OpenGL linear to sRGB conversion, because Godot will always do this conversion itself.
if (config->srgb_framebuffer_supported) {
glDisable(GL_FRAMEBUFFER_SRGB);
}
}
RasterizerGLES3::~RasterizerGLES3() {
}
void RasterizerGLES3::_blit_render_target_to_screen(DisplayServerEnums::WindowID p_screen, const RenderingServerTypes::BlitToScreen &p_blit, bool p_first) {
GLES3::RenderTarget *rt = GLES3::TextureStorage::get_singleton()->get_render_target(p_blit.render_target);
ERR_FAIL_NULL(rt);
// We normally render to the render target upside down, so flip Y when blitting to the screen.
bool flip_y = true;
bool linear_to_srgb = false;
if (rt->overridden.color.is_valid()) {
// If we've overridden the render target's color texture, that means we
// didn't render upside down, so we don't need to flip it.
// We're probably rendering directly to an XR device.
flip_y = false;
// It is 99% likely our texture uses the GL_SRGB8_ALPHA8 texture format in
// which case we have a GPU sRGB to Linear conversion on texture read.
// We need to counter this.
// Unfortunately we do not have an API to check this as Godot does not
// track this.
linear_to_srgb = true;
}
#ifdef WINDOWS_ENABLED
if (screen_flipped_y) {
flip_y = !flip_y;
}
#endif
glBindFramebuffer(GL_FRAMEBUFFER, GLES3::TextureStorage::system_fbo);
if (p_first) {
if (p_blit.dst_rect.position != Vector2() || p_blit.dst_rect.size != rt->size) {
// Viewport doesn't cover entire window so clear window to black before blitting.
// Querying the actual window size from the DisplayServer would deadlock in separate render thread mode,
// so let's set the biggest viewport the implementation supports, to be sure the window is fully covered.
Size2i max_vp = GLES3::Utilities::get_singleton()->get_maximum_viewport_size();
glViewport(0, 0, max_vp[0], max_vp[1]);
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
}
}
Vector2 screen_rect_end = p_blit.dst_rect.get_end();
Vector2 p1 = Vector2(p_blit.dst_rect.position.x, flip_y ? screen_rect_end.y : p_blit.dst_rect.position.y);
Vector2 p2 = Vector2(screen_rect_end.x, flip_y ? p_blit.dst_rect.position.y : screen_rect_end.y);
Vector2 size = p2 - p1;
Rect2 screenrect = Rect2(Vector2(0.0, flip_y ? 1.0 : 0.0), Vector2(1.0, flip_y ? -1.0 : 1.0));
glViewport(int(MIN(p1.x, p2.x)), int(MIN(p1.y, p2.y)), Math::abs(size.x), Math::abs(size.y));
glActiveTexture(GL_TEXTURE0);
GLenum target = rt->view_count > 1 ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D;
glBindTexture(target, rt->color);
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glDisable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ZERO);
if (p_blit.lens_distortion.apply && (p_blit.lens_distortion.k1 != 0.0 || p_blit.lens_distortion.k2)) {
copy_effects->copy_with_lens_distortion(screenrect, p_blit.multi_view.use_layer ? p_blit.multi_view.layer : 0, p_blit.lens_distortion.eye_center, p_blit.lens_distortion.k1, p_blit.lens_distortion.k2, p_blit.lens_distortion.upscale, p_blit.lens_distortion.aspect_ratio, linear_to_srgb);
} else if (rt->view_count > 1) {
copy_effects->copy_to_rect_3d(screenrect, p_blit.multi_view.use_layer ? p_blit.multi_view.layer : 0, GLES3::Texture::TYPE_LAYERED, 0.0, linear_to_srgb);
} else {
copy_effects->copy_to_rect(screenrect, linear_to_srgb);
}
glBindTexture(GL_TEXTURE_2D, 0);
}
// is this p_screen useless in a multi window environment?
void RasterizerGLES3::blit_render_targets_to_screen(DisplayServerEnums::WindowID p_screen, const RenderingServerTypes::BlitToScreen *p_render_targets, int p_amount) {
for (int i = 0; i < p_amount; i++) {
_blit_render_target_to_screen(p_screen, p_render_targets[i], i == 0);
}
}
void RasterizerGLES3::set_boot_image_with_stretch(const Ref<Image> &p_image, const Color &p_color, RSE::SplashStretchMode p_stretch_mode, bool p_use_filter) {
if (p_image.is_null() || p_image->is_empty()) {
return;
}
Size2i win_size = DisplayServer::get_singleton()->window_get_size();
glBindFramebuffer(GL_FRAMEBUFFER, GLES3::TextureStorage::system_fbo);
glViewport(0, 0, win_size.width, win_size.height);
glEnable(GL_BLEND);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);
glDepthMask(GL_FALSE);
glClearColor(p_color.r, p_color.g, p_color.b, OS::get_singleton()->is_layered_allowed() ? p_color.a : 1.0);
glClear(GL_COLOR_BUFFER_BIT);
RID texture = texture_storage->texture_allocate();
texture_storage->texture_2d_initialize(texture, p_image);
Rect2 screenrect = RenderingServerTypes::get_splash_stretched_screen_rect(p_image->get_size(), win_size, p_stretch_mode);
#ifdef WINDOWS_ENABLED
if (!screen_flipped_y)
#endif
{
// Flip Y.
screenrect.position.y = win_size.y - screenrect.position.y;
screenrect.size.y = -screenrect.size.y;
}
// Normalize texture coordinates to window size.
screenrect.position /= win_size;
screenrect.size /= win_size;
GLES3::Texture *t = texture_storage->get_texture(texture);
t->gl_set_filter(p_use_filter ? RSE::CANVAS_ITEM_TEXTURE_FILTER_LINEAR : RSE::CANVAS_ITEM_TEXTURE_FILTER_NEAREST);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, t->tex_id);
copy_effects->copy_to_rect(screenrect);
glBindTexture(GL_TEXTURE_2D, 0);
gl_end_frame(true);
texture_storage->texture_free(texture);
}
#endif // GLES3_ENABLED

View file

@ -0,0 +1,132 @@
/**************************************************************************/
/* rasterizer_gles3.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifdef GLES3_ENABLED
#include "drivers/gles3/effects/copy_effects.h"
#include "drivers/gles3/effects/cubemap_filter.h"
#include "drivers/gles3/effects/feed_effects.h"
#include "drivers/gles3/effects/glow.h"
#include "drivers/gles3/effects/post_effects.h"
#include "drivers/gles3/environment/fog.h"
#include "drivers/gles3/environment/gi.h"
#include "drivers/gles3/rasterizer_canvas_gles3.h"
#include "drivers/gles3/rasterizer_scene_gles3.h"
#include "drivers/gles3/storage/config.h"
#include "drivers/gles3/storage/light_storage.h"
#include "drivers/gles3/storage/material_storage.h"
#include "drivers/gles3/storage/mesh_storage.h"
#include "drivers/gles3/storage/particles_storage.h"
#include "drivers/gles3/storage/texture_storage.h"
#include "drivers/gles3/storage/utilities.h"
#include "servers/rendering/renderer_compositor.h"
class RasterizerGLES3 : public RendererCompositor {
private:
uint64_t frame = 1;
float delta = 0;
double time_total = 0.0;
#ifdef WINDOWS_ENABLED
static bool screen_flipped_y;
#endif
protected:
GLES3::Config *config = nullptr;
GLES3::Utilities *utilities = nullptr;
GLES3::TextureStorage *texture_storage = nullptr;
GLES3::MaterialStorage *material_storage = nullptr;
GLES3::MeshStorage *mesh_storage = nullptr;
GLES3::ParticlesStorage *particles_storage = nullptr;
GLES3::LightStorage *light_storage = nullptr;
GLES3::GI *gi = nullptr;
GLES3::Fog *fog = nullptr;
GLES3::CopyEffects *copy_effects = nullptr;
GLES3::CubemapFilter *cubemap_filter = nullptr;
GLES3::Glow *glow = nullptr;
GLES3::PostEffects *post_effects = nullptr;
GLES3::FeedEffects *feed_effects = nullptr;
RasterizerCanvasGLES3 *canvas = nullptr;
RasterizerSceneGLES3 *scene = nullptr;
static RasterizerGLES3 *singleton;
void _blit_render_target_to_screen(DisplayServerEnums::WindowID p_screen, const RenderingServerTypes::BlitToScreen &p_blit, bool p_first = true);
public:
RendererUtilities *get_utilities() { return utilities; }
RendererLightStorage *get_light_storage() { return light_storage; }
RendererMaterialStorage *get_material_storage() { return material_storage; }
RendererMeshStorage *get_mesh_storage() { return mesh_storage; }
RendererParticlesStorage *get_particles_storage() { return particles_storage; }
RendererTextureStorage *get_texture_storage() { return texture_storage; }
RendererGI *get_gi() { return gi; }
RendererFog *get_fog() { return fog; }
RendererCanvasRender *get_canvas() { return canvas; }
RendererSceneRender *get_scene() { return scene; }
void set_boot_image_with_stretch(const Ref<Image> &p_image, const Color &p_color, RSE::SplashStretchMode p_stretch_mode, bool p_use_filter = true);
void initialize();
void begin_frame(double frame_step);
void blit_render_targets_to_screen(DisplayServerEnums::WindowID p_screen, const RenderingServerTypes::BlitToScreen *p_render_targets, int p_amount);
bool is_opengl() { return true; }
void gl_end_frame(bool p_swap_buffers);
void end_frame(bool p_swap_buffers);
void finalize();
static RendererCompositor *_create_current() {
return memnew(RasterizerGLES3);
}
static void make_current(bool p_gles_over_gl);
#ifdef WINDOWS_ENABLED
static void set_screen_flipped_y(bool p_flipped) {
screen_flipped_y = p_flipped;
}
#endif
_ALWAYS_INLINE_ uint64_t get_frame_number() const { return frame; }
_ALWAYS_INLINE_ double get_frame_delta_time() const { return delta; }
_ALWAYS_INLINE_ double get_total_time() const { return time_total; }
_ALWAYS_INLINE_ bool can_create_resources_async() const { return false; }
static RasterizerGLES3 *get_singleton() { return singleton; }
RasterizerGLES3();
~RasterizerGLES3();
};
#endif // GLES3_ENABLED

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,965 @@
/**************************************************************************/
/* rasterizer_scene_gles3.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifdef GLES3_ENABLED
#include "core/math/projection.h"
#include "core/templates/paged_allocator.h"
#include "core/templates/rid_owner.h"
#include "core/templates/self_list.h"
#include "drivers/gles3/storage/light_storage.h"
#include "drivers/gles3/storage/material_storage.h"
#include "servers/rendering/renderer_scene_render.h"
#include "servers/rendering/rendering_server_enums.h"
#include "servers/rendering/rendering_server_types.h"
class RenderSceneBuffersGLES3;
enum RenderListType {
RENDER_LIST_OPAQUE, //used for opaque objects
RENDER_LIST_ALPHA, //used for transparent objects
RENDER_LIST_SECONDARY, //used for shadows and other objects
RENDER_LIST_MAX
};
enum PassMode {
PASS_MODE_COLOR,
PASS_MODE_COLOR_TRANSPARENT,
PASS_MODE_SHADOW,
PASS_MODE_DEPTH,
PASS_MODE_MATERIAL,
PASS_MODE_MOTION_VECTORS,
};
// These should share as much as possible with SkyUniform Location
enum SceneUniformLocation {
SCENE_TONEMAP_UNIFORM_LOCATION,
SCENE_GLOBALS_UNIFORM_LOCATION,
SCENE_DATA_UNIFORM_LOCATION,
SCENE_MATERIAL_UNIFORM_LOCATION,
SCENE_EMPTY1, // Unused, put here to avoid conflicts with SKY_DIRECTIONAL_LIGHT_UNIFORM_LOCATION.
SCENE_OMNILIGHT_UNIFORM_LOCATION,
SCENE_SPOTLIGHT_UNIFORM_LOCATION,
SCENE_DIRECTIONAL_LIGHT_UNIFORM_LOCATION,
SCENE_MULTIVIEW_UNIFORM_LOCATION,
SCENE_POSITIONAL_SHADOW_UNIFORM_LOCATION,
SCENE_DIRECTIONAL_SHADOW_UNIFORM_LOCATION,
SCENE_EMPTY2, // Unused, put here to avoid conflicts with SKY_MULTIVIEW_UNIFORM_LOCATION.
SCENE_PREV_DATA_UNIFORM_LOCATION,
SCENE_PREV_MULTIVIEW_UNIFORM_LOCATION,
};
enum SkyUniformLocation {
SKY_TONEMAP_UNIFORM_LOCATION,
SKY_GLOBALS_UNIFORM_LOCATION,
SKY_EMPTY1, // Unused, put here to avoid conflicts with SCENE_DATA_UNIFORM_LOCATION.
SKY_MATERIAL_UNIFORM_LOCATION,
SKY_DIRECTIONAL_LIGHT_UNIFORM_LOCATION,
SKY_EMPTY2, // Unused, put here to avoid conflicts with SCENE_OMNILIGHT_UNIFORM_LOCATION.
SKY_EMPTY3, // Unused, put here to avoid conflicts with SCENE_SPOTLIGHT_UNIFORM_LOCATION.
SKY_EMPTY4, // Unused, put here to avoid conflicts with SCENE_DIRECTIONAL_LIGHT_UNIFORM_LOCATION.
SKY_EMPTY5, // Unused, put here to avoid conflicts with SCENE_MULTIVIEW_UNIFORM_LOCATION.
SKY_EMPTY6, // Unused, put here to avoid conflicts with SCENE_POSITIONAL_SHADOW_UNIFORM_LOCATION.
SKY_EMPTY7, // Unused, put here to avoid conflicts with SCENE_DIRECTIONAL_SHADOW_UNIFORM_LOCATION.
SKY_MULTIVIEW_UNIFORM_LOCATION,
SKY_EMPTY8, // Unused, put here to avoid conflicts with SCENE_PREV_DATA_UNIFORM_LOCATION.
SKY_EMPTY9, // Unused, put here to avoid conflicts with SCENE_PREV_MULTIVIEW_UNIFORM_LOCATION.
};
struct RenderDataGLES3 {
Ref<RenderSceneBuffersGLES3> render_buffers;
bool transparent_bg = false;
Rect2i render_region;
Transform3D cam_transform;
Transform3D inv_cam_transform;
Projection cam_projection;
bool cam_orthogonal = false;
uint32_t camera_visible_layers = 0xFFFFFFFF;
// For billboards to cast correct shadows.
Transform3D main_cam_transform;
// For stereo rendering
uint32_t view_count = 1;
Vector3 view_eye_offset[RendererSceneRender::MAX_RENDER_VIEWS];
Projection view_projection[RendererSceneRender::MAX_RENDER_VIEWS];
float z_near = 0.0;
float z_far = 0.0;
const PagedArray<RenderGeometryInstance *> *instances = nullptr;
const PagedArray<RID> *lights = nullptr;
const PagedArray<RID> *reflection_probes = nullptr;
RID environment;
RID camera_attributes;
RID shadow_atlas;
RID reflection_probe;
int reflection_probe_pass = 0;
float lod_distance_multiplier = 0.0;
float screen_mesh_lod_threshold = 0.0;
uint32_t directional_light_count = 0;
uint32_t directional_shadow_count = 0;
uint32_t spot_light_count = 0;
uint32_t omni_light_count = 0;
float luminance_multiplier = 1.0;
RenderingServerTypes::RenderInfo *render_info = nullptr;
/* Shadow data */
const RendererSceneRender::RenderShadowData *render_shadows = nullptr;
int render_shadow_count = 0;
};
class RasterizerCanvasGLES3;
class RasterizerSceneGLES3 : public RendererSceneRender {
private:
static RasterizerSceneGLES3 *singleton;
RSE::ViewportDebugDraw debug_draw = RSE::VIEWPORT_DEBUG_DRAW_DISABLED;
uint64_t scene_pass = 0;
template <typename T>
struct InstanceSort {
float depth;
T *instance = nullptr;
bool operator<(const InstanceSort &p_sort) const {
return depth < p_sort.depth;
}
};
struct SceneGlobals {
RID shader_default_version;
RID default_material;
RID default_shader;
RID overdraw_material;
RID overdraw_shader;
} scene_globals;
GLES3::SceneMaterialData *default_material_data_ptr = nullptr;
GLES3::SceneMaterialData *overdraw_material_data_ptr = nullptr;
/* LIGHT INSTANCE */
struct LightData {
float position[3];
float inv_radius;
float direction[3]; // Only used by SpotLight
float size;
float color[3];
float attenuation;
float inv_spot_attenuation;
float cos_spot_angle;
float specular_amount;
float shadow_opacity;
float pad[3];
uint32_t bake_mode;
};
static_assert(sizeof(LightData) % 16 == 0, "LightData size must be a multiple of 16 bytes");
struct DirectionalLightData {
float direction[3];
float energy;
float color[3];
float size;
uint32_t enabled : 1; // For use by SkyShaders
uint32_t bake_mode : 2;
float shadow_opacity;
float specular;
uint32_t mask;
};
static_assert(sizeof(DirectionalLightData) % 16 == 0, "DirectionalLightData size must be a multiple of 16 bytes");
struct ShadowData {
float shadow_matrix[16];
float light_position[3];
float shadow_normal_bias;
float pad[3];
float shadow_atlas_pixel_size;
};
static_assert(sizeof(ShadowData) % 16 == 0, "ShadowData size must be a multiple of 16 bytes");
struct DirectionalShadowData {
float direction[3];
float shadow_atlas_pixel_size;
float shadow_normal_bias[4];
float shadow_split_offsets[4];
float shadow_matrices[4][16];
float fade_from;
float fade_to;
uint32_t blend_splits; // Not exposed to the shader.
uint32_t pad;
};
static_assert(sizeof(DirectionalShadowData) % 16 == 0, "DirectionalShadowData size must be a multiple of 16 bytes");
class GeometryInstanceGLES3;
// Cached data for drawing surfaces
struct GeometryInstanceSurface {
enum {
FLAG_PASS_DEPTH = 1,
FLAG_PASS_OPAQUE = 2,
FLAG_PASS_ALPHA = 4,
FLAG_PASS_SHADOW = 8,
FLAG_USES_SHARED_SHADOW_MATERIAL = 128,
FLAG_USES_SCREEN_TEXTURE = 2048,
FLAG_USES_DEPTH_TEXTURE = 4096,
FLAG_USES_NORMAL_TEXTURE = 8192,
FLAG_USES_DOUBLE_SIDED_SHADOWS = 16384,
FLAG_USES_STENCIL = 32768,
};
union {
struct {
uint64_t sort_key1;
uint64_t sort_key2;
};
struct {
uint64_t lod_index : 8;
uint64_t surface_index : 8;
uint64_t geometry_id : 32;
uint64_t material_id_low : 16;
uint64_t material_id_hi : 16;
uint64_t shader_id : 32;
uint64_t uses_softshadow : 1;
uint64_t uses_projector : 1;
uint64_t uses_forward_gi : 1;
uint64_t uses_lightmap : 1;
uint64_t depth_layer : 4;
uint64_t priority : 8;
};
} sort;
RSE::PrimitiveType primitive = RSE::PRIMITIVE_MAX;
uint32_t flags = 0;
uint32_t surface_index = 0;
uint32_t lod_index = 0;
uint32_t index_count = 0;
int32_t light_pass_index = -1;
bool finished_base_pass = false;
void *surface = nullptr;
GLES3::SceneShaderData *shader = nullptr;
GLES3::SceneMaterialData *material = nullptr;
void *surface_shadow = nullptr;
GLES3::SceneShaderData *shader_shadow = nullptr;
GLES3::SceneMaterialData *material_shadow = nullptr;
GeometryInstanceSurface *next = nullptr;
GeometryInstanceGLES3 *owner = nullptr;
};
struct GeometryInstanceLightmapSH {
Color sh[9];
};
class GeometryInstanceGLES3 : public RenderGeometryInstanceBase {
public:
//used during rendering
bool store_transform_cache = true;
// Used for generating motion vectors.
Transform3D prev_transform;
bool is_prev_transform_stored = false;
int32_t instance_count = 0;
bool can_sdfgi = false;
bool using_projectors = false;
bool using_softshadows = false;
struct LightPass {
int32_t light_id = -1; // Position in the light uniform buffer.
int32_t shadow_id = -1; // Position in the shadow uniform buffer.
RID light_instance_rid;
bool is_omni = false;
};
LocalVector<LightPass> light_passes;
uint32_t paired_omni_light_count = 0;
uint32_t paired_spot_light_count = 0;
LocalVector<RID> paired_omni_lights;
LocalVector<RID> paired_spot_lights;
LocalVector<uint32_t> omni_light_gl_cache;
LocalVector<uint32_t> spot_light_gl_cache;
LocalVector<RID> paired_reflection_probes;
LocalVector<RID> reflection_probe_rid_cache;
LocalVector<Transform3D> reflection_probes_local_transform_cache;
RID lightmap_instance;
Rect2 lightmap_uv_scale;
uint32_t lightmap_slice_index;
GeometryInstanceLightmapSH *lightmap_sh = nullptr;
// Used during setup.
GeometryInstanceSurface *surface_caches = nullptr;
SelfList<GeometryInstanceGLES3> dirty_list_element;
GeometryInstanceGLES3() :
dirty_list_element(this) {}
virtual void _mark_dirty() override;
virtual void set_use_lightmap(RID p_lightmap_instance, const Rect2 &p_lightmap_uv_scale, int p_lightmap_slice_index) override;
virtual void set_lightmap_capture(const Color *p_sh9) override;
virtual void clear_light_instances() override;
virtual void pair_light_instance(const RID p_light_instance, RSE::LightType light_type, uint32_t placement_idx) override;
virtual void pair_reflection_probe_instances(const RID *p_reflection_probe_instances, uint32_t p_reflection_probe_instance_count) override;
virtual void pair_decal_instances(const RID *p_decal_instances, uint32_t p_decal_instance_count) override {}
virtual void pair_voxel_gi_instances(const RID *p_voxel_gi_instances, uint32_t p_voxel_gi_instance_count) override {}
virtual void set_softshadow_projector_pairing(bool p_softshadow, bool p_projector) override {}
};
virtual uint32_t get_max_lights_total() override;
virtual uint32_t get_max_lights_per_mesh() override;
enum {
INSTANCE_DATA_FLAGS_DYNAMIC = 1 << 3,
INSTANCE_DATA_FLAGS_NON_UNIFORM_SCALE = 1 << 4,
INSTANCE_DATA_FLAG_USE_GI_BUFFERS = 1 << 5,
INSTANCE_DATA_FLAG_USE_LIGHTMAP_CAPTURE = 1 << 7,
INSTANCE_DATA_FLAG_USE_LIGHTMAP = 1 << 8,
INSTANCE_DATA_FLAG_USE_SH_LIGHTMAP = 1 << 9,
INSTANCE_DATA_FLAG_USE_VOXEL_GI = 1 << 10,
INSTANCE_DATA_FLAG_PARTICLES = 1 << 11,
INSTANCE_DATA_FLAG_MULTIMESH = 1 << 12,
INSTANCE_DATA_FLAG_MULTIMESH_FORMAT_2D = 1 << 13,
INSTANCE_DATA_FLAG_MULTIMESH_HAS_COLOR = 1 << 14,
INSTANCE_DATA_FLAG_MULTIMESH_HAS_CUSTOM_DATA = 1 << 15,
};
static void _geometry_instance_dependency_changed(Dependency::DependencyChangedNotification p_notification, DependencyTracker *p_tracker);
static void _geometry_instance_dependency_deleted(const RID &p_dependency, DependencyTracker *p_tracker);
SelfList<GeometryInstanceGLES3>::List geometry_instance_dirty_list;
// Use PagedAllocator instead of RID to maximize performance
PagedAllocator<GeometryInstanceGLES3> geometry_instance_alloc;
PagedAllocator<GeometryInstanceSurface> geometry_instance_surface_alloc;
void _geometry_instance_add_surface_with_material(GeometryInstanceGLES3 *ginstance, uint32_t p_surface, GLES3::SceneMaterialData *p_material, uint32_t p_material_id, uint32_t p_shader_id, RID p_mesh);
void _geometry_instance_add_surface_with_material_chain(GeometryInstanceGLES3 *ginstance, uint32_t p_surface, GLES3::SceneMaterialData *p_material, RID p_mat_src, RID p_mesh);
void _geometry_instance_add_surface(GeometryInstanceGLES3 *ginstance, uint32_t p_surface, RID p_material, RID p_mesh);
void _geometry_instance_update(RenderGeometryInstance *p_geometry_instance);
void _update_dirty_geometry_instances();
struct SceneState {
struct UBO {
float projection_matrix[16];
float inv_projection_matrix[16];
float inv_view_matrix[16];
float view_matrix[16];
float main_cam_inv_view_matrix[16];
float viewport_size[2];
float screen_pixel_size[2];
float ambient_light_color_energy[4];
float ambient_color_sky_mix;
uint32_t directional_shadow_count;
float emissive_exposure_normalization;
uint32_t use_ambient_light = 0;
uint32_t use_ambient_cubemap = 0;
uint32_t use_reflection_cubemap = 0;
float fog_aerial_perspective;
float time;
float radiance_inverse_xform[12];
uint32_t directional_light_count;
float z_far;
float z_near;
float IBL_exposure_normalization;
uint32_t fog_enabled;
uint32_t fog_mode;
float fog_density;
float fog_height;
float fog_height_density;
float fog_depth_curve;
float fog_sun_scatter;
float fog_depth_begin;
float fog_light_color[3];
float fog_depth_end;
float shadow_bias;
float luminance_multiplier;
uint32_t camera_visible_layers;
bool pancake_shadows;
};
static_assert(sizeof(UBO) % 16 == 0, "Scene UBO size must be a multiple of 16 bytes");
static_assert(sizeof(UBO) < 16384, "Scene UBO size must be 16384 bytes or smaller");
struct MultiviewUBO {
float projection_matrix_view[RendererSceneRender::MAX_RENDER_VIEWS][16];
float inv_projection_matrix_view[RendererSceneRender::MAX_RENDER_VIEWS][16];
float eye_offset[RendererSceneRender::MAX_RENDER_VIEWS][4];
};
static_assert(sizeof(MultiviewUBO) % 16 == 0, "Multiview UBO size must be a multiple of 16 bytes");
static_assert(sizeof(MultiviewUBO) < 16384, "MultiviewUBO size must be 16384 bytes or smaller");
struct TonemapUBO {
float exposure = 1.0;
int32_t tonemapper = 0;
int32_t pad = 0;
int32_t pad2 = 0;
float tonemapper_params[4] = { 0.0, 0.0, 0.0, 0.0 };
float brightness = 1.0;
float contrast = 1.0;
float saturation = 1.0;
int32_t pad3 = 0;
};
static_assert(sizeof(TonemapUBO) % 16 == 0, "Tonemap UBO size must be a multiple of 16 bytes");
UBO data;
UBO prev_data;
GLuint ubo_buffer = 0;
GLuint prev_ubo_buffer = 0;
MultiviewUBO multiview_data;
MultiviewUBO prev_multiview_data;
GLuint multiview_buffer = 0;
GLuint prev_multiview_buffer = 0;
GLuint tonemap_buffer = 0;
int prev_data_state = 0; // 0 = Motion vectors not used, 1 = use data (first frame only), 2 = use previous data
bool used_depth_prepass = false;
GLES3::SceneShaderData::BlendMode current_blend_mode = GLES3::SceneShaderData::BLEND_MODE_MIX;
RSE::CullMode cull_mode = RSE::CULL_MODE_BACK;
GLenum current_depth_function = GL_GEQUAL;
bool current_blend_enabled = false;
bool current_depth_draw_enabled = false;
bool current_depth_test_enabled = false;
bool current_scissor_test_enabled = false;
void reset_gl_state() {
glDisable(GL_BLEND);
current_blend_enabled = false;
glDisable(GL_SCISSOR_TEST);
current_scissor_test_enabled = false;
glCullFace(GL_BACK);
glEnable(GL_CULL_FACE);
cull_mode = RSE::CULL_MODE_BACK;
glDepthMask(GL_FALSE);
current_depth_draw_enabled = false;
glDisable(GL_DEPTH_TEST);
current_depth_test_enabled = false;
glDepthFunc(GL_GEQUAL);
current_depth_function = GL_GEQUAL;
glDisable(GL_STENCIL_TEST);
current_stencil_test_enabled = false;
glStencilMask(255);
current_stencil_write_mask = 255;
glStencilFunc(GL_ALWAYS, 0, 255);
current_stencil_compare = GL_ALWAYS;
current_stencil_reference = 0;
current_stencil_compare_mask = 255;
}
void set_gl_cull_mode(RSE::CullMode p_mode) {
if (cull_mode != p_mode) {
if (p_mode == RSE::CULL_MODE_DISABLED) {
glDisable(GL_CULL_FACE);
} else {
if (cull_mode == RSE::CULL_MODE_DISABLED) {
// Last time was disabled, so enable and set proper face.
glEnable(GL_CULL_FACE);
}
glCullFace(p_mode == RSE::CULL_MODE_FRONT ? GL_FRONT : GL_BACK);
}
cull_mode = p_mode;
}
}
void enable_gl_blend(bool p_enabled) {
if (current_blend_enabled != p_enabled) {
if (p_enabled) {
glEnable(GL_BLEND);
} else {
glDisable(GL_BLEND);
}
current_blend_enabled = p_enabled;
}
}
void enable_gl_scissor_test(bool p_enabled) {
if (current_scissor_test_enabled != p_enabled) {
if (p_enabled) {
glEnable(GL_SCISSOR_TEST);
} else {
glDisable(GL_SCISSOR_TEST);
}
current_scissor_test_enabled = p_enabled;
}
}
void enable_gl_depth_draw(bool p_enabled) {
if (current_depth_draw_enabled != p_enabled) {
glDepthMask(p_enabled ? GL_TRUE : GL_FALSE);
current_depth_draw_enabled = p_enabled;
}
}
void enable_gl_depth_test(bool p_enabled) {
if (current_depth_test_enabled != p_enabled) {
if (p_enabled) {
glEnable(GL_DEPTH_TEST);
} else {
glDisable(GL_DEPTH_TEST);
}
current_depth_test_enabled = p_enabled;
}
}
void set_gl_depth_func(GLenum p_depth_func) {
if (current_depth_function != p_depth_func) {
glDepthFunc(p_depth_func);
current_depth_function = p_depth_func;
}
}
void enable_gl_stencil_test(bool p_enabled) {
if (current_stencil_test_enabled != p_enabled) {
if (p_enabled) {
glEnable(GL_STENCIL_TEST);
} else {
glDisable(GL_STENCIL_TEST);
}
current_stencil_test_enabled = p_enabled;
}
}
void set_gl_stencil_func(GLenum p_compare, GLint p_reference, GLenum p_compare_mask) {
if (current_stencil_compare != p_compare || current_stencil_reference != p_reference || current_stencil_compare_mask != p_compare_mask) {
glStencilFunc(p_compare, p_reference, p_compare_mask);
current_stencil_compare = p_compare;
current_stencil_reference = p_reference;
current_stencil_compare_mask = p_compare_mask;
}
}
void set_gl_stencil_write_mask(GLuint p_mask) {
if (current_stencil_write_mask != p_mask) {
glStencilMask(p_mask);
current_stencil_write_mask = p_mask;
}
}
void set_gl_stencil_op(GLenum p_op_fail, GLenum p_op_dpfail, GLenum p_op_dppass) {
if (current_stencil_op_fail != p_op_fail || current_stencil_op_dpfail != p_op_dpfail || current_stencil_op_dppass != p_op_dppass) {
glStencilOp(p_op_fail, p_op_dpfail, p_op_dppass);
current_stencil_op_fail = p_op_fail;
current_stencil_op_dpfail = p_op_dpfail;
current_stencil_op_dppass = p_op_dppass;
}
}
GLenum current_stencil_compare = GL_ALWAYS;
GLuint current_stencil_compare_mask = 255;
GLuint current_stencil_write_mask = 255;
GLint current_stencil_reference = 0;
GLenum current_stencil_op_fail = GL_KEEP;
GLenum current_stencil_op_dpfail = GL_KEEP;
GLenum current_stencil_op_dppass = GL_KEEP;
bool current_stencil_test_enabled = false;
bool texscreen_copied = false;
bool used_screen_texture = false;
bool used_normal_texture = false;
bool used_depth_texture = false;
bool used_opaque_stencil = false;
LightData *omni_lights = nullptr;
LightData *spot_lights = nullptr;
ShadowData *positional_shadows = nullptr;
InstanceSort<GLES3::LightInstance> *omni_light_sort;
InstanceSort<GLES3::LightInstance> *spot_light_sort;
GLuint omni_light_buffer = 0;
GLuint spot_light_buffer = 0;
GLuint positional_shadow_buffer = 0;
uint32_t omni_light_count = 0;
uint32_t spot_light_count = 0;
RSE::ShadowQuality positional_shadow_quality = RSE::ShadowQuality::SHADOW_QUALITY_SOFT_LOW;
DirectionalLightData *directional_lights = nullptr;
GLuint directional_light_buffer = 0;
DirectionalShadowData *directional_shadows = nullptr;
GLuint directional_shadow_buffer = 0;
RSE::ShadowQuality directional_shadow_quality = RSE::ShadowQuality::SHADOW_QUALITY_SOFT_LOW;
} scene_state;
struct RenderListParameters {
GeometryInstanceSurface **elements = nullptr;
int element_count = 0;
bool reverse_cull = false;
uint64_t spec_constant_base_flags = 0;
bool force_wireframe = false;
Vector2 uv_offset = Vector2(0, 0);
RenderListParameters(GeometryInstanceSurface **p_elements, int p_element_count, bool p_reverse_cull, uint64_t p_spec_constant_base_flags, bool p_force_wireframe = false, Vector2 p_uv_offset = Vector2()) {
elements = p_elements;
element_count = p_element_count;
reverse_cull = p_reverse_cull;
spec_constant_base_flags = p_spec_constant_base_flags;
force_wireframe = p_force_wireframe;
uv_offset = p_uv_offset;
}
};
struct RenderList {
LocalVector<GeometryInstanceSurface *> elements;
void clear() {
elements.clear();
}
//should eventually be replaced by radix
struct SortByKey {
_FORCE_INLINE_ bool operator()(const GeometryInstanceSurface *A, const GeometryInstanceSurface *B) const {
return (A->sort.sort_key2 == B->sort.sort_key2) ? (A->sort.sort_key1 < B->sort.sort_key1) : (A->sort.sort_key2 < B->sort.sort_key2);
}
};
void sort_by_key() {
SortArray<GeometryInstanceSurface *, SortByKey> sorter;
sorter.sort(elements.ptr(), elements.size());
}
void sort_by_key_range(uint32_t p_from, uint32_t p_size) {
SortArray<GeometryInstanceSurface *, SortByKey> sorter;
sorter.sort(elements.ptr() + p_from, p_size);
}
struct SortByDepth {
_FORCE_INLINE_ bool operator()(const GeometryInstanceSurface *A, const GeometryInstanceSurface *B) const {
return (A->owner->depth < B->owner->depth);
}
};
void sort_by_depth() { //used for shadows
SortArray<GeometryInstanceSurface *, SortByDepth> sorter;
sorter.sort(elements.ptr(), elements.size());
}
struct SortByReverseDepthAndPriority {
_FORCE_INLINE_ bool operator()(const GeometryInstanceSurface *A, const GeometryInstanceSurface *B) const {
return (A->sort.priority == B->sort.priority) ? (A->owner->depth > B->owner->depth) : (A->sort.priority < B->sort.priority);
}
};
void sort_by_reverse_depth_and_priority() { //used for alpha
SortArray<GeometryInstanceSurface *, SortByReverseDepthAndPriority> sorter;
sorter.sort(elements.ptr(), elements.size());
}
_FORCE_INLINE_ void add_element(GeometryInstanceSurface *p_element) {
elements.push_back(p_element);
}
};
RenderList render_list[RENDER_LIST_MAX];
void _update_scene_ubo(GLuint &p_ubo_buffer, GLuint p_index, uint32_t p_size, const void *p_source_data, String p_name = "");
void _setup_lights(const RenderDataGLES3 *p_render_data, bool p_using_shadows, uint32_t &r_directional_light_count, uint32_t &r_omni_light_count, uint32_t &r_spot_light_count, uint32_t &r_directional_shadow_count);
void _setup_environment(const RenderDataGLES3 *p_render_data, bool p_no_fog, const Size2i &p_screen_size, bool p_flip_y, const Color &p_default_bg_color, bool p_pancake_shadows, float p_shadow_bias = 0.0);
void _fill_render_list(RenderListType p_render_list, const RenderDataGLES3 *p_render_data, PassMode p_pass_mode, bool p_append = false);
void _render_shadows(const RenderDataGLES3 *p_render_data, const Size2i &p_viewport_size = Size2i(1, 1));
void _render_shadow_pass(RID p_light, RID p_shadow_atlas, int p_pass, const PagedArray<RenderGeometryInstance *> &p_instances, float p_lod_distance_multiplier = 0, float p_screen_mesh_lod_threshold = 0.0, RenderingServerTypes::RenderInfo *p_render_info = nullptr, const Size2i &p_viewport_size = Size2i(1, 1), const Transform3D &p_main_cam_transform = Transform3D());
void _render_post_processing(const RenderDataGLES3 *p_render_data);
template <PassMode p_pass_mode>
_FORCE_INLINE_ void _render_list_template(RenderListParameters *p_params, const RenderDataGLES3 *p_render_data, uint32_t p_from_element, uint32_t p_to_element, bool p_alpha_pass = false);
protected:
double time;
double time_step = 0;
bool screen_space_roughness_limiter = false;
float screen_space_roughness_limiter_amount = 0.25;
float screen_space_roughness_limiter_limit = 0.18;
void _render_buffers_debug_draw(Ref<RenderSceneBuffersGLES3> p_render_buffers, RID p_shadow_atlas, GLuint p_fbo);
/* Camera Attributes */
struct CameraAttributes {
float exposure_multiplier = 1.0;
float exposure_normalization = 1.0;
};
bool use_physical_light_units = false;
mutable RID_Owner<CameraAttributes, true> camera_attributes_owner;
/* Environment */
RSE::EnvironmentSSAOQuality ssao_quality = RSE::ENV_SSAO_QUALITY_MEDIUM;
bool ssao_half_size = false;
float ssao_adaptive_target = 0.5;
int ssao_blur_passes = 2;
float ssao_fadeout_from = 50.0;
float ssao_fadeout_to = 300.0;
bool glow_bicubic_upscale = false;
bool lightmap_bicubic_upscale = false;
/* Sky */
struct SkyGlobals {
float fog_aerial_perspective = 0.0;
Color fog_light_color;
float fog_sun_scatter = 0.0;
bool fog_enabled = false;
float fog_density = 0.0;
float z_far = 0.0;
uint32_t directional_light_count = 0;
DirectionalLightData *directional_lights = nullptr;
DirectionalLightData *last_frame_directional_lights = nullptr;
uint32_t last_frame_directional_light_count = 0;
GLuint directional_light_buffer = 0;
RID shader_default_version;
RID default_material;
RID default_shader;
RID fog_material;
RID fog_shader;
GLuint screen_triangle = 0;
GLuint screen_triangle_array = 0;
uint32_t max_directional_lights = 4;
uint32_t roughness_layers = 8;
} sky_globals;
struct Sky {
// Screen Buffers
GLuint half_res_pass = 0;
GLuint half_res_framebuffer = 0;
GLuint quarter_res_pass = 0;
GLuint quarter_res_framebuffer = 0;
Size2i screen_size = Size2i(0, 0);
// Radiance Cubemap
GLuint radiance = 0;
GLuint radiance_framebuffer = 0;
GLuint raw_radiance = 0;
RID material;
GLuint uniform_buffer;
int radiance_size = 256;
int mipmap_count = 1;
RSE::SkyMode mode = RSE::SKY_MODE_AUTOMATIC;
//ReflectionData reflection;
bool reflection_dirty = false;
bool dirty = false;
int processing_layer = 0;
Sky *dirty_list = nullptr;
float baked_exposure = 1.0;
//State to track when radiance cubemap needs updating
GLES3::SkyMaterialData *prev_material = nullptr;
Vector3 prev_position = Vector3(0.0, 0.0, 0.0);
float prev_time = 0.0f;
};
Sky *dirty_sky_list = nullptr;
mutable RID_Owner<Sky, true> sky_owner;
void _setup_sky(const RenderDataGLES3 *p_render_data, const PagedArray<RID> &p_lights, const Projection &p_projection, const Transform3D &p_transform, const Size2i p_screen_size);
void _invalidate_sky(Sky *p_sky);
void _update_dirty_skys();
void _update_sky_radiance(RID p_env, const Projection &p_projection, const Transform3D &p_transform, float p_sky_energy_multiplier);
void _draw_sky(RID p_env, const Projection &p_projection, const Transform3D &p_transform, float p_sky_energy_multiplier, float p_luminance_multiplier, bool p_use_multiview, bool p_flip_y, bool p_apply_color_adjustments_in_post);
void _free_sky_data(Sky *p_sky);
// Needed for a single argument calls (material and uv2).
PagedArrayPool<RenderGeometryInstance *> cull_argument_pool;
PagedArray<RenderGeometryInstance *> cull_argument;
public:
static RasterizerSceneGLES3 *get_singleton() { return singleton; }
RasterizerCanvasGLES3 *canvas = nullptr;
RenderGeometryInstance *geometry_instance_create(RID p_base) override;
void geometry_instance_free(RenderGeometryInstance *p_geometry_instance) override;
uint32_t geometry_instance_get_pair_mask() override;
/* PIPELINES */
virtual void mesh_generate_pipelines(RID p_mesh, bool p_background_compilation) override {}
virtual uint32_t get_pipeline_compilations(RSE::PipelineSource p_source) override { return 0; }
/* SDFGI UPDATE */
void sdfgi_update(const Ref<RenderSceneBuffers> &p_render_buffers, RID p_environment, const Vector3 &p_world_position) override {}
int sdfgi_get_pending_region_count(const Ref<RenderSceneBuffers> &p_render_buffers) const override {
return 0;
}
AABB sdfgi_get_pending_region_bounds(const Ref<RenderSceneBuffers> &p_render_buffers, int p_region) const override {
return AABB();
}
uint32_t sdfgi_get_pending_region_cascade(const Ref<RenderSceneBuffers> &p_render_buffers, int p_region) const override {
return 0;
}
/* SKY API */
RID sky_allocate() override;
void sky_initialize(RID p_rid) override;
void sky_set_radiance_size(RID p_sky, int p_radiance_size) override;
void sky_set_mode(RID p_sky, RSE::SkyMode p_mode) override;
void sky_set_material(RID p_sky, RID p_material) override;
Ref<Image> sky_bake_panorama(RID p_sky, float p_energy, bool p_bake_irradiance, const Size2i &p_size) override;
float sky_get_baked_exposure(RID p_sky) const;
/* ENVIRONMENT API */
void environment_glow_set_use_bicubic_upscale(bool p_enable) override;
void environment_set_ssr_half_size(bool p_half_size) override;
void environment_set_ssr_roughness_quality(RSE::EnvironmentSSRRoughnessQuality p_quality) override;
void environment_set_ssao_quality(RSE::EnvironmentSSAOQuality p_quality, bool p_half_size, float p_adaptive_target, int p_blur_passes, float p_fadeout_from, float p_fadeout_to) override;
void environment_set_ssil_quality(RSE::EnvironmentSSILQuality p_quality, bool p_half_size, float p_adaptive_target, int p_blur_passes, float p_fadeout_from, float p_fadeout_to) override;
void environment_set_sdfgi_ray_count(RSE::EnvironmentSDFGIRayCount p_ray_count) override;
void environment_set_sdfgi_frames_to_converge(RSE::EnvironmentSDFGIFramesToConverge p_frames) override;
void environment_set_sdfgi_frames_to_update_light(RSE::EnvironmentSDFGIFramesToUpdateLight p_update) override;
void environment_set_volumetric_fog_volume_size(int p_size, int p_depth) override;
void environment_set_volumetric_fog_filter_active(bool p_enable) override;
Ref<Image> environment_bake_panorama(RID p_env, bool p_bake_irradiance, const Size2i &p_size) override;
_FORCE_INLINE_ bool is_using_physical_light_units() {
return use_physical_light_units;
}
void positional_soft_shadow_filter_set_quality(RSE::ShadowQuality p_quality) override;
void directional_soft_shadow_filter_set_quality(RSE::ShadowQuality p_quality) override;
RID fog_volume_instance_create(RID p_fog_volume) override;
void fog_volume_instance_set_transform(RID p_fog_volume_instance, const Transform3D &p_transform) override;
void fog_volume_instance_set_active(RID p_fog_volume_instance, bool p_active) override;
RID fog_volume_instance_get_volume(RID p_fog_volume_instance) const override;
Vector3 fog_volume_instance_get_position(RID p_fog_volume_instance) const override;
RID voxel_gi_instance_create(RID p_voxel_gi) override;
void voxel_gi_instance_set_transform_to_data(RID p_probe, const Transform3D &p_xform) override;
bool voxel_gi_needs_update(RID p_probe) const override;
void voxel_gi_update(RID p_probe, bool p_update_light_instances, const Vector<RID> &p_light_instances, const PagedArray<RenderGeometryInstance *> &p_dynamic_objects) override;
void voxel_gi_set_quality(RSE::VoxelGIQuality) override;
void render_scene(const Ref<RenderSceneBuffers> &p_render_buffers, const CameraData *p_camera_data, const CameraData *p_prev_camera_data, const PagedArray<RenderGeometryInstance *> &p_instances, const PagedArray<RID> &p_lights, const PagedArray<RID> &p_reflection_probes, const PagedArray<RID> &p_voxel_gi_instances, const PagedArray<RID> &p_decals, const PagedArray<RID> &p_lightmaps, const PagedArray<RID> &p_fog_volumes, RID p_environment, RID p_camera_attributes, RID p_compositor, RID p_shadow_atlas, RID p_occluder_debug_tex, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass, float p_screen_mesh_lod_threshold, const RenderShadowData *p_render_shadows, int p_render_shadow_count, const RenderSDFGIData *p_render_sdfgi_regions, int p_render_sdfgi_region_count, float p_window_output_max_value, const RenderSDFGIUpdateData *p_sdfgi_update_data = nullptr, RenderingServerTypes::RenderInfo *r_render_info = nullptr) override;
void render_material(const Transform3D &p_cam_transform, const Projection &p_cam_projection, bool p_cam_orthogonal, const PagedArray<RenderGeometryInstance *> &p_instances, RID p_framebuffer, const Rect2i &p_region) override;
void render_particle_collider_heightfield(RID p_collider, const Transform3D &p_transform, const PagedArray<RenderGeometryInstance *> &p_instances) override;
void set_scene_pass(uint64_t p_pass) override {
scene_pass = p_pass;
}
_FORCE_INLINE_ uint64_t get_scene_pass() {
return scene_pass;
}
void set_time(double p_time, double p_step) override;
void set_debug_draw_mode(RSE::ViewportDebugDraw p_debug_draw) override;
_FORCE_INLINE_ RSE::ViewportDebugDraw get_debug_draw_mode() const {
return debug_draw;
}
Ref<RenderSceneBuffers> render_buffers_create() override;
void gi_set_use_half_resolution(bool p_enable) override;
void screen_space_roughness_limiter_set_active(bool p_enable, float p_amount, float p_curve) override;
bool screen_space_roughness_limiter_is_active() const override;
void sub_surface_scattering_set_quality(RSE::SubSurfaceScatteringQuality p_quality) override;
void sub_surface_scattering_set_scale(float p_scale, float p_depth_scale) override;
TypedArray<Image> bake_render_uv2(RID p_base, const TypedArray<RID> &p_material_overrides, const Size2i &p_image_size) override;
void _render_uv2(const PagedArray<RenderGeometryInstance *> &p_instances, GLuint p_framebuffer, const Rect2i &p_region);
bool free(RID p_rid) override;
void update() override;
void sdfgi_set_debug_probe_select(const Vector3 &p_position, const Vector3 &p_dir) override;
void decals_set_filter(RSE::DecalFilter p_filter) override;
void light_projectors_set_filter(RSE::LightProjectorFilter p_filter) override;
virtual void lightmaps_set_bicubic_filter(bool p_enable) override;
virtual void material_set_use_debanding(bool p_enable) override;
RasterizerSceneGLES3();
~RasterizerSceneGLES3();
};
#endif // GLES3_ENABLED

View file

@ -0,0 +1,52 @@
/**************************************************************************/
/* rasterizer_util_gles3.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "rasterizer_util_gles3.h"
#include "platform_gl.h"
bool RasterizerUtilGLES3::gles_over_gl = true;
void RasterizerUtilGLES3::clear_depth(float p_depth) {
#ifdef GL_API_ENABLED
if (RasterizerUtilGLES3::is_gles_over_gl()) {
glClearDepth(p_depth);
}
#endif // GL_API_ENABLED
#ifdef GLES_API_ENABLED
if (!RasterizerUtilGLES3::is_gles_over_gl()) {
glClearDepthf(p_depth);
}
#endif // GLES_API_ENABLED
}
void RasterizerUtilGLES3::clear_stencil(int32_t p_stencil) {
glClearStencil(p_stencil);
}

View file

@ -0,0 +1,51 @@
/**************************************************************************/
/* rasterizer_util_gles3.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include <cstdint>
// This class is meant to hold static utility methods with minimal dependencies.
class RasterizerUtilGLES3 {
private:
static bool gles_over_gl;
public:
static void set_gles_over_gl(bool p_gles_over_gl) {
gles_over_gl = p_gles_over_gl;
}
static bool is_gles_over_gl() {
return gles_over_gl;
}
static void clear_depth(float p_depth);
static void clear_stencil(int32_t p_stencil);
};

View file

@ -0,0 +1,841 @@
/**************************************************************************/
/* shader_gles3.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "shader_gles3.h"
#ifdef GLES3_ENABLED
#include "core/io/dir_access.h"
#include "core/io/file_access.h"
#include "core/string/string_builder.h"
#include "drivers/gles3/rasterizer_util_gles3.h"
#include "drivers/gles3/storage/config.h"
static String _mkid(const String &p_id) {
String id = "m_" + p_id.replace("__", "_dus_");
return id.replace("__", "_dus_"); //doubleunderscore is reserved in glsl
}
void ShaderGLES3::_add_stage(const char *p_code, StageType p_stage_type) {
Vector<String> lines = String::utf8(p_code).split("\n");
String text;
for (int i = 0; i < lines.size(); i++) {
const String &l = lines[i];
bool push_chunk = false;
StageTemplate::Chunk chunk;
if (l.begins_with("#GLOBALS")) {
switch (p_stage_type) {
case STAGE_TYPE_VERTEX:
chunk.type = StageTemplate::Chunk::TYPE_VERTEX_GLOBALS;
break;
case STAGE_TYPE_FRAGMENT:
chunk.type = StageTemplate::Chunk::TYPE_FRAGMENT_GLOBALS;
break;
default: {
}
}
push_chunk = true;
} else if (l.begins_with("#MATERIAL_UNIFORMS")) {
chunk.type = StageTemplate::Chunk::TYPE_MATERIAL_UNIFORMS;
push_chunk = true;
} else if (l.begins_with("#CODE")) {
chunk.type = StageTemplate::Chunk::TYPE_CODE;
push_chunk = true;
chunk.code = l.replace_first("#CODE", String()).remove_char(':').strip_edges().to_upper();
} else {
text += l + "\n";
}
if (push_chunk) {
if (text != String()) {
StageTemplate::Chunk text_chunk;
text_chunk.type = StageTemplate::Chunk::TYPE_TEXT;
text_chunk.text = text.utf8();
stage_templates[p_stage_type].chunks.push_back(text_chunk);
text = String();
}
stage_templates[p_stage_type].chunks.push_back(chunk);
}
if (text != String()) {
StageTemplate::Chunk text_chunk;
text_chunk.type = StageTemplate::Chunk::TYPE_TEXT;
text_chunk.text = text.utf8();
stage_templates[p_stage_type].chunks.push_back(text_chunk);
text = String();
}
}
}
void ShaderGLES3::_setup(const char *p_vertex_code, const char *p_fragment_code, const char *p_name, int p_uniform_count, const char **p_uniform_names, int p_ubo_count, const UBOPair *p_ubos, int p_feedback_count, const Feedback *p_feedback, int p_texture_count, const TexUnitPair *p_tex_units, int p_specialization_count, const Specialization *p_specializations, int p_variant_count, const char **p_variants) {
name = p_name;
if (p_vertex_code) {
_add_stage(p_vertex_code, STAGE_TYPE_VERTEX);
}
if (p_fragment_code) {
_add_stage(p_fragment_code, STAGE_TYPE_FRAGMENT);
}
uniform_names = p_uniform_names;
uniform_count = p_uniform_count;
ubo_pairs = p_ubos;
ubo_count = p_ubo_count;
texunit_pairs = p_tex_units;
texunit_pair_count = p_texture_count;
specializations = p_specializations;
specialization_count = p_specialization_count;
specialization_default_mask = 0;
for (int i = 0; i < specialization_count; i++) {
if (specializations[i].default_value) {
specialization_default_mask |= (uint64_t(1) << uint64_t(i));
}
}
variant_defines = p_variants;
variant_count = p_variant_count;
feedbacks = p_feedback;
feedback_count = p_feedback_count;
StringBuilder tohash;
tohash.append("[Vertex]");
tohash.append(p_vertex_code ? String::utf8(p_vertex_code) : "");
tohash.append("[Fragment]");
tohash.append(p_fragment_code ? String::utf8(p_fragment_code) : "");
tohash.append("[gl_implementation]");
const String &vendor = String::utf8((const char *)glGetString(GL_VENDOR));
tohash.append(vendor.is_empty() ? "unknown" : vendor);
const String &renderer = String::utf8((const char *)glGetString(GL_RENDERER));
tohash.append(renderer.is_empty() ? "unknown" : renderer);
const String &version = String::utf8((const char *)glGetString(GL_VERSION));
tohash.append(version.is_empty() ? "unknown" : version);
base_sha256 = tohash.as_string().sha256_text();
}
RID ShaderGLES3::version_create() {
//initialize() was never called
ERR_FAIL_COND_V(variant_count == 0, RID());
Version version;
return version_owner.make_rid(version);
}
void ShaderGLES3::_build_variant_code(StringBuilder &builder, uint32_t p_variant, const Version *p_version, StageType p_stage_type, uint64_t p_specialization) {
if (RasterizerUtilGLES3::is_gles_over_gl()) {
builder.append("#version 330\n");
builder.append("#define USE_GLES_OVER_GL\n");
} else {
builder.append("#version 300 es\n");
}
if (GLES3::Config::get_singleton()->polyfill_half2float) {
builder.append("#define USE_HALF2FLOAT\n");
}
for (int i = 0; i < specialization_count; i++) {
if (p_specialization & (uint64_t(1) << uint64_t(i))) {
builder.append("#define " + String(specializations[i].name) + "\n");
}
}
if (p_version->uniforms.size()) {
builder.append("#define MATERIAL_UNIFORMS_USED\n");
}
for (const KeyValue<StringName, CharString> &E : p_version->code_sections) {
builder.append(String("#define ") + String(E.key) + "_CODE_USED\n");
}
builder.append("\n"); //make sure defines begin at newline
builder.append(general_defines.get_data());
builder.append(variant_defines[p_variant]);
builder.append("\n");
for (int j = 0; j < p_version->custom_defines.size(); j++) {
builder.append(p_version->custom_defines[j].get_data());
}
builder.append("\n"); //make sure defines begin at newline
// Optional support for external textures.
if (GLES3::Config::get_singleton()->external_texture_supported) {
builder.append("#extension GL_OES_EGL_image_external : enable\n");
builder.append("#extension GL_OES_EGL_image_external_essl3 : enable\n");
} else {
builder.append("#define samplerExternalOES sampler2D\n");
}
// Insert multiview extension loading, because it needs to appear before
// any non-preprocessor code (like the "precision highp..." lines below).
builder.append("#ifdef USE_MULTIVIEW\n");
builder.append("#if defined(GL_OVR_multiview2)\n");
builder.append("#extension GL_OVR_multiview2 : require\n");
builder.append("#elif defined(GL_OVR_multiview)\n");
builder.append("#extension GL_OVR_multiview : require\n");
builder.append("#endif\n");
if (p_stage_type == StageType::STAGE_TYPE_VERTEX) {
builder.append("layout(num_views=2) in;\n");
}
builder.append("#define ViewIndex gl_ViewID_OVR\n");
builder.append("#define MAX_VIEWS 2\n");
builder.append("#else\n");
builder.append("#define ViewIndex uint(0)\n");
builder.append("#define MAX_VIEWS 1\n");
builder.append("#endif\n");
// Default to highp precision unless specified otherwise.
builder.append("precision highp float;\n");
builder.append("precision highp int;\n");
if (!RasterizerUtilGLES3::is_gles_over_gl()) {
builder.append("precision highp sampler2D;\n");
builder.append("precision highp samplerCube;\n");
builder.append("precision highp sampler2DArray;\n");
builder.append("precision highp sampler3D;\n");
}
const StageTemplate &stage_template = stage_templates[p_stage_type];
for (uint32_t i = 0; i < stage_template.chunks.size(); i++) {
const StageTemplate::Chunk &chunk = stage_template.chunks[i];
switch (chunk.type) {
case StageTemplate::Chunk::TYPE_MATERIAL_UNIFORMS: {
builder.append(String::utf8(p_version->uniforms.get_data())); //uniforms (same for vertex and fragment)
} break;
case StageTemplate::Chunk::TYPE_VERTEX_GLOBALS: {
builder.append(String::utf8(p_version->vertex_globals.get_data())); // vertex globals
} break;
case StageTemplate::Chunk::TYPE_FRAGMENT_GLOBALS: {
builder.append(String::utf8(p_version->fragment_globals.get_data())); // fragment globals
} break;
case StageTemplate::Chunk::TYPE_CODE: {
if (p_version->code_sections.has(chunk.code)) {
builder.append(String::utf8(p_version->code_sections[chunk.code].get_data()));
}
} break;
case StageTemplate::Chunk::TYPE_TEXT: {
builder.append(String::utf8(chunk.text.get_data()));
} break;
}
}
}
static void _display_error_with_code(const String &p_error, const String &p_code) {
int line = 1;
Vector<String> lines = p_code.split("\n");
for (int j = 0; j < lines.size(); j++) {
print_line(itos(line) + ": " + lines[j]);
line++;
}
ERR_PRINT(p_error);
}
void ShaderGLES3::_get_uniform_locations(Version::Specialization &spec, Version *p_version) {
glUseProgram(spec.id);
spec.uniform_location.resize(uniform_count);
for (int i = 0; i < uniform_count; i++) {
spec.uniform_location[i] = glGetUniformLocation(spec.id, uniform_names[i]);
}
for (int i = 0; i < texunit_pair_count; i++) {
GLint loc = glGetUniformLocation(spec.id, texunit_pairs[i].name);
if (loc >= 0) {
if (texunit_pairs[i].index < 0) {
glUniform1i(loc, max_image_units + texunit_pairs[i].index);
} else {
glUniform1i(loc, texunit_pairs[i].index);
}
}
}
for (int i = 0; i < ubo_count; i++) {
GLint loc = glGetUniformBlockIndex(spec.id, ubo_pairs[i].name);
if (loc >= 0) {
glUniformBlockBinding(spec.id, loc, ubo_pairs[i].index);
}
}
// textures
int texture_index = 0;
for (uint32_t i = 0; i < p_version->texture_uniforms.size(); i++) {
String native_uniform_name = _mkid(p_version->texture_uniforms[i].name);
GLint location = glGetUniformLocation(spec.id, (native_uniform_name).ascii().get_data());
Vector<int32_t> texture_uniform_bindings;
int texture_count = p_version->texture_uniforms[i].array_size;
for (int j = 0; j < texture_count; j++) {
texture_uniform_bindings.append(texture_index + base_texture_index);
texture_index++;
}
glUniform1iv(location, texture_uniform_bindings.size(), texture_uniform_bindings.ptr());
}
glUseProgram(0);
}
void ShaderGLES3::_compile_specialization(Version::Specialization &spec, uint32_t p_variant, Version *p_version, uint64_t p_specialization) {
spec.id = glCreateProgram();
spec.ok = false;
GLint status;
//vertex stage
{
StringBuilder builder;
_build_variant_code(builder, p_variant, p_version, STAGE_TYPE_VERTEX, p_specialization);
spec.vert_id = glCreateShader(GL_VERTEX_SHADER);
String builder_string = builder.as_string();
CharString cs = builder_string.utf8();
const char *cstr = cs.ptr();
GLint cstr_len = cs.length();
glShaderSource(spec.vert_id, 1, &cstr, &cstr_len);
glCompileShader(spec.vert_id);
glGetShaderiv(spec.vert_id, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE) {
GLsizei iloglen;
glGetShaderiv(spec.vert_id, GL_INFO_LOG_LENGTH, &iloglen);
if (iloglen < 0) {
glDeleteShader(spec.vert_id);
glDeleteProgram(spec.id);
spec.id = 0;
ERR_PRINT("No OpenGL vertex shader compiler log.");
} else {
if (iloglen == 0) {
iloglen = 4096; // buggy driver (Adreno 220+)
}
char *ilogmem = (char *)Memory::alloc_static_zeroed(iloglen + 1);
glGetShaderInfoLog(spec.vert_id, iloglen, &iloglen, ilogmem);
String err_string = name + ": Vertex shader compilation failed:\n";
err_string += ilogmem;
_display_error_with_code(err_string, builder_string);
Memory::free_static(ilogmem);
glDeleteShader(spec.vert_id);
glDeleteProgram(spec.id);
spec.id = 0;
}
ERR_FAIL();
}
}
//fragment stage
{
StringBuilder builder;
_build_variant_code(builder, p_variant, p_version, STAGE_TYPE_FRAGMENT, p_specialization);
spec.frag_id = glCreateShader(GL_FRAGMENT_SHADER);
String builder_string = builder.as_string();
CharString cs = builder_string.utf8();
const char *cstr = cs.ptr();
GLint cstr_len = cs.length();
glShaderSource(spec.frag_id, 1, &cstr, &cstr_len);
glCompileShader(spec.frag_id);
glGetShaderiv(spec.frag_id, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE) {
GLsizei iloglen;
glGetShaderiv(spec.frag_id, GL_INFO_LOG_LENGTH, &iloglen);
if (iloglen < 0) {
glDeleteShader(spec.frag_id);
glDeleteProgram(spec.id);
spec.id = 0;
ERR_PRINT("No OpenGL fragment shader compiler log.");
} else {
if (iloglen == 0) {
iloglen = 4096; // buggy driver (Adreno 220+)
}
char *ilogmem = (char *)Memory::alloc_static_zeroed(iloglen + 1);
glGetShaderInfoLog(spec.frag_id, iloglen, &iloglen, ilogmem);
String err_string = name + ": Fragment shader compilation failed:\n";
err_string += ilogmem;
_display_error_with_code(err_string, builder_string);
Memory::free_static(ilogmem);
glDeleteShader(spec.frag_id);
glDeleteProgram(spec.id);
spec.id = 0;
}
ERR_FAIL();
}
}
glAttachShader(spec.id, spec.frag_id);
glAttachShader(spec.id, spec.vert_id);
// If feedback exists, set it up.
if (feedback_count) {
Vector<const char *> feedback;
for (int i = 0; i < feedback_count; i++) {
if (feedbacks[i].specialization == 0 || (feedbacks[i].specialization & p_specialization)) {
// Specialization for this feedback is enabled
feedback.push_back(feedbacks[i].name);
}
}
if (feedback.size()) {
glTransformFeedbackVaryings(spec.id, feedback.size(), feedback.ptr(), GL_INTERLEAVED_ATTRIBS);
}
}
glLinkProgram(spec.id);
glGetProgramiv(spec.id, GL_LINK_STATUS, &status);
if (status == GL_FALSE) {
GLsizei iloglen;
glGetProgramiv(spec.id, GL_INFO_LOG_LENGTH, &iloglen);
if (iloglen < 0) {
glDeleteShader(spec.frag_id);
glDeleteShader(spec.vert_id);
glDeleteProgram(spec.id);
spec.id = 0;
ERR_PRINT("No OpenGL program link log. Something is wrong.");
ERR_FAIL();
}
if (iloglen == 0) {
iloglen = 4096; // buggy driver (Adreno 220+)
}
char *ilogmem = (char *)Memory::alloc_static(iloglen + 1);
ilogmem[iloglen] = '\0';
glGetProgramInfoLog(spec.id, iloglen, &iloglen, ilogmem);
String err_string = name + ": Program linking failed:\n";
err_string += ilogmem;
_display_error_with_code(err_string, String());
Memory::free_static(ilogmem);
glDeleteShader(spec.frag_id);
glDeleteShader(spec.vert_id);
glDeleteProgram(spec.id);
spec.id = 0;
ERR_FAIL();
}
_get_uniform_locations(spec, p_version);
spec.ok = true;
}
RenderingServerTypes::ShaderNativeSourceCode ShaderGLES3::version_get_native_source_code(RID p_version) {
Version *version = version_owner.get_or_null(p_version);
RenderingServerTypes::ShaderNativeSourceCode source_code;
ERR_FAIL_NULL_V(version, source_code);
source_code.versions.resize(variant_count);
for (int i = 0; i < source_code.versions.size(); i++) {
//vertex stage
{
StringBuilder builder;
_build_variant_code(builder, i, version, STAGE_TYPE_VERTEX, specialization_default_mask);
RenderingServerTypes::ShaderNativeSourceCode::Version::Stage stage;
stage.name = "vertex";
stage.code = builder.as_string();
source_code.versions.write[i].stages.push_back(stage);
}
//fragment stage
{
StringBuilder builder;
_build_variant_code(builder, i, version, STAGE_TYPE_FRAGMENT, specialization_default_mask);
RenderingServerTypes::ShaderNativeSourceCode::Version::Stage stage;
stage.name = "fragment";
stage.code = builder.as_string();
source_code.versions.write[i].stages.push_back(stage);
}
}
return source_code;
}
String ShaderGLES3::_version_get_sha1(Version *p_version) const {
StringBuilder hash_build;
hash_build.append("[uniforms]");
hash_build.append(p_version->uniforms.get_data());
hash_build.append("[vertex_globals]");
hash_build.append(p_version->vertex_globals.get_data());
hash_build.append("[fragment_globals]");
hash_build.append(p_version->fragment_globals.get_data());
Vector<StringName> code_sections;
for (const KeyValue<StringName, CharString> &E : p_version->code_sections) {
code_sections.push_back(E.key);
}
code_sections.sort_custom<StringName::AlphCompare>();
for (int i = 0; i < code_sections.size(); i++) {
hash_build.append(String("[code:") + String(code_sections[i]) + "]");
hash_build.append(p_version->code_sections[code_sections[i]].get_data());
}
for (int i = 0; i < p_version->custom_defines.size(); i++) {
hash_build.append("[custom_defines:" + itos(i) + "]");
hash_build.append(p_version->custom_defines[i].get_data());
}
if (RasterizerUtilGLES3::is_gles_over_gl()) {
hash_build.append("[gl]");
} else {
hash_build.append("[gles]");
}
return hash_build.as_string().sha1_text();
}
#ifndef WEB_ENABLED // not supported in webgl
static const char *shader_file_header = "GLSC";
static const uint32_t cache_file_version = 3;
#endif
bool ShaderGLES3::_load_from_cache(Version *p_version) {
#ifdef WEB_ENABLED // not supported in webgl
return false;
#else
#if !defined(ANDROID_ENABLED) && !defined(IOS_ENABLED)
if (RasterizerUtilGLES3::is_gles_over_gl() && (glProgramBinary == nullptr)) { // ARB_get_program_binary extension not available.
return false;
}
#endif
String sha1 = _version_get_sha1(p_version);
String path = shader_cache_dir.path_join(name).path_join(base_sha256).path_join(sha1) + ".cache";
Ref<FileAccess> f = FileAccess::open(path, FileAccess::READ);
if (f.is_null()) {
return false;
}
char header[5] = {};
f->get_buffer((uint8_t *)header, 4);
ERR_FAIL_COND_V(header != String(shader_file_header), false);
uint32_t file_version = f->get_32();
if (file_version != cache_file_version) {
return false; // wrong version
}
int cache_variant_count = static_cast<int>(f->get_32());
ERR_FAIL_COND_V_MSG(cache_variant_count != variant_count, false, "shader cache variant count mismatch, expected " + itos(variant_count) + " got " + itos(cache_variant_count)); //should not happen but check
LocalVector<AHashMap<uint64_t, Version::Specialization>> variants;
for (int i = 0; i < cache_variant_count; i++) {
uint32_t cache_specialization_count = f->get_32();
AHashMap<uint64_t, Version::Specialization> variant;
for (uint32_t j = 0; j < cache_specialization_count; j++) {
uint64_t specialization_key = f->get_64();
uint32_t variant_size = f->get_32();
if (variant_size == 0) {
continue;
}
uint32_t variant_format = f->get_32();
Vector<uint8_t> variant_bytes;
variant_bytes.resize(variant_size);
uint32_t br = f->get_buffer(variant_bytes.ptrw(), variant_size);
ERR_FAIL_COND_V(br != variant_size, false);
Version::Specialization specialization;
specialization.id = glCreateProgram();
if (feedback_count) {
Vector<const char *> feedback;
for (int feedback_index = 0; feedback_index < feedback_count; feedback_index++) {
if (feedbacks[feedback_index].specialization == 0 || (feedbacks[feedback_index].specialization & specialization_key)) {
// Specialization for this feedback is enabled.
feedback.push_back(feedbacks[feedback_index].name);
}
}
if (!feedback.is_empty()) {
glTransformFeedbackVaryings(specialization.id, feedback.size(), feedback.ptr(), GL_INTERLEAVED_ATTRIBS);
}
}
glProgramBinary(specialization.id, variant_format, variant_bytes.ptr(), variant_bytes.size());
GLint link_status = 0;
glGetProgramiv(specialization.id, GL_LINK_STATUS, &link_status);
if (link_status != GL_TRUE) {
WARN_PRINT_ONCE("Failed to load cached shader, recompiling.");
return false;
}
_get_uniform_locations(specialization, p_version);
specialization.ok = true;
variant.insert(specialization_key, specialization);
}
variants.push_back(std::move(variant));
}
p_version->variants = variants;
return true;
#endif // WEB_ENABLED
}
void ShaderGLES3::_save_to_cache(Version *p_version) {
#ifdef WEB_ENABLED // not supported in webgl
return;
#else
ERR_FAIL_COND(!shader_cache_dir_valid);
#if !defined(ANDROID_ENABLED) && !defined(IOS_ENABLED)
if (RasterizerUtilGLES3::is_gles_over_gl() && (glGetProgramBinary == nullptr)) { // ARB_get_program_binary extension not available.
return;
}
#endif
String sha1 = _version_get_sha1(p_version);
String path = shader_cache_dir.path_join(name).path_join(base_sha256).path_join(sha1) + ".cache";
Error error;
Ref<FileAccess> f = FileAccess::open(path, FileAccess::WRITE, &error);
ERR_FAIL_COND(f.is_null());
f->store_buffer((const uint8_t *)shader_file_header, 4);
f->store_32(cache_file_version);
f->store_32(variant_count);
for (int i = 0; i < variant_count; i++) {
int cache_specialization_count = p_version->variants[i].size();
f->store_32(cache_specialization_count);
for (KeyValue<uint64_t, ShaderGLES3::Version::Specialization> &kv : p_version->variants[i]) {
const uint64_t specialization_key = kv.key;
f->store_64(specialization_key);
const Version::Specialization *specialization = &kv.value;
GLint program_size = 0;
glGetProgramiv(specialization->id, GL_PROGRAM_BINARY_LENGTH, &program_size);
if (program_size == 0) {
f->store_32(0);
continue;
}
PackedByteArray compiled_program;
compiled_program.resize(program_size);
GLenum binary_format = 0;
glGetProgramBinary(specialization->id, program_size, nullptr, &binary_format, compiled_program.ptrw());
if (program_size != compiled_program.size()) {
f->store_32(0);
continue;
}
f->store_32(program_size);
f->store_32(binary_format);
f->store_buffer(compiled_program.ptr(), compiled_program.size());
}
}
#endif // WEB_ENABLED
}
void ShaderGLES3::_clear_version(Version *p_version) {
// Variants not compiled yet, just return
if (p_version->variants.is_empty()) {
return;
}
for (int i = 0; i < variant_count; i++) {
for (KeyValue<uint64_t, Version::Specialization> &kv : p_version->variants[i]) {
if (kv.value.id != 0) {
glDeleteShader(kv.value.vert_id);
glDeleteShader(kv.value.frag_id);
glDeleteProgram(kv.value.id);
}
}
}
p_version->variants.clear();
}
void ShaderGLES3::_initialize_version(Version *p_version) {
ERR_FAIL_COND(p_version->variants.size() > 0);
bool use_cache = shader_cache_dir_valid && !(feedback_count > 0 && GLES3::Config::get_singleton()->disable_transform_feedback_shader_cache);
if (use_cache && _load_from_cache(p_version)) {
return;
}
p_version->variants.reserve(variant_count);
for (int i = 0; i < variant_count; i++) {
p_version->variants.push_back(AHashMap<uint64_t, Version::Specialization>());
Version::Specialization spec;
_compile_specialization(spec, i, p_version, specialization_default_mask);
p_version->variants[i].insert(specialization_default_mask, spec);
}
if (use_cache) {
_save_to_cache(p_version);
}
}
void ShaderGLES3::version_set_code(RID p_version, const HashMap<String, String> &p_code, const String &p_uniforms, const String &p_vertex_globals, const String &p_fragment_globals, const Vector<String> &p_custom_defines, const LocalVector<ShaderGLES3::TextureUniformData> &p_texture_uniforms, bool p_initialize) {
Version *version = version_owner.get_or_null(p_version);
ERR_FAIL_NULL(version);
_clear_version(version); //clear if existing
version->vertex_globals = p_vertex_globals.utf8();
version->fragment_globals = p_fragment_globals.utf8();
version->uniforms = p_uniforms.utf8();
version->code_sections.clear();
version->texture_uniforms = p_texture_uniforms;
for (const KeyValue<String, String> &E : p_code) {
version->code_sections[StringName(E.key.to_upper())] = E.value.utf8();
}
version->custom_defines.clear();
for (int i = 0; i < p_custom_defines.size(); i++) {
version->custom_defines.push_back(p_custom_defines[i].utf8());
}
if (p_initialize) {
_initialize_version(version);
}
}
bool ShaderGLES3::version_is_valid(RID p_version) {
Version *version = version_owner.get_or_null(p_version);
return version != nullptr;
}
bool ShaderGLES3::version_free(RID p_version) {
if (version_owner.owns(p_version)) {
Version *version = version_owner.get_or_null(p_version);
_clear_version(version);
version_owner.free(p_version);
} else {
return false;
}
return true;
}
bool ShaderGLES3::shader_cache_cleanup_on_start = false;
ShaderGLES3::ShaderGLES3() {
}
void ShaderGLES3::initialize(const String &p_general_defines, int p_base_texture_index) {
general_defines = p_general_defines.utf8();
base_texture_index = p_base_texture_index;
_init();
if (shader_cache_dir != String()) {
StringBuilder hash_build;
hash_build.append("[base_hash]");
hash_build.append(base_sha256);
hash_build.append("[general_defines]");
hash_build.append(general_defines.get_data());
for (int i = 0; i < variant_count; i++) {
hash_build.append("[variant_defines:" + itos(i) + "]");
hash_build.append(variant_defines[i]);
}
base_sha256 = hash_build.as_string().sha256_text();
Ref<DirAccess> d = DirAccess::open(shader_cache_dir);
ERR_FAIL_COND(d.is_null());
if (d->change_dir(name) != OK) {
Error err = d->make_dir(name);
ERR_FAIL_COND(err != OK);
d->change_dir(name);
}
//erase other versions?
if (shader_cache_cleanup_on_start) {
}
//
if (d->change_dir(base_sha256) != OK) {
Error err = d->make_dir(base_sha256);
ERR_FAIL_COND(err != OK);
}
shader_cache_dir_valid = true;
print_verbose("Shader '" + name + "' SHA256: " + base_sha256);
}
GLES3::Config *config = GLES3::Config::get_singleton();
ERR_FAIL_NULL(config);
max_image_units = config->max_texture_image_units;
}
void ShaderGLES3::set_shader_cache_dir(const String &p_dir) {
shader_cache_dir = p_dir;
}
void ShaderGLES3::set_shader_cache_save_compressed(bool p_enable) {
shader_cache_save_compressed = p_enable;
}
void ShaderGLES3::set_shader_cache_save_compressed_zstd(bool p_enable) {
shader_cache_save_compressed_zstd = p_enable;
}
void ShaderGLES3::set_shader_cache_save_debug(bool p_enable) {
shader_cache_save_debug = p_enable;
}
String ShaderGLES3::shader_cache_dir;
bool ShaderGLES3::shader_cache_save_compressed = true;
bool ShaderGLES3::shader_cache_save_compressed_zstd = true;
bool ShaderGLES3::shader_cache_save_debug = true;
ShaderGLES3::~ShaderGLES3() {
LocalVector<RID> remaining = version_owner.get_owned_list();
if (remaining.size()) {
ERR_PRINT(itos(remaining.size()) + " shaders of type " + name + " were never freed");
for (RID &rid : remaining) {
version_free(rid);
}
}
}
#endif

View file

@ -0,0 +1,257 @@
/**************************************************************************/
/* shader_gles3.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifdef GLES3_ENABLED
#include "core/templates/a_hash_map.h"
#include "core/templates/hash_map.h"
#include "core/templates/local_vector.h"
#include "core/templates/rid_owner.h"
#include "servers/rendering/rendering_server_types.h"
#include "platform_gl.h"
class StringBuilder;
class ShaderGLES3 {
public:
struct TextureUniformData {
StringName name;
int array_size;
};
protected:
struct TexUnitPair {
const char *name;
int index;
};
struct UBOPair {
const char *name;
int index;
};
struct Specialization {
const char *name;
bool default_value = false;
};
struct Feedback {
const char *name;
uint64_t specialization;
};
private:
//versions
CharString general_defines;
// A version is a high-level construct which is a combination of built-in and user-defined shader code, Each user-created Shader makes one version
// Variants use #ifdefs to toggle behavior on and off to change behavior of the shader
// All variants are compiled each time a new version is created
// Specializations use #ifdefs to toggle behavior on and off for performance, on supporting hardware, they will compile a version with everything enabled, and then compile more copies to improve performance
// Use specializations to enable and disabled advanced features, use variants to toggle behavior when different data may be used (e.g. using a samplerArray vs a sampler, or doing a depth prepass vs a color pass)
struct Version {
LocalVector<TextureUniformData> texture_uniforms;
CharString uniforms;
CharString vertex_globals;
CharString fragment_globals;
HashMap<StringName, CharString> code_sections;
Vector<CharString> custom_defines;
struct Specialization {
GLuint id;
GLuint vert_id;
GLuint frag_id;
LocalVector<GLint> uniform_location;
LocalVector<GLint> texture_uniform_locations;
bool build_queued = false;
bool ok = false;
Specialization() {
id = 0;
vert_id = 0;
frag_id = 0;
}
};
LocalVector<AHashMap<uint64_t, Specialization>> variants;
};
void _get_uniform_locations(Version::Specialization &spec, Version *p_version);
void _compile_specialization(Version::Specialization &spec, uint32_t p_variant, Version *p_version, uint64_t p_specialization);
void _clear_version(Version *p_version);
void _initialize_version(Version *p_version);
RID_Owner<Version, true> version_owner;
struct StageTemplate {
struct Chunk {
enum Type {
TYPE_MATERIAL_UNIFORMS,
TYPE_VERTEX_GLOBALS,
TYPE_FRAGMENT_GLOBALS,
TYPE_CODE,
TYPE_TEXT
};
Type type;
StringName code;
CharString text;
};
LocalVector<Chunk> chunks;
};
String name;
String base_sha256;
static String shader_cache_dir;
static bool shader_cache_cleanup_on_start;
static bool shader_cache_save_compressed;
static bool shader_cache_save_compressed_zstd;
static bool shader_cache_save_debug;
bool shader_cache_dir_valid = false;
GLint max_image_units = 0;
enum StageType {
STAGE_TYPE_VERTEX,
STAGE_TYPE_FRAGMENT,
STAGE_TYPE_MAX,
};
StageTemplate stage_templates[STAGE_TYPE_MAX];
void _build_variant_code(StringBuilder &p_builder, uint32_t p_variant, const Version *p_version, StageType p_stage_type, uint64_t p_specialization);
void _add_stage(const char *p_code, StageType p_stage_type);
String _version_get_sha1(Version *p_version) const;
bool _load_from_cache(Version *p_version);
void _save_to_cache(Version *p_version);
const char **uniform_names = nullptr;
int uniform_count = 0;
const UBOPair *ubo_pairs = nullptr;
int ubo_count = 0;
const Feedback *feedbacks;
int feedback_count = 0;
const TexUnitPair *texunit_pairs = nullptr;
int texunit_pair_count = 0;
int specialization_count = 0;
const Specialization *specializations = nullptr;
uint64_t specialization_default_mask = 0;
const char **variant_defines = nullptr;
int variant_count = 0;
int base_texture_index = 0;
Version::Specialization *current_shader = nullptr;
protected:
ShaderGLES3();
void _setup(const char *p_vertex_code, const char *p_fragment_code, const char *p_name, int p_uniform_count, const char **p_uniform_names, int p_ubo_count, const UBOPair *p_ubos, int p_feedback_count, const Feedback *p_feedback, int p_texture_count, const TexUnitPair *p_tex_units, int p_specialization_count, const Specialization *p_specializations, int p_variant_count, const char **p_variants);
_FORCE_INLINE_ bool _version_bind_shader(RID p_version, int p_variant, uint64_t p_specialization) {
ERR_FAIL_INDEX_V(p_variant, variant_count, false);
Version *version = version_owner.get_or_null(p_version);
ERR_FAIL_NULL_V(version, false);
if (version->variants.is_empty()) {
_initialize_version(version); //may lack initialization
}
Version::Specialization *spec = version->variants[p_variant].getptr(p_specialization);
if (!spec) {
if (false) {
// Queue load this specialization and use defaults in the meantime (TODO)
spec = version->variants[p_variant].getptr(specialization_default_mask);
} else {
// Compile on the spot
Version::Specialization s;
_compile_specialization(s, p_variant, version, p_specialization);
version->variants[p_variant].insert(p_specialization, s);
spec = version->variants[p_variant].getptr(p_specialization);
if (shader_cache_dir_valid) {
_save_to_cache(version);
}
}
} else if (spec->build_queued) {
// Still queued, wait
spec = version->variants[p_variant].getptr(specialization_default_mask);
}
if (!spec || !spec->ok) {
WARN_PRINT_ONCE("shader failed to compile, unable to bind shader.");
return false;
}
glUseProgram(spec->id);
current_shader = spec;
return true;
}
_FORCE_INLINE_ int _version_get_uniform(int p_which, RID p_version, int p_variant, uint64_t p_specialization) {
ERR_FAIL_INDEX_V(p_which, uniform_count, -1);
Version *version = version_owner.get_or_null(p_version);
ERR_FAIL_NULL_V(version, -1);
ERR_FAIL_INDEX_V(p_variant, int(version->variants.size()), -1);
Version::Specialization *spec = version->variants[p_variant].getptr(p_specialization);
ERR_FAIL_NULL_V(spec, -1);
ERR_FAIL_INDEX_V(p_which, int(spec->uniform_location.size()), -1);
return spec->uniform_location[p_which];
}
virtual void _init() = 0;
public:
RID version_create();
void version_set_code(RID p_version, const HashMap<String, String> &p_code, const String &p_uniforms, const String &p_vertex_globals, const String &p_fragment_globals, const Vector<String> &p_custom_defines, const LocalVector<ShaderGLES3::TextureUniformData> &p_texture_uniforms, bool p_initialize = false);
bool version_is_valid(RID p_version);
bool version_free(RID p_version);
static void set_shader_cache_dir(const String &p_dir);
static void set_shader_cache_save_compressed(bool p_enable);
static void set_shader_cache_save_compressed_zstd(bool p_enable);
static void set_shader_cache_save_debug(bool p_enable);
RenderingServerTypes::ShaderNativeSourceCode version_get_native_source_code(RID p_version);
void initialize(const String &p_general_defines = "", int p_base_texture_index = 0);
virtual ~ShaderGLES3();
};
#endif // GLES3_ENABLED

Some files were not shown because too many files have changed in this diff Show more