initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
This commit is contained in:
74
drivers/SCsub
Normal file
74
drivers/SCsub
Normal 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"]:
|
||||
# TODO: Evaluate support for Android, iOS, 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])
|
||||
16
drivers/accesskit/SCsub
Normal file
16
drivers/accesskit/SCsub
Normal file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
# Driver source files
|
||||
env.add_source_files(env.drivers_sources, "accessibility_driver_accesskit.cpp")
|
||||
|
||||
if env["accesskit_sdk_path"] == "":
|
||||
if env["platform"] == "windows":
|
||||
env.add_source_files(env.drivers_sources, "dynwrappers/accesskit-dll_wrap.c")
|
||||
if env["platform"] == "macos":
|
||||
env.add_source_files(env.drivers_sources, "dynwrappers/accesskit-dylib_wrap.c")
|
||||
if env["platform"] == "linuxbsd":
|
||||
env.add_source_files(env.drivers_sources, "dynwrappers/accesskit-so_wrap.c")
|
||||
env.Prepend(CPPPATH=["#thirdparty/accesskit/include"])
|
||||
1671
drivers/accesskit/accessibility_driver_accesskit.cpp
Normal file
1671
drivers/accesskit/accessibility_driver_accesskit.cpp
Normal file
File diff suppressed because it is too large
Load Diff
197
drivers/accesskit/accessibility_driver_accesskit.h
Normal file
197
drivers/accesskit/accessibility_driver_accesskit.h
Normal file
@@ -0,0 +1,197 @@
|
||||
/**************************************************************************/
|
||||
/* accessibility_driver_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_server.h"
|
||||
|
||||
#ifdef ACCESSKIT_DYNAMIC
|
||||
#ifdef LINUXBSD_ENABLED
|
||||
#include "drivers/accesskit/dynwrappers/accesskit-so_wrap.h"
|
||||
#endif
|
||||
#ifdef MACOS_ENABLED
|
||||
#include "drivers/accesskit/dynwrappers/accesskit-dylib_wrap.h"
|
||||
#endif
|
||||
#ifdef WINDOWS_ENABLED
|
||||
#include "drivers/accesskit/dynwrappers/accesskit-dll_wrap.h"
|
||||
#endif
|
||||
#else
|
||||
#include <accesskit.h>
|
||||
#endif
|
||||
|
||||
class AccessibilityDriverAccessKit : public AccessibilityDriver {
|
||||
static AccessibilityDriverAccessKit *singleton;
|
||||
|
||||
struct AccessibilityElement {
|
||||
HashMap<accesskit_action, Callable> actions;
|
||||
|
||||
DisplayServer::WindowID window_id = DisplayServer::INVALID_WINDOW_ID;
|
||||
RID parent;
|
||||
LocalVector<RID> children;
|
||||
Vector3i run;
|
||||
Variant meta;
|
||||
String name;
|
||||
String name_extra_info;
|
||||
|
||||
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;
|
||||
HashSet<RID> update;
|
||||
};
|
||||
|
||||
RID focus;
|
||||
|
||||
HashMap<DisplayServer::WindowID, WindowData> windows;
|
||||
|
||||
HashMap<DisplayServer::AccessibilityRole, accesskit_role> role_map;
|
||||
HashMap<DisplayServer::AccessibilityAction, accesskit_action> action_map;
|
||||
|
||||
_FORCE_INLINE_ accesskit_role _accessibility_role(DisplayServer::AccessibilityRole p_role) const;
|
||||
_FORCE_INLINE_ accesskit_action _accessibility_action(DisplayServer::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:
|
||||
Error init() override;
|
||||
|
||||
bool window_create(DisplayServer::WindowID p_window_id, void *p_handle) override;
|
||||
void window_destroy(DisplayServer::WindowID p_window_id) override;
|
||||
|
||||
RID accessibility_create_element(DisplayServer::WindowID p_window_id, DisplayServer::AccessibilityRole p_role) override;
|
||||
RID accessibility_create_sub_element(const RID &p_parent_rid, DisplayServer::AccessibilityRole p_role, int p_insert_pos = -1) override;
|
||||
virtual RID accessibility_create_sub_text_edit_elements(const RID &p_parent_rid, const RID &p_shaped_text, float p_min_height, int p_insert_pos = -1) override;
|
||||
bool accessibility_has_element(const RID &p_id) const override;
|
||||
void accessibility_free_element(const RID &p_id) override;
|
||||
|
||||
void accessibility_element_set_meta(const RID &p_id, const Variant &p_meta) override;
|
||||
Variant accessibility_element_get_meta(const RID &p_id) const override;
|
||||
|
||||
void accessibility_update_if_active(const Callable &p_callable) override;
|
||||
|
||||
void accessibility_update_set_focus(const RID &p_id) override;
|
||||
RID accessibility_get_window_root(DisplayServer::WindowID p_window_id) const override;
|
||||
|
||||
void accessibility_set_window_rect(DisplayServer::WindowID p_window_id, const Rect2 &p_rect_out, const Rect2 &p_rect_in) override;
|
||||
void accessibility_set_window_focused(DisplayServer::WindowID p_window_id, bool p_focused) override;
|
||||
|
||||
void accessibility_update_set_role(const RID &p_id, DisplayServer::AccessibilityRole p_role) override;
|
||||
void accessibility_update_set_name(const RID &p_id, const String &p_name) override;
|
||||
void accessibility_update_set_extra_info(const RID &p_id, const String &p_name_extra_info) override;
|
||||
void accessibility_update_set_description(const RID &p_id, const String &p_description) override;
|
||||
void accessibility_update_set_value(const RID &p_id, const String &p_value) override;
|
||||
void accessibility_update_set_tooltip(const RID &p_id, const String &p_tooltip) override;
|
||||
void accessibility_update_set_bounds(const RID &p_id, const Rect2 &p_rect) override;
|
||||
void accessibility_update_set_transform(const RID &p_id, const Transform2D &p_transform) override;
|
||||
void accessibility_update_add_child(const RID &p_id, const RID &p_child_id) override;
|
||||
void accessibility_update_add_related_controls(const RID &p_id, const RID &p_related_id) override;
|
||||
void accessibility_update_add_related_details(const RID &p_id, const RID &p_related_id) override;
|
||||
void accessibility_update_add_related_described_by(const RID &p_id, const RID &p_related_id) override;
|
||||
void accessibility_update_add_related_flow_to(const RID &p_id, const RID &p_related_id) override;
|
||||
void accessibility_update_add_related_labeled_by(const RID &p_id, const RID &p_related_id) override;
|
||||
void accessibility_update_add_related_radio_group(const RID &p_id, const RID &p_related_id) override;
|
||||
void accessibility_update_set_active_descendant(const RID &p_id, const RID &p_other_id) override;
|
||||
void accessibility_update_set_next_on_line(const RID &p_id, const RID &p_other_id) override;
|
||||
void accessibility_update_set_previous_on_line(const RID &p_id, const RID &p_other_id) override;
|
||||
void accessibility_update_set_member_of(const RID &p_id, const RID &p_group_id) override;
|
||||
void accessibility_update_set_in_page_link_target(const RID &p_id, const RID &p_other_id) override;
|
||||
void accessibility_update_set_error_message(const RID &p_id, const RID &p_other_id) override;
|
||||
void accessibility_update_set_live(const RID &p_id, DisplayServer::AccessibilityLiveMode p_live) override;
|
||||
void accessibility_update_add_action(const RID &p_id, DisplayServer::AccessibilityAction p_action, const Callable &p_callable) override;
|
||||
void accessibility_update_add_custom_action(const RID &p_id, int p_action_id, const String &p_action_description) override;
|
||||
void accessibility_update_set_table_row_count(const RID &p_id, int p_count) override;
|
||||
void accessibility_update_set_table_column_count(const RID &p_id, int p_count) override;
|
||||
void accessibility_update_set_table_row_index(const RID &p_id, int p_index) override;
|
||||
void accessibility_update_set_table_column_index(const RID &p_id, int p_index) override;
|
||||
void accessibility_update_set_table_cell_position(const RID &p_id, int p_row_index, int p_column_index) override;
|
||||
void accessibility_update_set_table_cell_span(const RID &p_id, int p_row_span, int p_column_span) override;
|
||||
void accessibility_update_set_list_item_count(const RID &p_id, int p_size) override;
|
||||
void accessibility_update_set_list_item_index(const RID &p_id, int p_index) override;
|
||||
void accessibility_update_set_list_item_level(const RID &p_id, int p_level) override;
|
||||
void accessibility_update_set_list_item_selected(const RID &p_id, bool p_selected) override;
|
||||
void accessibility_update_set_list_item_expanded(const RID &p_id, bool p_expanded) override;
|
||||
void accessibility_update_set_popup_type(const RID &p_id, DisplayServer::AccessibilityPopupType p_popup) override;
|
||||
void accessibility_update_set_checked(const RID &p_id, bool p_checekd) override;
|
||||
void accessibility_update_set_num_value(const RID &p_id, double p_position) override;
|
||||
void accessibility_update_set_num_range(const RID &p_id, double p_min, double p_max) override;
|
||||
void accessibility_update_set_num_step(const RID &p_id, double p_step) override;
|
||||
void accessibility_update_set_num_jump(const RID &p_id, double p_jump) override;
|
||||
void accessibility_update_set_scroll_x(const RID &p_id, double p_position) override;
|
||||
void accessibility_update_set_scroll_x_range(const RID &p_id, double p_min, double p_max) override;
|
||||
void accessibility_update_set_scroll_y(const RID &p_id, double p_position) override;
|
||||
void accessibility_update_set_scroll_y_range(const RID &p_id, double p_min, double p_max) override;
|
||||
void accessibility_update_set_text_decorations(const RID &p_id, bool p_underline, bool p_strikethrough, bool p_overline) override;
|
||||
void accessibility_update_set_text_align(const RID &p_id, HorizontalAlignment p_align) override;
|
||||
void accessibility_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 accessibility_update_set_flag(const RID &p_id, DisplayServer::AccessibilityFlags p_flag, bool p_value) override;
|
||||
void accessibility_update_set_classname(const RID &p_id, const String &p_classname) override;
|
||||
void accessibility_update_set_placeholder(const RID &p_id, const String &p_placeholder) override;
|
||||
void accessibility_update_set_language(const RID &p_id, const String &p_language) override;
|
||||
void accessibility_update_set_text_orientation(const RID &p_id, bool p_vertical) override;
|
||||
void accessibility_update_set_list_orientation(const RID &p_id, bool p_vertical) override;
|
||||
void accessibility_update_set_shortcut(const RID &p_id, const String &p_shortcut) override;
|
||||
void accessibility_update_set_url(const RID &p_id, const String &p_url) override;
|
||||
void accessibility_update_set_role_description(const RID &p_id, const String &p_description) override;
|
||||
void accessibility_update_set_state_description(const RID &p_id, const String &p_description) override;
|
||||
void accessibility_update_set_color_value(const RID &p_id, const Color &p_color) override;
|
||||
void accessibility_update_set_background_color(const RID &p_id, const Color &p_color) override;
|
||||
void accessibility_update_set_foreground_color(const RID &p_id, const Color &p_color) override;
|
||||
|
||||
AccessibilityDriverAccessKit();
|
||||
~AccessibilityDriverAccessKit();
|
||||
};
|
||||
|
||||
#endif // ACCESSKIT_ENABLED
|
||||
4286
drivers/accesskit/dynwrappers/accesskit-dll_wrap.c
Normal file
4286
drivers/accesskit/dynwrappers/accesskit-dll_wrap.c
Normal file
File diff suppressed because it is too large
Load Diff
1562
drivers/accesskit/dynwrappers/accesskit-dll_wrap.h
Normal file
1562
drivers/accesskit/dynwrappers/accesskit-dll_wrap.h
Normal file
File diff suppressed because it is too large
Load Diff
4316
drivers/accesskit/dynwrappers/accesskit-dylib_wrap.c
Normal file
4316
drivers/accesskit/dynwrappers/accesskit-dylib_wrap.c
Normal file
File diff suppressed because it is too large
Load Diff
1582
drivers/accesskit/dynwrappers/accesskit-dylib_wrap.h
Normal file
1582
drivers/accesskit/dynwrappers/accesskit-dylib_wrap.h
Normal file
File diff suppressed because it is too large
Load Diff
4217
drivers/accesskit/dynwrappers/accesskit-so_wrap.c
Normal file
4217
drivers/accesskit/dynwrappers/accesskit-so_wrap.c
Normal file
File diff suppressed because it is too large
Load Diff
1546
drivers/accesskit/dynwrappers/accesskit-so_wrap.h
Normal file
1546
drivers/accesskit/dynwrappers/accesskit-so_wrap.h
Normal file
File diff suppressed because it is too large
Load Diff
10
drivers/alsa/SCsub
Normal file
10
drivers/alsa/SCsub
Normal 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")
|
||||
14004
drivers/alsa/asound-so_wrap.c
Normal file
14004
drivers/alsa/asound-so_wrap.c
Normal file
File diff suppressed because it is too large
Load Diff
5102
drivers/alsa/asound-so_wrap.h
Normal file
5102
drivers/alsa/asound-so_wrap.h
Normal file
File diff suppressed because it is too large
Load Diff
349
drivers/alsa/audio_driver_alsa.cpp
Normal file
349
drivers/alsa/audio_driver_alsa.cpp
Normal file
@@ -0,0 +1,349 @@
|
||||
/**************************************************************************/
|
||||
/* 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/project_settings.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 = 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
|
||||
96
drivers/alsa/audio_driver_alsa.h
Normal file
96
drivers/alsa/audio_driver_alsa.h
Normal 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_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
|
||||
7
drivers/alsamidi/SCsub
Normal file
7
drivers/alsamidi/SCsub
Normal 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")
|
||||
147
drivers/alsamidi/midi_driver_alsamidi.cpp
Normal file
147
drivers/alsamidi/midi_driver_alsamidi.cpp
Normal 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
|
||||
79
drivers/alsamidi/midi_driver_alsamidi.h
Normal file
79
drivers/alsamidi/midi_driver_alsamidi.h
Normal 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
|
||||
8
drivers/apple/SCsub
Normal file
8
drivers/apple/SCsub
Normal 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")
|
||||
56
drivers/apple/foundation_helpers.h
Normal file
56
drivers/apple/foundation_helpers.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/**************************************************************************/
|
||||
/* 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
|
||||
|
||||
#import <Foundation/NSString.h>
|
||||
|
||||
class String;
|
||||
template <typename T>
|
||||
class CharStringT;
|
||||
|
||||
using CharString = CharStringT<char>;
|
||||
|
||||
namespace conv {
|
||||
|
||||
/**
|
||||
* Converts a Godot String to an NSString without allocating an intermediate UTF-8 buffer.
|
||||
* */
|
||||
NSString *to_nsstring(const String &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);
|
||||
|
||||
} //namespace conv
|
||||
85
drivers/apple/foundation_helpers.mm
Normal file
85
drivers/apple/foundation_helpers.mm
Normal file
@@ -0,0 +1,85 @@
|
||||
/**************************************************************************/
|
||||
/* 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 <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(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
|
||||
80
drivers/apple/joypad_apple.h
Normal file
80
drivers/apple/joypad_apple.h
Normal file
@@ -0,0 +1,80 @@
|
||||
/**************************************************************************/
|
||||
/* joypad_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/input/input.h"
|
||||
#include "core/input/input_enums.h"
|
||||
|
||||
#define Key _QKey
|
||||
#import <GameController/GameController.h>
|
||||
#undef Key
|
||||
|
||||
@class GCController;
|
||||
class RumbleContext;
|
||||
|
||||
struct GameController {
|
||||
int joy_id;
|
||||
GCController *controller;
|
||||
RumbleContext *rumble_context API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) = nil;
|
||||
NSInteger ff_effect_timestamp = 0;
|
||||
bool force_feedback = false;
|
||||
bool double_nintendo_joycon_layout = false;
|
||||
bool single_nintendo_joycon_layout = false;
|
||||
|
||||
uint32_t axis_changed_mask = 0;
|
||||
static_assert(static_cast<uint32_t>(JoyAxis::MAX) < 32, "JoyAxis::MAX must be less than 32");
|
||||
double axis_value[(int)JoyAxis::MAX];
|
||||
|
||||
GameController(int p_joy_id, GCController *p_controller);
|
||||
~GameController();
|
||||
};
|
||||
|
||||
class JoypadApple {
|
||||
private:
|
||||
id<NSObject> connect_observer = nil;
|
||||
id<NSObject> disconnect_observer = nil;
|
||||
HashMap<int, GameController *> joypads;
|
||||
HashMap<GCController *, int> controller_to_joy_id;
|
||||
|
||||
GCControllerPlayerIndex get_free_player_index();
|
||||
|
||||
void add_joypad(GCController *p_controller);
|
||||
void remove_joypad(GCController *p_controller);
|
||||
|
||||
public:
|
||||
JoypadApple();
|
||||
~JoypadApple();
|
||||
|
||||
void joypad_vibration_start(GameController &p_joypad, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0));
|
||||
void joypad_vibration_stop(GameController &p_joypad, uint64_t p_timestamp) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0));
|
||||
|
||||
void process_joypads();
|
||||
};
|
||||
634
drivers/apple/joypad_apple.mm
Normal file
634
drivers/apple/joypad_apple.mm
Normal file
@@ -0,0 +1,634 @@
|
||||
/**************************************************************************/
|
||||
/* joypad_apple.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 "joypad_apple.h"
|
||||
|
||||
#import <CoreHaptics/CoreHaptics.h>
|
||||
#import <os/log.h>
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "main/main.h"
|
||||
|
||||
class API_AVAILABLE(macos(11), ios(14.0), tvos(14.0)) RumbleMotor {
|
||||
CHHapticEngine *engine;
|
||||
id<CHHapticPatternPlayer> player;
|
||||
bool is_started;
|
||||
|
||||
RumbleMotor(GCController *p_controller, GCHapticsLocality p_locality) {
|
||||
engine = [p_controller.haptics createEngineWithLocality:p_locality];
|
||||
engine.autoShutdownEnabled = YES;
|
||||
}
|
||||
|
||||
public:
|
||||
static RumbleMotor *create(GCController *p_controller, GCHapticsLocality p_locality) {
|
||||
if ([p_controller.haptics.supportedLocalities containsObject:p_locality]) {
|
||||
return memnew(RumbleMotor(p_controller, p_locality));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
_ALWAYS_INLINE_ bool has_active_player() {
|
||||
return player != nil;
|
||||
}
|
||||
|
||||
void execute_pattern(CHHapticPattern *p_pattern) {
|
||||
NSError *error;
|
||||
if (!is_started) {
|
||||
ERR_FAIL_COND_MSG(![engine startAndReturnError:&error], "Couldn't start controller haptic engine: " + String::utf8(error.localizedDescription.UTF8String));
|
||||
is_started = YES;
|
||||
}
|
||||
|
||||
player = [engine createPlayerWithPattern:p_pattern error:&error];
|
||||
ERR_FAIL_COND_MSG(error, "Couldn't create controller haptic pattern player: " + String::utf8(error.localizedDescription.UTF8String));
|
||||
ERR_FAIL_COND_MSG(![player startAtTime:CHHapticTimeImmediate error:&error], "Couldn't execute controller haptic pattern: " + String::utf8(error.localizedDescription.UTF8String));
|
||||
}
|
||||
|
||||
void stop() {
|
||||
id<CHHapticPatternPlayer> old_player = player;
|
||||
player = nil;
|
||||
|
||||
NSError *error;
|
||||
ERR_FAIL_COND_MSG(![old_player stopAtTime:CHHapticTimeImmediate error:&error], "Couldn't stop controller haptic pattern: " + String::utf8(error.localizedDescription.UTF8String));
|
||||
}
|
||||
};
|
||||
|
||||
class API_AVAILABLE(macos(11), ios(14.0), tvos(14.0)) RumbleContext {
|
||||
RumbleMotor *weak_motor;
|
||||
RumbleMotor *strong_motor;
|
||||
|
||||
public:
|
||||
RumbleContext(GCController *p_controller) {
|
||||
weak_motor = RumbleMotor::create(p_controller, GCHapticsLocalityRightHandle);
|
||||
strong_motor = RumbleMotor::create(p_controller, GCHapticsLocalityLeftHandle);
|
||||
}
|
||||
|
||||
~RumbleContext() {
|
||||
if (weak_motor) {
|
||||
memdelete(weak_motor);
|
||||
}
|
||||
if (strong_motor) {
|
||||
memdelete(strong_motor);
|
||||
}
|
||||
}
|
||||
|
||||
_ALWAYS_INLINE_ bool has_motors() {
|
||||
return weak_motor != nullptr && strong_motor != nullptr;
|
||||
}
|
||||
|
||||
_ALWAYS_INLINE_ bool has_active_players() {
|
||||
if (!has_motors()) {
|
||||
return false;
|
||||
}
|
||||
return (weak_motor && weak_motor->has_active_player()) || (strong_motor && strong_motor->has_active_player());
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (weak_motor) {
|
||||
weak_motor->stop();
|
||||
}
|
||||
if (strong_motor) {
|
||||
strong_motor->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void play_weak_pattern(CHHapticPattern *p_pattern) {
|
||||
if (weak_motor) {
|
||||
weak_motor->execute_pattern(p_pattern);
|
||||
}
|
||||
}
|
||||
|
||||
void play_strong_pattern(CHHapticPattern *p_pattern) {
|
||||
if (strong_motor) {
|
||||
strong_motor->execute_pattern(p_pattern);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
GameController::GameController(int p_joy_id, GCController *p_controller) :
|
||||
joy_id(p_joy_id), controller(p_controller) {
|
||||
force_feedback = NO;
|
||||
|
||||
for (int i = 0; i < (int)JoyAxis::MAX; i++) {
|
||||
axis_value[i] = 0.0;
|
||||
}
|
||||
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
|
||||
if (controller.haptics != nil) {
|
||||
// Create a rumble context for the controller.
|
||||
rumble_context = memnew(RumbleContext(p_controller));
|
||||
|
||||
// If the rumble motors aren't available, disable force feedback.
|
||||
force_feedback = rumble_context->has_motors();
|
||||
}
|
||||
}
|
||||
|
||||
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
|
||||
if ([controller.productCategory isEqualToString:@"Switch Pro Controller"] || [controller.productCategory isEqualToString:@"Nintendo Switch Joy-Con (L/R)"]) {
|
||||
double_nintendo_joycon_layout = true;
|
||||
}
|
||||
|
||||
if ([controller.productCategory isEqualToString:@"Nintendo Switch Joy-Con (L)"] || [controller.productCategory isEqualToString:@"Nintendo Switch Joy-Con (R)"]) {
|
||||
single_nintendo_joycon_layout = true;
|
||||
}
|
||||
}
|
||||
|
||||
int l_joy_id = joy_id;
|
||||
|
||||
auto BUTTON = [l_joy_id](JoyButton p_button) {
|
||||
return ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
||||
Input::get_singleton()->joy_button(l_joy_id, p_button, pressed);
|
||||
};
|
||||
};
|
||||
|
||||
auto JOYSTICK_LEFT = ^(GCControllerDirectionPad *dpad, float xValue, float yValue) {
|
||||
if (axis_value[(int)JoyAxis::LEFT_X] != xValue) {
|
||||
axis_changed_mask |= (1 << (int)JoyAxis::LEFT_X);
|
||||
axis_value[(int)JoyAxis::LEFT_X] = xValue;
|
||||
}
|
||||
if (axis_value[(int)JoyAxis::LEFT_Y] != -yValue) {
|
||||
axis_changed_mask |= (1 << (int)JoyAxis::LEFT_Y);
|
||||
axis_value[(int)JoyAxis::LEFT_Y] = -yValue;
|
||||
}
|
||||
};
|
||||
|
||||
auto JOYSTICK_RIGHT = ^(GCControllerDirectionPad *dpad, float xValue, float yValue) {
|
||||
if (axis_value[(int)JoyAxis::RIGHT_X] != xValue) {
|
||||
axis_changed_mask |= (1 << (int)JoyAxis::RIGHT_X);
|
||||
axis_value[(int)JoyAxis::RIGHT_X] = xValue;
|
||||
}
|
||||
if (axis_value[(int)JoyAxis::RIGHT_Y] != -yValue) {
|
||||
axis_changed_mask |= (1 << (int)JoyAxis::RIGHT_Y);
|
||||
axis_value[(int)JoyAxis::RIGHT_Y] = -yValue;
|
||||
}
|
||||
};
|
||||
|
||||
auto TRIGGER_LEFT = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
||||
if (axis_value[(int)JoyAxis::TRIGGER_LEFT] != value) {
|
||||
axis_changed_mask |= (1 << (int)JoyAxis::TRIGGER_LEFT);
|
||||
axis_value[(int)JoyAxis::TRIGGER_LEFT] = value;
|
||||
}
|
||||
};
|
||||
|
||||
auto TRIGGER_RIGHT = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
||||
if (axis_value[(int)JoyAxis::TRIGGER_RIGHT] != value) {
|
||||
axis_changed_mask |= (1 << (int)JoyAxis::TRIGGER_RIGHT);
|
||||
axis_value[(int)JoyAxis::TRIGGER_RIGHT] = value;
|
||||
}
|
||||
};
|
||||
|
||||
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
|
||||
if (controller.physicalInputProfile != nil) {
|
||||
GCPhysicalInputProfile *profile = controller.physicalInputProfile;
|
||||
|
||||
GCControllerButtonInput *buttonA = profile.buttons[GCInputButtonA];
|
||||
GCControllerButtonInput *buttonB = profile.buttons[GCInputButtonB];
|
||||
GCControllerButtonInput *buttonX = profile.buttons[GCInputButtonX];
|
||||
GCControllerButtonInput *buttonY = profile.buttons[GCInputButtonY];
|
||||
if (double_nintendo_joycon_layout) {
|
||||
if (buttonA) {
|
||||
buttonA.pressedChangedHandler = BUTTON(JoyButton::B);
|
||||
}
|
||||
if (buttonB) {
|
||||
buttonB.pressedChangedHandler = BUTTON(JoyButton::A);
|
||||
}
|
||||
if (buttonX) {
|
||||
buttonX.pressedChangedHandler = BUTTON(JoyButton::Y);
|
||||
}
|
||||
if (buttonY) {
|
||||
buttonY.pressedChangedHandler = BUTTON(JoyButton::X);
|
||||
}
|
||||
} else if (single_nintendo_joycon_layout) {
|
||||
if (buttonA) {
|
||||
buttonA.pressedChangedHandler = BUTTON(JoyButton::A);
|
||||
}
|
||||
if (buttonB) {
|
||||
buttonB.pressedChangedHandler = BUTTON(JoyButton::X);
|
||||
}
|
||||
if (buttonX) {
|
||||
buttonX.pressedChangedHandler = BUTTON(JoyButton::B);
|
||||
}
|
||||
if (buttonY) {
|
||||
buttonY.pressedChangedHandler = BUTTON(JoyButton::Y);
|
||||
}
|
||||
} else {
|
||||
if (buttonA) {
|
||||
buttonA.pressedChangedHandler = BUTTON(JoyButton::A);
|
||||
}
|
||||
if (buttonB) {
|
||||
buttonB.pressedChangedHandler = BUTTON(JoyButton::B);
|
||||
}
|
||||
if (buttonX) {
|
||||
buttonX.pressedChangedHandler = BUTTON(JoyButton::X);
|
||||
}
|
||||
if (buttonY) {
|
||||
buttonY.pressedChangedHandler = BUTTON(JoyButton::Y);
|
||||
}
|
||||
}
|
||||
|
||||
GCControllerButtonInput *leftThumbstickButton = profile.buttons[GCInputLeftThumbstickButton];
|
||||
GCControllerButtonInput *rightThumbstickButton = profile.buttons[GCInputRightThumbstickButton];
|
||||
if (leftThumbstickButton) {
|
||||
leftThumbstickButton.pressedChangedHandler = BUTTON(JoyButton::LEFT_STICK);
|
||||
}
|
||||
if (rightThumbstickButton) {
|
||||
rightThumbstickButton.pressedChangedHandler = BUTTON(JoyButton::RIGHT_STICK);
|
||||
}
|
||||
|
||||
GCControllerButtonInput *leftShoulder = profile.buttons[GCInputLeftShoulder];
|
||||
GCControllerButtonInput *rightShoulder = profile.buttons[GCInputRightShoulder];
|
||||
if (leftShoulder) {
|
||||
leftShoulder.pressedChangedHandler = BUTTON(JoyButton::LEFT_SHOULDER);
|
||||
}
|
||||
if (rightShoulder) {
|
||||
rightShoulder.pressedChangedHandler = BUTTON(JoyButton::RIGHT_SHOULDER);
|
||||
}
|
||||
|
||||
GCControllerButtonInput *leftTrigger = profile.buttons[GCInputLeftTrigger];
|
||||
GCControllerButtonInput *rightTrigger = profile.buttons[GCInputRightTrigger];
|
||||
if (leftTrigger) {
|
||||
leftTrigger.valueChangedHandler = TRIGGER_LEFT;
|
||||
}
|
||||
if (rightTrigger) {
|
||||
rightTrigger.valueChangedHandler = TRIGGER_RIGHT;
|
||||
}
|
||||
|
||||
GCControllerButtonInput *buttonMenu = profile.buttons[GCInputButtonMenu];
|
||||
GCControllerButtonInput *buttonHome = profile.buttons[GCInputButtonHome];
|
||||
GCControllerButtonInput *buttonOptions = profile.buttons[GCInputButtonOptions];
|
||||
if (buttonMenu) {
|
||||
buttonMenu.pressedChangedHandler = BUTTON(JoyButton::START);
|
||||
}
|
||||
if (buttonHome) {
|
||||
buttonHome.pressedChangedHandler = BUTTON(JoyButton::GUIDE);
|
||||
}
|
||||
if (buttonOptions) {
|
||||
buttonOptions.pressedChangedHandler = BUTTON(JoyButton::BACK);
|
||||
}
|
||||
|
||||
// Xbox controller buttons.
|
||||
if (@available(macOS 12.0, iOS 15.0, tvOS 15.0, *)) {
|
||||
GCControllerButtonInput *buttonShare = profile.buttons[GCInputButtonShare];
|
||||
if (buttonShare) {
|
||||
buttonShare.pressedChangedHandler = BUTTON(JoyButton::MISC1);
|
||||
}
|
||||
}
|
||||
|
||||
GCControllerButtonInput *paddleButton1 = profile.buttons[GCInputXboxPaddleOne];
|
||||
GCControllerButtonInput *paddleButton2 = profile.buttons[GCInputXboxPaddleTwo];
|
||||
GCControllerButtonInput *paddleButton3 = profile.buttons[GCInputXboxPaddleThree];
|
||||
GCControllerButtonInput *paddleButton4 = profile.buttons[GCInputXboxPaddleFour];
|
||||
if (paddleButton1) {
|
||||
paddleButton1.pressedChangedHandler = BUTTON(JoyButton::PADDLE1);
|
||||
}
|
||||
if (paddleButton2) {
|
||||
paddleButton2.pressedChangedHandler = BUTTON(JoyButton::PADDLE2);
|
||||
}
|
||||
if (paddleButton3) {
|
||||
paddleButton3.pressedChangedHandler = BUTTON(JoyButton::PADDLE3);
|
||||
}
|
||||
if (paddleButton4) {
|
||||
paddleButton4.pressedChangedHandler = BUTTON(JoyButton::PADDLE4);
|
||||
}
|
||||
|
||||
GCControllerDirectionPad *leftThumbstick = profile.dpads[GCInputLeftThumbstick];
|
||||
if (leftThumbstick) {
|
||||
leftThumbstick.valueChangedHandler = JOYSTICK_LEFT;
|
||||
}
|
||||
|
||||
GCControllerDirectionPad *rightThumbstick = profile.dpads[GCInputRightThumbstick];
|
||||
if (rightThumbstick) {
|
||||
rightThumbstick.valueChangedHandler = JOYSTICK_RIGHT;
|
||||
}
|
||||
|
||||
GCControllerDirectionPad *dpad = nil;
|
||||
if (controller.extendedGamepad != nil) {
|
||||
dpad = controller.extendedGamepad.dpad;
|
||||
} else if (controller.microGamepad != nil) {
|
||||
dpad = controller.microGamepad.dpad;
|
||||
}
|
||||
if (dpad) {
|
||||
dpad.up.pressedChangedHandler = BUTTON(JoyButton::DPAD_UP);
|
||||
dpad.down.pressedChangedHandler = BUTTON(JoyButton::DPAD_DOWN);
|
||||
dpad.left.pressedChangedHandler = BUTTON(JoyButton::DPAD_LEFT);
|
||||
dpad.right.pressedChangedHandler = BUTTON(JoyButton::DPAD_RIGHT);
|
||||
}
|
||||
}
|
||||
} else if (controller.extendedGamepad != nil) {
|
||||
GCExtendedGamepad *gamepad = controller.extendedGamepad;
|
||||
|
||||
if (double_nintendo_joycon_layout) {
|
||||
gamepad.buttonA.pressedChangedHandler = BUTTON(JoyButton::B);
|
||||
gamepad.buttonB.pressedChangedHandler = BUTTON(JoyButton::A);
|
||||
gamepad.buttonX.pressedChangedHandler = BUTTON(JoyButton::Y);
|
||||
gamepad.buttonY.pressedChangedHandler = BUTTON(JoyButton::X);
|
||||
} else if (single_nintendo_joycon_layout) {
|
||||
gamepad.buttonA.pressedChangedHandler = BUTTON(JoyButton::A);
|
||||
gamepad.buttonB.pressedChangedHandler = BUTTON(JoyButton::X);
|
||||
gamepad.buttonX.pressedChangedHandler = BUTTON(JoyButton::B);
|
||||
gamepad.buttonY.pressedChangedHandler = BUTTON(JoyButton::Y);
|
||||
} else {
|
||||
gamepad.buttonA.pressedChangedHandler = BUTTON(JoyButton::A);
|
||||
gamepad.buttonB.pressedChangedHandler = BUTTON(JoyButton::B);
|
||||
gamepad.buttonX.pressedChangedHandler = BUTTON(JoyButton::X);
|
||||
gamepad.buttonY.pressedChangedHandler = BUTTON(JoyButton::Y);
|
||||
}
|
||||
|
||||
gamepad.leftShoulder.pressedChangedHandler = BUTTON(JoyButton::LEFT_SHOULDER);
|
||||
gamepad.rightShoulder.pressedChangedHandler = BUTTON(JoyButton::RIGHT_SHOULDER);
|
||||
gamepad.dpad.up.pressedChangedHandler = BUTTON(JoyButton::DPAD_UP);
|
||||
gamepad.dpad.down.pressedChangedHandler = BUTTON(JoyButton::DPAD_DOWN);
|
||||
gamepad.dpad.left.pressedChangedHandler = BUTTON(JoyButton::DPAD_LEFT);
|
||||
gamepad.dpad.right.pressedChangedHandler = BUTTON(JoyButton::DPAD_RIGHT);
|
||||
|
||||
gamepad.leftThumbstick.valueChangedHandler = JOYSTICK_LEFT;
|
||||
gamepad.rightThumbstick.valueChangedHandler = JOYSTICK_RIGHT;
|
||||
gamepad.leftTrigger.valueChangedHandler = TRIGGER_LEFT;
|
||||
gamepad.rightTrigger.valueChangedHandler = TRIGGER_RIGHT;
|
||||
|
||||
if (@available(macOS 10.14.1, iOS 12.1, tvOS 12.1, *)) {
|
||||
gamepad.leftThumbstickButton.pressedChangedHandler = BUTTON(JoyButton::LEFT_STICK);
|
||||
gamepad.rightThumbstickButton.pressedChangedHandler = BUTTON(JoyButton::RIGHT_STICK);
|
||||
}
|
||||
|
||||
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
|
||||
gamepad.buttonOptions.pressedChangedHandler = BUTTON(JoyButton::BACK);
|
||||
gamepad.buttonMenu.pressedChangedHandler = BUTTON(JoyButton::START);
|
||||
}
|
||||
|
||||
if (@available(macOS 11, iOS 14.0, tvOS 14.0, *)) {
|
||||
gamepad.buttonHome.pressedChangedHandler = BUTTON(JoyButton::GUIDE);
|
||||
if ([gamepad isKindOfClass:[GCXboxGamepad class]]) {
|
||||
GCXboxGamepad *xboxGamepad = (GCXboxGamepad *)gamepad;
|
||||
xboxGamepad.paddleButton1.pressedChangedHandler = BUTTON(JoyButton::PADDLE1);
|
||||
xboxGamepad.paddleButton2.pressedChangedHandler = BUTTON(JoyButton::PADDLE2);
|
||||
xboxGamepad.paddleButton3.pressedChangedHandler = BUTTON(JoyButton::PADDLE3);
|
||||
xboxGamepad.paddleButton4.pressedChangedHandler = BUTTON(JoyButton::PADDLE4);
|
||||
}
|
||||
}
|
||||
|
||||
if (@available(macOS 12, iOS 15.0, tvOS 15.0, *)) {
|
||||
if ([gamepad isKindOfClass:[GCXboxGamepad class]]) {
|
||||
GCXboxGamepad *xboxGamepad = (GCXboxGamepad *)gamepad;
|
||||
xboxGamepad.buttonShare.pressedChangedHandler = BUTTON(JoyButton::MISC1);
|
||||
}
|
||||
}
|
||||
} else if (controller.microGamepad != nil) {
|
||||
GCMicroGamepad *gamepad = controller.microGamepad;
|
||||
|
||||
gamepad.buttonA.pressedChangedHandler = BUTTON(JoyButton::A);
|
||||
gamepad.buttonX.pressedChangedHandler = BUTTON(JoyButton::X);
|
||||
gamepad.dpad.up.pressedChangedHandler = BUTTON(JoyButton::DPAD_UP);
|
||||
gamepad.dpad.down.pressedChangedHandler = BUTTON(JoyButton::DPAD_DOWN);
|
||||
gamepad.dpad.left.pressedChangedHandler = BUTTON(JoyButton::DPAD_LEFT);
|
||||
gamepad.dpad.right.pressedChangedHandler = BUTTON(JoyButton::DPAD_RIGHT);
|
||||
}
|
||||
|
||||
// TODO: Need to add support for controller.motion which gives us access to
|
||||
// the orientation of the device (if supported).
|
||||
}
|
||||
|
||||
GameController::~GameController() {
|
||||
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
|
||||
if (rumble_context) {
|
||||
memdelete(rumble_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JoypadApple::JoypadApple() {
|
||||
connect_observer = [NSNotificationCenter.defaultCenter
|
||||
addObserverForName:GCControllerDidConnectNotification
|
||||
object:nil
|
||||
queue:NSOperationQueue.mainQueue
|
||||
usingBlock:^(NSNotification *notification) {
|
||||
GCController *controller = notification.object;
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
add_joypad(controller);
|
||||
}];
|
||||
|
||||
disconnect_observer = [NSNotificationCenter.defaultCenter
|
||||
addObserverForName:GCControllerDidDisconnectNotification
|
||||
object:nil
|
||||
queue:NSOperationQueue.mainQueue
|
||||
usingBlock:^(NSNotification *notification) {
|
||||
GCController *controller = notification.object;
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
remove_joypad(controller);
|
||||
}];
|
||||
|
||||
if (@available(macOS 11.3, iOS 14.5, tvOS 14.5, *)) {
|
||||
GCController.shouldMonitorBackgroundEvents = YES;
|
||||
}
|
||||
}
|
||||
|
||||
JoypadApple::~JoypadApple() {
|
||||
for (KeyValue<int, GameController *> &E : joypads) {
|
||||
memdelete(E.value);
|
||||
E.value = nullptr;
|
||||
}
|
||||
|
||||
[NSNotificationCenter.defaultCenter removeObserver:connect_observer];
|
||||
[NSNotificationCenter.defaultCenter removeObserver:disconnect_observer];
|
||||
}
|
||||
|
||||
// Finds the rightmost set bit in a number, n.
|
||||
// variation of https://www.geeksforgeeks.org/position-of-rightmost-set-bit/
|
||||
int rightmost_one(int n) {
|
||||
return __builtin_ctz(n & -n) + 1;
|
||||
}
|
||||
|
||||
GCControllerPlayerIndex JoypadApple::get_free_player_index() {
|
||||
// player_set will be a bitfield where each bit represents a player index.
|
||||
__block uint32_t player_set = 0;
|
||||
for (const KeyValue<GCController *, int> &E : controller_to_joy_id) {
|
||||
player_set |= 1U << E.key.playerIndex;
|
||||
}
|
||||
|
||||
// invert, as we want to find the first unset player index.
|
||||
int n = rightmost_one((int)(~player_set));
|
||||
if (n >= 5) {
|
||||
return GCControllerPlayerIndexUnset;
|
||||
}
|
||||
|
||||
return (GCControllerPlayerIndex)(n - 1);
|
||||
}
|
||||
|
||||
void JoypadApple::add_joypad(GCController *p_controller) {
|
||||
if (controller_to_joy_id.has(p_controller)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get a new id for our controller.
|
||||
int joy_id = Input::get_singleton()->get_unused_joy_id();
|
||||
|
||||
if (joy_id == -1) {
|
||||
print_verbose("Couldn't retrieve new joy ID.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Assign our player index.
|
||||
if (p_controller.playerIndex == GCControllerPlayerIndexUnset) {
|
||||
p_controller.playerIndex = get_free_player_index();
|
||||
}
|
||||
|
||||
// Tell Godot about our new controller.
|
||||
char const *device_name;
|
||||
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
|
||||
device_name = p_controller.productCategory.UTF8String;
|
||||
} else {
|
||||
device_name = p_controller.vendorName.UTF8String;
|
||||
}
|
||||
Input::get_singleton()->joy_connection_changed(joy_id, true, String::utf8(device_name));
|
||||
|
||||
// Assign our player index.
|
||||
joypads.insert(joy_id, memnew(GameController(joy_id, p_controller)));
|
||||
controller_to_joy_id.insert(p_controller, joy_id);
|
||||
}
|
||||
|
||||
void JoypadApple::remove_joypad(GCController *p_controller) {
|
||||
if (!controller_to_joy_id.has(p_controller)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int joy_id = controller_to_joy_id[p_controller];
|
||||
controller_to_joy_id.erase(p_controller);
|
||||
|
||||
// Tell Godot this joystick is no longer there.
|
||||
Input::get_singleton()->joy_connection_changed(joy_id, false, "");
|
||||
|
||||
// And remove it from our dictionary.
|
||||
GameController **old = joypads.getptr(joy_id);
|
||||
memdelete(*old);
|
||||
*old = nullptr;
|
||||
joypads.erase(joy_id);
|
||||
}
|
||||
|
||||
API_AVAILABLE(macos(10.15), ios(13.0), tvos(14.0))
|
||||
CHHapticPattern *get_vibration_pattern(float p_magnitude, float p_duration) {
|
||||
// Creates a vibration pattern with an intensity and duration.
|
||||
NSDictionary *hapticDict = @{
|
||||
CHHapticPatternKeyPattern : @[
|
||||
@{
|
||||
CHHapticPatternKeyEvent : @{
|
||||
CHHapticPatternKeyEventType : CHHapticEventTypeHapticContinuous,
|
||||
CHHapticPatternKeyTime : @(CHHapticTimeImmediate),
|
||||
CHHapticPatternKeyEventDuration : [NSNumber numberWithFloat:p_duration],
|
||||
|
||||
CHHapticPatternKeyEventParameters : @[
|
||||
@{
|
||||
CHHapticPatternKeyParameterID : CHHapticEventParameterIDHapticIntensity,
|
||||
CHHapticPatternKeyParameterValue : [NSNumber numberWithFloat:p_magnitude]
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
NSError *error;
|
||||
CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithDictionary:hapticDict error:&error];
|
||||
return pattern;
|
||||
}
|
||||
|
||||
void JoypadApple::joypad_vibration_start(GameController &p_joypad, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp) {
|
||||
if (!p_joypad.force_feedback || p_weak_magnitude < 0.f || p_weak_magnitude > 1.f || p_strong_magnitude < 0.f || p_strong_magnitude > 1.f) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is active vibration players, stop them.
|
||||
if (p_joypad.rumble_context->has_active_players()) {
|
||||
joypad_vibration_stop(p_joypad, p_timestamp);
|
||||
}
|
||||
|
||||
// Gets the default vibration pattern and creates a player for each motor.
|
||||
CHHapticPattern *weak_pattern = get_vibration_pattern(p_weak_magnitude, p_duration);
|
||||
CHHapticPattern *strong_pattern = get_vibration_pattern(p_strong_magnitude, p_duration);
|
||||
|
||||
p_joypad.rumble_context->play_weak_pattern(weak_pattern);
|
||||
p_joypad.rumble_context->play_strong_pattern(strong_pattern);
|
||||
|
||||
p_joypad.ff_effect_timestamp = p_timestamp;
|
||||
}
|
||||
|
||||
void JoypadApple::joypad_vibration_stop(GameController &p_joypad, uint64_t p_timestamp) {
|
||||
if (!p_joypad.force_feedback) {
|
||||
return;
|
||||
}
|
||||
// If there is no active vibration players, exit.
|
||||
if (!p_joypad.rumble_context->has_active_players()) {
|
||||
return;
|
||||
}
|
||||
|
||||
p_joypad.rumble_context->stop();
|
||||
|
||||
p_joypad.ff_effect_timestamp = p_timestamp;
|
||||
}
|
||||
|
||||
void JoypadApple::process_joypads() {
|
||||
Input *input = Input::get_singleton();
|
||||
|
||||
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
|
||||
for (KeyValue<int, GameController *> &E : joypads) {
|
||||
int id = E.key;
|
||||
GameController &joypad = *E.value;
|
||||
|
||||
uint32_t changed = joypad.axis_changed_mask;
|
||||
joypad.axis_changed_mask = 0;
|
||||
// Loop over changed axes.
|
||||
while (changed) {
|
||||
// Find the index of the next set bit.
|
||||
uint32_t i = (uint32_t)__builtin_ctzll(changed);
|
||||
// Clear the set bit.
|
||||
changed &= (changed - 1);
|
||||
input->joy_axis(id, (JoyAxis)i, joypad.axis_value[i]);
|
||||
}
|
||||
|
||||
if (joypad.force_feedback) {
|
||||
uint64_t timestamp = input->get_joy_vibration_timestamp(id);
|
||||
|
||||
if (timestamp > (unsigned)joypad.ff_effect_timestamp) {
|
||||
Vector2 strength = input->get_joy_vibration_strength(id);
|
||||
float duration = input->get_joy_vibration_duration(id);
|
||||
if (duration == 0) {
|
||||
duration = GCHapticDurationInfinite;
|
||||
}
|
||||
|
||||
if (strength.x == 0 && strength.y == 0) {
|
||||
joypad_vibration_stop(joypad, timestamp);
|
||||
} else {
|
||||
joypad_vibration_start(joypad, strength.x, strength.y, duration, timestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
125
drivers/apple/os_log_logger.cpp
Normal file
125
drivers/apple/os_log_logger.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
/**************************************************************************/
|
||||
/* 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/string/print_string.h"
|
||||
|
||||
#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());
|
||||
}
|
||||
}
|
||||
55
drivers/apple/os_log_logger.h
Normal file
55
drivers/apple/os_log_logger.h
Normal 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);
|
||||
};
|
||||
129
drivers/apple/thread_apple.cpp
Normal file
129
drivers/apple/thread_apple.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
/**************************************************************************/
|
||||
/* 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;
|
||||
}
|
||||
|
||||
if (p_settings.stack_size > 0) {
|
||||
pthread_attr_setstacksize(&attr, p_settings.stack_size);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
110
drivers/apple/thread_apple.h
Normal file
110
drivers/apple/thread_apple.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/**************************************************************************/
|
||||
/* 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;
|
||||
/// Override the default stack size (0 means default)
|
||||
uint64_t stack_size = 0;
|
||||
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();
|
||||
};
|
||||
16
drivers/apple_embedded/SCsub
Normal file
16
drivers/apple_embedded/SCsub
Normal file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
env_apple_embedded = env.Clone()
|
||||
|
||||
# Enable module support
|
||||
env_apple_embedded.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
|
||||
|
||||
# Use bundled Vulkan headers
|
||||
vulkan_dir = "#thirdparty/vulkan"
|
||||
env_apple_embedded.Prepend(CPPPATH=[vulkan_dir, vulkan_dir + "/include"])
|
||||
|
||||
# Driver source files
|
||||
env_apple_embedded.add_source_files(env.drivers_sources, "*.mm")
|
||||
42
drivers/apple_embedded/app_delegate_service.h
Normal file
42
drivers/apple_embedded/app_delegate_service.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/**************************************************************************/
|
||||
/* 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>
|
||||
|
||||
@property(strong, nonatomic) UIWindow *window;
|
||||
@property(strong, class, readonly, nonatomic) GDTViewController *viewController;
|
||||
|
||||
@end
|
||||
189
drivers/apple_embedded/app_delegate_service.mm
Normal file
189
drivers/apple_embedded/app_delegate_service.mm
Normal file
@@ -0,0 +1,189 @@
|
||||
/**************************************************************************/
|
||||
/* 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 "os_apple_embedded.h"
|
||||
#import "view_controller.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#import "drivers/coreaudio/audio_driver_coreaudio.h"
|
||||
#include "main/main.h"
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <AudioToolbox/AudioServices.h>
|
||||
|
||||
#define kRenderingFrequency 60
|
||||
|
||||
extern int gargc;
|
||||
extern 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;
|
||||
}
|
||||
|
||||
- (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
|
||||
|
||||
#if !defined(VISIONOS_ENABLED)
|
||||
// Create a full-screen window
|
||||
CGRect windowBounds = [[UIScreen mainScreen] bounds];
|
||||
self.window = [[UIWindow alloc] initWithFrame:windowBounds];
|
||||
#else
|
||||
self.window = [[UIWindow alloc] init];
|
||||
#endif
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
GDTViewController *viewController = [[GDTViewController alloc] init];
|
||||
viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
|
||||
viewController.godotView.renderingInterval = 1.0 / kRenderingFrequency;
|
||||
|
||||
self.window.rootViewController = viewController;
|
||||
|
||||
// Show the window
|
||||
[self.window makeKeyAndVisible];
|
||||
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserver:self
|
||||
selector:@selector(onAudioInterruption:)
|
||||
name:AVAudioSessionInterruptionNotification
|
||||
object:[AVAudioSession sharedInstance]];
|
||||
|
||||
mainViewController = viewController;
|
||||
|
||||
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)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
|
||||
59
drivers/apple_embedded/apple_embedded.h
Normal file
59
drivers/apple_embedded/apple_embedded.h
Normal 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/class_db.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();
|
||||
};
|
||||
200
drivers/apple_embedded/apple_embedded.mm
Normal file
200
drivers/apple_embedded/apple_embedded.mm
Normal file
@@ -0,0 +1,200 @@
|
||||
/**************************************************************************/
|
||||
/* 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 "view_controller.h"
|
||||
|
||||
#import <CoreHaptics/CoreHaptics.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#include <sys/sysctl.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() {}
|
||||
42
drivers/apple_embedded/display_layer_apple_embedded.h
Normal file
42
drivers/apple_embedded/display_layer_apple_embedded.h
Normal 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
|
||||
233
drivers/apple_embedded/display_server_apple_embedded.h
Normal file
233
drivers/apple_embedded/display_server_apple_embedded.h
Normal file
@@ -0,0 +1,233 @@
|
||||
/**************************************************************************/
|
||||
/* 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 "core/input/input.h"
|
||||
#include "servers/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 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;
|
||||
|
||||
DisplayServer::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;
|
||||
|
||||
protected:
|
||||
DisplayServerAppleEmbedded(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, 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, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = 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(DisplayServer::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(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, int 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 = SCREEN_OF_MAIN_WINDOW) const override;
|
||||
virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
|
||||
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
|
||||
|
||||
virtual Vector<DisplayServer::WindowID> get_window_list() const override;
|
||||
|
||||
virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override;
|
||||
|
||||
virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
|
||||
virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
|
||||
virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
|
||||
virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
|
||||
virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
virtual Point2i window_get_position_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
|
||||
virtual void window_set_transient(WindowID p_window, WindowID p_parent) override;
|
||||
|
||||
virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
|
||||
virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
|
||||
virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
virtual Size2i window_get_size_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
|
||||
virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
|
||||
virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
|
||||
virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
|
||||
virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual bool window_is_focused(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
|
||||
virtual float screen_get_max_scale() const override;
|
||||
|
||||
virtual void screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) override;
|
||||
virtual DisplayServer::ScreenOrientation screen_get_orientation(int p_screen) const override;
|
||||
|
||||
virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override;
|
||||
|
||||
virtual bool can_any_window_draw() const override;
|
||||
|
||||
virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override;
|
||||
virtual DisplayServer::VSyncMode window_get_vsync_mode(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, 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;
|
||||
|
||||
void resize_window(CGSize size);
|
||||
virtual void swap_buffers() override {}
|
||||
};
|
||||
820
drivers/apple_embedded/display_server_apple_embedded.mm
Normal file
820
drivers/apple_embedded/display_server_apple_embedded.mm
Normal file
@@ -0,0 +1,820 @@
|
||||
/**************************************************************************/
|
||||
/* 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_view_apple_embedded.h"
|
||||
#import "key_mapping_apple_embedded.h"
|
||||
#import "keyboard_input_view.h"
|
||||
#import "os_apple_embedded.h"
|
||||
#import "tts_apple_embedded.h"
|
||||
#import "view_controller.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/file_access_pack.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, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, 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 = (CAMetalLayer *)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::get_singleton()->set_current_rendering_driver_name(rendering_driver);
|
||||
} 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(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(MAIN_WINDOW_ID, size.width, size.height);
|
||||
rendering_context->window_set_vsync_mode(MAIN_WINDOW_ID, p_vsync_mode);
|
||||
|
||||
rendering_device = memnew(RenderingDevice);
|
||||
if (rendering_device->initialize(rendering_context, MAIN_WINDOW_ID) != OK) {
|
||||
rendering_device = nullptr;
|
||||
memdelete(rendering_context);
|
||||
rendering_context = nullptr;
|
||||
r_error = ERR_UNAVAILABLE;
|
||||
return;
|
||||
}
|
||||
rendering_device->screen_create(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(MAIN_WINDOW_ID);
|
||||
memdelete(rendering_device);
|
||||
rendering_device = nullptr;
|
||||
}
|
||||
|
||||
if (rendering_context) {
|
||||
rendering_context->window_destroy(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, WindowID p_window) {
|
||||
window_resize_callback = p_callable;
|
||||
}
|
||||
|
||||
void DisplayServerAppleEmbedded::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {
|
||||
window_event_callback = p_callable;
|
||||
}
|
||||
void DisplayServerAppleEmbedded::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {
|
||||
input_event_callback = p_callable;
|
||||
}
|
||||
|
||||
void DisplayServerAppleEmbedded::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {
|
||||
input_text_callback = p_callable;
|
||||
}
|
||||
|
||||
void DisplayServerAppleEmbedded::window_set_drop_files_callback(const Callable &p_callable, 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(DisplayServer::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(Feature p_feature) const {
|
||||
switch (p_feature) {
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
case FEATURE_GLOBAL_MENU: {
|
||||
return (native_menu && native_menu->has_feature(NativeMenu::FEATURE_GLOBAL_MENU));
|
||||
} break;
|
||||
#endif
|
||||
// case FEATURE_CURSOR_SHAPE:
|
||||
// case FEATURE_CUSTOM_CURSOR_SHAPE:
|
||||
// case FEATURE_HIDPI:
|
||||
// case FEATURE_ICON:
|
||||
// case FEATURE_IME:
|
||||
// case FEATURE_MOUSE:
|
||||
// case FEATURE_MOUSE_WARP:
|
||||
// case FEATURE_NATIVE_DIALOG:
|
||||
// case FEATURE_NATIVE_DIALOG_INPUT:
|
||||
// case FEATURE_NATIVE_DIALOG_FILE:
|
||||
// case FEATURE_NATIVE_DIALOG_FILE_EXTRA:
|
||||
// case FEATURE_NATIVE_DIALOG_FILE_MIME:
|
||||
// case FEATURE_NATIVE_ICON:
|
||||
// case FEATURE_WINDOW_TRANSPARENCY:
|
||||
case FEATURE_CLIPBOARD:
|
||||
case FEATURE_KEEP_SCREEN_ON:
|
||||
case FEATURE_ORIENTATION:
|
||||
case FEATURE_TOUCHSCREEN:
|
||||
case FEATURE_VIRTUAL_KEYBOARD:
|
||||
case 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, int 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<DisplayServer::WindowID> DisplayServerAppleEmbedded::get_window_list() const {
|
||||
Vector<DisplayServer::WindowID> list;
|
||||
list.push_back(MAIN_WINDOW_ID);
|
||||
return list;
|
||||
}
|
||||
|
||||
DisplayServer::WindowID DisplayServerAppleEmbedded::get_window_at_screen_position(const Point2i &p_position) const {
|
||||
return MAIN_WINDOW_ID;
|
||||
}
|
||||
|
||||
int64_t DisplayServerAppleEmbedded::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const {
|
||||
ERR_FAIL_COND_V(p_window != MAIN_WINDOW_ID, 0);
|
||||
switch (p_handle_type) {
|
||||
case DISPLAY_HANDLE: {
|
||||
return 0; // Not supported.
|
||||
}
|
||||
case WINDOW_HANDLE: {
|
||||
return (int64_t)GDTAppDelegateService.viewController;
|
||||
}
|
||||
case WINDOW_VIEW: {
|
||||
return (int64_t)GDTAppDelegateService.viewController.godotView;
|
||||
}
|
||||
default: {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayServerAppleEmbedded::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
|
||||
window_attached_instance_id = p_instance;
|
||||
}
|
||||
|
||||
ObjectID DisplayServerAppleEmbedded::window_get_attached_instance_id(WindowID p_window) const {
|
||||
return window_attached_instance_id;
|
||||
}
|
||||
|
||||
void DisplayServerAppleEmbedded::window_set_title(const String &p_title, WindowID p_window) {
|
||||
// Probably not supported for iOS
|
||||
}
|
||||
|
||||
int DisplayServerAppleEmbedded::window_get_current_screen(WindowID p_window) const {
|
||||
ERR_FAIL_COND_V(p_window != MAIN_WINDOW_ID, INVALID_SCREEN);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void DisplayServerAppleEmbedded::window_set_current_screen(int p_screen, WindowID p_window) {
|
||||
// Probably not supported for iOS
|
||||
}
|
||||
|
||||
Point2i DisplayServerAppleEmbedded::window_get_position(WindowID p_window) const {
|
||||
return Point2i();
|
||||
}
|
||||
|
||||
Point2i DisplayServerAppleEmbedded::window_get_position_with_decorations(WindowID p_window) const {
|
||||
return Point2i();
|
||||
}
|
||||
|
||||
void DisplayServerAppleEmbedded::window_set_position(const Point2i &p_position, WindowID p_window) {
|
||||
// Probably not supported for single window iOS app
|
||||
}
|
||||
|
||||
void DisplayServerAppleEmbedded::window_set_transient(WindowID p_window, WindowID p_parent) {
|
||||
// Probably not supported for iOS
|
||||
}
|
||||
|
||||
void DisplayServerAppleEmbedded::window_set_max_size(const Size2i p_size, WindowID p_window) {
|
||||
// Probably not supported for iOS
|
||||
}
|
||||
|
||||
Size2i DisplayServerAppleEmbedded::window_get_max_size(WindowID p_window) const {
|
||||
return Size2i();
|
||||
}
|
||||
|
||||
void DisplayServerAppleEmbedded::window_set_min_size(const Size2i p_size, WindowID p_window) {
|
||||
// Probably not supported for iOS
|
||||
}
|
||||
|
||||
Size2i DisplayServerAppleEmbedded::window_get_min_size(WindowID p_window) const {
|
||||
return Size2i();
|
||||
}
|
||||
|
||||
void DisplayServerAppleEmbedded::window_set_size(const Size2i p_size, WindowID p_window) {
|
||||
// Probably not supported for iOS
|
||||
}
|
||||
|
||||
Size2i DisplayServerAppleEmbedded::window_get_size(WindowID p_window) const {
|
||||
id<UIApplicationDelegate> appDelegate = [[UIApplication sharedApplication] delegate];
|
||||
CGRect windowBounds = appDelegate.window.bounds;
|
||||
return Size2i(windowBounds.size.width, windowBounds.size.height) * screen_get_max_scale();
|
||||
}
|
||||
|
||||
Size2i DisplayServerAppleEmbedded::window_get_size_with_decorations(WindowID p_window) const {
|
||||
return window_get_size(p_window);
|
||||
}
|
||||
|
||||
void DisplayServerAppleEmbedded::window_set_mode(WindowMode p_mode, WindowID p_window) {
|
||||
// Probably not supported for iOS
|
||||
}
|
||||
|
||||
DisplayServer::WindowMode DisplayServerAppleEmbedded::window_get_mode(WindowID p_window) const {
|
||||
return WindowMode::WINDOW_MODE_FULLSCREEN;
|
||||
}
|
||||
|
||||
bool DisplayServerAppleEmbedded::window_is_maximize_allowed(WindowID p_window) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void DisplayServerAppleEmbedded::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
|
||||
// Probably not supported for iOS
|
||||
}
|
||||
|
||||
bool DisplayServerAppleEmbedded::window_get_flag(WindowFlags p_flag, WindowID p_window) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void DisplayServerAppleEmbedded::window_request_attention(WindowID p_window) {
|
||||
// Probably not supported for iOS
|
||||
}
|
||||
|
||||
void DisplayServerAppleEmbedded::window_move_to_foreground(WindowID p_window) {
|
||||
// Probably not supported for iOS
|
||||
}
|
||||
|
||||
bool DisplayServerAppleEmbedded::window_is_focused(WindowID p_window) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
float DisplayServerAppleEmbedded::screen_get_max_scale() const {
|
||||
return screen_get_scale(SCREEN_OF_MAIN_WINDOW);
|
||||
}
|
||||
|
||||
void DisplayServerAppleEmbedded::screen_set_orientation(DisplayServer::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
|
||||
}
|
||||
|
||||
DisplayServer::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, SCREEN_LANDSCAPE);
|
||||
|
||||
return screen_orientation;
|
||||
}
|
||||
|
||||
bool DisplayServerAppleEmbedded::window_can_draw(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, 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 KEYBOARD_TYPE_DEFAULT: {
|
||||
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault;
|
||||
} break;
|
||||
case KEYBOARD_TYPE_MULTILINE: {
|
||||
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault;
|
||||
} break;
|
||||
case KEYBOARD_TYPE_NUMBER: {
|
||||
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeNumberPad;
|
||||
} break;
|
||||
case KEYBOARD_TYPE_NUMBER_DECIMAL: {
|
||||
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDecimalPad;
|
||||
} break;
|
||||
case KEYBOARD_TYPE_PHONE: {
|
||||
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypePhonePad;
|
||||
GDTAppDelegateService.viewController.keyboardView.textContentType = UITextContentTypeTelephoneNumber;
|
||||
} break;
|
||||
case KEYBOARD_TYPE_EMAIL_ADDRESS: {
|
||||
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeEmailAddress;
|
||||
GDTAppDelegateService.viewController.keyboardView.textContentType = UITextContentTypeEmailAddress;
|
||||
} break;
|
||||
case KEYBOARD_TYPE_PASSWORD: {
|
||||
GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault;
|
||||
GDTAppDelegateService.viewController.keyboardView.textContentType = UITextContentTypePassword;
|
||||
} break;
|
||||
case 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(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(DisplayServer::VSyncMode p_vsync_mode, 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
|
||||
}
|
||||
|
||||
DisplayServer::VSyncMode DisplayServerAppleEmbedded::window_get_vsync_mode(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 DisplayServer::VSYNC_ENABLED;
|
||||
}
|
||||
43
drivers/apple_embedded/godot_app_delegate.h
Normal file
43
drivers/apple_embedded/godot_app_delegate.h
Normal 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> GDTAppDelegateServiceProtocol;
|
||||
|
||||
@interface GDTApplicationDelegate : NSObject <UIApplicationDelegate>
|
||||
|
||||
@property(class, readonly, strong) NSArray<GDTAppDelegateServiceProtocol *> *services;
|
||||
|
||||
+ (void)addService:(GDTAppDelegateServiceProtocol *)service;
|
||||
|
||||
@end
|
||||
463
drivers/apple_embedded/godot_app_delegate.mm
Normal file
463
drivers/apple_embedded/godot_app_delegate.mm
Normal file
@@ -0,0 +1,463 @@
|
||||
/**************************************************************************/
|
||||
/* 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"
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
/* Can be handled by Info.plist. Not yet supported by Godot.
|
||||
|
||||
// MARK: Scene
|
||||
|
||||
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {}
|
||||
|
||||
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions {}
|
||||
|
||||
*/
|
||||
|
||||
// MARK: Life-Cycle
|
||||
|
||||
- (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
|
||||
72
drivers/apple_embedded/godot_view_apple_embedded.h
Normal file
72
drivers/apple_embedded/godot_view_apple_embedded.h
Normal 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) NSTimeInterval renderingInterval;
|
||||
|
||||
// Can be extended by subclasses
|
||||
- (void)godot_commonInit;
|
||||
|
||||
// Implemented in subclasses
|
||||
- (CALayer<GDTDisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName;
|
||||
|
||||
- (void)stopRendering;
|
||||
- (void)startRendering;
|
||||
|
||||
@end
|
||||
|
||||
// Implemented in subclasses
|
||||
extern GDTView *GDTViewCreate();
|
||||
470
drivers/apple_embedded/godot_view_apple_embedded.mm
Normal file
470
drivers/apple_embedded/godot_view_apple_embedded.mm
Normal file
@@ -0,0 +1,470 @@
|
||||
/**************************************************************************/
|
||||
/* 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];
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
@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 {
|
||||
#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)];
|
||||
|
||||
if (GLOBAL_GET("display/window/ios/allow_high_refresh_rate")) {
|
||||
self.displayLink.preferredFramesPerSecond = 120;
|
||||
} else {
|
||||
self.displayLink.preferredFramesPerSecond = 60;
|
||||
}
|
||||
|
||||
// Setup DisplayLink in main thread
|
||||
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
|
||||
} else {
|
||||
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60) 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) {
|
||||
BOOL delegateFinishedSetup = [self.delegate godotViewFinishedSetup:self];
|
||||
|
||||
if (!delegateFinishedSetup) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[self handleMotion];
|
||||
[self.renderer renderOnView:self];
|
||||
|
||||
[self.renderingLayer stopRenderDisplayLayer];
|
||||
}
|
||||
|
||||
- (BOOL)canRender {
|
||||
if (self.useCADisplayLink) {
|
||||
return self.displayLink != nil;
|
||||
} else {
|
||||
return self.animationTimer != nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setRenderingInterval:(NSTimeInterval)renderingInterval {
|
||||
_renderingInterval = renderingInterval;
|
||||
|
||||
if (self.canRender) {
|
||||
[self stopRendering];
|
||||
[self startRendering];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
if (self.renderingLayer) {
|
||||
self.renderingLayer.frame = self.bounds;
|
||||
[self.renderingLayer layoutDisplayLayer];
|
||||
|
||||
if (DisplayServerAppleEmbedded::get_singleton()) {
|
||||
DisplayServerAppleEmbedded::get_singleton()->resize_window(self.bounds.size);
|
||||
}
|
||||
}
|
||||
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
///@TODO Using [[UIApplication sharedApplication] statusBarOrientation]
|
||||
/// is a bit of a hack. Godot obviously knows the orientation so maybe
|
||||
/// we
|
||||
// can use that instead? (note that left and right seem swapped)
|
||||
|
||||
UIInterfaceOrientation interfaceOrientation = UIInterfaceOrientationUnknown;
|
||||
|
||||
#if !defined(VISIONOS_ENABLED)
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED < 140000
|
||||
interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
|
||||
#else
|
||||
if (@available(iOS 13, *)) {
|
||||
interfaceOrientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
|
||||
#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
|
||||
} else {
|
||||
interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
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
|
||||
46
drivers/apple_embedded/godot_view_renderer.h
Normal file
46
drivers/apple_embedded/godot_view_renderer.h
Normal 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
|
||||
119
drivers/apple_embedded/godot_view_renderer.mm
Normal file
119
drivers/apple_embedded/godot_view_renderer.mm
Normal file
@@ -0,0 +1,119 @@
|
||||
/**************************************************************************/
|
||||
/* 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 "main/main.h"
|
||||
#include "servers/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
|
||||
44
drivers/apple_embedded/key_mapping_apple_embedded.h
Normal file
44
drivers/apple_embedded/key_mapping_apple_embedded.h
Normal 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);
|
||||
};
|
||||
206
drivers/apple_embedded/key_mapping_apple_embedded.mm
Normal file
206
drivers/apple_embedded/key_mapping_apple_embedded.mm
Normal 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;
|
||||
}
|
||||
39
drivers/apple_embedded/keyboard_input_view.h
Normal file
39
drivers/apple_embedded/keyboard_input_view.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/**************************************************************************/
|
||||
/* 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
|
||||
201
drivers/apple_embedded/keyboard_input_view.mm
Normal file
201
drivers/apple_embedded/keyboard_input_view.mm
Normal file
@@ -0,0 +1,201 @@
|
||||
/**************************************************************************/
|
||||
/* 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 "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
|
||||
35
drivers/apple_embedded/main_utilities.h
Normal file
35
drivers/apple_embedded/main_utilities.h
Normal 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);
|
||||
95
drivers/apple_embedded/main_utilities.mm
Normal file
95
drivers/apple_embedded/main_utilities.mm
Normal file
@@ -0,0 +1,95 @@
|
||||
/**************************************************************************/
|
||||
/* 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_path(int p_argc, char **p_args) {
|
||||
NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"];
|
||||
if (!str) {
|
||||
return p_argc;
|
||||
}
|
||||
|
||||
p_args[p_argc++] = (char *)"--path";
|
||||
p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
p_args[p_argc] = nullptr;
|
||||
|
||||
return p_argc;
|
||||
}
|
||||
|
||||
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_path(p_argc, r_args);
|
||||
p_argc = add_cmdline(p_argc, r_args);
|
||||
return p_argc;
|
||||
}
|
||||
145
drivers/apple_embedded/os_apple_embedded.h
Normal file
145
drivers/apple_embedded/os_apple_embedded.h
Normal file
@@ -0,0 +1,145 @@
|
||||
/**************************************************************************/
|
||||
/* 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/apple/joypad_apple.h"
|
||||
#import "drivers/coreaudio/audio_driver_coreaudio.h"
|
||||
#include "drivers/unix/os_unix.h"
|
||||
#include "servers/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 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;
|
||||
|
||||
JoypadApple *joypad_apple = nullptr;
|
||||
|
||||
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_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;
|
||||
};
|
||||
|
||||
#endif // APPLE_EMBEDDED_ENABLED
|
||||
768
drivers/apple_embedded/os_apple_embedded.mm
Normal file
768
drivers/apple_embedded/os_apple_embedded.mm
Normal file
@@ -0,0 +1,768 @@
|
||||
/**************************************************************************/
|
||||
/* 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 "view_controller.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/io/file_access.h"
|
||||
#import "drivers/apple/os_log_logger.h"
|
||||
#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>
|
||||
|
||||
#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() {
|
||||
joypad_apple = memnew(JoypadApple);
|
||||
}
|
||||
|
||||
void OS_AppleEmbedded::initialize_modules() {
|
||||
apple_embedded = memnew(AppleEmbedded);
|
||||
Engine::get_singleton()->add_singleton(Engine::Singleton("AppleEmbedded", apple_embedded));
|
||||
}
|
||||
|
||||
void OS_AppleEmbedded::deinitialize_modules() {
|
||||
if (joypad_apple) {
|
||||
memdelete(joypad_apple);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (DisplayServer::get_singleton()) {
|
||||
DisplayServer::get_singleton()->process_events();
|
||||
}
|
||||
|
||||
joypad_apple->process_joypads();
|
||||
|
||||
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 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"));
|
||||
}
|
||||
|
||||
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_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]);
|
||||
}
|
||||
|
||||
String OS_AppleEmbedded::get_processor_name() const {
|
||||
char buffer[256];
|
||||
size_t buffer_len = 256;
|
||||
if (sysctlbyname("machdep.cpu.brand_string", &buffer, &buffer_len, nullptr, 0) == 0) {
|
||||
return String::utf8(buffer, buffer_len);
|
||||
}
|
||||
ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string."));
|
||||
}
|
||||
|
||||
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(DisplayServer::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(DisplayServer::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
|
||||
44
drivers/apple_embedded/platform_config.h
Normal file
44
drivers/apple_embedded/platform_config.h
Normal 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")
|
||||
@@ -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
|
||||
@@ -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
|
||||
60
drivers/apple_embedded/tts_apple_embedded.h
Normal file
60
drivers/apple_embedded/tts_apple_embedded.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/**************************************************************************/
|
||||
/* 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"
|
||||
#include "servers/display_server.h"
|
||||
|
||||
#if __has_include(<AVFAudio/AVSpeechSynthesis.h>)
|
||||
#import <AVFAudio/AVSpeechSynthesis.h>
|
||||
#else
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#endif
|
||||
|
||||
@interface GDTTTS : NSObject <AVSpeechSynthesizerDelegate> {
|
||||
bool speaking;
|
||||
HashMap<id, int> ids;
|
||||
|
||||
AVSpeechSynthesizer *av_synth;
|
||||
List<DisplayServer::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:(int)utterance_id interrupt:(bool)interrupt;
|
||||
- (Array)getVoices;
|
||||
@end
|
||||
164
drivers/apple_embedded/tts_apple_embedded.mm
Normal file
164
drivers/apple_embedded/tts_apple_embedded.mm
Normal file
@@ -0,0 +1,164 @@
|
||||
/**************************************************************************/
|
||||
/* 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"
|
||||
|
||||
@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(DisplayServer::TTS_UTTERANCE_BOUNDARY, ids[utterance], pos);
|
||||
}
|
||||
|
||||
- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth didCancelSpeechUtterance:(AVSpeechUtterance *)utterance {
|
||||
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::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(DisplayServer::TTS_UTTERANCE_ENDED, ids[utterance]);
|
||||
ids.erase(utterance);
|
||||
speaking = false;
|
||||
[self update];
|
||||
}
|
||||
|
||||
- (void)update {
|
||||
if (!speaking && queue.size() > 0) {
|
||||
DisplayServer::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];
|
||||
|
||||
queue.pop_front();
|
||||
|
||||
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_STARTED, message.id);
|
||||
speaking = true;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)pauseSpeaking {
|
||||
[av_synth pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate];
|
||||
}
|
||||
|
||||
- (void)resumeSpeaking {
|
||||
[av_synth continueSpeaking];
|
||||
}
|
||||
|
||||
- (void)stopSpeaking {
|
||||
for (DisplayServer::TTSUtterance &message : queue) {
|
||||
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::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:(int)utterance_id interrupt:(bool)interrupt {
|
||||
if (interrupt) {
|
||||
[self stopSpeaking];
|
||||
}
|
||||
|
||||
if (text.is_empty()) {
|
||||
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, utterance_id);
|
||||
return;
|
||||
}
|
||||
|
||||
DisplayServer::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
|
||||
43
drivers/apple_embedded/view_controller.h
Normal file
43
drivers/apple_embedded/view_controller.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/**************************************************************************/
|
||||
/* 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
|
||||
321
drivers/apple_embedded/view_controller.mm
Normal file
321
drivers/apple_embedded/view_controller.mm
Normal file
@@ -0,0 +1,321 @@
|
||||
/**************************************************************************/
|
||||
/* 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 "view_controller.h"
|
||||
|
||||
#import "display_server_apple_embedded.h"
|
||||
#import "godot_view_apple_embedded.h"
|
||||
#import "godot_view_renderer.h"
|
||||
#import "key_mapping_apple_embedded.h"
|
||||
#import "keyboard_input_view.h"
|
||||
#import "os_apple_embedded.h"
|
||||
|
||||
#include "core/config/project_settings.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)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
|
||||
|
||||
- (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(DisplayServer::SCREEN_OF_MAIN_WINDOW)) {
|
||||
case DisplayServer::SCREEN_SENSOR:
|
||||
case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
|
||||
case DisplayServer::SCREEN_SENSOR_PORTRAIT:
|
||||
return YES;
|
||||
default:
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
|
||||
if (!DisplayServerAppleEmbedded::get_singleton()) {
|
||||
return UIInterfaceOrientationMaskAll;
|
||||
}
|
||||
|
||||
switch (DisplayServerAppleEmbedded::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) {
|
||||
case DisplayServer::SCREEN_PORTRAIT:
|
||||
return UIInterfaceOrientationMaskPortrait;
|
||||
case DisplayServer::SCREEN_REVERSE_LANDSCAPE:
|
||||
if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) {
|
||||
return UIInterfaceOrientationMaskLandscapeLeft;
|
||||
} else {
|
||||
return UIInterfaceOrientationMaskLandscapeRight;
|
||||
}
|
||||
case DisplayServer::SCREEN_REVERSE_PORTRAIT:
|
||||
return UIInterfaceOrientationMaskPortraitUpsideDown;
|
||||
case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
|
||||
return UIInterfaceOrientationMaskLandscape;
|
||||
case DisplayServer::SCREEN_SENSOR_PORTRAIT:
|
||||
return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown;
|
||||
case DisplayServer::SCREEN_SENSOR:
|
||||
return UIInterfaceOrientationMaskAll;
|
||||
case DisplayServer::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
|
||||
45
drivers/backtrace/SCsub
Normal file
45
drivers/backtrace/SCsub
Normal 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(CPPEXTPATH=[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)
|
||||
7
drivers/coreaudio/SCsub
Normal file
7
drivers/coreaudio/SCsub
Normal 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")
|
||||
127
drivers/coreaudio/audio_driver_coreaudio.h
Normal file
127
drivers/coreaudio/audio_driver_coreaudio.h
Normal 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_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
|
||||
721
drivers/coreaudio/audio_driver_coreaudio.mm
Normal file
721
drivers/coreaudio/audio_driver_coreaudio.mm
Normal file
@@ -0,0 +1,721 @@
|
||||
/**************************************************************************/
|
||||
/* 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/project_settings.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 = 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);
|
||||
|
||||
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 = 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() {
|
||||
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
|
||||
7
drivers/coremidi/SCsub
Normal file
7
drivers/coremidi/SCsub
Normal 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")
|
||||
68
drivers/coremidi/midi_driver_coremidi.h
Normal file
68
drivers/coremidi/midi_driver_coremidi.h
Normal 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
|
||||
132
drivers/coremidi/midi_driver_coremidi.mm
Normal file
132
drivers/coremidi/midi_driver_coremidi.mm
Normal 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
|
||||
184
drivers/d3d12/SCsub
Normal file
184
drivers/d3d12/SCsub
Normal file
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import methods
|
||||
|
||||
Import("env")
|
||||
|
||||
env_d3d12_rdd = env.Clone()
|
||||
|
||||
thirdparty_obj = []
|
||||
|
||||
|
||||
# DirectX Headers (must take precedence over Windows SDK's).
|
||||
|
||||
env.Prepend(CPPEXTPATH=["#thirdparty/directx_headers/include/directx"])
|
||||
env_d3d12_rdd.Prepend(CPPEXTPATH=["#thirdparty/directx_headers/include/directx"])
|
||||
env_d3d12_rdd.Prepend(CPPEXTPATH=["#thirdparty/directx_headers/include/dxguids"])
|
||||
|
||||
|
||||
# Direct3D 12 Memory Allocator.
|
||||
|
||||
env.Append(CPPEXTPATH=["#thirdparty/d3d12ma"])
|
||||
env_d3d12_rdd.Append(CPPEXTPATH=["#thirdparty/d3d12ma"])
|
||||
|
||||
|
||||
# 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(CPPEXTPATH=[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")
|
||||
|
||||
# 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"),
|
||||
]
|
||||
|
||||
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",
|
||||
("_HAS_EXCEPTIONS", 0),
|
||||
"NOMINMAX",
|
||||
"HAVE_STRUCT_TIMESPEC",
|
||||
]
|
||||
else:
|
||||
extra_defines += [
|
||||
"HAVE_STRUCT_TIMESPEC",
|
||||
]
|
||||
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"])
|
||||
env.Append(CCFLAGS=["-Wno-unknown-pragmas"])
|
||||
|
||||
# This is needed since rendering_device_d3d12.cpp needs to include some Mesa internals.
|
||||
# FIXME: Should be CPPEXTPATH, but doing so introduces an include-order bug when combined with
|
||||
# godot-nir-static; this necessitates warning macro wrappers. See #106376.
|
||||
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)
|
||||
|
||||
|
||||
# Add all.
|
||||
|
||||
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)
|
||||
57
drivers/d3d12/d3d12_godot_nir_bridge.h
Normal file
57
drivers/d3d12/d3d12_godot_nir_bridge.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/**************************************************************************/
|
||||
/* 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;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
45
drivers/d3d12/d3d12_hooks.cpp
Normal file
45
drivers/d3d12/d3d12_hooks.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
48
drivers/d3d12/d3d12_hooks.h
Normal file
48
drivers/d3d12/d3d12_hooks.h
Normal 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; }
|
||||
};
|
||||
41
drivers/d3d12/d3d12ma.cpp
Normal file
41
drivers/d3d12/d3d12ma.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
/**************************************************************************/
|
||||
/* d3d12ma.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
// Wrapper needed to set the required rpcndr version for MinGW compatibility.
|
||||
// Since we're compiling thirdparty code in a Godot SCons environment with
|
||||
// warnings enabled, we also need to silence them manually.
|
||||
|
||||
#include "rendering_device_driver_d3d12.h" // For __REQUIRED_RPCNDR_H_VERSION__.
|
||||
|
||||
GODOT_GCC_WARNING_PUSH_AND_IGNORE("-Wmaybe-uninitialized")
|
||||
|
||||
#include <D3D12MemAlloc.cpp>
|
||||
|
||||
GODOT_GCC_WARNING_POP
|
||||
209
drivers/d3d12/dxil_hash.cpp
Normal file
209
drivers/d3d12/dxil_hash.cpp
Normal 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);
|
||||
}
|
||||
36
drivers/d3d12/dxil_hash.h
Normal file
36
drivers/d3d12/dxil_hash.h
Normal 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);
|
||||
348
drivers/d3d12/rendering_context_driver_d3d12.cpp
Normal file
348
drivers/d3d12/rendering_context_driver_d3d12.cpp
Normal file
@@ -0,0 +1,348 @@
|
||||
/**************************************************************************/
|
||||
/* 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"
|
||||
#include "servers/rendering/rendering_device.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")
|
||||
GODOT_MSVC_WARNING_PUSH
|
||||
|
||||
#include <dxcapi.h>
|
||||
|
||||
GODOT_GCC_WARNING_POP
|
||||
GODOT_CLANG_WARNING_POP
|
||||
|
||||
#if !defined(_MSC_VER)
|
||||
#include <guiddef.h>
|
||||
|
||||
#include <dxguids.h>
|
||||
#endif
|
||||
|
||||
// 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_D3D12DeviceFactoryGodot = { 0x114863bf, 0xc386, 0x4aee, { 0xb3, 0x9d, 0x8f, 0x0b, 0xbb, 0x06, 0x29, 0x55 } };
|
||||
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.
|
||||
}
|
||||
|
||||
ID3D12SDKConfiguration *sdk_config = nullptr;
|
||||
if (SUCCEEDED(d3d_D3D12GetInterface(CLSID_D3D12SDKConfigurationGodot, IID_PPV_ARGS(&sdk_config)))) {
|
||||
ID3D12SDKConfiguration1 *sdk_config1 = nullptr;
|
||||
if (SUCCEEDED(sdk_config->QueryInterface(&sdk_config1))) {
|
||||
if (SUCCEEDED(sdk_config1->CreateDeviceFactory(agility_sdk_version, agility_sdk_path.ascii().get_data(), IID_PPV_ARGS(device_factory.GetAddressOf())))) {
|
||||
d3d_D3D12GetInterface(CLSID_D3D12DeviceFactoryGodot, IID_PPV_ARGS(device_factory.GetAddressOf()));
|
||||
} else if (SUCCEEDED(sdk_config1->CreateDeviceFactory(agility_sdk_version, ".\\", IID_PPV_ARGS(device_factory.GetAddressOf())))) {
|
||||
d3d_D3D12GetInterface(CLSID_D3D12DeviceFactoryGodot, IID_PPV_ARGS(device_factory.GetAddressOf()));
|
||||
}
|
||||
sdk_config1->Release();
|
||||
}
|
||||
sdk_config->Release();
|
||||
}
|
||||
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::_initialize_devices() {
|
||||
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);
|
||||
|
||||
// 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, DisplayServer::VSyncMode p_vsync_mode) {
|
||||
Surface *surface = (Surface *)(p_surface);
|
||||
surface->vsync_mode = p_vsync_mode;
|
||||
surface->needs_resize = true;
|
||||
}
|
||||
|
||||
DisplayServer::VSyncMode RenderingContextDriverD3D12::surface_get_vsync_mode(SurfaceID p_surface) const {
|
||||
Surface *surface = (Surface *)(p_surface);
|
||||
return surface->vsync_mode;
|
||||
}
|
||||
|
||||
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() const {
|
||||
return dxgi_factory.Get();
|
||||
}
|
||||
|
||||
bool RenderingContextDriverD3D12::get_tearing_supported() const {
|
||||
return tearing_supported;
|
||||
}
|
||||
118
drivers/d3d12/rendering_context_driver_d3d12.h
Normal file
118
drivers/d3d12/rendering_context_driver_d3d12.h
Normal file
@@ -0,0 +1,118 @@
|
||||
/**************************************************************************/
|
||||
/* 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_server.h"
|
||||
#include "servers/rendering/rendering_context_driver.h"
|
||||
|
||||
#if defined(AS)
|
||||
#undef AS
|
||||
#endif
|
||||
|
||||
#ifdef DCOMP_ENABLED
|
||||
#include <dcomp.h>
|
||||
#endif
|
||||
|
||||
#include <d3dx12.h>
|
||||
#include <dxgi1_6.h>
|
||||
|
||||
#include <wrl/client.h>
|
||||
|
||||
using Microsoft::WRL::ComPtr;
|
||||
|
||||
#define ARRAY_SIZE(a) std::size(a)
|
||||
|
||||
class RenderingContextDriverD3D12 : public RenderingContextDriver {
|
||||
ComPtr<ID3D12DeviceFactory> device_factory;
|
||||
ComPtr<IDXGIFactory2> dxgi_factory;
|
||||
TightLocalVector<Device> driver_devices;
|
||||
bool tearing_supported = false;
|
||||
|
||||
Error _init_device_factory();
|
||||
Error _initialize_debug_layers();
|
||||
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, DisplayServer::VSyncMode p_vsync_mode) override;
|
||||
virtual DisplayServer::VSyncMode surface_get_vsync_mode(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;
|
||||
DisplayServer::VSyncMode vsync_mode = DisplayServer::VSYNC_ENABLED;
|
||||
bool needs_resize = false;
|
||||
#ifdef DCOMP_ENABLED
|
||||
ComPtr<IDCompositionDevice> composition_device;
|
||||
ComPtr<IDCompositionTarget> composition_target;
|
||||
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() const;
|
||||
bool get_tearing_supported() const;
|
||||
bool use_validation_layers() const;
|
||||
|
||||
RenderingContextDriverD3D12();
|
||||
virtual ~RenderingContextDriverD3D12() override;
|
||||
};
|
||||
6117
drivers/d3d12/rendering_device_driver_d3d12.cpp
Normal file
6117
drivers/d3d12/rendering_device_driver_d3d12.cpp
Normal file
File diff suppressed because it is too large
Load Diff
928
drivers/d3d12/rendering_device_driver_d3d12.h
Normal file
928
drivers/d3d12/rendering_device_driver_d3d12.h
Normal file
@@ -0,0 +1,928 @@
|
||||
/**************************************************************************/
|
||||
/* 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/hash_map.h"
|
||||
#include "core/templates/paged_allocator.h"
|
||||
#include "core/templates/self_list.h"
|
||||
#include "rendering_shader_container_d3d12.h"
|
||||
#include "servers/rendering/rendering_device_driver.h"
|
||||
|
||||
#ifndef _MSC_VER
|
||||
// Match current version used by MinGW, MSVC and Direct3D 12 headers use 500.
|
||||
#define __REQUIRED_RPCNDR_H_VERSION__ 475
|
||||
#endif
|
||||
|
||||
#include <d3dx12.h>
|
||||
#include <dxgi1_6.h>
|
||||
#define D3D12MA_D3D12_HEADERS_ALREADY_INCLUDED
|
||||
#include <D3D12MemAlloc.h>
|
||||
|
||||
#include <wrl/client.h>
|
||||
|
||||
#if defined(_MSC_VER) && defined(MemoryBarrier)
|
||||
// Annoying define from winnt.h. Reintroduced by some of the headers above.
|
||||
#undef MemoryBarrier
|
||||
#endif
|
||||
|
||||
using Microsoft::WRL::ComPtr;
|
||||
|
||||
#ifdef DEV_ENABLED
|
||||
#define CUSTOM_INFO_QUEUE_ENABLED 0
|
||||
#endif
|
||||
|
||||
class RenderingContextDriverD3D12;
|
||||
|
||||
// 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];
|
||||
|
||||
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 StorageBufferCapabilities {
|
||||
bool storage_buffer_16_bit_access_is_supported = false;
|
||||
};
|
||||
|
||||
struct FormatCapabilities {
|
||||
bool relaxed_casting_supported = false;
|
||||
};
|
||||
|
||||
struct BarrierCapabilities {
|
||||
bool enhanced_barriers_supported = false;
|
||||
};
|
||||
|
||||
struct MiscFeaturesSupport {
|
||||
bool depth_bounds_supported = false;
|
||||
};
|
||||
|
||||
RenderingContextDriverD3D12 *context_driver = nullptr;
|
||||
RenderingContextDriver::Device context_device;
|
||||
ComPtr<IDXGIAdapter> adapter;
|
||||
DXGI_ADAPTER_DESC adapter_desc;
|
||||
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;
|
||||
StorageBufferCapabilities storage_buffer_capabilities;
|
||||
FormatCapabilities format_capabilities;
|
||||
BarrierCapabilities barrier_capabilities;
|
||||
MiscFeaturesSupport misc_features_support;
|
||||
RenderingShaderContainerFormatD3D12 shader_container_format;
|
||||
String pipeline_cache_id;
|
||||
|
||||
class DescriptorsHeap {
|
||||
D3D12_DESCRIPTOR_HEAP_DESC desc = {};
|
||||
ComPtr<ID3D12DescriptorHeap> heap;
|
||||
uint32_t handle_size = 0;
|
||||
|
||||
public:
|
||||
class Walker { // Texas Ranger.
|
||||
friend class DescriptorsHeap;
|
||||
|
||||
uint32_t handle_size = 0;
|
||||
uint32_t handle_count = 0;
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE first_cpu_handle = {};
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE first_gpu_handle = {};
|
||||
uint32_t handle_index = 0;
|
||||
|
||||
public:
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE get_curr_cpu_handle();
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE get_curr_gpu_handle();
|
||||
_FORCE_INLINE_ void rewind() { handle_index = 0; }
|
||||
void advance(uint32_t p_count = 1);
|
||||
uint32_t get_current_handle_index() const { return handle_index; }
|
||||
uint32_t get_free_handles() { return handle_count - handle_index; }
|
||||
bool is_at_eof() { return handle_index == handle_count; }
|
||||
};
|
||||
|
||||
Error allocate(ID3D12Device *m_device, D3D12_DESCRIPTOR_HEAP_TYPE m_type, uint32_t m_descriptor_count, bool p_for_gpu);
|
||||
uint32_t get_descriptor_count() const { return desc.NumDescriptors; }
|
||||
ID3D12DescriptorHeap *get_heap() const { return heap.Get(); }
|
||||
|
||||
Walker make_walker() const;
|
||||
};
|
||||
|
||||
struct {
|
||||
ComPtr<ID3D12CommandSignature> draw;
|
||||
ComPtr<ID3D12CommandSignature> draw_indexed;
|
||||
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 ****/
|
||||
/****************/
|
||||
|
||||
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 {
|
||||
ComPtr<ID3D12Resource> resource;
|
||||
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 {
|
||||
DataFormat texel_format = DATA_FORMAT_MAX;
|
||||
uint64_t size = 0;
|
||||
struct {
|
||||
bool usable_as_uav : 1;
|
||||
} flags = {};
|
||||
};
|
||||
|
||||
public:
|
||||
virtual BufferID buffer_create(uint64_t p_size, BitField<BufferUsageBits> p_usage, MemoryAllocationType p_allocation_type) 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 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;
|
||||
|
||||
UINT mapped_subresource = UINT_MAX;
|
||||
SelfList<TextureInfo> pending_clear{ this };
|
||||
#ifdef DEBUG_ENABLED
|
||||
bool created_from_extension = false;
|
||||
#endif
|
||||
};
|
||||
SelfList<TextureInfo>::List textures_pending_clear;
|
||||
|
||||
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 uint8_t *texture_map(TextureID p_texture, const TextureSubresource &p_subresource) override final;
|
||||
virtual void texture_unmap(TextureID p_texture) 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;
|
||||
|
||||
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(VectorView<VertexAttribute> p_vertex_attribs) 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::MemoryBarrier> p_memory_barriers,
|
||||
VectorView<RDD::BufferBarrier> p_buffer_barriers,
|
||||
VectorView<RDD::TextureBarrier> p_texture_barriers) override final;
|
||||
|
||||
private:
|
||||
/****************/
|
||||
/**** FENCES ****/
|
||||
/****************/
|
||||
|
||||
struct FenceInfo {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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;
|
||||
|
||||
const VertexFormatInfo *vf_info = nullptr;
|
||||
D3D12_VERTEX_BUFFER_VIEW vertex_buffer_views[8] = {};
|
||||
uint32_t vertex_buffer_count = 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 };
|
||||
|
||||
ComPtr<ID3D12CommandAllocator> cmd_allocator;
|
||||
ComPtr<ID3D12GraphicsCommandList> cmd_list;
|
||||
|
||||
ID3D12PipelineState *graphics_pso = nullptr;
|
||||
ID3D12PipelineState *compute_pso = nullptr;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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 {
|
||||
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;
|
||||
};
|
||||
|
||||
void _swap_chain_release(SwapChain *p_swap_chain);
|
||||
void _swap_chain_release_buffers(SwapChain *p_swap_chain);
|
||||
|
||||
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 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.
|
||||
DescriptorsHeap rtv_heap;
|
||||
DescriptorsHeap dsv_heap; // Used only if not for screen and some 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);
|
||||
|
||||
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;
|
||||
bool is_compute = false;
|
||||
|
||||
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;
|
||||
#ifdef DEV_ENABLED
|
||||
bool writable = false;
|
||||
#endif
|
||||
struct RootSignatureLocation {
|
||||
uint32_t root_param_idx = UINT32_MAX;
|
||||
uint32_t range_idx = UINT32_MAX;
|
||||
};
|
||||
struct {
|
||||
RootSignatureLocation resource;
|
||||
RootSignatureLocation sampler;
|
||||
} root_sig_locations;
|
||||
};
|
||||
|
||||
struct UniformSet {
|
||||
TightLocalVector<UniformBindingInfo> bindings;
|
||||
struct {
|
||||
uint32_t resources = 0;
|
||||
uint32_t samplers = 0;
|
||||
} num_root_params;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
ComPtr<ID3D12RootSignature> root_signature;
|
||||
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 RootDescriptorTable {
|
||||
uint32_t root_param_idx = UINT32_MAX;
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE start_gpu_handle = {};
|
||||
};
|
||||
|
||||
struct UniformSetInfo {
|
||||
struct {
|
||||
DescriptorsHeap resources;
|
||||
DescriptorsHeap samplers;
|
||||
} desc_heaps;
|
||||
|
||||
struct StateRequirement {
|
||||
ResourceInfo *resource = nullptr;
|
||||
bool is_buffer = false;
|
||||
D3D12_RESOURCE_STATES states = {};
|
||||
uint64_t shader_uniform_idx_mask = 0;
|
||||
};
|
||||
TightLocalVector<StateRequirement> resource_states;
|
||||
|
||||
struct RecentBind {
|
||||
uint64_t segment_serial = 0;
|
||||
uint32_t root_signature_crc = 0;
|
||||
struct {
|
||||
TightLocalVector<RootDescriptorTable> resources;
|
||||
TightLocalVector<RootDescriptorTable> samplers;
|
||||
} root_tables;
|
||||
int uses = 0;
|
||||
} recent_binds[4]; // A better amount may be empirically found.
|
||||
|
||||
#ifdef DEV_ENABLED
|
||||
// Filthy, but useful for dev.
|
||||
struct ResourceDescInfo {
|
||||
D3D12_DESCRIPTOR_RANGE_TYPE type;
|
||||
D3D12_SRV_DIMENSION srv_dimension;
|
||||
};
|
||||
TightLocalVector<ResourceDescInfo> resources_desc_info;
|
||||
#endif
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
// ----- 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);
|
||||
void _command_bind_uniform_set(CommandBufferID p_cmd_buffer, UniformSetID p_uniform_set, ShaderID p_shader, uint32_t p_set_index, bool p_for_compute);
|
||||
void _command_bind_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, bool p_for_compute);
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
struct {
|
||||
D3D12_PRIMITIVE_TOPOLOGY primitive_topology = {};
|
||||
Color blend_constant;
|
||||
float depth_bounds_min = 0.0f;
|
||||
float depth_bounds_max = 0.0f;
|
||||
uint32_t stencil_reference = 0;
|
||||
} dyn_params;
|
||||
};
|
||||
|
||||
struct PipelineInfo {
|
||||
ID3D12PipelineState *pso = nullptr;
|
||||
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 _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_set(CommandBufferID p_cmd_buffer, UniformSetID p_uniform_set, ShaderID p_shader, uint32_t p_set_index) 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) 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) 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_set(CommandBufferID p_cmd_buffer, UniformSetID p_uniform_set, ShaderID p_shader, uint32_t p_set_index) 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) 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;
|
||||
|
||||
/*****************/
|
||||
/**** QUERIES ****/
|
||||
/*****************/
|
||||
|
||||
// ----- TIMESTAMP -----
|
||||
|
||||
private:
|
||||
struct TimestampQueryPoolInfo {
|
||||
ComPtr<ID3D12QueryHeap> query_heap;
|
||||
uint32_t query_count = 0;
|
||||
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 {
|
||||
struct {
|
||||
DescriptorsHeap resources;
|
||||
DescriptorsHeap samplers;
|
||||
DescriptorsHeap aux;
|
||||
DescriptorsHeap rtv;
|
||||
} desc_heaps;
|
||||
struct {
|
||||
DescriptorsHeap::Walker resources;
|
||||
DescriptorsHeap::Walker samplers;
|
||||
DescriptorsHeap::Walker aux;
|
||||
DescriptorsHeap::Walker rtv;
|
||||
} desc_heap_walkers;
|
||||
struct {
|
||||
bool resources = false;
|
||||
bool samplers = false;
|
||||
bool aux = false;
|
||||
bool rtv = false;
|
||||
} desc_heaps_exhausted_reported;
|
||||
CD3DX12_CPU_DESCRIPTOR_HANDLE null_rtv_handle = {}; // For [[MANUAL_SUBPASSES]].
|
||||
uint32_t segment_serial = 0;
|
||||
|
||||
#ifdef DEV_ENABLED
|
||||
uint32_t uniform_set_reused = 0;
|
||||
#endif
|
||||
};
|
||||
TightLocalVector<FrameInfo> frames;
|
||||
uint32_t frame_idx = 0;
|
||||
uint32_t frames_drawn = 0;
|
||||
uint32_t segment_serial = 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();
|
||||
};
|
||||
901
drivers/d3d12/rendering_shader_container_d3d12.cpp
Normal file
901
drivers/d3d12/rendering_shader_container_d3d12.cpp
Normal file
@@ -0,0 +1,901 @@
|
||||
/**************************************************************************/
|
||||
/* rendering_shader_container_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_shader_container_d3d12.h"
|
||||
|
||||
#include "core/templates/sort_array.h"
|
||||
|
||||
#include "dxil_hash.h"
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
#ifndef _MSC_VER
|
||||
// Match current version used by MinGW, MSVC and Direct3D 12 headers use 500.
|
||||
#define __REQUIRED_RPCNDR_H_VERSION__ 475
|
||||
#endif
|
||||
|
||||
#include <d3dx12.h>
|
||||
#include <dxgi1_6.h>
|
||||
#define D3D12MA_D3D12_HEADERS_ALREADY_INCLUDED
|
||||
#include <D3D12MemAlloc.h>
|
||||
|
||||
#include <wrl/client.h>
|
||||
|
||||
#if defined(_MSC_VER) && defined(MemoryBarrier)
|
||||
// Annoying define from winnt.h. Reintroduced by some of the headers above.
|
||||
#undef MemoryBarrier
|
||||
#endif
|
||||
|
||||
GODOT_GCC_WARNING_PUSH
|
||||
GODOT_GCC_WARNING_IGNORE("-Wimplicit-fallthrough")
|
||||
GODOT_GCC_WARNING_IGNORE("-Wlogical-not-parentheses")
|
||||
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("-Wlogical-not-parentheses")
|
||||
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")
|
||||
GODOT_MSVC_WARNING_PUSH
|
||||
GODOT_MSVC_WARNING_IGNORE(4200) // "nonstandard extension used: zero-sized array in struct/union".
|
||||
GODOT_MSVC_WARNING_IGNORE(4806) // "'&': unsafe operation: no value of type 'bool' promoted to type 'uint32_t' can equal the given constant".
|
||||
|
||||
#include <nir_spirv.h>
|
||||
#include <nir_to_dxil.h>
|
||||
#include <spirv_to_dxil.h>
|
||||
extern "C" {
|
||||
#include <dxil_spirv_nir.h>
|
||||
}
|
||||
|
||||
GODOT_GCC_WARNING_POP
|
||||
GODOT_CLANG_WARNING_POP
|
||||
GODOT_MSVC_WARNING_POP
|
||||
|
||||
static D3D12_SHADER_VISIBILITY stages_to_d3d12_visibility(uint32_t p_stages_mask) {
|
||||
switch (p_stages_mask) {
|
||||
case RenderingDeviceCommons::SHADER_STAGE_VERTEX_BIT:
|
||||
return D3D12_SHADER_VISIBILITY_VERTEX;
|
||||
case RenderingDeviceCommons::SHADER_STAGE_FRAGMENT_BIT:
|
||||
return D3D12_SHADER_VISIBILITY_PIXEL;
|
||||
default:
|
||||
return D3D12_SHADER_VISIBILITY_ALL;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t RenderingDXIL::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) {
|
||||
uint32_t patch_val = 0;
|
||||
switch (p_type) {
|
||||
case RenderingDeviceCommons::PIPELINE_SPECIALIZATION_CONSTANT_TYPE_INT: {
|
||||
uint32_t int_value = *((const int *)p_value);
|
||||
ERR_FAIL_COND_V(int_value & (1 << 31), 0);
|
||||
patch_val = int_value;
|
||||
} break;
|
||||
case RenderingDeviceCommons::PIPELINE_SPECIALIZATION_CONSTANT_TYPE_BOOL: {
|
||||
bool bool_value = *((const bool *)p_value);
|
||||
patch_val = (uint32_t)bool_value;
|
||||
} break;
|
||||
case RenderingDeviceCommons::PIPELINE_SPECIALIZATION_CONSTANT_TYPE_FLOAT: {
|
||||
uint32_t int_value = *((const int *)p_value);
|
||||
ERR_FAIL_COND_V(int_value & (1 << 31), 0);
|
||||
patch_val = (int_value >> 1);
|
||||
} break;
|
||||
}
|
||||
// For VBR encoding to encode the number of bits we expect (32), we need to set the MSB unconditionally.
|
||||
// However, signed VBR moves the MSB to the LSB, so setting the MSB to 1 wouldn't help. Therefore,
|
||||
// the bit we set to 1 is the one at index 30.
|
||||
patch_val |= (1 << 30);
|
||||
patch_val <<= 1; // What signed VBR does.
|
||||
|
||||
auto tamper_bits = [](uint8_t *p_start, uint64_t p_bit_offset, uint64_t p_tb_value) -> uint64_t {
|
||||
uint64_t original = 0;
|
||||
uint32_t curr_input_byte = p_bit_offset / 8;
|
||||
uint8_t curr_input_bit = p_bit_offset % 8;
|
||||
auto get_curr_input_bit = [&]() -> bool {
|
||||
return ((p_start[curr_input_byte] >> curr_input_bit) & 1);
|
||||
};
|
||||
auto move_to_next_input_bit = [&]() {
|
||||
if (curr_input_bit == 7) {
|
||||
curr_input_bit = 0;
|
||||
curr_input_byte++;
|
||||
} else {
|
||||
curr_input_bit++;
|
||||
}
|
||||
};
|
||||
auto tamper_input_bit = [&](bool p_new_bit) {
|
||||
p_start[curr_input_byte] &= ~((uint8_t)1 << curr_input_bit);
|
||||
if (p_new_bit) {
|
||||
p_start[curr_input_byte] |= (uint8_t)1 << curr_input_bit;
|
||||
}
|
||||
};
|
||||
uint8_t value_bit_idx = 0;
|
||||
for (uint32_t i = 0; i < 5; i++) { // 32 bits take 5 full bytes in VBR.
|
||||
for (uint32_t j = 0; j < 7; j++) {
|
||||
bool input_bit = get_curr_input_bit();
|
||||
original |= (uint64_t)(input_bit ? 1 : 0) << value_bit_idx;
|
||||
tamper_input_bit((p_tb_value >> value_bit_idx) & 1);
|
||||
move_to_next_input_bit();
|
||||
value_bit_idx++;
|
||||
}
|
||||
#ifdef DEV_ENABLED
|
||||
bool input_bit = get_curr_input_bit();
|
||||
DEV_ASSERT((i < 4 && input_bit) || (i == 4 && !input_bit));
|
||||
#endif
|
||||
move_to_next_input_bit();
|
||||
}
|
||||
return original;
|
||||
};
|
||||
uint32_t stages_patched_mask = 0;
|
||||
for (int stage = 0; stage < RenderingDeviceCommons::SHADER_STAGE_MAX; stage++) {
|
||||
if (!r_stages_bytecodes.has((RenderingDeviceCommons::ShaderStage)stage)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64_t offset = p_stages_bit_offsets[RenderingShaderContainerD3D12::SHADER_STAGES_BIT_OFFSET_INDICES[stage]];
|
||||
if (offset == 0) {
|
||||
// This constant does not appear at this stage.
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector<uint8_t> &bytecode = r_stages_bytecodes[(RenderingDeviceCommons::ShaderStage)stage];
|
||||
#ifdef DEV_ENABLED
|
||||
uint64_t orig_patch_val = tamper_bits(bytecode.ptrw(), offset, patch_val);
|
||||
// Checking against the value the NIR patch should have set.
|
||||
DEV_ASSERT(!p_is_first_patch || ((orig_patch_val >> 1) & GODOT_NIR_SC_SENTINEL_MAGIC_MASK) == GODOT_NIR_SC_SENTINEL_MAGIC);
|
||||
uint64_t readback_patch_val = tamper_bits(bytecode.ptrw(), offset, patch_val);
|
||||
DEV_ASSERT(readback_patch_val == patch_val);
|
||||
#else
|
||||
tamper_bits(bytecode.ptrw(), offset, patch_val);
|
||||
#endif
|
||||
|
||||
stages_patched_mask |= (1 << stage);
|
||||
}
|
||||
|
||||
return stages_patched_mask;
|
||||
}
|
||||
|
||||
void RenderingDXIL::sign_bytecode(RenderingDeviceCommons::ShaderStage p_stage, Vector<uint8_t> &r_dxil_blob) {
|
||||
uint8_t *w = r_dxil_blob.ptrw();
|
||||
compute_dxil_hash(w + 20, r_dxil_blob.size() - 20, w + 4);
|
||||
}
|
||||
|
||||
// RenderingShaderContainerD3D12
|
||||
|
||||
uint32_t RenderingShaderContainerD3D12::_format() const {
|
||||
return 0x43443344;
|
||||
}
|
||||
|
||||
uint32_t RenderingShaderContainerD3D12::_format_version() const {
|
||||
return FORMAT_VERSION;
|
||||
}
|
||||
|
||||
uint32_t RenderingShaderContainerD3D12::_from_bytes_reflection_extra_data(const uint8_t *p_bytes) {
|
||||
reflection_data_d3d12 = *(const ReflectionDataD3D12 *)(p_bytes);
|
||||
return sizeof(ReflectionDataD3D12);
|
||||
}
|
||||
|
||||
uint32_t RenderingShaderContainerD3D12::_from_bytes_reflection_binding_uniform_extra_data_start(const uint8_t *p_bytes) {
|
||||
reflection_binding_set_uniforms_data_d3d12.resize(reflection_binding_set_uniforms_data.size());
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t RenderingShaderContainerD3D12::_from_bytes_reflection_binding_uniform_extra_data(const uint8_t *p_bytes, uint32_t p_index) {
|
||||
reflection_binding_set_uniforms_data_d3d12.ptrw()[p_index] = *(const ReflectionBindingDataD3D12 *)(p_bytes);
|
||||
return sizeof(ReflectionBindingDataD3D12);
|
||||
}
|
||||
|
||||
uint32_t RenderingShaderContainerD3D12::_from_bytes_reflection_specialization_extra_data_start(const uint8_t *p_bytes) {
|
||||
reflection_specialization_data_d3d12.resize(reflection_specialization_data.size());
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t RenderingShaderContainerD3D12::_from_bytes_reflection_specialization_extra_data(const uint8_t *p_bytes, uint32_t p_index) {
|
||||
reflection_specialization_data_d3d12.ptrw()[p_index] = *(const ReflectionSpecializationDataD3D12 *)(p_bytes);
|
||||
return sizeof(ReflectionSpecializationDataD3D12);
|
||||
}
|
||||
|
||||
uint32_t RenderingShaderContainerD3D12::_from_bytes_footer_extra_data(const uint8_t *p_bytes) {
|
||||
ContainerFooterD3D12 footer = *(const ContainerFooterD3D12 *)(p_bytes);
|
||||
root_signature_crc = footer.root_signature_crc;
|
||||
root_signature_bytes.resize(footer.root_signature_length);
|
||||
memcpy(root_signature_bytes.ptrw(), p_bytes + sizeof(ContainerFooterD3D12), root_signature_bytes.size());
|
||||
return sizeof(ContainerFooterD3D12) + footer.root_signature_length;
|
||||
}
|
||||
|
||||
uint32_t RenderingShaderContainerD3D12::_to_bytes_reflection_extra_data(uint8_t *p_bytes) const {
|
||||
if (p_bytes != nullptr) {
|
||||
*(ReflectionDataD3D12 *)(p_bytes) = reflection_data_d3d12;
|
||||
}
|
||||
|
||||
return sizeof(ReflectionDataD3D12);
|
||||
}
|
||||
|
||||
uint32_t RenderingShaderContainerD3D12::_to_bytes_reflection_binding_uniform_extra_data(uint8_t *p_bytes, uint32_t p_index) const {
|
||||
if (p_bytes != nullptr) {
|
||||
*(ReflectionBindingDataD3D12 *)(p_bytes) = reflection_binding_set_uniforms_data_d3d12[p_index];
|
||||
}
|
||||
|
||||
return sizeof(ReflectionBindingDataD3D12);
|
||||
}
|
||||
|
||||
uint32_t RenderingShaderContainerD3D12::_to_bytes_reflection_specialization_extra_data(uint8_t *p_bytes, uint32_t p_index) const {
|
||||
if (p_bytes != nullptr) {
|
||||
*(ReflectionSpecializationDataD3D12 *)(p_bytes) = reflection_specialization_data_d3d12[p_index];
|
||||
}
|
||||
|
||||
return sizeof(ReflectionSpecializationDataD3D12);
|
||||
}
|
||||
|
||||
uint32_t RenderingShaderContainerD3D12::_to_bytes_footer_extra_data(uint8_t *p_bytes) const {
|
||||
if (p_bytes != nullptr) {
|
||||
ContainerFooterD3D12 &footer = *(ContainerFooterD3D12 *)(p_bytes);
|
||||
footer.root_signature_length = root_signature_bytes.size();
|
||||
footer.root_signature_crc = root_signature_crc;
|
||||
memcpy(p_bytes + sizeof(ContainerFooterD3D12), root_signature_bytes.ptr(), root_signature_bytes.size());
|
||||
}
|
||||
|
||||
return sizeof(ContainerFooterD3D12) + root_signature_bytes.size();
|
||||
}
|
||||
|
||||
#if NIR_ENABLED
|
||||
bool RenderingShaderContainerD3D12::_convert_spirv_to_nir(const Vector<RenderingDeviceCommons::ShaderStageSPIRVData> &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) {
|
||||
r_stages_processed.clear();
|
||||
|
||||
dxil_spirv_runtime_conf dxil_runtime_conf = {};
|
||||
dxil_runtime_conf.runtime_data_cbv.base_shader_register = RUNTIME_DATA_REGISTER;
|
||||
dxil_runtime_conf.push_constant_cbv.base_shader_register = ROOT_CONSTANT_REGISTER;
|
||||
dxil_runtime_conf.zero_based_vertex_instance_id = true;
|
||||
dxil_runtime_conf.zero_based_compute_workgroup_id = true;
|
||||
dxil_runtime_conf.declared_read_only_images_as_srvs = true;
|
||||
|
||||
// Making this explicit to let maintainers know that in practice this didn't improve performance,
|
||||
// probably because data generated by one shader and consumed by another one forces the resource
|
||||
// to transition from UAV to SRV, and back, instead of being an UAV all the time.
|
||||
// In case someone wants to try, care must be taken so in case of incompatible bindings across stages
|
||||
// happen as a result, all the stages are re-translated. That can happen if, for instance, a stage only
|
||||
// uses an allegedly writable resource only for reading but the next stage doesn't.
|
||||
dxil_runtime_conf.inferred_read_only_images_as_srvs = false;
|
||||
|
||||
// Translate SPIR-V to NIR.
|
||||
for (int64_t i = 0; i < p_spirv.size(); i++) {
|
||||
RenderingDeviceCommons::ShaderStage stage = p_spirv[i].shader_stage;
|
||||
RenderingDeviceCommons::ShaderStage stage_flag = (RenderingDeviceCommons::ShaderStage)(1 << stage);
|
||||
r_stages.push_back(stage);
|
||||
r_stages_processed.set_flag(stage_flag);
|
||||
|
||||
const char *entry_point = "main";
|
||||
static const gl_shader_stage SPIRV_TO_MESA_STAGES[RenderingDeviceCommons::SHADER_STAGE_MAX] = {
|
||||
MESA_SHADER_VERTEX, // SHADER_STAGE_VERTEX
|
||||
MESA_SHADER_FRAGMENT, // SHADER_STAGE_FRAGMENT
|
||||
MESA_SHADER_TESS_CTRL, // SHADER_STAGE_TESSELATION_CONTROL
|
||||
MESA_SHADER_TESS_EVAL, // SHADER_STAGE_TESSELATION_EVALUATION
|
||||
MESA_SHADER_COMPUTE, // SHADER_STAGE_COMPUTE
|
||||
};
|
||||
|
||||
nir_shader *shader = spirv_to_nir(
|
||||
(const uint32_t *)(p_spirv[i].spirv.ptr()),
|
||||
p_spirv[i].spirv.size() / sizeof(uint32_t),
|
||||
nullptr,
|
||||
0,
|
||||
SPIRV_TO_MESA_STAGES[stage],
|
||||
entry_point,
|
||||
dxil_spirv_nir_get_spirv_options(),
|
||||
p_compiler_options);
|
||||
|
||||
ERR_FAIL_NULL_V_MSG(shader, false, "Shader translation (step 1) at stage " + String(RenderingDeviceCommons::SHADER_STAGE_NAMES[stage]) + " failed.");
|
||||
|
||||
#ifdef DEV_ENABLED
|
||||
nir_validate_shader(shader, "Validate before feeding NIR to the DXIL compiler");
|
||||
#endif
|
||||
|
||||
if (stage == RenderingDeviceCommons::SHADER_STAGE_VERTEX) {
|
||||
dxil_runtime_conf.yz_flip.y_mask = 0xffff;
|
||||
dxil_runtime_conf.yz_flip.mode = DXIL_SPIRV_Y_FLIP_UNCONDITIONAL;
|
||||
} else {
|
||||
dxil_runtime_conf.yz_flip.y_mask = 0;
|
||||
dxil_runtime_conf.yz_flip.mode = DXIL_SPIRV_YZ_FLIP_NONE;
|
||||
}
|
||||
|
||||
dxil_spirv_nir_prep(shader);
|
||||
bool requires_runtime_data = false;
|
||||
dxil_spirv_nir_passes(shader, &dxil_runtime_conf, &requires_runtime_data);
|
||||
|
||||
r_stages_nir_shaders[stage] = shader;
|
||||
}
|
||||
|
||||
// Link NIR shaders.
|
||||
for (int i = RenderingDeviceCommons::SHADER_STAGE_MAX - 1; i >= 0; i--) {
|
||||
if (!r_stages_nir_shaders.has(i)) {
|
||||
continue;
|
||||
}
|
||||
nir_shader *shader = r_stages_nir_shaders[i];
|
||||
nir_shader *prev_shader = nullptr;
|
||||
for (int j = i - 1; j >= 0; j--) {
|
||||
if (r_stages_nir_shaders.has(j)) {
|
||||
prev_shader = r_stages_nir_shaders[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
// There is a bug in the Direct3D runtime during creation of a PSO with view instancing. If a fragment
|
||||
// shader uses front/back face detection (SV_IsFrontFace), its signature must include the pixel position
|
||||
// builtin variable (SV_Position), otherwise an Internal Runtime error will occur.
|
||||
if (i == RenderingDeviceCommons::SHADER_STAGE_FRAGMENT) {
|
||||
const bool use_front_face =
|
||||
nir_find_variable_with_location(shader, nir_var_shader_in, VARYING_SLOT_FACE) ||
|
||||
(shader->info.inputs_read & VARYING_BIT_FACE) ||
|
||||
nir_find_variable_with_location(shader, nir_var_system_value, SYSTEM_VALUE_FRONT_FACE) ||
|
||||
BITSET_TEST(shader->info.system_values_read, SYSTEM_VALUE_FRONT_FACE);
|
||||
const bool use_position =
|
||||
nir_find_variable_with_location(shader, nir_var_shader_in, VARYING_SLOT_POS) ||
|
||||
(shader->info.inputs_read & VARYING_BIT_POS) ||
|
||||
nir_find_variable_with_location(shader, nir_var_system_value, SYSTEM_VALUE_FRAG_COORD) ||
|
||||
BITSET_TEST(shader->info.system_values_read, SYSTEM_VALUE_FRAG_COORD);
|
||||
if (use_front_face && !use_position) {
|
||||
nir_variable *const pos = nir_variable_create(shader, nir_var_shader_in, glsl_vec4_type(), "gl_FragCoord");
|
||||
pos->data.location = VARYING_SLOT_POS;
|
||||
shader->info.inputs_read |= VARYING_BIT_POS;
|
||||
}
|
||||
}
|
||||
if (prev_shader) {
|
||||
bool requires_runtime_data = {};
|
||||
dxil_spirv_nir_link(shader, prev_shader, &dxil_runtime_conf, &requires_runtime_data);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct GodotNirCallbackUserData {
|
||||
RenderingShaderContainerD3D12 *container;
|
||||
RenderingDeviceCommons::ShaderStage stage;
|
||||
};
|
||||
|
||||
static dxil_shader_model shader_model_d3d_to_dxil(D3D_SHADER_MODEL p_d3d_shader_model) {
|
||||
static_assert(SHADER_MODEL_6_0 == 0x60000);
|
||||
static_assert(SHADER_MODEL_6_3 == 0x60003);
|
||||
static_assert(D3D_SHADER_MODEL_6_0 == 0x60);
|
||||
static_assert(D3D_SHADER_MODEL_6_3 == 0x63);
|
||||
return (dxil_shader_model)((p_d3d_shader_model >> 4) * 0x10000 + (p_d3d_shader_model & 0xf));
|
||||
}
|
||||
|
||||
bool RenderingShaderContainerD3D12::_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) {
|
||||
// Translate NIR to DXIL.
|
||||
for (KeyValue<int, nir_shader *> it : p_stages_nir_shaders) {
|
||||
RenderingDeviceCommons::ShaderStage stage = (RenderingDeviceCommons::ShaderStage)(it.key);
|
||||
GodotNirCallbackUserData godot_nir_callback_user_data;
|
||||
godot_nir_callback_user_data.container = this;
|
||||
godot_nir_callback_user_data.stage = stage;
|
||||
|
||||
GodotNirCallbacks godot_nir_callbacks = {};
|
||||
godot_nir_callbacks.data = &godot_nir_callback_user_data;
|
||||
godot_nir_callbacks.report_resource = _nir_report_resource;
|
||||
godot_nir_callbacks.report_sc_bit_offset_fn = _nir_report_sc_bit_offset;
|
||||
godot_nir_callbacks.report_bitcode_bit_offset_fn = _nir_report_bitcode_bit_offset;
|
||||
|
||||
nir_to_dxil_options nir_to_dxil_options = {};
|
||||
nir_to_dxil_options.environment = DXIL_ENVIRONMENT_VULKAN;
|
||||
nir_to_dxil_options.shader_model_max = shader_model_d3d_to_dxil(D3D_SHADER_MODEL(REQUIRED_SHADER_MODEL));
|
||||
nir_to_dxil_options.validator_version_max = NO_DXIL_VALIDATION;
|
||||
nir_to_dxil_options.godot_nir_callbacks = &godot_nir_callbacks;
|
||||
|
||||
dxil_logger logger = {};
|
||||
logger.log = [](void *p_priv, const char *p_msg) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
print_verbose(p_msg);
|
||||
#endif
|
||||
};
|
||||
|
||||
blob dxil_blob = {};
|
||||
bool ok = nir_to_dxil(it.value, &nir_to_dxil_options, &logger, &dxil_blob);
|
||||
ERR_FAIL_COND_V_MSG(!ok, false, "Shader translation at stage " + String(RenderingDeviceCommons::SHADER_STAGE_NAMES[stage]) + " failed.");
|
||||
|
||||
Vector<uint8_t> blob_copy;
|
||||
blob_copy.resize(dxil_blob.size);
|
||||
memcpy(blob_copy.ptrw(), dxil_blob.data, dxil_blob.size);
|
||||
blob_finish(&dxil_blob);
|
||||
r_dxil_blobs.insert(stage, blob_copy);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderingShaderContainerD3D12::_convert_spirv_to_dxil(const Vector<RenderingDeviceCommons::ShaderStageSPIRVData> &p_spirv, HashMap<RenderingDeviceCommons::ShaderStage, Vector<uint8_t>> &r_dxil_blobs, Vector<RenderingDeviceCommons::ShaderStage> &r_stages, BitField<RenderingDeviceCommons::ShaderStage> &r_stages_processed) {
|
||||
r_dxil_blobs.clear();
|
||||
|
||||
HashMap<int, nir_shader *> stages_nir_shaders;
|
||||
auto free_nir_shaders = [&]() {
|
||||
for (KeyValue<int, nir_shader *> &E : stages_nir_shaders) {
|
||||
ralloc_free(E.value);
|
||||
}
|
||||
stages_nir_shaders.clear();
|
||||
};
|
||||
|
||||
// This structure must live as long as the shaders are alive.
|
||||
nir_shader_compiler_options compiler_options = *dxil_get_nir_compiler_options();
|
||||
compiler_options.lower_base_vertex = false;
|
||||
|
||||
// This is based on spirv2dxil.c. May need updates when it changes.
|
||||
// Also, this has to stay around until after linking.
|
||||
if (!_convert_spirv_to_nir(p_spirv, &compiler_options, stages_nir_shaders, r_stages, r_stages_processed)) {
|
||||
free_nir_shaders();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_convert_nir_to_dxil(stages_nir_shaders, r_stages_processed, r_dxil_blobs)) {
|
||||
free_nir_shaders();
|
||||
return false;
|
||||
}
|
||||
|
||||
free_nir_shaders();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderingShaderContainerD3D12::_generate_root_signature(BitField<RenderingDeviceCommons::ShaderStage> p_stages_processed) {
|
||||
// Root (push) constants.
|
||||
LocalVector<D3D12_ROOT_PARAMETER1> root_params;
|
||||
if (reflection_data_d3d12.dxil_push_constant_stages) {
|
||||
CD3DX12_ROOT_PARAMETER1 push_constant;
|
||||
push_constant.InitAsConstants(
|
||||
reflection_data.push_constant_size / sizeof(uint32_t),
|
||||
ROOT_CONSTANT_REGISTER,
|
||||
0,
|
||||
stages_to_d3d12_visibility(reflection_data_d3d12.dxil_push_constant_stages));
|
||||
|
||||
root_params.push_back(push_constant);
|
||||
}
|
||||
|
||||
// NIR-DXIL runtime data.
|
||||
if (reflection_data_d3d12.nir_runtime_data_root_param_idx == 1) { // Set above to 1 when discovering runtime data is needed.
|
||||
DEV_ASSERT(!reflection_data.is_compute); // Could be supported if needed, but it's pointless as of now.
|
||||
reflection_data_d3d12.nir_runtime_data_root_param_idx = root_params.size();
|
||||
CD3DX12_ROOT_PARAMETER1 nir_runtime_data;
|
||||
nir_runtime_data.InitAsConstants(
|
||||
sizeof(dxil_spirv_vertex_runtime_data) / sizeof(uint32_t),
|
||||
RUNTIME_DATA_REGISTER,
|
||||
0,
|
||||
D3D12_SHADER_VISIBILITY_VERTEX);
|
||||
root_params.push_back(nir_runtime_data);
|
||||
}
|
||||
|
||||
// Descriptor tables (up to two per uniform set, for resources and/or samplers).
|
||||
// These have to stay around until serialization!
|
||||
struct TraceableDescriptorTable {
|
||||
uint32_t stages_mask = {};
|
||||
Vector<D3D12_DESCRIPTOR_RANGE1> ranges;
|
||||
Vector<RootSignatureLocation *> root_signature_locations;
|
||||
};
|
||||
|
||||
uint32_t binding_start = 0;
|
||||
Vector<TraceableDescriptorTable> resource_tables_maps;
|
||||
Vector<TraceableDescriptorTable> sampler_tables_maps;
|
||||
for (uint32_t i = 0; i < reflection_binding_set_uniforms_count.size(); i++) {
|
||||
bool first_resource_in_set = true;
|
||||
bool first_sampler_in_set = true;
|
||||
uint32_t uniform_count = reflection_binding_set_uniforms_count[i];
|
||||
for (uint32_t j = 0; j < uniform_count; j++) {
|
||||
const ReflectionBindingData &uniform = reflection_binding_set_uniforms_data[binding_start + j];
|
||||
ReflectionBindingDataD3D12 &uniform_d3d12 = reflection_binding_set_uniforms_data_d3d12.ptrw()[binding_start + j];
|
||||
bool really_used = uniform_d3d12.dxil_stages != 0;
|
||||
#ifdef DEV_ENABLED
|
||||
bool anybody_home = (ResourceClass)(uniform_d3d12.resource_class) != RES_CLASS_INVALID || uniform_d3d12.has_sampler;
|
||||
DEV_ASSERT(anybody_home == really_used);
|
||||
#endif
|
||||
if (!really_used) {
|
||||
continue; // Existed in SPIR-V; went away in DXIL.
|
||||
}
|
||||
|
||||
auto insert_range = [](D3D12_DESCRIPTOR_RANGE_TYPE p_range_type,
|
||||
uint32_t p_num_descriptors,
|
||||
uint32_t p_dxil_register,
|
||||
uint32_t p_dxil_stages_mask,
|
||||
RootSignatureLocation *p_root_sig_locations,
|
||||
Vector<TraceableDescriptorTable> &r_tables,
|
||||
bool &r_first_in_set) {
|
||||
if (r_first_in_set) {
|
||||
r_tables.resize(r_tables.size() + 1);
|
||||
r_first_in_set = false;
|
||||
}
|
||||
|
||||
TraceableDescriptorTable &table = r_tables.write[r_tables.size() - 1];
|
||||
table.stages_mask |= p_dxil_stages_mask;
|
||||
|
||||
CD3DX12_DESCRIPTOR_RANGE1 range;
|
||||
// Due to the aliasing hack for SRV-UAV of different families,
|
||||
// we can be causing an unintended change of data (sometimes the validation layers catch it).
|
||||
D3D12_DESCRIPTOR_RANGE_FLAGS flags = D3D12_DESCRIPTOR_RANGE_FLAG_NONE;
|
||||
if (p_range_type == D3D12_DESCRIPTOR_RANGE_TYPE_SRV || p_range_type == D3D12_DESCRIPTOR_RANGE_TYPE_UAV) {
|
||||
flags = D3D12_DESCRIPTOR_RANGE_FLAG_DATA_VOLATILE;
|
||||
} else if (p_range_type == D3D12_DESCRIPTOR_RANGE_TYPE_CBV) {
|
||||
flags = D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC_WHILE_SET_AT_EXECUTE;
|
||||
}
|
||||
range.Init(p_range_type, p_num_descriptors, p_dxil_register, 0, flags);
|
||||
|
||||
table.ranges.push_back(range);
|
||||
table.root_signature_locations.push_back(p_root_sig_locations);
|
||||
};
|
||||
|
||||
uint32_t num_descriptors = 1;
|
||||
D3D12_DESCRIPTOR_RANGE_TYPE resource_range_type = {};
|
||||
switch ((ResourceClass)(uniform_d3d12.resource_class)) {
|
||||
case RES_CLASS_INVALID: {
|
||||
num_descriptors = uniform.length;
|
||||
DEV_ASSERT(uniform_d3d12.has_sampler);
|
||||
} break;
|
||||
case RES_CLASS_CBV: {
|
||||
resource_range_type = D3D12_DESCRIPTOR_RANGE_TYPE_CBV;
|
||||
DEV_ASSERT(!uniform_d3d12.has_sampler);
|
||||
} break;
|
||||
case RES_CLASS_SRV: {
|
||||
resource_range_type = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
|
||||
num_descriptors = MAX(1u, uniform.length); // An unbound R/O buffer is reflected as zero-size.
|
||||
} break;
|
||||
case RES_CLASS_UAV: {
|
||||
resource_range_type = D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
|
||||
num_descriptors = MAX(1u, uniform.length); // An unbound R/W buffer is reflected as zero-size.
|
||||
DEV_ASSERT(!uniform_d3d12.has_sampler);
|
||||
} break;
|
||||
}
|
||||
|
||||
uint32_t dxil_register = i * GODOT_NIR_DESCRIPTOR_SET_MULTIPLIER + uniform.binding * GODOT_NIR_BINDING_MULTIPLIER;
|
||||
if (uniform_d3d12.resource_class != RES_CLASS_INVALID) {
|
||||
insert_range(
|
||||
resource_range_type,
|
||||
num_descriptors,
|
||||
dxil_register,
|
||||
uniform_d3d12.dxil_stages,
|
||||
&uniform_d3d12.root_signature_locations[RS_LOC_TYPE_RESOURCE],
|
||||
resource_tables_maps,
|
||||
first_resource_in_set);
|
||||
}
|
||||
|
||||
if (uniform_d3d12.has_sampler) {
|
||||
insert_range(
|
||||
D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER,
|
||||
num_descriptors,
|
||||
dxil_register,
|
||||
uniform_d3d12.dxil_stages,
|
||||
&uniform_d3d12.root_signature_locations[RS_LOC_TYPE_SAMPLER],
|
||||
sampler_tables_maps,
|
||||
first_sampler_in_set);
|
||||
}
|
||||
}
|
||||
|
||||
binding_start += uniform_count;
|
||||
}
|
||||
|
||||
auto make_descriptor_tables = [&root_params](const Vector<TraceableDescriptorTable> &p_tables) {
|
||||
for (const TraceableDescriptorTable &table : p_tables) {
|
||||
D3D12_SHADER_VISIBILITY visibility = stages_to_d3d12_visibility(table.stages_mask);
|
||||
DEV_ASSERT(table.ranges.size() == table.root_signature_locations.size());
|
||||
for (int i = 0; i < table.ranges.size(); i++) {
|
||||
// By now we know very well which root signature location corresponds to the pointed uniform.
|
||||
table.root_signature_locations[i]->root_param_index = root_params.size();
|
||||
table.root_signature_locations[i]->range_index = i;
|
||||
}
|
||||
|
||||
CD3DX12_ROOT_PARAMETER1 root_table;
|
||||
root_table.InitAsDescriptorTable(table.ranges.size(), table.ranges.ptr(), visibility);
|
||||
root_params.push_back(root_table);
|
||||
}
|
||||
};
|
||||
|
||||
make_descriptor_tables(resource_tables_maps);
|
||||
make_descriptor_tables(sampler_tables_maps);
|
||||
|
||||
CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC root_sig_desc = {};
|
||||
D3D12_ROOT_SIGNATURE_FLAGS root_sig_flags =
|
||||
D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS |
|
||||
D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS |
|
||||
D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS |
|
||||
D3D12_ROOT_SIGNATURE_FLAG_DENY_AMPLIFICATION_SHADER_ROOT_ACCESS |
|
||||
D3D12_ROOT_SIGNATURE_FLAG_DENY_MESH_SHADER_ROOT_ACCESS;
|
||||
|
||||
if (!p_stages_processed.has_flag(RenderingDeviceCommons::SHADER_STAGE_VERTEX_BIT)) {
|
||||
root_sig_flags |= D3D12_ROOT_SIGNATURE_FLAG_DENY_VERTEX_SHADER_ROOT_ACCESS;
|
||||
}
|
||||
|
||||
if (!p_stages_processed.has_flag(RenderingDeviceCommons::SHADER_STAGE_FRAGMENT_BIT)) {
|
||||
root_sig_flags |= D3D12_ROOT_SIGNATURE_FLAG_DENY_PIXEL_SHADER_ROOT_ACCESS;
|
||||
}
|
||||
|
||||
if (reflection_data.vertex_input_mask) {
|
||||
root_sig_flags |= D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
|
||||
}
|
||||
|
||||
root_sig_desc.Init_1_1(root_params.size(), root_params.ptr(), 0, nullptr, root_sig_flags);
|
||||
|
||||
// Create and store the root signature and its CRC32.
|
||||
ID3DBlob *error_blob = nullptr;
|
||||
ID3DBlob *root_sig_blob = nullptr;
|
||||
HRESULT res = D3DX12SerializeVersionedRootSignature(HMODULE(lib_d3d12), &root_sig_desc, D3D_ROOT_SIGNATURE_VERSION_1_1, &root_sig_blob, &error_blob);
|
||||
if (SUCCEEDED(res)) {
|
||||
root_signature_bytes.resize(root_sig_blob->GetBufferSize());
|
||||
memcpy(root_signature_bytes.ptrw(), root_sig_blob->GetBufferPointer(), root_sig_blob->GetBufferSize());
|
||||
|
||||
root_signature_crc = crc32(0, nullptr, 0);
|
||||
root_signature_crc = crc32(root_signature_crc, (const Bytef *)root_sig_blob->GetBufferPointer(), root_sig_blob->GetBufferSize());
|
||||
|
||||
return true;
|
||||
} else {
|
||||
if (root_sig_blob != nullptr) {
|
||||
root_sig_blob->Release();
|
||||
}
|
||||
|
||||
String error_string;
|
||||
if (error_blob != nullptr) {
|
||||
error_string = vformat("Serialization of root signature failed with error 0x%08ux and the following message:\n%s", uint32_t(res), String::ascii(Span((char *)error_blob->GetBufferPointer(), error_blob->GetBufferSize())));
|
||||
error_blob->Release();
|
||||
} else {
|
||||
error_string = vformat("Serialization of root signature failed with error 0x%08ux", uint32_t(res));
|
||||
}
|
||||
|
||||
ERR_FAIL_V_MSG(false, error_string);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderingShaderContainerD3D12::_nir_report_resource(uint32_t p_register, uint32_t p_space, uint32_t p_dxil_type, void *p_data) {
|
||||
const GodotNirCallbackUserData &user_data = *(GodotNirCallbackUserData *)p_data;
|
||||
|
||||
// Types based on Mesa's dxil_container.h.
|
||||
static const uint32_t DXIL_RES_SAMPLER = 1;
|
||||
static const ResourceClass DXIL_TYPE_TO_CLASS[] = {
|
||||
RES_CLASS_INVALID, // DXIL_RES_INVALID
|
||||
RES_CLASS_INVALID, // DXIL_RES_SAMPLER
|
||||
RES_CLASS_CBV, // DXIL_RES_CBV
|
||||
RES_CLASS_SRV, // DXIL_RES_SRV_TYPED
|
||||
RES_CLASS_SRV, // DXIL_RES_SRV_RAW
|
||||
RES_CLASS_SRV, // DXIL_RES_SRV_STRUCTURED
|
||||
RES_CLASS_UAV, // DXIL_RES_UAV_TYPED
|
||||
RES_CLASS_UAV, // DXIL_RES_UAV_RAW
|
||||
RES_CLASS_UAV, // DXIL_RES_UAV_STRUCTURED
|
||||
RES_CLASS_INVALID, // DXIL_RES_UAV_STRUCTURED_WITH_COUNTER
|
||||
};
|
||||
|
||||
DEV_ASSERT(p_dxil_type < ARRAY_SIZE(DXIL_TYPE_TO_CLASS));
|
||||
ResourceClass resource_class = DXIL_TYPE_TO_CLASS[p_dxil_type];
|
||||
|
||||
if (p_register == ROOT_CONSTANT_REGISTER && p_space == 0) {
|
||||
DEV_ASSERT(resource_class == RES_CLASS_CBV);
|
||||
user_data.container->reflection_data_d3d12.dxil_push_constant_stages |= (1 << user_data.stage);
|
||||
} else if (p_register == RUNTIME_DATA_REGISTER && p_space == 0) {
|
||||
DEV_ASSERT(resource_class == RES_CLASS_CBV);
|
||||
user_data.container->reflection_data_d3d12.nir_runtime_data_root_param_idx = 1; // Temporary, to be determined later.
|
||||
} else {
|
||||
DEV_ASSERT(p_space == 0);
|
||||
|
||||
uint32_t set = p_register / GODOT_NIR_DESCRIPTOR_SET_MULTIPLIER;
|
||||
uint32_t binding = (p_register % GODOT_NIR_DESCRIPTOR_SET_MULTIPLIER) / GODOT_NIR_BINDING_MULTIPLIER;
|
||||
|
||||
DEV_ASSERT(set < (uint32_t)user_data.container->reflection_binding_set_uniforms_count.size());
|
||||
|
||||
uint32_t binding_start = 0;
|
||||
for (uint32_t i = 0; i < set; i++) {
|
||||
binding_start += user_data.container->reflection_binding_set_uniforms_count[i];
|
||||
}
|
||||
|
||||
[[maybe_unused]] bool found = false;
|
||||
for (uint32_t i = 0; i < user_data.container->reflection_binding_set_uniforms_count[set]; i++) {
|
||||
const ReflectionBindingData &uniform = user_data.container->reflection_binding_set_uniforms_data[binding_start + i];
|
||||
ReflectionBindingDataD3D12 &uniform_d3d12 = user_data.container->reflection_binding_set_uniforms_data_d3d12.ptrw()[binding_start + i];
|
||||
if (uniform.binding != binding) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uniform_d3d12.dxil_stages |= (1 << user_data.stage);
|
||||
if (resource_class != RES_CLASS_INVALID) {
|
||||
DEV_ASSERT(uniform_d3d12.resource_class == (uint32_t)RES_CLASS_INVALID || uniform_d3d12.resource_class == (uint32_t)resource_class);
|
||||
uniform_d3d12.resource_class = resource_class;
|
||||
} else if (p_dxil_type == DXIL_RES_SAMPLER) {
|
||||
uniform_d3d12.has_sampler = (uint32_t)true;
|
||||
} else {
|
||||
DEV_ASSERT(false && "Unknown resource class.");
|
||||
}
|
||||
found = true;
|
||||
}
|
||||
|
||||
DEV_ASSERT(found);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderingShaderContainerD3D12::_nir_report_sc_bit_offset(uint32_t p_sc_id, uint64_t p_bit_offset, void *p_data) {
|
||||
const GodotNirCallbackUserData &user_data = *(GodotNirCallbackUserData *)p_data;
|
||||
[[maybe_unused]] bool found = false;
|
||||
for (int64_t i = 0; i < user_data.container->reflection_specialization_data.size(); i++) {
|
||||
const ReflectionSpecializationData &sc = user_data.container->reflection_specialization_data[i];
|
||||
ReflectionSpecializationDataD3D12 &sc_d3d12 = user_data.container->reflection_specialization_data_d3d12.ptrw()[i];
|
||||
if (sc.constant_id != p_sc_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t offset_idx = SHADER_STAGES_BIT_OFFSET_INDICES[user_data.stage];
|
||||
DEV_ASSERT(sc_d3d12.stages_bit_offsets[offset_idx] == 0);
|
||||
sc_d3d12.stages_bit_offsets[offset_idx] = p_bit_offset;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
DEV_ASSERT(found);
|
||||
}
|
||||
|
||||
void RenderingShaderContainerD3D12::_nir_report_bitcode_bit_offset(uint64_t p_bit_offset, void *p_data) {
|
||||
DEV_ASSERT(p_bit_offset % 8 == 0);
|
||||
|
||||
const GodotNirCallbackUserData &user_data = *(GodotNirCallbackUserData *)p_data;
|
||||
uint32_t offset_idx = SHADER_STAGES_BIT_OFFSET_INDICES[user_data.stage];
|
||||
for (int64_t i = 0; i < user_data.container->reflection_specialization_data.size(); i++) {
|
||||
ReflectionSpecializationDataD3D12 &sc_d3d12 = user_data.container->reflection_specialization_data_d3d12.ptrw()[i];
|
||||
if (sc_d3d12.stages_bit_offsets[offset_idx] == 0) {
|
||||
// This SC has been optimized out from this stage.
|
||||
continue;
|
||||
}
|
||||
|
||||
sc_d3d12.stages_bit_offsets[offset_idx] += p_bit_offset;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void RenderingShaderContainerD3D12::_set_from_shader_reflection_post(const String &p_shader_name, const RenderingDeviceCommons::ShaderReflection &p_reflection) {
|
||||
reflection_binding_set_uniforms_data_d3d12.resize(reflection_binding_set_uniforms_data.size());
|
||||
reflection_specialization_data_d3d12.resize(reflection_specialization_data.size());
|
||||
|
||||
// Sort bindings inside each uniform set. This guarantees the root signature will be generated in the correct order.
|
||||
SortArray<ReflectionBindingData> sorter;
|
||||
uint32_t binding_start = 0;
|
||||
for (uint32_t i = 0; i < reflection_binding_set_uniforms_count.size(); i++) {
|
||||
uint32_t uniform_count = reflection_binding_set_uniforms_count[i];
|
||||
if (uniform_count > 0) {
|
||||
sorter.sort(&reflection_binding_set_uniforms_data.ptrw()[binding_start], uniform_count);
|
||||
binding_start += uniform_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderingShaderContainerD3D12::_set_code_from_spirv(const Vector<RenderingDeviceCommons::ShaderStageSPIRVData> &p_spirv) {
|
||||
#if NIR_ENABLED
|
||||
reflection_data_d3d12.nir_runtime_data_root_param_idx = UINT32_MAX;
|
||||
|
||||
for (int64_t i = 0; i < reflection_specialization_data.size(); i++) {
|
||||
DEV_ASSERT(reflection_specialization_data[i].constant_id < (sizeof(reflection_data_d3d12.spirv_specialization_constants_ids_mask) * 8) && "Constant IDs with values above 31 are not supported.");
|
||||
reflection_data_d3d12.spirv_specialization_constants_ids_mask |= (1 << reflection_specialization_data[i].constant_id);
|
||||
}
|
||||
|
||||
// Translate SPIR-V shaders to DXIL, and collect shader info from the new representation.
|
||||
HashMap<RenderingDeviceCommons::ShaderStage, Vector<uint8_t>> dxil_blobs;
|
||||
Vector<RenderingDeviceCommons::ShaderStage> stages;
|
||||
BitField<RenderingDeviceCommons::ShaderStage> stages_processed = {};
|
||||
if (!_convert_spirv_to_dxil(p_spirv, dxil_blobs, stages, stages_processed)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Patch with default values of specialization constants.
|
||||
DEV_ASSERT(reflection_specialization_data.size() == reflection_specialization_data_d3d12.size());
|
||||
for (int32_t i = 0; i < reflection_specialization_data.size(); i++) {
|
||||
const ReflectionSpecializationData &sc = reflection_specialization_data[i];
|
||||
const ReflectionSpecializationDataD3D12 &sc_d3d12 = reflection_specialization_data_d3d12[i];
|
||||
RenderingDXIL::patch_specialization_constant((RenderingDeviceCommons::PipelineSpecializationConstantType)(sc.type), &sc.int_value, sc_d3d12.stages_bit_offsets, dxil_blobs, true);
|
||||
}
|
||||
|
||||
// Sign.
|
||||
uint32_t shader_index = 0;
|
||||
for (KeyValue<RenderingDeviceCommons::ShaderStage, Vector<uint8_t>> &E : dxil_blobs) {
|
||||
RenderingDXIL::sign_bytecode(E.key, E.value);
|
||||
}
|
||||
|
||||
// Store compressed DXIL blobs as the shaders.
|
||||
shaders.resize(p_spirv.size());
|
||||
for (int64_t i = 0; i < shaders.size(); i++) {
|
||||
const PackedByteArray &dxil_bytes = dxil_blobs[stages[i]];
|
||||
RenderingShaderContainer::Shader &shader = shaders.ptrw()[i];
|
||||
uint32_t compressed_size = 0;
|
||||
shader.shader_stage = stages[i];
|
||||
shader.code_decompressed_size = dxil_bytes.size();
|
||||
shader.code_compressed_bytes.resize(dxil_bytes.size());
|
||||
|
||||
bool compressed = compress_code(dxil_bytes.ptr(), dxil_bytes.size(), shader.code_compressed_bytes.ptrw(), &compressed_size, &shader.code_compression_flags);
|
||||
ERR_FAIL_COND_V_MSG(!compressed, false, vformat("Failed to compress native code to native for SPIR-V #%d.", shader_index));
|
||||
|
||||
shader.code_compressed_bytes.resize(compressed_size);
|
||||
}
|
||||
|
||||
if (!_generate_root_signature(stages_processed)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
ERR_FAIL_V_MSG(false, "Shader compilation is not supported at runtime without NIR.");
|
||||
#endif
|
||||
}
|
||||
|
||||
RenderingShaderContainerD3D12::RenderingShaderContainerD3D12() {
|
||||
// Default empty constructor.
|
||||
}
|
||||
|
||||
RenderingShaderContainerD3D12::RenderingShaderContainerD3D12(void *p_lib_d3d12) {
|
||||
lib_d3d12 = p_lib_d3d12;
|
||||
}
|
||||
|
||||
RenderingShaderContainerD3D12::ShaderReflectionD3D12 RenderingShaderContainerD3D12::get_shader_reflection_d3d12() const {
|
||||
ShaderReflectionD3D12 reflection;
|
||||
reflection.spirv_specialization_constants_ids_mask = reflection_data_d3d12.spirv_specialization_constants_ids_mask;
|
||||
reflection.dxil_push_constant_stages = reflection_data_d3d12.dxil_push_constant_stages;
|
||||
reflection.nir_runtime_data_root_param_idx = reflection_data_d3d12.nir_runtime_data_root_param_idx;
|
||||
reflection.reflection_specialization_data_d3d12 = reflection_specialization_data_d3d12;
|
||||
reflection.root_signature_bytes = root_signature_bytes;
|
||||
reflection.root_signature_crc = root_signature_crc;
|
||||
|
||||
// Transform data vector into a vector of vectors that's easier to user.
|
||||
uint32_t uniform_index = 0;
|
||||
reflection.reflection_binding_set_uniforms_d3d12.resize(reflection_binding_set_uniforms_count.size());
|
||||
for (int64_t i = 0; i < reflection.reflection_binding_set_uniforms_d3d12.size(); i++) {
|
||||
Vector<ReflectionBindingDataD3D12> &uniforms = reflection.reflection_binding_set_uniforms_d3d12.ptrw()[i];
|
||||
uniforms.resize(reflection_binding_set_uniforms_count[i]);
|
||||
for (int64_t j = 0; j < uniforms.size(); j++) {
|
||||
uniforms.ptrw()[j] = reflection_binding_set_uniforms_data_d3d12[uniform_index];
|
||||
uniform_index++;
|
||||
}
|
||||
}
|
||||
|
||||
return reflection;
|
||||
}
|
||||
|
||||
// RenderingShaderContainerFormatD3D12
|
||||
|
||||
void RenderingShaderContainerFormatD3D12::set_lib_d3d12(void *p_lib_d3d12) {
|
||||
lib_d3d12 = p_lib_d3d12;
|
||||
}
|
||||
|
||||
Ref<RenderingShaderContainer> RenderingShaderContainerFormatD3D12::create_container() const {
|
||||
return memnew(RenderingShaderContainerD3D12(lib_d3d12));
|
||||
}
|
||||
|
||||
RenderingDeviceCommons::ShaderLanguageVersion RenderingShaderContainerFormatD3D12::get_shader_language_version() const {
|
||||
// NIR-DXIL is Vulkan 1.1-conformant.
|
||||
return SHADER_LANGUAGE_VULKAN_VERSION_1_1;
|
||||
}
|
||||
|
||||
RenderingDeviceCommons::ShaderSpirvVersion RenderingShaderContainerFormatD3D12::get_shader_spirv_version() const {
|
||||
// The SPIR-V part of Mesa supports 1.6, but:
|
||||
// - SPIRV-Reflect won't be able to parse the compute workgroup size.
|
||||
// - We want to play it safe with NIR-DXIL.
|
||||
return SHADER_SPIRV_VERSION_1_5;
|
||||
}
|
||||
|
||||
RenderingShaderContainerFormatD3D12::RenderingShaderContainerFormatD3D12() {}
|
||||
|
||||
RenderingShaderContainerFormatD3D12::~RenderingShaderContainerFormatD3D12() {}
|
||||
179
drivers/d3d12/rendering_shader_container_d3d12.h
Normal file
179
drivers/d3d12/rendering_shader_container_d3d12.h
Normal file
@@ -0,0 +1,179 @@
|
||||
/**************************************************************************/
|
||||
/* 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 RootSignatureLocation {
|
||||
uint32_t root_param_index = UINT32_MAX;
|
||||
uint32_t range_index = UINT32_MAX;
|
||||
};
|
||||
|
||||
struct ReflectionBindingDataD3D12 {
|
||||
uint32_t resource_class = 0;
|
||||
uint32_t has_sampler = 0;
|
||||
uint32_t dxil_stages = 0;
|
||||
RootSignatureLocation root_signature_locations[2];
|
||||
};
|
||||
|
||||
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<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(const Vector<RenderingDeviceCommons::ShaderStageSPIRVData> &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(const Vector<RenderingDeviceCommons::ShaderStageSPIRVData> &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 String &p_shader_name, const RenderingDeviceCommons::ShaderReflection &p_reflection) override;
|
||||
virtual bool _set_code_from_spirv(const Vector<RenderingDeviceCommons::ShaderStageSPIRVData> &p_spirv) 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<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
drivers/egl/SCsub
Normal file
7
drivers/egl/SCsub
Normal 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")
|
||||
538
drivers/egl/egl_manager.cpp
Normal file
538
drivers/egl/egl_manager.cpp
Normal file
@@ -0,0 +1,538 @@
|
||||
/**************************************************************************/
|
||||
/* 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/crypto/crypto_core.h"
|
||||
#include "core/io/dir_access.h"
|
||||
#include "drivers/gles3/rasterizer_gles3.h"
|
||||
|
||||
#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(DisplayServer::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(DisplayServer::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 ¤t_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 ¤t_display = displays[current_window->gldisplay_id];
|
||||
|
||||
eglSwapBuffers(current_display.egl_display, current_window->egl_surface);
|
||||
}
|
||||
|
||||
void EGLManager::window_make_current(DisplayServer::WindowID p_window_id) {
|
||||
if (p_window_id == DisplayServer::INVALID_WINDOW_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
GLWindow &glwindow = windows[p_window_id];
|
||||
|
||||
if (&glwindow == current_window || !glwindow.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
current_window = &glwindow;
|
||||
|
||||
GLDisplay ¤t_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(DisplayServer::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(DisplayServer::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(DisplayServer::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
|
||||
120
drivers/egl/egl_manager.h
Normal file
120
drivers/egl/egl_manager.h
Normal 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_server.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(DisplayServer::WindowID p_window_id, void *p_display, void *p_native_window, int p_width, int p_height);
|
||||
|
||||
void window_destroy(DisplayServer::WindowID p_window_id);
|
||||
|
||||
void release_current();
|
||||
void swap_buffers();
|
||||
|
||||
void window_make_current(DisplayServer::WindowID p_window_id);
|
||||
|
||||
void set_use_vsync(bool p_use);
|
||||
bool is_using_vsync() const;
|
||||
|
||||
EGLContext get_context(DisplayServer::WindowID p_window_id);
|
||||
EGLDisplay get_display(DisplayServer::WindowID p_window_id);
|
||||
EGLConfig get_config(DisplayServer::WindowID p_window_id);
|
||||
|
||||
Error initialize(void *p_native_display = nullptr);
|
||||
|
||||
EGLManager();
|
||||
virtual ~EGLManager();
|
||||
};
|
||||
|
||||
#endif // EGL_ENABLED
|
||||
25
drivers/gl_context/SCsub
Normal file
25
drivers/gl_context/SCsub
Normal file
@@ -0,0 +1,25 @@
|
||||
#!/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(CPPEXTPATH=[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")
|
||||
11
drivers/gles3/SCsub
Normal file
11
drivers/gles3/SCsub
Normal 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")
|
||||
6
drivers/gles3/effects/SCsub
Normal file
6
drivers/gles3/effects/SCsub
Normal 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")
|
||||
335
drivers/gles3/effects/copy_effects.cpp
Normal file
335
drivers/gles3/effects/copy_effects.cpp
Normal file
@@ -0,0 +1,335 @@
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifdef GLES3_ENABLED
|
||||
|
||||
#include "copy_effects.h"
|
||||
#include "../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(RS::ARRAY_VERTEX, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, nullptr);
|
||||
glEnableVertexAttribArray(RS::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(RS::ARRAY_VERTEX, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, nullptr);
|
||||
glEnableVertexAttribArray(RS::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
|
||||
79
drivers/gles3/effects/copy_effects.h
Normal file
79
drivers/gles3/effects/copy_effects.h
Normal 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
|
||||
213
drivers/gles3/effects/cubemap_filter.cpp
Normal file
213
drivers/gles3/effects/cubemap_filter.cpp
Normal 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifdef GLES3_ENABLED
|
||||
|
||||
#include "cubemap_filter.h"
|
||||
|
||||
#include "../storage/texture_storage.h"
|
||||
#include "core/config/project_settings.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(RS::ARRAY_VERTEX, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, nullptr);
|
||||
glEnableVertexAttribArray(RS::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
|
||||
67
drivers/gles3/effects/cubemap_filter.h
Normal file
67
drivers/gles3/effects/cubemap_filter.h
Normal 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
|
||||
128
drivers/gles3/effects/feed_effects.cpp
Normal file
128
drivers/gles3/effects/feed_effects.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifdef GLES3_ENABLED
|
||||
|
||||
#include "feed_effects.h"
|
||||
|
||||
#ifdef ANDROID_ENABLED
|
||||
#include <GLES3/gl3ext.h>
|
||||
#endif
|
||||
|
||||
#define GL_PROGRAM_POINT_SIZE 0x8642
|
||||
|
||||
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(RS::ARRAY_VERTEX, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, nullptr);
|
||||
glEnableVertexAttribArray(RS::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
|
||||
66
drivers/gles3/effects/feed_effects.h
Normal file
66
drivers/gles3/effects/feed_effects.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/**************************************************************************/
|
||||
/* 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/shader_gles3.h"
|
||||
#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
|
||||
173
drivers/gles3/effects/glow.cpp
Normal file
173
drivers/gles3/effects/glow.cpp
Normal file
@@ -0,0 +1,173 @@
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifdef GLES3_ENABLED
|
||||
|
||||
#include "glow.h"
|
||||
#include "../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(RS::ARRAY_VERTEX, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, nullptr);
|
||||
glEnableVertexAttribArray(RS::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::GLOWLEVEL *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
|
||||
86
drivers/gles3/effects/glow.h
Normal file
86
drivers/gles3/effects/glow.h
Normal 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 GLOW {
|
||||
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 GLOWLEVEL {
|
||||
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 GLOWLEVEL *p_glow_buffers, uint32_t p_view = 0, bool p_use_multiview = false);
|
||||
};
|
||||
|
||||
} //namespace GLES3
|
||||
|
||||
#endif // GLES3_ENABLED
|
||||
153
drivers/gles3/effects/post_effects.cpp
Normal file
153
drivers/gles3/effects/post_effects.cpp
Normal file
@@ -0,0 +1,153 @@
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifdef GLES3_ENABLED
|
||||
|
||||
#include "post_effects.h"
|
||||
#include "../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(RS::ARRAY_VERTEX, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, nullptr);
|
||||
glEnableVertexAttribArray(RS::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, Size2i p_source_size, float p_luminance_multiplier, const Glow::GLOWLEVEL *p_glow_buffers, float p_glow_intensity, 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_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_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::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);
|
||||
}
|
||||
|
||||
// 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
|
||||
66
drivers/gles3/effects/post_effects.h
Normal file
66
drivers/gles3/effects/post_effects.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/**************************************************************************/
|
||||
/* 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/shaders/effects/post.glsl.gen.h"
|
||||
#include "glow.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, Size2i p_source_size, float p_luminance_multiplier, const Glow::GLOWLEVEL *p_glow_buffers, float p_glow_intensity, uint32_t p_view = 0, bool p_use_multiview = false, uint64_t p_spec_constants = 0);
|
||||
};
|
||||
|
||||
} //namespace GLES3
|
||||
|
||||
#endif // GLES3_ENABLED
|
||||
6
drivers/gles3/environment/SCsub
Normal file
6
drivers/gles3/environment/SCsub
Normal 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")
|
||||
66
drivers/gles3/environment/fog.cpp
Normal file
66
drivers/gles3/environment/fog.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifdef GLES3_ENABLED
|
||||
|
||||
#include "fog.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, RS::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();
|
||||
}
|
||||
|
||||
RS::FogVolumeShape Fog::fog_volume_get_shape(RID p_fog_volume) const {
|
||||
return RS::FOG_VOLUME_SHAPE_BOX;
|
||||
}
|
||||
|
||||
#endif // GLES3_ENABLED
|
||||
56
drivers/gles3/environment/fog.h
Normal file
56
drivers/gles3/environment/fog.h
Normal 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, RS::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 RS::FogVolumeShape fog_volume_get_shape(RID p_fog_volume) const override;
|
||||
};
|
||||
|
||||
} // namespace GLES3
|
||||
|
||||
#endif // GLES3_ENABLED
|
||||
143
drivers/gles3/environment/gi.cpp
Normal file
143
drivers/gles3/environment/gi.cpp
Normal file
@@ -0,0 +1,143 @@
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifdef GLES3_ENABLED
|
||||
|
||||
#include "gi.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
|
||||
88
drivers/gles3/environment/gi.h
Normal file
88
drivers/gles3/environment/gi.h
Normal 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
|
||||
2943
drivers/gles3/rasterizer_canvas_gles3.cpp
Normal file
2943
drivers/gles3/rasterizer_canvas_gles3.cpp
Normal file
File diff suppressed because it is too large
Load Diff
389
drivers/gles3/rasterizer_canvas_gles3.h
Normal file
389
drivers/gles3/rasterizer_canvas_gles3.h
Normal file
@@ -0,0 +1,389 @@
|
||||
/**************************************************************************/
|
||||
/* 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 "rasterizer_scene_gles3.h"
|
||||
#include "servers/rendering/renderer_canvas_render.h"
|
||||
#include "servers/rendering/renderer_compositor.h"
|
||||
#include "storage/material_storage.h"
|
||||
#include "storage/texture_storage.h"
|
||||
|
||||
#include "drivers/gles3/shaders/canvas.glsl.gen.h"
|
||||
#include "drivers/gles3/shaders/canvas_occlusion.glsl.gen.h"
|
||||
|
||||
class RasterizerSceneGLES3;
|
||||
|
||||
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 {
|
||||
RS::CanvasOccluderPolygonCullMode cull_mode = RS::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;
|
||||
RS::CanvasItemTextureFilter filter = RS::CANVAS_ITEM_TEXTURE_FILTER_MAX;
|
||||
RS::CanvasItemTextureRepeat repeat = RS::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 = RS::ARRAY_FORMAT_VERTEX | RS::ARRAY_FORMAT_COLOR | RS::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;
|
||||
RS::CanvasItemTextureFilter current_filter_mode = RS::CANVAS_ITEM_TEXTURE_FILTER_MAX;
|
||||
RS::CanvasItemTextureRepeat current_repeat_mode = RS::CANVAS_ITEM_TEXTURE_REPEAT_MAX;
|
||||
|
||||
bool transparent_render_target = false;
|
||||
|
||||
double time = 0.0;
|
||||
|
||||
RS::CanvasItemTextureFilter default_filter = RS::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT;
|
||||
RS::CanvasItemTextureRepeat default_repeat = RS::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, RS::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, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat);
|
||||
void _prepare_canvas_texture(RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::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, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used, RenderingMethod::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, RenderingMethod::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 *¤t_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, RenderingMethod::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(RS::PipelineSource p_source) override { return 0; }
|
||||
|
||||
static RasterizerCanvasGLES3 *get_singleton();
|
||||
RasterizerCanvasGLES3();
|
||||
~RasterizerCanvasGLES3();
|
||||
};
|
||||
|
||||
#endif // GLES3_ENABLED
|
||||
529
drivers/gles3/rasterizer_gles3.cpp
Normal file
529
drivers/gles3/rasterizer_gles3.cpp
Normal file
@@ -0,0 +1,529 @@
|
||||
/**************************************************************************/
|
||||
/* 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"
|
||||
#include "storage/utilities.h"
|
||||
|
||||
#ifdef GLES3_ENABLED
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/io/image.h"
|
||||
#include "core/os/os.h"
|
||||
#include "storage/texture_storage.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
|
||||
|
||||
bool RasterizerGLES3::gles_over_gl = true;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerGLES3::clear_depth(float p_depth) {
|
||||
#ifdef GL_API_ENABLED
|
||||
if (is_gles_over_gl()) {
|
||||
glClearDepth(p_depth);
|
||||
}
|
||||
#endif // GL_API_ENABLED
|
||||
#ifdef GLES_API_ENABLED
|
||||
if (!is_gles_over_gl()) {
|
||||
glClearDepthf(p_depth);
|
||||
}
|
||||
#endif // GLES_API_ENABLED
|
||||
}
|
||||
|
||||
void RasterizerGLES3::clear_stencil(int32_t p_stencil) {
|
||||
glClearStencil(p_stencil);
|
||||
}
|
||||
|
||||
#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() {
|
||||
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);
|
||||
}
|
||||
|
||||
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 (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 (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 (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 (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 (!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());
|
||||
|
||||
// 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(DisplayServer::WindowID p_screen, const 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_DRAW_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(DisplayServer::WindowID p_screen, const 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(const Ref<Image> &p_image, const Color &p_color, bool p_scale, 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 imgrect(0, 0, p_image->get_width(), p_image->get_height());
|
||||
Rect2 screenrect;
|
||||
if (p_scale) {
|
||||
if (win_size.width > win_size.height) {
|
||||
//scale horizontally
|
||||
screenrect.size.y = win_size.height;
|
||||
screenrect.size.x = imgrect.size.x * win_size.height / imgrect.size.y;
|
||||
screenrect.position.x = (win_size.width - screenrect.size.x) / 2;
|
||||
|
||||
} else {
|
||||
//scale vertically
|
||||
screenrect.size.x = win_size.width;
|
||||
screenrect.size.y = imgrect.size.y * win_size.width / imgrect.size.x;
|
||||
screenrect.position.y = (win_size.height - screenrect.size.y) / 2;
|
||||
}
|
||||
} else {
|
||||
screenrect = imgrect;
|
||||
screenrect.position += ((Size2(win_size.width, win_size.height) - screenrect.size) / 2.0).floor();
|
||||
}
|
||||
|
||||
#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 ? RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR : RS::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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user