diff options
Diffstat (limited to 'modules')
285 files changed, 8531 insertions, 1841 deletions
diff --git a/modules/SCsub b/modules/SCsub index 5ff4623743..fcc01e2c1b 100644 --- a/modules/SCsub +++ b/modules/SCsub @@ -22,36 +22,45 @@ env.CommandNoCache( ), ) -# Header to be included in `tests/test_main.cpp` to run module-specific tests. -if env["tests"]: - env.Depends("modules_tests.gen.h", Value(env.module_list)) - env.CommandNoCache( - "modules_tests.gen.h", - Value(env.module_list), - env.Run( - modules_builders.generate_modules_tests, - "Generating modules tests header.", - # NOTE: No need to run in subprocess since this is still executed serially. - subprocess=False, - ), - ) - env.AlwaysBuild("modules_tests.gen.h") vs_sources = [] +test_headers = [] # libmodule_<name>.a for each active module. for name, path in env.module_list.items(): env.modules_sources = [] - if not os.path.isabs(path): - SConscript(name + "/SCsub") # Built-in. - else: - SConscript(path + "/SCsub") # Custom. + # Name for built-in modules, (absolute) path for custom ones. + base_path = path if os.path.isabs(path) else name + SConscript(base_path + "/SCsub") lib = env_modules.add_library("module_%s" % name, env.modules_sources) env.Prepend(LIBS=[lib]) if env["vsproj"]: vs_sources += env.modules_sources + if env["tests"]: + # Lookup potential headers in `tests` subfolder. + import glob + + module_tests = sorted(glob.glob(os.path.join(base_path, "tests", "*.h"))) + if module_tests != []: + test_headers += module_tests + + +# Generate header to be included in `tests/test_main.cpp` to run module-specific tests. +if env["tests"]: + env.Depends("modules_tests.gen.h", test_headers) + env.CommandNoCache( + "modules_tests.gen.h", + test_headers, + env.Run( + modules_builders.generate_modules_tests, + "Generating modules tests header.", + # NOTE: No need to run in subprocess since this is still executed serially. + subprocess=False, + ), + ) + # libmodules.a with only register_module_types. # Must be last so that all libmodule_<name>.a libraries are on the right side # in the linker command. diff --git a/modules/basis_universal/register_types.cpp b/modules/basis_universal/register_types.cpp index 12f9c6fc00..a3c662ba08 100644 --- a/modules/basis_universal/register_types.cpp +++ b/modules/basis_universal/register_types.cpp @@ -32,7 +32,6 @@ #include "core/os/os.h" #include "servers/rendering_server.h" -#include "texture_basisu.h" #ifdef TOOLS_ENABLED #include <encoder/basisu_comp.h> @@ -272,7 +271,6 @@ void register_basis_universal_types() { Image::basis_universal_packer = basis_universal_packer; #endif Image::basis_universal_unpacker = basis_universal_unpacker; - //GDREGISTER_CLASS(TextureBasisU); } void unregister_basis_universal_types() { diff --git a/modules/basis_universal/texture_basisu.cpp b/modules/basis_universal/texture_basisu.cpp deleted file mode 100644 index 1ac4df8d19..0000000000 --- a/modules/basis_universal/texture_basisu.cpp +++ /dev/null @@ -1,218 +0,0 @@ -/*************************************************************************/ -/* texture_basisu.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 "texture_basisu.h" -#if 0 -#include "core/os/os.h" - -#ifdef TOOLS_ENABLED -#include <encoder/basisu_comp.h> -#endif - -#include <transcoder/basisu_transcoder.h> - -void TextureBasisU::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_basisu_data", "data"), &TextureBasisU::set_basisu_data); - ClassDB::bind_method(D_METHOD("get_basisu_data"), &TextureBasisU::get_data); - ClassDB::bind_method(D_METHOD("import"), &TextureBasisU::import); - - ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "basisu_data"), "set_basisu_data", "get_basisu_data"); -}; - -int TextureBasisU::get_width() const { - return tex_size.x; -}; - -int TextureBasisU::get_height() const { - return tex_size.y; -}; - -RID TextureBasisU::get_rid() const { - return texture; -}; - - -bool TextureBasisU::has_alpha() const { - return false; -}; - -void TextureBasisU::set_flags(uint32_t p_flags) { - flags = p_flags; - RenderingServer::get_singleton()->texture_set_flags(texture, p_flags); -}; - -uint32_t TextureBasisU::get_flags() const { - return flags; -}; - - -void TextureBasisU::set_basisu_data(const Vector<uint8_t>& p_data) { - -#ifdef TOOLS_ENABLED - data = p_data; -#endif - - const uint8_t* r = p_data.ptr(); - const void* ptr = r.ptr(); - int size = p_data.size(); - - basist::transcoder_texture_format format; - Image::Format imgfmt; - - if (OS::get_singleton()->has_feature("s3tc")) { - format = basist::cTFBC3; // get this from renderer - imgfmt = Image::FORMAT_DXT5; - - } else if (OS::get_singleton()->has_feature("etc2")) { - format = basist::cTFETC2; - imgfmt = Image::FORMAT_ETC2_RGBA8; - }; - - basist::basisu_transcoder tr(nullptr); - - ERR_FAIL_COND(!tr.validate_header(ptr, size)); - - basist::basisu_image_info info; - tr.get_image_info(ptr, size, info, 0); - tex_size = Size2(info.m_width, info.m_height); - - int block_size = basist::basis_get_bytes_per_block(format); - Vector<uint8_t> gpudata; - gpudata.resize(info.m_total_blocks * block_size); - - { - uint8_t* w = gpudata.ptrw(); - uint8_t* dst = w.ptr(); - for (int i=0; i<gpudata.size(); i++) - dst[i] = 0x00; - - int ofs = 0; - tr.start_transcoding(ptr, size); - for (int i=0; i<info.m_total_levels; i++) { - basist::basisu_image_level_info level; - tr.get_image_level_info(ptr, size, level, 0, i); - - bool ret = tr.transcode_image_level(ptr, size, 0, i, dst + ofs, level.m_total_blocks - i, format); - if (!ret) { - printf("failed! on level %i\n", i); - break; - }; - - ofs += level.m_total_blocks * block_size; - }; - }; - - Ref<Image> img; - img.instantiate(); - img->create(info.m_width, info.m_height, info.m_total_levels > 1, imgfmt, gpudata); - - RenderingServer::get_singleton()->texture_allocate(texture, tex_size.x, tex_size.y, 0, img->get_format(), RS::TEXTURE_TYPE_2D, flags); - RenderingServer::get_singleton()->texture_set_data(texture, img); -}; - -Error TextureBasisU::import(const Ref<Image>& p_img) { - -#ifdef TOOLS_ENABLED - - Vector<uint8_t> budata; - - { - Image::Format format = p_img->get_format(); - if (format != Image::FORMAT_RGB8 && format != Image::FORMAT_RGBA8) { - ERR_FAIL_V(ERR_INVALID_PARAMETER); - return ERR_INVALID_PARAMETER; - }; - - Ref<Image> copy = p_img->duplicate(); - if (format == Image::FORMAT_RGB8) - copy->convert(Image::FORMAT_RGBA8); - - basisu::image buimg(p_img->get_width(), p_img->get_height()); - int size = p_img->get_width() * p_img->get_height() * 4; - - Vector<uint8_t> vec = copy->get_data(); - { - const uint8_t* r = vec.ptr(); - memcpy(buimg.get_ptr(), r.ptr(), size); - }; - - basisu::basis_compressor_params params; - params.m_max_endpoint_clusters = 512; - params.m_max_selector_clusters = 512; - params.m_multithreading = true; - - basisu::job_pool jpool(1); - params.m_pJob_pool = &jpool; - - params.m_mip_gen = p_img->get_mipmap_count() > 0; - params.m_source_images.push_back(buimg); - - basisu::basis_compressor c; - c.init(params); - - int buerr = c.process(); - if (buerr != basisu::basis_compressor::cECSuccess) { - ERR_FAIL_V(ERR_INVALID_PARAMETER); - return ERR_INVALID_PARAMETER; - }; - - const basisu::uint8_vec& buvec = c.get_output_basis_file(); - budata.resize(buvec.size()); - - { - uint8_t* w = budata.ptrw(); - memcpy(w.ptr(), &buvec[0], budata.size()); - }; - }; - - set_basisu_data(budata); - - return OK; -#else - - return ERR_UNAVAILABLE; -#endif -}; - - -Vector<uint8_t> TextureBasisU::get_basisu_data() const { - return data; -}; - -TextureBasisU::TextureBasisU() { - texture = RenderingServer::get_singleton()->texture_create(); -}; - - -TextureBasisU::~TextureBasisU() { - RenderingServer::get_singleton()->free(texture); -}; - -#endif diff --git a/modules/bullet/shape_bullet.h b/modules/bullet/shape_bullet.h index 6377f8915d..dffcadbcdc 100644 --- a/modules/bullet/shape_bullet.h +++ b/modules/bullet/shape_bullet.h @@ -105,7 +105,7 @@ private: }; class SphereShapeBullet : public ShapeBullet { - real_t radius; + real_t radius = 0.0; public: SphereShapeBullet(); @@ -137,8 +137,8 @@ private: }; class CapsuleShapeBullet : public ShapeBullet { - real_t height; - real_t radius; + real_t height = 0.0; + real_t radius = 0.0; public: CapsuleShapeBullet(); @@ -155,8 +155,8 @@ private: }; class CylinderShapeBullet : public ShapeBullet { - real_t height; - real_t radius; + real_t height = 0.0; + real_t radius = 0.0; public: CylinderShapeBullet(); diff --git a/modules/bullet/slider_joint_bullet.cpp b/modules/bullet/slider_joint_bullet.cpp index 61c3b3b0a3..b06cdeaa6a 100644 --- a/modules/bullet/slider_joint_bullet.cpp +++ b/modules/bullet/slider_joint_bullet.cpp @@ -334,7 +334,6 @@ real_t SliderJointBullet::getMaxAngMotorForce() { real_t SliderJointBullet::getLinearPos() { return sliderConstraint->getLinearPos(); - ; } void SliderJointBullet::set_param(PhysicsServer3D::SliderJointParam p_param, real_t p_value) { diff --git a/modules/camera/camera_osx.h b/modules/camera/camera_osx.h index 9d67235839..b0db844599 100644 --- a/modules/camera/camera_osx.h +++ b/modules/camera/camera_osx.h @@ -39,7 +39,6 @@ class CameraOSX : public CameraServer { public: CameraOSX(); - ~CameraOSX(); void update_feeds(); }; diff --git a/modules/camera/camera_osx.mm b/modules/camera/camera_osx.mm index 391006bfc2..d199c31b2f 100644 --- a/modules/camera/camera_osx.mm +++ b/modules/camera/camera_osx.mm @@ -114,18 +114,12 @@ if (output) { [self removeOutput:output]; [output setSampleBufferDelegate:nil queue:nullptr]; - [output release]; output = nullptr; } [self commitConfiguration]; } -- (void)dealloc { - // bye bye - [super dealloc]; -} - - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { // This gets called every time our camera has a new image for us to process. // May need to investigate in a way to throttle this if we get more images then we're rendering frames.. @@ -208,7 +202,6 @@ public: AVCaptureDevice *get_device() const; CameraFeedOSX(); - ~CameraFeedOSX(); void set_device(AVCaptureDevice *p_device); @@ -227,7 +220,6 @@ CameraFeedOSX::CameraFeedOSX() { void CameraFeedOSX::set_device(AVCaptureDevice *p_device) { device = p_device; - [device retain]; // get some info NSString *device_name = p_device.localizedName; @@ -240,18 +232,6 @@ void CameraFeedOSX::set_device(AVCaptureDevice *p_device) { }; }; -CameraFeedOSX::~CameraFeedOSX() { - if (capture_session != nullptr) { - [capture_session release]; - capture_session = nullptr; - }; - - if (device != nullptr) { - [device release]; - device = nullptr; - }; -}; - bool CameraFeedOSX::activate_feed() { if (capture_session) { // Already recording! @@ -282,7 +262,6 @@ void CameraFeedOSX::deactivate_feed() { // end camera capture if we have one if (capture_session) { [capture_session cleanup]; - [capture_session release]; capture_session = nullptr; }; }; @@ -317,8 +296,6 @@ void CameraFeedOSX::deactivate_feed() { // remove notifications [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasConnectedNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasDisconnectedNotification object:nil]; - - [super dealloc]; } @end @@ -376,7 +353,3 @@ CameraOSX::CameraOSX() { // should only have one of these.... device_notifications = [[MyDeviceNotifications alloc] initForServer:this]; }; - -CameraOSX::~CameraOSX() { - [device_notifications release]; -}; diff --git a/modules/csg/csg_gizmos.cpp b/modules/csg/csg_gizmos.cpp index 9a6a33fad3..95a0fc7ada 100644 --- a/modules/csg/csg_gizmos.cpp +++ b/modules/csg/csg_gizmos.cpp @@ -29,6 +29,8 @@ /*************************************************************************/ #include "csg_gizmos.h" + +#include "editor/editor_settings.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "scene/3d/camera_3d.h" @@ -419,7 +421,7 @@ void CSGShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { } } -EditorPluginCSG::EditorPluginCSG(EditorNode *p_editor) { +EditorPluginCSG::EditorPluginCSG() { Ref<CSGShape3DGizmoPlugin> gizmo_plugin = Ref<CSGShape3DGizmoPlugin>(memnew(CSGShape3DGizmoPlugin)); Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin); } diff --git a/modules/csg/csg_gizmos.h b/modules/csg/csg_gizmos.h index 46761370dd..43efe57e64 100644 --- a/modules/csg/csg_gizmos.h +++ b/modules/csg/csg_gizmos.h @@ -57,7 +57,7 @@ class EditorPluginCSG : public EditorPlugin { GDCLASS(EditorPluginCSG, EditorPlugin); public: - EditorPluginCSG(EditorNode *p_editor); + EditorPluginCSG(); }; #endif // CSG_GIZMOS_H diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index 4b36bca02a..39e4751be3 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -491,61 +491,63 @@ Vector<Face3> CSGShape3D::get_faces(uint32_t p_usage_flags) const { } void CSGShape3D::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - Node *parentn = get_parent(); - if (parentn) { - parent = Object::cast_to<CSGShape3D>(parentn); - if (parent) { - set_base(RID()); - root_mesh.unref(); + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + Node *parentn = get_parent(); + if (parentn) { + parent = Object::cast_to<CSGShape3D>(parentn); + if (parent) { + set_base(RID()); + root_mesh.unref(); + } } - } - if (use_collision && is_root_shape()) { - root_collision_shape.instantiate(); - root_collision_instance = PhysicsServer3D::get_singleton()->body_create(); - PhysicsServer3D::get_singleton()->body_set_mode(root_collision_instance, PhysicsServer3D::BODY_MODE_STATIC); - PhysicsServer3D::get_singleton()->body_set_state(root_collision_instance, PhysicsServer3D::BODY_STATE_TRANSFORM, get_global_transform()); - PhysicsServer3D::get_singleton()->body_add_shape(root_collision_instance, root_collision_shape->get_rid()); - PhysicsServer3D::get_singleton()->body_set_space(root_collision_instance, get_world_3d()->get_space()); - PhysicsServer3D::get_singleton()->body_attach_object_instance_id(root_collision_instance, get_instance_id()); - set_collision_layer(collision_layer); - set_collision_mask(collision_mask); - } + if (use_collision && is_root_shape()) { + root_collision_shape.instantiate(); + root_collision_instance = PhysicsServer3D::get_singleton()->body_create(); + PhysicsServer3D::get_singleton()->body_set_mode(root_collision_instance, PhysicsServer3D::BODY_MODE_STATIC); + PhysicsServer3D::get_singleton()->body_set_state(root_collision_instance, PhysicsServer3D::BODY_STATE_TRANSFORM, get_global_transform()); + PhysicsServer3D::get_singleton()->body_add_shape(root_collision_instance, root_collision_shape->get_rid()); + PhysicsServer3D::get_singleton()->body_set_space(root_collision_instance, get_world_3d()->get_space()); + PhysicsServer3D::get_singleton()->body_attach_object_instance_id(root_collision_instance, get_instance_id()); + set_collision_layer(collision_layer); + set_collision_mask(collision_mask); + } - _make_dirty(); - } + _make_dirty(); + } break; - if (p_what == NOTIFICATION_TRANSFORM_CHANGED) { - if (use_collision && is_root_shape() && root_collision_instance.is_valid()) { - PhysicsServer3D::get_singleton()->body_set_state(root_collision_instance, PhysicsServer3D::BODY_STATE_TRANSFORM, get_global_transform()); - } - } + case NOTIFICATION_TRANSFORM_CHANGED: { + if (use_collision && is_root_shape() && root_collision_instance.is_valid()) { + PhysicsServer3D::get_singleton()->body_set_state(root_collision_instance, PhysicsServer3D::BODY_STATE_TRANSFORM, get_global_transform()); + } + } break; - if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) { - if (parent) { - parent->_make_dirty(); - } - } + case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { + if (parent) { + parent->_make_dirty(); + } + } break; - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - if (parent) { - parent->_make_dirty(); - } - } + case NOTIFICATION_VISIBILITY_CHANGED: { + if (parent) { + parent->_make_dirty(); + } + } break; - if (p_what == NOTIFICATION_EXIT_TREE) { - if (parent) { - parent->_make_dirty(); - } - parent = nullptr; + case NOTIFICATION_EXIT_TREE: { + if (parent) { + parent->_make_dirty(); + } + parent = nullptr; - if (use_collision && is_root_shape() && root_collision_instance.is_valid()) { - PhysicsServer3D::get_singleton()->free(root_collision_instance); - root_collision_instance = RID(); - root_collision_shape.unref(); - } - _make_dirty(); + if (use_collision && is_root_shape() && root_collision_instance.is_valid()) { + PhysicsServer3D::get_singleton()->free(root_collision_instance); + root_collision_instance = RID(); + root_collision_shape.unref(); + } + _make_dirty(); + } break; } } @@ -1451,8 +1453,8 @@ Ref<Material> CSGCylinder3D::get_material() const { CSGCylinder3D::CSGCylinder3D() { // defaults - radius = 1.0; - height = 1.0; + radius = 0.5; + height = 2.0; sides = 8; cone = false; smooth_faces = true; @@ -1671,8 +1673,8 @@ Ref<Material> CSGTorus3D::get_material() const { CSGTorus3D::CSGTorus3D() { // defaults - inner_radius = 2.0; - outer_radius = 3.0; + inner_radius = 0.5; + outer_radius = 1.0; sides = 8; ring_sides = 6; smooth_faces = true; diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h index eed995a40e..6da9893368 100644 --- a/modules/csg/csg_shape.h +++ b/modules/csg/csg_shape.h @@ -239,7 +239,7 @@ class CSGBox3D : public CSGPrimitive3D { virtual CSGBrush *_build_brush() override; Ref<Material> material; - Vector3 size = Vector3(2, 2, 2); + Vector3 size = Vector3(1, 1, 1); protected: static void _bind_methods(); diff --git a/modules/csg/doc_classes/CSGBox3D.xml b/modules/csg/doc_classes/CSGBox3D.xml index d64e58ae4d..591110fa38 100644 --- a/modules/csg/doc_classes/CSGBox3D.xml +++ b/modules/csg/doc_classes/CSGBox3D.xml @@ -1,18 +1,20 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="CSGBox3D" inherits="CSGPrimitive3D" version="4.0"> +<class name="CSGBox3D" inherits="CSGPrimitive3D" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A CSG Box shape. </brief_description> <description> This node allows you to create a box for use with the CSG system. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. </description> <tutorials> + <link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link> </tutorials> <members> <member name="material" type="Material" setter="set_material" getter="get_material"> The material used to render the box. </member> - <member name="size" type="Vector3" setter="set_size" getter="get_size" default="Vector3(2, 2, 2)"> + <member name="size" type="Vector3" setter="set_size" getter="get_size" default="Vector3(1, 1, 1)"> The box's width, height and depth. </member> </members> diff --git a/modules/csg/doc_classes/CSGCombiner3D.xml b/modules/csg/doc_classes/CSGCombiner3D.xml index 422c5d35b7..1601602601 100644 --- a/modules/csg/doc_classes/CSGCombiner3D.xml +++ b/modules/csg/doc_classes/CSGCombiner3D.xml @@ -1,11 +1,13 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="CSGCombiner3D" inherits="CSGShape3D" version="4.0"> +<class name="CSGCombiner3D" inherits="CSGShape3D" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A CSG node that allows you to combine other CSG modifiers. </brief_description> <description> For complex arrangements of shapes, it is sometimes needed to add structure to your CSG nodes. The CSGCombiner3D node allows you to create this structure. The node encapsulates the result of the CSG operations of its children. In this way, it is possible to do operations on one set of shapes that are children of one CSGCombiner3D node, and a set of separate operations on a second set of shapes that are children of a second CSGCombiner3D node, and then do an operation that takes the two end results as its input to create the final shape. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. </description> <tutorials> + <link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link> </tutorials> </class> diff --git a/modules/csg/doc_classes/CSGCylinder3D.xml b/modules/csg/doc_classes/CSGCylinder3D.xml index 40e989bfb3..ea026c5ee9 100644 --- a/modules/csg/doc_classes/CSGCylinder3D.xml +++ b/modules/csg/doc_classes/CSGCylinder3D.xml @@ -1,24 +1,26 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="CSGCylinder3D" inherits="CSGPrimitive3D" version="4.0"> +<class name="CSGCylinder3D" inherits="CSGPrimitive3D" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A CSG Cylinder shape. </brief_description> <description> This node allows you to create a cylinder (or cone) for use with the CSG system. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. </description> <tutorials> + <link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link> </tutorials> <members> <member name="cone" type="bool" setter="set_cone" getter="is_cone" default="false"> If [code]true[/code] a cone is created, the [member radius] will only apply to one side. </member> - <member name="height" type="float" setter="set_height" getter="get_height" default="1.0"> + <member name="height" type="float" setter="set_height" getter="get_height" default="2.0"> The height of the cylinder. </member> <member name="material" type="Material" setter="set_material" getter="get_material"> The material used to render the cylinder. </member> - <member name="radius" type="float" setter="set_radius" getter="get_radius" default="1.0"> + <member name="radius" type="float" setter="set_radius" getter="get_radius" default="0.5"> The radius of the cylinder. </member> <member name="sides" type="int" setter="set_sides" getter="get_sides" default="8"> diff --git a/modules/csg/doc_classes/CSGMesh3D.xml b/modules/csg/doc_classes/CSGMesh3D.xml index 42fcb7bd2b..3318fb21c8 100644 --- a/modules/csg/doc_classes/CSGMesh3D.xml +++ b/modules/csg/doc_classes/CSGMesh3D.xml @@ -1,12 +1,14 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="CSGMesh3D" inherits="CSGPrimitive3D" version="4.0"> +<class name="CSGMesh3D" inherits="CSGPrimitive3D" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A CSG Mesh shape that uses a mesh resource. </brief_description> <description> This CSG node allows you to use any mesh resource as a CSG shape, provided it is closed, does not self-intersect, does not contain internal faces and has no edges that connect to more than two faces. See also [CSGPolygon3D] for drawing 2D extruded polygons to be used as CSG nodes. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. </description> <tutorials> + <link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link> </tutorials> <members> <member name="material" type="Material" setter="set_material" getter="get_material"> diff --git a/modules/csg/doc_classes/CSGPolygon3D.xml b/modules/csg/doc_classes/CSGPolygon3D.xml index 5a49eebc7b..cacad21453 100644 --- a/modules/csg/doc_classes/CSGPolygon3D.xml +++ b/modules/csg/doc_classes/CSGPolygon3D.xml @@ -1,12 +1,14 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="CSGPolygon3D" inherits="CSGPrimitive3D" version="4.0"> +<class name="CSGPolygon3D" inherits="CSGPrimitive3D" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Extrudes a 2D polygon shape to create a 3D mesh. </brief_description> <description> An array of 2D points is extruded to quickly and easily create a variety of 3D meshes. See also [CSGMesh3D] for using 3D meshes as CSG nodes. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. </description> <tutorials> + <link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link> </tutorials> <members> <member name="depth" type="float" setter="set_depth" getter="get_depth" default="1.0"> diff --git a/modules/csg/doc_classes/CSGPrimitive3D.xml b/modules/csg/doc_classes/CSGPrimitive3D.xml index 8f4c8b9451..39f4fa320d 100644 --- a/modules/csg/doc_classes/CSGPrimitive3D.xml +++ b/modules/csg/doc_classes/CSGPrimitive3D.xml @@ -1,12 +1,14 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="CSGPrimitive3D" inherits="CSGShape3D" version="4.0"> +<class name="CSGPrimitive3D" inherits="CSGShape3D" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Base class for CSG primitives. </brief_description> <description> Parent class for various CSG primitives. It contains code and functionality that is common between them. It cannot be used directly. Instead use one of the various classes that inherit from it. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. </description> <tutorials> + <link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link> </tutorials> <members> <member name="invert_faces" type="bool" setter="set_invert_faces" getter="is_inverting_faces" default="false"> diff --git a/modules/csg/doc_classes/CSGShape3D.xml b/modules/csg/doc_classes/CSGShape3D.xml index f5031064d6..f1cd28e00f 100644 --- a/modules/csg/doc_classes/CSGShape3D.xml +++ b/modules/csg/doc_classes/CSGShape3D.xml @@ -1,12 +1,14 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="CSGShape3D" inherits="GeometryInstance3D" version="4.0"> +<class name="CSGShape3D" inherits="GeometryInstance3D" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> The CSG base class. </brief_description> <description> This is the CSG base class that provides CSG operation support to the various CSG nodes in Godot. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. </description> <tutorials> + <link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link> </tutorials> <methods> <method name="get_collision_layer_value" qualifiers="const"> diff --git a/modules/csg/doc_classes/CSGSphere3D.xml b/modules/csg/doc_classes/CSGSphere3D.xml index b8dfb4cf5f..227f620a4e 100644 --- a/modules/csg/doc_classes/CSGSphere3D.xml +++ b/modules/csg/doc_classes/CSGSphere3D.xml @@ -1,12 +1,14 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="CSGSphere3D" inherits="CSGPrimitive3D" version="4.0"> +<class name="CSGSphere3D" inherits="CSGPrimitive3D" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A CSG Sphere shape. </brief_description> <description> This node allows you to create a sphere for use with the CSG system. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. </description> <tutorials> + <link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link> </tutorials> <members> <member name="material" type="Material" setter="set_material" getter="get_material"> diff --git a/modules/csg/doc_classes/CSGTorus3D.xml b/modules/csg/doc_classes/CSGTorus3D.xml index 91ee63a4c9..ea555a5449 100644 --- a/modules/csg/doc_classes/CSGTorus3D.xml +++ b/modules/csg/doc_classes/CSGTorus3D.xml @@ -1,21 +1,23 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="CSGTorus3D" inherits="CSGPrimitive3D" version="4.0"> +<class name="CSGTorus3D" inherits="CSGPrimitive3D" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A CSG Torus shape. </brief_description> <description> This node allows you to create a torus for use with the CSG system. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. </description> <tutorials> + <link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link> </tutorials> <members> - <member name="inner_radius" type="float" setter="set_inner_radius" getter="get_inner_radius" default="2.0"> + <member name="inner_radius" type="float" setter="set_inner_radius" getter="get_inner_radius" default="0.5"> The inner radius of the torus. </member> <member name="material" type="Material" setter="set_material" getter="get_material"> The material used to render the torus. </member> - <member name="outer_radius" type="float" setter="set_outer_radius" getter="get_outer_radius" default="3.0"> + <member name="outer_radius" type="float" setter="set_outer_radius" getter="get_outer_radius" default="1.0"> The outer radius of the torus. </member> <member name="ring_sides" type="int" setter="set_ring_sides" getter="get_ring_sides" default="6"> diff --git a/modules/cvtt/SCsub b/modules/cvtt/SCsub index e56177d6e9..1d5a7ff6a3 100644 --- a/modules/cvtt/SCsub +++ b/modules/cvtt/SCsub @@ -11,7 +11,16 @@ thirdparty_obj = [] thirdparty_dir = "#thirdparty/cvtt/" thirdparty_sources = [ - "ConvectionKernels.cpp", + "ConvectionKernels_API.cpp", + "ConvectionKernels_ETC.cpp", + "ConvectionKernels_BC67.cpp", + "ConvectionKernels_IndexSelector.cpp", + "ConvectionKernels_BC6H_IO.cpp", + "ConvectionKernels_S3TC.cpp", + "ConvectionKernels_BC7_PrioData.cpp", + "ConvectionKernels_SingleFile.cpp", + "ConvectionKernels_BCCommon.cpp", + "ConvectionKernels_Util.cpp", ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] diff --git a/modules/cvtt/image_compress_cvtt.cpp b/modules/cvtt/image_compress_cvtt.cpp index 9e0579740b..d18340a2c8 100644 --- a/modules/cvtt/image_compress_cvtt.cpp +++ b/modules/cvtt/image_compress_cvtt.cpp @@ -41,7 +41,7 @@ struct CVTTCompressionJobParams { bool is_hdr = false; bool is_signed = false; int bytes_per_pixel = 0; - + cvtt::BC7EncodingPlan bc7_plan; cvtt::Options options; }; @@ -116,7 +116,7 @@ static void _digest_row_task(const CVTTCompressionJobParams &p_job_params, const cvtt::Kernels::EncodeBC6HU(output_blocks, input_blocks_hdr, p_job_params.options); } } else { - cvtt::Kernels::EncodeBC7(output_blocks, input_blocks_ldr, p_job_params.options); + cvtt::Kernels::EncodeBC7(output_blocks, input_blocks_ldr, p_job_params.options, p_job_params.bc7_plan); } unsigned int num_real_blocks = ((w - x_start) + 3) / 4; @@ -141,7 +141,6 @@ void image_compress_cvtt(Image *p_image, float p_lossy_quality, Image::UsedChann if (p_image->get_format() >= Image::FORMAT_BPTC_RGBA) { return; //do not compress, already compressed } - int w = p_image->get_width(); int h = p_image->get_height(); @@ -153,22 +152,8 @@ void image_compress_cvtt(Image *p_image, float p_lossy_quality, Image::UsedChann } cvtt::Options options; - uint32_t flags = cvtt::Flags::Fastest; - - if (p_lossy_quality > 0.85) { - flags = cvtt::Flags::Ultra; - } else if (p_lossy_quality > 0.75) { - flags = cvtt::Flags::Better; - } else if (p_lossy_quality > 0.55) { - flags = cvtt::Flags::Default; - } else if (p_lossy_quality > 0.35) { - flags = cvtt::Flags::Fast; - } else if (p_lossy_quality > 0.15) { - flags = cvtt::Flags::Faster; - } - + uint32_t flags = cvtt::Flags::Default; flags |= cvtt::Flags::BC7_RespectPunchThrough; - if (p_channels == Image::USED_CHANNELS_RG) { //guessing this is a normal map flags |= cvtt::Flags::Uniform; } @@ -215,12 +200,15 @@ void image_compress_cvtt(Image *p_image, float p_lossy_quality, Image::UsedChann job_queue.job_params.is_signed = is_signed; job_queue.job_params.options = options; job_queue.job_params.bytes_per_pixel = is_hdr ? 6 : 4; + cvtt::Kernels::ConfigureBC7EncodingPlanFromQuality(job_queue.job_params.bc7_plan, 5); -#ifdef NO_THREADS int num_job_threads = 0; -#else - int num_job_threads = OS::get_singleton()->can_use_threads() ? (OS::get_singleton()->get_processor_count() - 1) : 0; -#endif + // Amdahl's law (Wikipedia) + // If a program needs 20 hours to complete using a single thread, but a one-hour portion of the program cannot be parallelized, + // therefore only the remaining 19 hours (p = 0.95) of execution time can be parallelized, then regardless of how many threads are devoted + // to a parallelized execution of this program, the minimum execution time cannot be less than one hour. + // + // The number of executions with different inputs can be increased while the latency is the same. Vector<CVTTCompressionRowTask> tasks; @@ -278,7 +266,6 @@ void image_compress_cvtt(Image *p_image, float p_lossy_quality, Image::UsedChann memdelete(threads_wb[i]); } } - p_image->create(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data); } @@ -388,6 +375,5 @@ void image_decompress_cvtt(Image *p_image) { w >>= 1; h >>= 1; } - p_image->create(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data); } diff --git a/modules/enet/doc_classes/ENetConnection.xml b/modules/enet/doc_classes/ENetConnection.xml index d6b0553eba..14aad0cb39 100644 --- a/modules/enet/doc_classes/ENetConnection.xml +++ b/modules/enet/doc_classes/ENetConnection.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="ENetConnection" inherits="RefCounted" version="4.0"> +<class name="ENetConnection" inherits="RefCounted" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A wrapper class for an [url=http://enet.bespin.org/group__host.html]ENetHost[/url]. </brief_description> diff --git a/modules/enet/doc_classes/ENetMultiplayerPeer.xml b/modules/enet/doc_classes/ENetMultiplayerPeer.xml index 723149843a..2ecf6b4122 100644 --- a/modules/enet/doc_classes/ENetMultiplayerPeer.xml +++ b/modules/enet/doc_classes/ENetMultiplayerPeer.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="ENetMultiplayerPeer" inherits="MultiplayerPeer" version="4.0"> +<class name="ENetMultiplayerPeer" inherits="MultiplayerPeer" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A MultiplayerPeer implementation using the [url=http://enet.bespin.org/index.html]ENet[/url] library. </brief_description> diff --git a/modules/enet/doc_classes/ENetPacketPeer.xml b/modules/enet/doc_classes/ENetPacketPeer.xml index 4116ba17f2..5de5a60853 100644 --- a/modules/enet/doc_classes/ENetPacketPeer.xml +++ b/modules/enet/doc_classes/ENetPacketPeer.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="ENetPacketPeer" inherits="PacketPeer" version="4.0"> +<class name="ENetPacketPeer" inherits="PacketPeer" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A wrapper class for an [url=http://enet.bespin.org/group__peer.html]ENetPeer[/url]. </brief_description> diff --git a/modules/fbx/data/fbx_material.cpp b/modules/fbx/data/fbx_material.cpp index bc638244d8..36e20df3a9 100644 --- a/modules/fbx/data/fbx_material.cpp +++ b/modules/fbx/data/fbx_material.cpp @@ -426,7 +426,7 @@ Ref<StandardMaterial3D> FBXMaterial::import_material(ImportState &state) { // meaning is that approx equal to zero is disabled not actually zero. ;) if (real_value && Math::is_zero_approx(real_value->Value())) { print_verbose("clearcoat real value: " + rtos(real_value->Value())); - spatial_material->set_clearcoat_gloss(1.0 - real_value->Value()); + spatial_material->set_clearcoat_roughness(real_value->Value()); } else { print_error("unsupported value type for clearcoat gloss"); } diff --git a/modules/fbx/editor_scene_importer_fbx.cpp b/modules/fbx/editor_scene_importer_fbx.cpp index 4cca907bf2..758c47eecc 100644 --- a/modules/fbx/editor_scene_importer_fbx.cpp +++ b/modules/fbx/editor_scene_importer_fbx.cpp @@ -123,7 +123,7 @@ Node3D *EditorSceneFormatImporterFBX::import_scene(const String &p_path, uint32_ bool corrupt = false; // safer to check this way as there can be different formatted headers - if (fbx_header_string.find("Kaydara FBX Binary", 0) != -1) { + if (fbx_header_string.contains("Kaydara FBX Binary")) { is_binary = true; print_verbose("[doc] is binary"); diff --git a/modules/fbx/fbx_parser/FBXMeshGeometry.cpp b/modules/fbx/fbx_parser/FBXMeshGeometry.cpp index b3956af762..591f2e5503 100644 --- a/modules/fbx/fbx_parser/FBXMeshGeometry.cpp +++ b/modules/fbx/fbx_parser/FBXMeshGeometry.cpp @@ -212,32 +212,6 @@ MeshGeometry::MeshGeometry(uint64_t id, const ElementPtr element, const std::str m_normals = resolve_vertex_data_array<Vector3>(layer_scope, MappingInformationType, ReferenceInformationType, "Normals"); } else if (layer_type_name == "LayerElementColor") { m_colors = resolve_vertex_data_array<Color>(layer_scope, MappingInformationType, ReferenceInformationType, "Colors", "ColorIndex"); - // NOTE: this is a useful sanity check to ensure you're getting any color data which is not default. - // const Color first_color_check = m_colors.data[0]; - // bool colors_are_all_the_same = true; - // size_t i = 1; - // for(i = 1; i < m_colors.data.size(); i++) - // { - // const Color current_color = m_colors.data[i]; - // if(current_color.is_equal_approx(first_color_check)) - // { - // continue; - // } - // else - // { - // colors_are_all_the_same = false; - // break; - // } - // } - // - // if(colors_are_all_the_same) - // { - // print_error("Color serialisation is not working for vertex colors some should be different in the test asset."); - // } - // else - // { - // print_verbose("Color array has unique colors at index: " + itos(i)); - // } } } } diff --git a/modules/fbx/register_types.cpp b/modules/fbx/register_types.cpp index 3eafb4af45..73e15e38b4 100644 --- a/modules/fbx/register_types.cpp +++ b/modules/fbx/register_types.cpp @@ -31,7 +31,7 @@ #include "register_types.h" #include "editor/editor_node.h" -#include "editor_scene_importer_fbx.h" +#include "modules/fbx/editor_scene_importer_fbx.h" #ifdef TOOLS_ENABLED static void _editor_init() { diff --git a/modules/gdnative/doc_classes/GDNative.xml b/modules/gdnative/doc_classes/GDNative.xml index 4bc149b119..405365ad68 100644 --- a/modules/gdnative/doc_classes/GDNative.xml +++ b/modules/gdnative/doc_classes/GDNative.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="GDNative" inherits="RefCounted" version="4.0"> +<class name="GDNative" inherits="RefCounted" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/gdnative/doc_classes/GDNativeLibrary.xml b/modules/gdnative/doc_classes/GDNativeLibrary.xml index 21df640ebc..66811467fc 100644 --- a/modules/gdnative/doc_classes/GDNativeLibrary.xml +++ b/modules/gdnative/doc_classes/GDNativeLibrary.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="GDNativeLibrary" inherits="Resource" version="4.0"> +<class name="GDNativeLibrary" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> An external library containing functions or script classes to use in Godot. </brief_description> diff --git a/modules/gdnative/doc_classes/NativeScript.xml b/modules/gdnative/doc_classes/NativeScript.xml index 221374a7a4..b752b66f7e 100644 --- a/modules/gdnative/doc_classes/NativeScript.xml +++ b/modules/gdnative/doc_classes/NativeScript.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="NativeScript" inherits="Script" version="4.0"> +<class name="NativeScript" inherits="Script" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/gdnative/doc_classes/PluginScript.xml b/modules/gdnative/doc_classes/PluginScript.xml index ec80ade394..1fe6d95d3b 100644 --- a/modules/gdnative/doc_classes/PluginScript.xml +++ b/modules/gdnative/doc_classes/PluginScript.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="PluginScript" inherits="Script" version="4.0"> +<class name="PluginScript" inherits="Script" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/gdnative/doc_classes/VideoStreamGDNative.xml b/modules/gdnative/doc_classes/VideoStreamGDNative.xml index dc64e8fc18..2b27556fab 100644 --- a/modules/gdnative/doc_classes/VideoStreamGDNative.xml +++ b/modules/gdnative/doc_classes/VideoStreamGDNative.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VideoStreamGDNative" inherits="VideoStream" version="4.0"> +<class name="VideoStreamGDNative" inherits="VideoStream" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> [VideoStream] resource for for video formats implemented via GDNative. </brief_description> diff --git a/modules/gdnative/gdnative/aabb.cpp b/modules/gdnative/gdnative/aabb.cpp index 82aa7215b2..b8909433cc 100644 --- a/modules/gdnative/gdnative/aabb.cpp +++ b/modules/gdnative/gdnative/aabb.cpp @@ -31,6 +31,7 @@ #include "gdnative/aabb.h" #include "core/math/aabb.h" +#include "core/os/memory.h" static_assert(sizeof(godot_aabb) == sizeof(AABB), "AABB size mismatch"); diff --git a/modules/gdnative/gdnative/packed_arrays.cpp b/modules/gdnative/gdnative/packed_arrays.cpp index bb6f0324a8..0c49694e0b 100644 --- a/modules/gdnative/gdnative/packed_arrays.cpp +++ b/modules/gdnative/gdnative/packed_arrays.cpp @@ -32,7 +32,7 @@ #include "core/variant/variant.h" -#include "core/math/vector2.h" +#include "core/math/vector2i.h" #include "core/math/vector3i.h" static_assert(sizeof(godot_packed_byte_array) == sizeof(PackedByteArray), "PackedByteArray size mismatch"); diff --git a/modules/gdnative/gdnative/plane.cpp b/modules/gdnative/gdnative/plane.cpp index 9ac5cfb3b7..41fa0da5db 100644 --- a/modules/gdnative/gdnative/plane.cpp +++ b/modules/gdnative/gdnative/plane.cpp @@ -31,6 +31,7 @@ #include "gdnative/plane.h" #include "core/math/plane.h" +#include "core/os/memory.h" static_assert(sizeof(godot_plane) == sizeof(Plane), "Plane size mismatch"); diff --git a/modules/gdnative/gdnative/rect2.cpp b/modules/gdnative/gdnative/rect2.cpp index f4674850e3..7e0ce76c26 100644 --- a/modules/gdnative/gdnative/rect2.cpp +++ b/modules/gdnative/gdnative/rect2.cpp @@ -31,6 +31,8 @@ #include "gdnative/rect2.h" #include "core/math/rect2.h" +#include "core/math/rect2i.h" +#include "core/os/memory.h" static_assert(sizeof(godot_rect2) == sizeof(Rect2), "Rect2 size mismatch"); static_assert(sizeof(godot_rect2i) == sizeof(Rect2i), "Rect2i size mismatch"); diff --git a/modules/gdnative/gdnative/transform2d.cpp b/modules/gdnative/gdnative/transform2d.cpp index 45ba790dc1..7dc07024e5 100644 --- a/modules/gdnative/gdnative/transform2d.cpp +++ b/modules/gdnative/gdnative/transform2d.cpp @@ -31,6 +31,7 @@ #include "gdnative/transform2d.h" #include "core/math/transform_2d.h" +#include "core/os/memory.h" static_assert(sizeof(godot_transform2d) == sizeof(Transform2D), "Transform2D size mismatch"); diff --git a/modules/gdnative/gdnative/vector2.cpp b/modules/gdnative/gdnative/vector2.cpp index eb8ffd74cd..a8d4281d25 100644 --- a/modules/gdnative/gdnative/vector2.cpp +++ b/modules/gdnative/gdnative/vector2.cpp @@ -31,6 +31,8 @@ #include "gdnative/vector2.h" #include "core/math/vector2.h" +#include "core/math/vector2i.h" +#include "core/os/memory.h" static_assert(sizeof(godot_vector2) == sizeof(Vector2), "Vector2 size mismatch"); static_assert(sizeof(godot_vector2i) == sizeof(Vector2i), "Vector2i size mismatch"); diff --git a/modules/gdnative/gdnative/vector3.cpp b/modules/gdnative/gdnative/vector3.cpp index 26e94d7e5c..37c88c3cca 100644 --- a/modules/gdnative/gdnative/vector3.cpp +++ b/modules/gdnative/gdnative/vector3.cpp @@ -31,6 +31,8 @@ #include "gdnative/vector3.h" #include "core/math/vector3.h" +#include "core/math/vector3i.h" +#include "core/os/memory.h" static_assert(sizeof(godot_vector3) == sizeof(Vector3), "Vector3 size mismatch"); static_assert(sizeof(godot_vector3i) == sizeof(Vector3i), "Vector3i size mismatch"); diff --git a/modules/gdnative/gdnative_library_editor_plugin.cpp b/modules/gdnative/gdnative_library_editor_plugin.cpp index f94464826e..66c8ab7b37 100644 --- a/modules/gdnative/gdnative_library_editor_plugin.cpp +++ b/modules/gdnative/gdnative_library_editor_plugin.cpp @@ -29,10 +29,13 @@ /*************************************************************************/ #ifdef TOOLS_ENABLED + #include "gdnative_library_editor_plugin.h" -#include "gdnative.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "gdnative.h" void GDNativeLibraryEditor::edit(Ref<GDNativeLibrary> p_library) { library = p_library; @@ -315,7 +318,6 @@ GDNativeLibraryEditor::GDNativeLibraryEditor() { NativePlatformConfig platform_ios; platform_ios.name = "iOS"; - platform_ios.entries.push_back("armv7"); platform_ios.entries.push_back("arm64"); platform_ios.entries.push_back("x86_64"); // iOS can use both Static and Dynamic libraries. @@ -408,10 +410,10 @@ void GDNativeLibraryEditorPlugin::make_visible(bool p_visible) { } } -GDNativeLibraryEditorPlugin::GDNativeLibraryEditorPlugin(EditorNode *p_node) { +GDNativeLibraryEditorPlugin::GDNativeLibraryEditorPlugin() { library_editor = memnew(GDNativeLibraryEditor); library_editor->set_custom_minimum_size(Size2(0, 250 * EDSCALE)); - button = p_node->add_bottom_panel_item(TTR("GDNativeLibrary"), library_editor); + button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("GDNativeLibrary"), library_editor); button->hide(); } diff --git a/modules/gdnative/gdnative_library_editor_plugin.h b/modules/gdnative/gdnative_library_editor_plugin.h index 27848893fa..797695366c 100644 --- a/modules/gdnative/gdnative_library_editor_plugin.h +++ b/modules/gdnative/gdnative_library_editor_plugin.h @@ -32,8 +32,14 @@ #define GDNATIVE_LIBRARY_EDITOR_PLUGIN_H #ifdef TOOLS_ENABLED -#include "editor/editor_node.h" + +#include "editor/editor_plugin.h" #include "gdnative.h" +#include "scene/gui/control.h" +#include "scene/gui/menu_button.h" +#include "scene/gui/tree.h" + +class EditorFileDialog; class GDNativeLibraryEditor : public Control { GDCLASS(GDNativeLibraryEditor, Control); @@ -96,7 +102,6 @@ class GDNativeLibraryEditorPlugin : public EditorPlugin { GDCLASS(GDNativeLibraryEditorPlugin, EditorPlugin); GDNativeLibraryEditor *library_editor = nullptr; - EditorNode *editor = nullptr; Button *button = nullptr; public: @@ -106,7 +111,9 @@ public: virtual bool handles(Object *p_node) const override; virtual void make_visible(bool p_visible) override; - GDNativeLibraryEditorPlugin(EditorNode *p_node); + GDNativeLibraryEditorPlugin(); }; -#endif + +#endif // TOOLS_ENABLED + #endif // GDNATIVE_LIBRARY_EDITOR_PLUGIN_H diff --git a/modules/gdnative/gdnative_library_singleton_editor.cpp b/modules/gdnative/gdnative_library_singleton_editor.cpp index 6eb412eccb..ce1f41bdf1 100644 --- a/modules/gdnative/gdnative_library_singleton_editor.cpp +++ b/modules/gdnative/gdnative_library_singleton_editor.cpp @@ -32,6 +32,7 @@ #include "gdnative_library_singleton_editor.h" #include "gdnative.h" +#include "core/config/project_settings.h" #include "editor/editor_node.h" Set<String> GDNativeLibrarySingletonEditor::_find_singletons_recursive(EditorFileSystemDirectory *p_dir) { @@ -182,10 +183,12 @@ void GDNativeLibrarySingletonEditor::_item_edited() { } void GDNativeLibrarySingletonEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - if (is_visible_in_tree()) { - _update_libraries(); - } + switch (p_what) { + case NOTIFICATION_VISIBILITY_CHANGED: { + if (is_visible_in_tree()) { + _update_libraries(); + } + } break; } } diff --git a/modules/gdnative/nativescript/api_generator.cpp b/modules/gdnative/nativescript/api_generator.cpp index ddde28811c..0309d1d9c7 100644 --- a/modules/gdnative/nativescript/api_generator.cpp +++ b/modules/gdnative/nativescript/api_generator.cpp @@ -288,7 +288,7 @@ List<ClassAPI> generate_c_api_classes() { String type; String name = argument.name; - if (argument.name.find(":") != -1) { + if (argument.name.contains(":")) { type = argument.name.get_slice(":", 1); name = argument.name.get_slice(":", 0); } else { @@ -324,7 +324,7 @@ List<ClassAPI> generate_c_api_classes() { property_api.getter = ClassDB::get_property_getter(class_name, p->get().name); property_api.setter = ClassDB::get_property_setter(class_name, p->get().name); - if (p->get().name.find(":") != -1) { + if (p->get().name.contains(":")) { property_api.type = p->get().name.get_slice(":", 1); property_api.name = p->get().name.get_slice(":", 0); } else { @@ -355,7 +355,7 @@ List<ClassAPI> generate_c_api_classes() { //method name method_api.method_name = method_info.name; //method return type - if (method_api.method_name.find(":") != -1) { + if (method_api.method_name.contains(":")) { method_api.return_type = method_api.method_name.get_slice(":", 1); method_api.method_name = method_api.method_name.get_slice(":", 0); } else { @@ -388,7 +388,7 @@ List<ClassAPI> generate_c_api_classes() { arg_name = arg_info.name; - if (arg_info.name.find(":") != -1) { + if (arg_info.name.contains(":")) { arg_type = arg_info.name.get_slice(":", 1); arg_name = arg_info.name.get_slice(":", 0); } else if (arg_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { diff --git a/modules/gdnative/nativescript/nativescript.cpp b/modules/gdnative/nativescript/nativescript.cpp index be304a43f0..95976a8827 100644 --- a/modules/gdnative/nativescript/nativescript.cpp +++ b/modules/gdnative/nativescript/nativescript.cpp @@ -763,17 +763,19 @@ Variant NativeScriptInstance::call(const StringName &p_method, const Variant **p return Variant(); } -void NativeScriptInstance::notification(int p_notification) { +void NativeScriptInstance::notification(int p_what) { #ifdef DEBUG_ENABLED - if (p_notification == MainLoop::NOTIFICATION_CRASH) { - if (current_method_call != StringName("")) { - ERR_PRINT("NativeScriptInstance detected crash on method: " + current_method_call); - current_method_call = ""; - } + switch (p_what) { + case MainLoop::NOTIFICATION_CRASH: { + if (current_method_call != StringName()) { + ERR_PRINT("NativeScriptInstance detected crash on method: " + current_method_call); + current_method_call = ""; + } + } break; } #endif - Variant value = p_notification; + Variant value = p_what; const Variant *args[1] = { &value }; Callable::CallError error; call("_notification", args, 1, error); @@ -1639,7 +1641,6 @@ void NativeReloadNode::_bind_methods() { void NativeReloadNode::_notification(int p_what) { #ifdef TOOLS_ENABLED - switch (p_what) { case NOTIFICATION_APPLICATION_FOCUS_OUT: { if (unloaded) { @@ -1672,7 +1673,6 @@ void NativeReloadNode::_notification(int p_what) { } unloaded = true; - } break; case NOTIFICATION_APPLICATION_FOCUS_IN: { @@ -1736,10 +1736,7 @@ void NativeReloadNode::_notification(int p_what) { for (Set<StringName>::Element *R = libs_to_remove.front(); R; R = R->next()) { NSL->library_gdnatives.erase(R->get()); } - } break; - default: { - }; } #endif } diff --git a/modules/gdnative/nativescript/nativescript.h b/modules/gdnative/nativescript/nativescript.h index 6c47d35abc..2d01de5832 100644 --- a/modules/gdnative/nativescript/nativescript.h +++ b/modules/gdnative/nativescript/nativescript.h @@ -209,7 +209,7 @@ public: virtual void get_method_list(List<MethodInfo> *p_list) const; virtual bool has_method(const StringName &p_method) const; virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error); - virtual void notification(int p_notification); + virtual void notification(int p_what); String to_string(bool *r_valid); virtual Ref<Script> get_script() const; diff --git a/modules/gdnative/pluginscript/pluginscript_script.cpp b/modules/gdnative/pluginscript/pluginscript_script.cpp index 71ab8ef0a2..ec3c9eb4ff 100644 --- a/modules/gdnative/pluginscript/pluginscript_script.cpp +++ b/modules/gdnative/pluginscript/pluginscript_script.cpp @@ -93,7 +93,7 @@ Variant PluginScript::_new(const Variant **p_args, int p_argcount, Callable::Cal REF ref; Object *owner = nullptr; - if (get_instance_base_type() == "") { + if (get_instance_base_type() == StringName()) { owner = memnew(RefCounted); } else { owner = ClassDB::instantiate(get_instance_base_type()); diff --git a/modules/gdnative/register_types.cpp b/modules/gdnative/register_types.cpp index fb682beccc..1121fd0e03 100644 --- a/modules/gdnative/register_types.cpp +++ b/modules/gdnative/register_types.cpp @@ -226,7 +226,7 @@ static void editor_init_callback() { EditorExport::get_singleton()->add_export_plugin(export_plugin); - EditorNode::get_singleton()->add_editor_plugin(memnew(GDNativeLibraryEditorPlugin(EditorNode::get_singleton()))); + EditorNode::get_singleton()->add_editor_plugin(memnew(GDNativeLibraryEditorPlugin)); } #endif diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 33f4198ac1..d9fab01dce 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="@GDScript" version="4.0"> +<class name="@GDScript" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Built-in GDScript functions. </brief_description> @@ -186,6 +186,7 @@ <description> Returns an array with the given range. Range can be 1 argument [code]N[/code] (0 to [code]N[/code] - 1), two arguments ([code]initial[/code], [code]final - 1[/code]) or three arguments ([code]initial[/code], [code]final - 1[/code], [code]increment[/code]). Returns an empty array if the range isn't valid (e.g. [code]range(2, 5, -1)[/code] or [code]range(5, 5, 1)[/code]). Returns an array with the given range. [code]range()[/code] can have 1 argument N ([code]0[/code] to [code]N - 1[/code]), two arguments ([code]initial[/code], [code]final - 1[/code]) or three arguments ([code]initial[/code], [code]final - 1[/code], [code]increment[/code]). [code]increment[/code] can be negative. If [code]increment[/code] is negative, [code]final - 1[/code] will become [code]final + 1[/code]. Also, the initial value must be greater than the final value for the loop to run. + [code]range()(/code] converts all arguments to [int] before processing. [codeblock] print(range(4)) print(range(2, 5)) @@ -211,6 +212,17 @@ 6 3 [/codeblock] + To iterate over [float], convert them in the loop. + [codeblock] + for i in range (3, 0, -1): + print(i / 10.0) + [/codeblock] + Output: + [codeblock] + 0.3 + 0.2 + 0.1 + [/codeblock] </description> </method> <method name="str" qualifiers="vararg"> diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml index 5acb29e748..578e7a34f3 100644 --- a/modules/gdscript/doc_classes/GDScript.xml +++ b/modules/gdscript/doc_classes/GDScript.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="GDScript" inherits="Script" version="4.0"> +<class name="GDScript" inherits="Script" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A script implemented in the GDScript programming language. </brief_description> diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index 5cc295bbab..e3f0ddfc35 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -31,20 +31,9 @@ #include "gdscript_highlighter.h" #include "../gdscript.h" #include "../gdscript_tokenizer.h" +#include "core/config/project_settings.h" #include "editor/editor_settings.h" -static bool _is_char(char32_t c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; -} - -static bool _is_hex_symbol(char32_t c) { - return ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); -} - -static bool _is_bin_symbol(char32_t c) { - return (c == '0' || c == '1'); -} - Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_line) { Dictionary color_map; @@ -102,7 +91,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l color = font_color; bool is_char = !is_symbol(str[j]); bool is_a_symbol = is_symbol(str[j]); - bool is_number = (str[j] >= '0' && str[j] <= '9'); + bool is_number = is_digit(str[j]); /* color regions */ if (is_a_symbol || in_region != -1) { @@ -241,14 +230,14 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } // allow ABCDEF in hex notation - if (is_hex_notation && (_is_hex_symbol(str[j]) || is_number)) { + if (is_hex_notation && (is_hex_digit(str[j]) || is_number)) { is_number = true; } else { is_hex_notation = false; } // disallow anything not a 0 or 1 - if (is_bin_notation && (_is_bin_symbol(str[j]))) { + if (is_bin_notation && (is_binary_digit(str[j]))) { is_number = true; } else if (is_bin_notation) { is_bin_notation = false; @@ -270,7 +259,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } } - if (!in_word && _is_char(str[j]) && !is_number) { + if (!in_word && (is_ascii_char(str[j]) || is_underscore(str[j])) && !is_number) { in_word = true; } @@ -585,7 +574,7 @@ void GDScriptSyntaxHighlighter::_update_cache() { if (E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP) { continue; } - if (name.find("/") != -1) { + if (name.contains("/")) { continue; } member_keywords[name] = member_variable_color; diff --git a/modules/gdscript/editor_templates/CharacterBody2D/basic_movement.gd b/modules/gdscript/editor_templates/CharacterBody2D/basic_movement.gd index 0824d894c5..34b5ba45b7 100644 --- a/modules/gdscript/editor_templates/CharacterBody2D/basic_movement.gd +++ b/modules/gdscript/editor_templates/CharacterBody2D/basic_movement.gd @@ -3,7 +3,7 @@ extends _BASE_ const SPEED: float = 300.0 -const JUMP_FORCE: float = -400.0 +const JUMP_VELOCITY: float = -400.0 # Get the gravity from the project settings to be synced with RigidDynamicBody nodes. var gravity: int = ProjectSettings.get_setting("physics/2d/default_gravity") @@ -12,18 +12,18 @@ var gravity: int = ProjectSettings.get_setting("physics/2d/default_gravity") func _physics_process(delta: float) -> void: # Add the gravity. if not is_on_floor(): - motion_velocity.y += gravity * delta + velocity.y += gravity * delta # Handle Jump. if Input.is_action_just_pressed("ui_accept") and is_on_floor(): - motion_velocity.y = JUMP_FORCE + velocity.y = JUMP_VELOCITY # Get the input direction and handle the movement/deceleration. # As good practice, you should replace UI actions with custom gameplay actions. var direction := Input.get_axis("ui_left", "ui_right") if direction: - motion_velocity.x = direction * SPEED + velocity.x = direction * SPEED else: - motion_velocity.x = move_toward(motion_velocity.x, 0, SPEED) + velocity.x = move_toward(velocity.x, 0, SPEED) move_and_slide() diff --git a/modules/gdscript/editor_templates/CharacterBody3D/basic_movement.gd b/modules/gdscript/editor_templates/CharacterBody3D/basic_movement.gd index ce6d67ae84..cbc9cf1064 100644 --- a/modules/gdscript/editor_templates/CharacterBody3D/basic_movement.gd +++ b/modules/gdscript/editor_templates/CharacterBody3D/basic_movement.gd @@ -3,7 +3,7 @@ extends _BASE_ const SPEED: float = 5.0 -const JUMP_FORCE: float = 4.5 +const JUMP_VELOCITY: float = 4.5 # Get the gravity from the project settings to be synced with RigidDynamicBody nodes. var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity") @@ -12,21 +12,21 @@ var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity") func _physics_process(delta: float) -> void: # Add the gravity. if not is_on_floor(): - motion_velocity.y -= gravity * delta + velocity.y -= gravity * delta # Handle Jump. if Input.is_action_just_pressed("ui_accept") and is_on_floor(): - motion_velocity.y = JUMP_FORCE + velocity.y = JUMP_VELOCITY # Get the input direction and handle the movement/deceleration. # As good practice, you should replace UI actions with custom gameplay actions. var input_dir := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down") var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized() if direction: - motion_velocity.x = direction.x * SPEED - motion_velocity.z = direction.z * SPEED + velocity.x = direction.x * SPEED + velocity.z = direction.z * SPEED else: - motion_velocity.x = move_toward(motion_velocity.x, 0, SPEED) - motion_velocity.z = move_toward(motion_velocity.z, 0, SPEED) + velocity.x = move_toward(velocity.x, 0, SPEED) + velocity.z = move_toward(velocity.z, 0, SPEED) move_and_slide() diff --git a/modules/gdscript/editor_templates/VisualShaderNodeCustom/basic.gd b/modules/gdscript/editor_templates/VisualShaderNodeCustom/basic.gd index 27383b878d..d71f2592fe 100644 --- a/modules/gdscript/editor_templates/VisualShaderNodeCustom/basic.gd +++ b/modules/gdscript/editor_templates/VisualShaderNodeCustom/basic.gd @@ -34,8 +34,5 @@ func _get_output_port_name(port: int) -> String: func _get_output_port_type(port: int) -> int: return PORT_TYPE_SCALAR -func _get_global_code(mode: Shader.Mode) -> String: - return "" - func _get_code(input_vars: Array[String], output_vars: Array[String], mode: Shader.Mode, type: VisualShader.Type) -> String: return output_vars[0] + " = 0.0;" diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 84db97625b..58a788e255 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -43,6 +43,7 @@ #include "gdscript_cache.h" #include "gdscript_compiler.h" #include "gdscript_parser.h" +#include "gdscript_rpc_callable.h" #include "gdscript_warning.h" #ifdef TESTS_ENABLED @@ -827,7 +828,7 @@ Error GDScript::reload(bool p_keep_state) { return OK; } #else - if (source.find("_BASE_") != -1) { + if (source.contains("_BASE_")) { return OK; } #endif @@ -1375,7 +1376,13 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { while (sl) { const Map<StringName, GDScriptFunction *>::Element *E = sl->member_functions.find(p_name); if (E) { - r_ret = Callable(this->owner, E->key()); + Multiplayer::RPCConfig config; + config.name = p_name; + if (sptr->rpc_functions.find(config) != -1) { + r_ret = Callable(memnew(GDScriptRPCCallable(this->owner, E->key()))); + } else { + r_ret = Callable(this->owner, E->key()); + } return true; //index found } sl = sl->_base; @@ -2022,8 +2029,6 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { "preload", "signal", "super", - "trait", - "yield", // var "const", "enum", @@ -2040,6 +2045,11 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { "return", "match", "while", + // These keywords are not implemented currently, but reserved for (potential) future use. + // We highlight them as keywords to make errors easier to understand. + "trait", + "namespace", + "yield", nullptr }; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index d11174227a..204dde4d6a 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -108,7 +108,7 @@ static GDScriptParser::DataType make_native_enum_type(const StringName &p_native GDScriptParser::DataType type; type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; type.kind = GDScriptParser::DataType::ENUM; - type.builtin_type = Variant::OBJECT; + type.builtin_type = Variant::INT; type.is_constant = true; type.is_meta_type = true; @@ -196,7 +196,7 @@ Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::C } if (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::NATIVE) { - if (current_data_type->native_type != StringName("")) { + if (current_data_type->native_type != StringName()) { return check_native_member_name_conflict( p_member_name, p_member_node, @@ -250,7 +250,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, if (!p_class->extends_used) { result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; result.kind = GDScriptParser::DataType::NATIVE; - result.native_type = "RefCounted"; + result.native_type = SNAME("RefCounted"); } else { result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; @@ -438,7 +438,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type StringName first = p_type->type_chain[0]->name; - if (first == "Variant") { + if (first == SNAME("Variant")) { result.kind = GDScriptParser::DataType::VARIANT; if (p_type->type_chain.size() > 1) { push_error(R"("Variant" type don't contain nested types.)", p_type->type_chain[1]); @@ -447,9 +447,9 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type return result; } - if (first == "Object") { + if (first == SNAME("Object")) { result.kind = GDScriptParser::DataType::NATIVE; - result.native_type = "Object"; + result.native_type = SNAME("Object"); if (p_type->type_chain.size() > 1) { push_error(R"("Object" type don't contain nested types.)", p_type->type_chain[1]); return GDScriptParser::DataType(); @@ -646,41 +646,51 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } } - if (member.variable->datatype_specifier != nullptr) { - datatype = specified_type; + // Check if initalizer is an unset identifier (ie: a variable within scope, but declared below) + if (member.variable->initializer && !member.variable->initializer->get_datatype().is_set()) { + if (member.variable->initializer->type == GDScriptParser::Node::IDENTIFIER) { + GDScriptParser::IdentifierNode *initializer_identifier = static_cast<GDScriptParser::IdentifierNode *>(member.variable->initializer); + push_error(vformat(R"(Identifier "%s" must be declared above current variable.)", initializer_identifier->name), member.variable->initializer); + } else { + ERR_PRINT("Parser bug (please report): tried to assign unset node without an identifier."); + } + } else { + if (member.variable->datatype_specifier != nullptr) { + datatype = specified_type; - if (member.variable->initializer != nullptr) { - if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true)) { - // Try reverse test since it can be a masked subtype. - if (!is_type_compatible(member.variable->initializer->get_datatype(), datatype, true)) { - push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer); - } else { - // TODO: Add warning. + if (member.variable->initializer != nullptr) { + if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true, member.variable->initializer)) { + // Try reverse test since it can be a masked subtype. + if (!is_type_compatible(member.variable->initializer->get_datatype(), datatype, true, member.variable->initializer)) { + push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer); + } else { + // TODO: Add warning. + mark_node_unsafe(member.variable->initializer); + member.variable->use_conversion_assign = true; + } + } else if (datatype.builtin_type == Variant::INT && member.variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { +#ifdef DEBUG_ENABLED + parser->push_warning(member.variable->initializer, GDScriptWarning::NARROWING_CONVERSION); +#endif + } + if (member.variable->initializer->get_datatype().is_variant()) { + // TODO: Warn unsafe assign. mark_node_unsafe(member.variable->initializer); member.variable->use_conversion_assign = true; } - } else if (datatype.builtin_type == Variant::INT && member.variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { -#ifdef DEBUG_ENABLED - parser->push_warning(member.variable->initializer, GDScriptWarning::NARROWING_CONVERSION); -#endif } - if (member.variable->initializer->get_datatype().is_variant()) { - // TODO: Warn unsafe assign. - mark_node_unsafe(member.variable->initializer); - member.variable->use_conversion_assign = true; + } else if (member.variable->infer_datatype) { + if (member.variable->initializer == nullptr) { + push_error(vformat(R"(Cannot infer the type of "%s" variable because there's no default value.)", member.variable->identifier->name), member.variable->identifier); + } else if (!datatype.is_set() || datatype.has_no_type()) { + push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value doesn't have a set type.)", member.variable->identifier->name), member.variable->initializer); + } else if (datatype.is_variant()) { + push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value is Variant. Use explicit "Variant" type if this is intended.)", member.variable->identifier->name), member.variable->initializer); + } else if (datatype.builtin_type == Variant::NIL) { + push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value is "null".)", member.variable->identifier->name), member.variable->initializer); } + datatype.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; } - } else if (member.variable->infer_datatype) { - if (member.variable->initializer == nullptr) { - push_error(vformat(R"(Cannot infer the type of "%s" variable because there's no default value.)", member.variable->identifier->name), member.variable->identifier); - } else if (!datatype.is_set() || datatype.has_no_type()) { - push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value doesn't have a set type.)", member.variable->identifier->name), member.variable->initializer); - } else if (datatype.is_variant()) { - push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value is Variant. Use explicit "Variant" type if this is intended.)", member.variable->identifier->name), member.variable->initializer); - } else if (datatype.builtin_type == Variant::NIL) { - push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value is "null".)", member.variable->identifier->name), member.variable->initializer); - } - datatype.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; } datatype.is_constant = false; @@ -1241,7 +1251,7 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { bool list_resolved = false; // Optimize constant range() call to not allocate an array. - // Use int, Vector2, Vector3 instead, which also can be used as range iterators. + // Use int, Vector2i, Vector3i instead, which also can be used as range iterators. if (p_for->list && p_for->list->type == GDScriptParser::Node::CALL) { GDScriptParser::CallNode *call = static_cast<GDScriptParser::CallNode *>(p_for->list); GDScriptParser::Node::Type callee_type = call->get_callee_type(); @@ -1400,9 +1410,9 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable type.is_meta_type = false; if (p_variable->initializer != nullptr) { - if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true)) { + if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true, p_variable->initializer)) { // Try reverse test since it can be a masked subtype. - if (!is_type_compatible(p_variable->initializer->get_datatype(), type, true)) { + if (!is_type_compatible(p_variable->initializer->get_datatype(), type, true, p_variable->initializer)) { push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer); } else { // TODO: Add warning. @@ -1877,11 +1887,11 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig if (!assignee_type.is_variant() && assigned_value_type.is_hard_type()) { if (compatible) { - compatible = is_type_compatible(assignee_type, op_type, true); + compatible = is_type_compatible(assignee_type, op_type, true, p_assignment->assigned_value); if (!compatible) { if (assignee_type.is_hard_type()) { // Try reverse test since it can be a masked subtype. - if (!is_type_compatible(op_type, assignee_type, true)) { + if (!is_type_compatible(op_type, assignee_type, true, p_assignment->assigned_value)) { push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", assigned_value_type.to_string(), assignee_type.to_string()), p_assignment->assigned_value); } else { // TODO: Add warning. @@ -2416,6 +2426,11 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a } validate_call_arg(par_types, default_arg_count, is_vararg, p_call); + if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) { + // Enum type is treated as a dictionary value for function calls. + base_type.is_meta_type = false; + } + if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !is_static) { push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parser->current_function->identifier->name), p_call->callee); } else if (!is_self && base_type.is_meta_type && !is_static) { @@ -2474,17 +2489,24 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { GDScriptParser::DataType cast_type = resolve_datatype(p_cast->cast_type); if (!cast_type.is_set()) { + mark_node_unsafe(p_cast); return; } - cast_type.is_meta_type = false; // The casted value won't be a type name. + cast_type = type_from_metatype(cast_type); // The casted value won't be a type name. p_cast->set_datatype(cast_type); if (!cast_type.is_variant()) { GDScriptParser::DataType op_type = p_cast->operand->get_datatype(); if (!op_type.is_variant()) { bool valid = false; - if (op_type.kind == GDScriptParser::DataType::BUILTIN && cast_type.kind == GDScriptParser::DataType::BUILTIN) { + if (op_type.kind == GDScriptParser::DataType::ENUM && cast_type.kind == GDScriptParser::DataType::ENUM) { + // Enum types are compatible between each other, so it's a safe cast. + valid = true; + } else if (op_type.kind == GDScriptParser::DataType::BUILTIN && op_type.builtin_type == Variant::INT && cast_type.kind == GDScriptParser::DataType::ENUM) { + // Convertint int to enum is always valid. + valid = true; + } else if (op_type.kind == GDScriptParser::DataType::BUILTIN && cast_type.kind == GDScriptParser::DataType::BUILTIN) { valid = Variant::can_convert(op_type.builtin_type, cast_type.builtin_type); } else if (op_type.kind != GDScriptParser::DataType::BUILTIN && cast_type.kind != GDScriptParser::DataType::BUILTIN) { valid = is_type_compatible(cast_type, op_type) || is_type_compatible(op_type, cast_type); @@ -2540,7 +2562,7 @@ void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node) GDScriptParser::DataType result; result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; result.kind = GDScriptParser::DataType::NATIVE; - result.native_type = "Node"; + result.native_type = SNAME("Node"); result.builtin_type = Variant::OBJECT; if (!ClassDB::is_parent_class(parser->current_class->base_type.native_type, result.native_type)) { @@ -2586,6 +2608,34 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod const StringName &name = p_identifier->name; + if (base.kind == GDScriptParser::DataType::ENUM) { + if (base.is_meta_type) { + if (base.enum_values.has(name)) { + p_identifier->is_constant = true; + p_identifier->reduced_value = base.enum_values[name]; + + GDScriptParser::DataType result; + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + result.kind = GDScriptParser::DataType::ENUM; + result.is_constant = true; + result.builtin_type = Variant::INT; + result.native_type = base.native_type; + result.enum_type = base.enum_type; + p_identifier->set_datatype(result); + return; + } else { + // Consider as a Dictionary, so it can be anything. + // This will be evaluated in the next if block. + base.kind = GDScriptParser::DataType::BUILTIN; + base.builtin_type = Variant::DICTIONARY; + base.is_meta_type = false; + } + } else { + push_error(R"(Cannot get property from enum value.)", p_identifier); + return; + } + } + if (base.kind == GDScriptParser::DataType::BUILTIN) { if (base.is_meta_type) { bool valid = true; @@ -2632,32 +2682,6 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod return; } - if (base.kind == GDScriptParser::DataType::ENUM) { - if (base.is_meta_type) { - if (base.enum_values.has(name)) { - p_identifier->is_constant = true; - p_identifier->reduced_value = base.enum_values[name]; - - GDScriptParser::DataType result; - result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - result.kind = GDScriptParser::DataType::ENUM_VALUE; - result.is_constant = true; - result.builtin_type = Variant::INT; - result.native_type = base.native_type; - result.enum_type = name; - p_identifier->set_datatype(result); - } else { - // Consider as a Dictionary - GDScriptParser::DataType dummy; - dummy.kind = GDScriptParser::DataType::VARIANT; - p_identifier->set_datatype(dummy); - } - } else { - push_error(R"(Cannot get property from enum value.)", p_identifier); - } - return; - } - GDScriptParser::ClassNode *base_class = base.class_type; // TODO: Switch current class/function/suite here to avoid misrepresenting identifiers (in recursive reduce calls). @@ -2793,7 +2817,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident if (element.identifier->name == p_identifier->name) { GDScriptParser::DataType type; type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - type.kind = element.parent_enum->identifier ? GDScriptParser::DataType::ENUM_VALUE : GDScriptParser::DataType::BUILTIN; + type.kind = element.parent_enum->identifier ? GDScriptParser::DataType::ENUM : GDScriptParser::DataType::BUILTIN; type.builtin_type = Variant::INT; type.is_constant = true; if (element.parent_enum->identifier) { @@ -3493,6 +3517,9 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_metatype(const GDScriptPars GDScriptParser::DataType result = p_meta_type; result.is_meta_type = false; result.is_constant = false; + if (p_meta_type.kind == GDScriptParser::DataType::ENUM) { + result.builtin_type = Variant::INT; + } return result; } @@ -3507,7 +3534,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo result.builtin_type = p_property.type; if (p_property.type == Variant::OBJECT) { result.kind = GDScriptParser::DataType::NATIVE; - result.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name; + result.native_type = p_property.class_name == StringName() ? SNAME("Object") : p_property.class_name; } else { result.kind = GDScriptParser::DataType::BUILTIN; result.builtin_type = p_property.type; @@ -3549,6 +3576,18 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::CallNode *p_source r_default_arg_count = 0; StringName function_name = p_function; + if (p_base_type.kind == GDScriptParser::DataType::ENUM) { + if (p_base_type.is_meta_type) { + // Enum type can be treated as a dictionary value. + p_base_type.kind = GDScriptParser::DataType::BUILTIN; + p_base_type.builtin_type = Variant::DICTIONARY; + p_base_type.is_meta_type = false; + } else { + push_error("Cannot call function on enum value.", p_source); + return false; + } + } + if (p_base_type.kind == GDScriptParser::DataType::BUILTIN) { // Construct a base type to get methods. Callable::CallError err; @@ -3799,6 +3838,22 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator Variant::Type a_type = p_a.builtin_type; Variant::Type b_type = p_b.builtin_type; + + if (p_a.kind == GDScriptParser::DataType::ENUM) { + if (p_a.is_meta_type) { + a_type = Variant::DICTIONARY; + } else { + a_type = Variant::INT; + } + } + if (p_b.kind == GDScriptParser::DataType::ENUM) { + if (p_b.is_meta_type) { + b_type = Variant::DICTIONARY; + } else { + b_type = Variant::INT; + } + } + Variant::ValidatedOperatorEvaluator op_eval = Variant::get_validated_operator_evaluator(p_operation, a_type, b_type); bool hard_operation = p_a.is_hard_type() && p_b.is_hard_type(); @@ -3828,7 +3883,7 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator } // TODO: Add safe/unsafe return variable (for variant cases) -bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion) const { +bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion, const GDScriptParser::Node *p_source_node) { // These return "true" so it doesn't affect users negatively. ERR_FAIL_COND_V_MSG(!p_target.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset target type"); ERR_FAIL_COND_V_MSG(!p_source.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset value type"); @@ -3848,7 +3903,7 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ if (!valid && p_allow_implicit_conversion) { valid = Variant::can_convert_strict(p_source.builtin_type, p_target.builtin_type); } - if (!valid && p_target.builtin_type == Variant::INT && p_source.kind == GDScriptParser::DataType::ENUM_VALUE) { + if (!valid && p_target.builtin_type == Variant::INT && p_source.kind == GDScriptParser::DataType::ENUM && !p_source.is_meta_type) { // Enum value is also integer. valid = true; } @@ -3869,6 +3924,11 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ if (p_target.kind == GDScriptParser::DataType::ENUM) { if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::INT) { +#ifdef DEBUG_ENABLED + if (p_source_node) { + parser->push_warning(p_source_node, GDScriptWarning::INT_ASSIGNED_TO_ENUM); + } +#endif return true; } if (p_source.kind == GDScriptParser::DataType::ENUM) { @@ -3876,11 +3936,6 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ return true; } } - if (p_source.kind == GDScriptParser::DataType::ENUM_VALUE) { - if (p_source.native_type == p_target.native_type && p_target.enum_values.has(p_source.enum_type)) { - return true; - } - } return false; } @@ -3935,7 +3990,6 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ case GDScriptParser::DataType::VARIANT: case GDScriptParser::DataType::BUILTIN: case GDScriptParser::DataType::ENUM: - case GDScriptParser::DataType::ENUM_VALUE: case GDScriptParser::DataType::UNRESOLVED: break; // Already solved before. } @@ -3972,7 +4026,6 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ case GDScriptParser::DataType::VARIANT: case GDScriptParser::DataType::BUILTIN: case GDScriptParser::DataType::ENUM: - case GDScriptParser::DataType::ENUM_VALUE: case GDScriptParser::DataType::UNRESOLVED: break; // Already solved before. } diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 4cee5cb44a..2697a6ec2b 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -112,7 +112,7 @@ class GDScriptAnalyzer { GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source); GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source); void update_array_literal_element_type(const GDScriptParser::DataType &p_base_type, GDScriptParser::ArrayNode *p_array_literal); - bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false) const; + bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr); void push_error(const String &p_message, const GDScriptParser::Node *p_origin); void mark_node_unsafe(const GDScriptParser::Node *p_node); bool class_exists(const StringName &p_class) const; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index ca125d3a07..108c988add 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -141,10 +141,13 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } } break; case GDScriptParser::DataType::ENUM: - case GDScriptParser::DataType::ENUM_VALUE: result.has_type = true; result.kind = GDScriptDataType::BUILTIN; - result.builtin_type = Variant::INT; + if (p_datatype.is_meta_type) { + result.builtin_type = Variant::DICTIONARY; + } else { + result.builtin_type = Variant::INT; + } break; case GDScriptParser::DataType::UNRESOLVED: { ERR_PRINT("Parser bug: converting unresolved type."); @@ -469,7 +472,14 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } break; case GDScriptParser::Node::CAST: { const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression); - GDScriptDataType cast_type = _gdtype_from_datatype(cn->cast_type->get_datatype()); + GDScriptParser::DataType og_cast_type = cn->cast_type->get_datatype(); + GDScriptDataType cast_type = _gdtype_from_datatype(og_cast_type); + + if (og_cast_type.kind == GDScriptParser::DataType::ENUM) { + // Enum types are usually treated as dictionaries, but in this case we want to cast to an integer. + cast_type.kind = GDScriptDataType::BUILTIN; + cast_type.builtin_type = Variant::INT; + } // Create temporary for result first since it will be deleted last. GDScriptCodeGenerator::Address result = codegen.add_temporary(cast_type); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index d10e120410..6fb95d32ca 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -486,7 +486,7 @@ struct GDScriptCompletionIdentifier { static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg = true) { if (p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { String enum_name = p_info.class_name; - if (enum_name.find(".") == -1) { + if (!enum_name.contains(".")) { return enum_name; } return enum_name.get_slice(".", 1); @@ -610,7 +610,7 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio case GDScriptParser::Node::SUBSCRIPT: { const GDScriptParser::SubscriptNode *sub = static_cast<const GDScriptParser::SubscriptNode *>(par->default_value); if (sub->is_constant) { - if (sub->datatype.kind == GDScriptParser::DataType::ENUM_VALUE) { + if (sub->datatype.kind == GDScriptParser::DataType::ENUM) { def_val = sub->get_datatype().to_string(); } else if (sub->reduced) { const Variant::Type vt = sub->reduced_value.get_type(); @@ -652,7 +652,7 @@ static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Map<String } static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_annotation, int p_argument, const String p_quote_style, Map<String, ScriptCodeCompletionOption> &r_result) { - if (p_annotation->name == "@export_range") { + if (p_annotation->name == SNAME("@export_range")) { if (p_argument == 3 || p_argument == 4) { // Slider hint. ScriptCodeCompletionOption slider1("or_greater", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); @@ -662,7 +662,7 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a slider2.insert_text = slider2.display.quote(p_quote_style); r_result.insert(slider2.display, slider2); } - } else if (p_annotation->name == "@export_exp_easing") { + } else if (p_annotation->name == SNAME("@export_exp_easing")) { if (p_argument == 0 || p_argument == 1) { // Easing hint. ScriptCodeCompletionOption hint1("attenuation", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); @@ -672,7 +672,7 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a hint2.insert_text = hint2.display.quote(p_quote_style); r_result.insert(hint2.display, hint2); } - } else if (p_annotation->name == "@export_node_path") { + } else if (p_annotation->name == SNAME("@export_node_path")) { ScriptCodeCompletionOption node("Node", ScriptCodeCompletionOption::KIND_CLASS); r_result.insert(node.display, node); List<StringName> node_types; @@ -684,7 +684,7 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a ScriptCodeCompletionOption option(E, ScriptCodeCompletionOption::KIND_CLASS); r_result.insert(option.display, option); } - } else if (p_annotation->name == "@warning_ignore") { + } else if (p_annotation->name == SNAME("@warning_ignore")) { for (int warning_code = 0; warning_code < GDScriptWarning::WARNING_MAX; warning_code++) { ScriptCodeCompletionOption warning(GDScriptWarning::get_name_from_code((GDScriptWarning::Code)warning_code).to_lower(), ScriptCodeCompletionOption::KIND_PLAIN_TEXT); r_result.insert(warning.display, warning); @@ -955,7 +955,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base if (E.usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY)) { continue; } - if (E.name.find("/") != -1) { + if (E.name.contains("/")) { continue; } ScriptCodeCompletionOption option(E.name, ScriptCodeCompletionOption::KIND_MEMBER); @@ -1000,7 +1000,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base } for (const PropertyInfo &E : members) { - if (String(E.name).find("/") == -1) { + if (!String(E.name).contains("/")) { ScriptCodeCompletionOption option(E.name, ScriptCodeCompletionOption::KIND_MEMBER); r_result.insert(option.display, option); } @@ -1198,6 +1198,27 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type); static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type); +static bool _is_expression_named_identifier(const GDScriptParser::ExpressionNode *p_expression, const StringName &p_name) { + if (p_expression) { + switch (p_expression->type) { + case GDScriptParser::Node::IDENTIFIER: { + const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(p_expression); + if (id->name == p_name) { + return true; + } + } break; + case GDScriptParser::Node::CAST: { + const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression); + return _is_expression_named_identifier(cn->operand, p_name); + } break; + default: + break; + } + } + + return false; +} + static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::ExpressionNode *p_expression, GDScriptCompletionIdentifier &r_type) { bool found = false; @@ -1384,7 +1405,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, Object *baseptr = base.value; - if (all_is_const && String(call->function_name) == "get_node" && ClassDB::is_parent_class(native_type.native_type, "Node") && args.size()) { + if (all_is_const && call->function_name == SNAME("get_node") && ClassDB::is_parent_class(native_type.native_type, SNAME("Node")) && args.size()) { String arg1 = args[0]; if (arg1.begins_with("/root/")) { String which = arg1.get_slice("/", 2); @@ -1904,6 +1925,14 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & return true; } else if (init->start_line == p_context.current_line) { return false; + // Detects if variable is assigned to itself + } else if (_is_expression_named_identifier(init, member.variable->identifier->name)) { + if (member.variable->initializer->get_datatype().is_set()) { + r_type.type = member.variable->initializer->get_datatype(); + } else if (member.variable->get_datatype().is_set() && !member.variable->get_datatype().is_variant()) { + r_type.type = member.variable->get_datatype(); + } + return true; } else if (_guess_expression_type(p_context, init, r_type)) { return true; } else if (init->get_datatype().is_set() && !init->get_datatype().is_variant()) { @@ -2085,7 +2114,7 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex GDScriptParser::DataType base_type = p_base.type; bool is_static = base_type.is_meta_type; - if (is_static && p_method == "new") { + if (is_static && p_method == SNAME("new")) { r_type.type = base_type; r_type.type.is_meta_type = false; r_type.type.is_constant = false; @@ -2182,7 +2211,7 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex } static void _find_enumeration_candidates(GDScriptParser::CompletionContext &p_context, const String &p_enum_hint, Map<String, ScriptCodeCompletionOption> &r_result) { - if (p_enum_hint.find(".") == -1) { + if (!p_enum_hint.contains(".")) { // Global constant or in the current class. StringName current_enum = p_enum_hint; if (p_context.current_class && p_context.current_class->has_member(current_enum) && p_context.current_class->get_member(current_enum).type == GDScriptParser::ClassNode::Member::ENUM) { @@ -2274,7 +2303,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c r_arghint = _make_arguments_hint(info, p_argidx); } - if (p_argidx == 0 && ClassDB::is_parent_class(class_name, "Node") && (p_method == "get_node" || p_method == "has_node")) { + if (p_argidx == 0 && ClassDB::is_parent_class(class_name, SNAME("Node")) && (p_method == SNAME("get_node") || p_method == SNAME("has_node"))) { // Get autoloads List<PropertyInfo> props; ProjectSettings::get_singleton()->get_property_list(&props); @@ -2291,7 +2320,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } } - if (p_argidx == 0 && method_args > 0 && ClassDB::is_parent_class(class_name, "InputEvent") && p_method.operator String().find("action") != -1) { + if (p_argidx == 0 && method_args > 0 && ClassDB::is_parent_class(class_name, SNAME("InputEvent")) && p_method.operator String().contains("action")) { // Get input actions List<PropertyInfo> props; ProjectSettings::get_singleton()->get_property_list(&props); @@ -2639,7 +2668,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c ClassDB::get_virtual_methods(class_name, &virtual_methods); for (const MethodInfo &mi : virtual_methods) { String method_hint = mi.name; - if (method_hint.find(":") != -1) { + if (method_hint.contains(":")) { method_hint = method_hint.get_slice(":", 0); } method_hint += "("; @@ -2650,7 +2679,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c method_hint += ", "; } String arg = mi.arguments[i].name; - if (arg.find(":") != -1) { + if (arg.contains(":")) { arg = arg.substr(0, arg.find(":")); } method_hint += arg; diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index db663ca48f..3ee664c76d 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -502,6 +502,8 @@ private: List<StackDebug> stack_debug; + Variant _get_default_variant_for_data_type(const GDScriptDataType &p_data_type); + _FORCE_INLINE_ Variant *_get_variant(int p_address, GDScriptInstance *p_instance, Variant *p_stack, String &r_error) const; _FORCE_INLINE_ String _get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 460bd85a86..725b62f6d6 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -519,7 +519,7 @@ void GDScriptParser::parse_program() { // Check for @tool annotation. AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::CLASS_LEVEL); if (annotation != nullptr) { - if (annotation->name == "@tool") { + if (annotation->name == SNAME("@tool")) { // TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?). _is_tool = true; if (previous.type != GDScriptTokenizer::Token::NEWLINE) { @@ -573,7 +573,7 @@ void GDScriptParser::parse_program() { // Check for @icon annotation. AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::CLASS_LEVEL); if (annotation != nullptr) { - if (annotation->name == "@icon") { + if (annotation->name == SNAME("@icon")) { if (previous.type != GDScriptTokenizer::Token::NEWLINE) { push_error(R"(Expected newline after "@icon" annotation.)"); } @@ -2692,12 +2692,13 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode * } } - attribute->is_attribute = true; attribute->base = p_previous_operand; if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier after "." for attribute access.)")) { return attribute; } + + attribute->is_attribute = true; attribute->attribute = parse_identifier(); return attribute; @@ -3503,7 +3504,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node // This is called after the analyzer is done finding the type, so this should be set here. DataType export_type = variable->get_datatype(); - if (p_annotation->name == "@export") { + if (p_annotation->name == SNAME("@export")) { if (variable->datatype_specifier == nullptr && variable->initializer == nullptr) { push_error(R"(Cannot use simple "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation); return false; @@ -3528,7 +3529,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node variable->export_info.hint_string = Variant::get_type_name(export_type.builtin_type); break; case GDScriptParser::DataType::NATIVE: - if (ClassDB::is_parent_class(export_type.native_type, "Resource")) { + if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) { variable->export_info.type = Variant::OBJECT; variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; variable->export_info.hint_string = export_type.native_type; @@ -3740,8 +3741,6 @@ String GDScriptParser::DataType::to_string() const { } case ENUM: return enum_type.operator String() + " (enum)"; - case ENUM_VALUE: - return enum_type.operator String() + " (enum value)"; case UNRESOLVED: return "<unresolved type>"; } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index e4311d2d5e..c09b07282f 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -106,8 +106,7 @@ public: NATIVE, SCRIPT, CLASS, // GDScript. - ENUM, // Full enumeration. - ENUM_VALUE, // Value from enumeration. + ENUM, // Enumeration. VARIANT, // Can be any type. UNRESOLVED, }; @@ -185,8 +184,6 @@ public: return builtin_type == p_other.builtin_type; case NATIVE: case ENUM: - return native_type == p_other.native_type; - case ENUM_VALUE: return native_type == p_other.native_type && enum_type == p_other.enum_type; case SCRIPT: return script_type == p_other.script_type; diff --git a/modules/gdscript/gdscript_rpc_callable.cpp b/modules/gdscript/gdscript_rpc_callable.cpp new file mode 100644 index 0000000000..07e5ed4171 --- /dev/null +++ b/modules/gdscript/gdscript_rpc_callable.cpp @@ -0,0 +1,86 @@ +/*************************************************************************/ +/* gdscript_rpc_callable.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "gdscript_rpc_callable.h" + +#include "core/templates/hashfuncs.h" +#include "scene/main/node.h" + +bool GDScriptRPCCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) { + return p_a->hash() == p_b->hash(); +} + +bool GDScriptRPCCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) { + return p_a->hash() < p_b->hash(); +} + +uint32_t GDScriptRPCCallable::hash() const { + return h; +} + +String GDScriptRPCCallable::get_as_text() const { + String class_name = object->get_class(); + Ref<Script> script = object->get_script(); + return class_name + "(" + script->get_path().get_file() + ")::" + String(method) + " (rpc)"; +} + +CallableCustom::CompareEqualFunc GDScriptRPCCallable::get_compare_equal_func() const { + return compare_equal; +} + +CallableCustom::CompareLessFunc GDScriptRPCCallable::get_compare_less_func() const { + return compare_less; +} + +ObjectID GDScriptRPCCallable::get_object() const { + return object->get_instance_id(); +} + +void GDScriptRPCCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { + r_return_value = object->call(method, p_arguments, p_argcount, r_call_error); +} + +GDScriptRPCCallable::GDScriptRPCCallable(Object *p_object, const StringName &p_method) { + object = p_object; + method = p_method; + h = method.hash(); + h = hash_djb2_one_64(object->get_instance_id(), h); + node = Object::cast_to<Node>(object); + ERR_FAIL_COND_MSG(!node, "RPC can only be defined on class that extends Node."); +} + +void GDScriptRPCCallable::rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const { + if (unlikely(!node)) { + r_call_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; + return; + } + r_call_error.error = Callable::CallError::CALL_OK; + node->rpcp(p_peer_id, method, p_arguments, p_argcount); +} diff --git a/modules/gdscript/gdscript_rpc_callable.h b/modules/gdscript/gdscript_rpc_callable.h new file mode 100644 index 0000000000..2c8734a74b --- /dev/null +++ b/modules/gdscript/gdscript_rpc_callable.h @@ -0,0 +1,61 @@ +/*************************************************************************/ +/* gdscript_rpc_callable.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef GDSCRIPT_RPC_CALLABLE +#define GDSCRIPT_RPC_CALLABLE + +#include "core/variant/callable.h" +#include "core/variant/variant.h" + +class Node; + +class GDScriptRPCCallable : public CallableCustom { + Object *object = nullptr; + Node *node = nullptr; + StringName method; + uint32_t h = 0; + + static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b); + static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b); + +public: + uint32_t hash() const override; + String get_as_text() const override; + CompareEqualFunc get_compare_equal_func() const override; + CompareLessFunc get_compare_less_func() const override; + ObjectID get_object() const override; + void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; + void rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const override; + + GDScriptRPCCallable(Object *p_object, const StringName &p_method); + virtual ~GDScriptRPCCallable() = default; +}; + +#endif // GDSCRIPT_RPC_CALLABLE diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 05ea061798..d3287ab345 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -312,22 +312,6 @@ GDScriptTokenizer::Token GDScriptTokenizer::pop_error() { return error; } -static bool _is_alphanumeric(char32_t c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; -} - -static bool _is_digit(char32_t c) { - return (c >= '0' && c <= '9'); -} - -static bool _is_hex_digit(char32_t c) { - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); -} - -static bool _is_binary_digit(char32_t c) { - return (c == '0' || c == '1'); -} - GDScriptTokenizer::Token GDScriptTokenizer::make_token(Token::Type p_type) { Token token(p_type); token.start_line = start_line; @@ -448,10 +432,10 @@ GDScriptTokenizer::Token GDScriptTokenizer::check_vcs_marker(char32_t p_test, To } GDScriptTokenizer::Token GDScriptTokenizer::annotation() { - if (!_is_alphanumeric(_peek())) { + if (!is_ascii_identifier_char(_peek())) { push_error("Expected annotation identifier after \"@\"."); } - while (_is_alphanumeric(_peek())) { + while (is_ascii_identifier_char(_peek())) { // Consume all identifier characters. _advance(); } @@ -526,7 +510,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() { #define MAX_KEYWORD_LENGTH 10 // Consume all alphanumeric characters. - while (_is_alphanumeric(_peek())) { + while (is_ascii_identifier_char(_peek())) { _advance(); } @@ -612,7 +596,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { bool has_decimal = false; bool has_exponent = false; bool has_error = false; - bool (*digit_check_func)(char32_t) = _is_digit; + bool (*digit_check_func)(char32_t) = is_digit; if (_peek(-1) == '.') { has_decimal = true; @@ -620,20 +604,20 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { if (_peek() == 'x') { // Hexadecimal. base = 16; - digit_check_func = _is_hex_digit; + digit_check_func = is_hex_digit; _advance(); } else if (_peek() == 'b') { // Binary. base = 2; - digit_check_func = _is_binary_digit; + digit_check_func = is_binary_digit; _advance(); } } // Allow '_' to be used in a number, for readability. bool previous_was_underscore = false; - while (digit_check_func(_peek()) || _peek() == '_') { - if (_peek() == '_') { + while (digit_check_func(_peek()) || is_underscore(_peek())) { + if (is_underscore(_peek())) { if (previous_was_underscore) { Token error = make_error(R"(Only one underscore can be used as a numeric separator.)"); error.start_column = column; @@ -682,7 +666,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { _advance(); // Consume decimal digits. - while (_is_digit(_peek()) || _peek() == '_') { + while (is_digit(_peek()) || is_underscore(_peek())) { _advance(); } } @@ -696,7 +680,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { _advance(); } // Consume exponent digits. - if (!_is_digit(_peek())) { + if (!is_digit(_peek())) { Token error = make_error(R"(Expected exponent value after "e".)"); error.start_column = column; error.leftmost_column = column; @@ -705,8 +689,8 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { push_error(error); } previous_was_underscore = false; - while (_is_digit(_peek()) || _peek() == '_') { - if (_peek() == '_') { + while (is_digit(_peek()) || is_underscore(_peek())) { + if (is_underscore(_peek())) { if (previous_was_underscore) { Token error = make_error(R"(Only one underscore can be used as a numeric separator.)"); error.start_column = column; @@ -733,7 +717,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { error.rightmost_column = column + 1; push_error(error); has_error = true; - } else if (_is_alphanumeric(_peek())) { + } else if (is_ascii_identifier_char(_peek())) { // Letter at the end of the number. push_error("Invalid numeric notation."); } @@ -786,6 +770,8 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { } String result; + char32_t prev = 0; + int prev_pos = 0; for (;;) { // Consume actual string. @@ -852,16 +838,18 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { case '\\': escaped = '\\'; break; - case 'u': + case 'U': + case 'u': { // Hexadecimal sequence. - for (int i = 0; i < 4; i++) { + int hex_len = (code == 'U') ? 6 : 4; + for (int j = 0; j < hex_len; j++) { if (_is_at_end()) { return make_error("Unterminated string."); } char32_t digit = _peek(); char32_t value = 0; - if (digit >= '0' && digit <= '9') { + if (is_digit(digit)) { value = digit - '0'; } else if (digit >= 'a' && digit <= 'f') { value = digit - 'a'; @@ -886,7 +874,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { _advance(); } - break; + } break; case '\r': if (_peek() != '\n') { // Carriage return without newline in string. (???) @@ -909,11 +897,53 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { valid_escape = false; break; } + // Parse UTF-16 pair. + if (valid_escape) { + if ((escaped & 0xfffffc00) == 0xd800) { + if (prev == 0) { + prev = escaped; + prev_pos = column - 2; + continue; + } else { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + error.start_column = column - 2; + error.leftmost_column = error.start_column; + push_error(error); + valid_escape = false; + prev = 0; + } + } else if ((escaped & 0xfffffc00) == 0xdc00) { + if (prev == 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired trail surrogate"); + error.start_column = column - 2; + error.leftmost_column = error.start_column; + push_error(error); + valid_escape = false; + } else { + escaped = (prev << 10UL) + escaped - ((0xd800 << 10UL) + 0xdc00 - 0x10000); + prev = 0; + } + } + if (prev != 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + error.start_column = prev_pos; + error.leftmost_column = error.start_column; + push_error(error); + prev = 0; + } + } if (valid_escape) { result += escaped; } } else if (ch == quote_char) { + if (prev != 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + error.start_column = prev_pos; + error.leftmost_column = error.start_column; + push_error(error); + prev = 0; + } _advance(); if (is_multiline) { if (_peek() == quote_char && _peek(1) == quote_char) { @@ -930,6 +960,13 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { break; } } else { + if (prev != 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + error.start_column = prev_pos; + error.leftmost_column = error.start_column; + push_error(error); + prev = 0; + } result += ch; _advance(); if (ch == '\n') { @@ -937,6 +974,13 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { } } } + if (prev != 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + error.start_column = prev_pos; + error.leftmost_column = error.start_column; + push_error(error); + prev = 0; + } // Make the literal. Variant string; @@ -1262,9 +1306,9 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() { line_continuation = false; - if (_is_digit(c)) { + if (is_digit(c)) { return number(); - } else if (_is_alphanumeric(c)) { + } else if (is_ascii_identifier_char(c)) { return potential_identifier(); } @@ -1332,7 +1376,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() { if (_peek() == '.') { _advance(); return make_token(Token::PERIOD_PERIOD); - } else if (_is_digit(_peek())) { + } else if (is_digit(_peek())) { // Number starting with '.'. return number(); } else { diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 95122714f9..6964f27423 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -123,6 +123,34 @@ static String _get_var_type(const Variant *p_var) { } #endif // DEBUG_ENABLED +Variant GDScriptFunction::_get_default_variant_for_data_type(const GDScriptDataType &p_data_type) { + if (p_data_type.kind == GDScriptDataType::BUILTIN) { + if (p_data_type.builtin_type == Variant::ARRAY) { + Array array; + // Typed array. + if (p_data_type.has_container_element_type()) { + const GDScriptDataType &element_type = p_data_type.get_container_element_type(); + array.set_typed( + element_type.kind == GDScriptDataType::BUILTIN ? element_type.builtin_type : Variant::OBJECT, + element_type.native_type, + element_type.script_type); + } + + return array; + } else { + Callable::CallError ce; + Variant variant; + Variant::construct(p_data_type.builtin_type, variant, nullptr, 0, ce); + + ERR_FAIL_COND_V(ce.error != Callable::CallError::CALL_OK, Variant()); + + return variant; + } + } + + return Variant(); +} + String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const { String err_text; @@ -428,7 +456,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODES_TABLE; if (!_code_ptr) { - return Variant(); + return _get_default_variant_for_data_type(return_type); } r_err.error = Callable::CallError::CALL_OK; @@ -467,11 +495,11 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a r_err.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; r_err.argument = _argument_count; - return Variant(); + return _get_default_variant_for_data_type(return_type); } else if (p_argcount < _argument_count - _default_arg_count) { r_err.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; r_err.argument = _argument_count - _default_arg_count; - return Variant(); + return _get_default_variant_for_data_type(return_type); } else { defarg = _argument_count - p_argcount; } @@ -498,7 +526,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a r_err.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_err.argument = i; r_err.expected = argument_types[i].kind == GDScriptDataType::BUILTIN ? argument_types[i].builtin_type : Variant::OBJECT; - return Variant(); + return _get_default_variant_for_data_type(return_type); } if (argument_types[i].kind == GDScriptDataType::BUILTIN) { Variant arg; @@ -3324,6 +3352,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } #endif + // Get a default return type in case of failure + retvalue = _get_default_variant_for_data_type(return_type); + OPCODE_OUT; } diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index 73536f5f8e..ad96e36640 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -152,6 +152,9 @@ String GDScriptWarning::get_message() const { CHECK_SYMBOLS(3); return vformat(R"(The %s '%s' has the same name as a %s.)", symbols[0], symbols[1], symbols[2]); } + case INT_ASSIGNED_TO_ENUM: { + return "Integer used when an enum value is expected. If this is intended cast the integer to the enum type."; + } case WARNING_MAX: break; // Can't happen, but silences warning } @@ -199,6 +202,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "REDUNDANT_AWAIT", "EMPTY_FILE", "SHADOWED_GLOBAL_IDENTIFIER", + "INT_ASSIGNED_TO_ENUM", }; static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names."); diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h index 112b40781a..82efe3568f 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -70,6 +70,7 @@ public: REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine). EMPTY_FILE, // A script file is empty. SHADOWED_GLOBAL_IDENTIFIER, // A global class or function has the same name as variable. + INT_ASSIGNED_TO_ENUM, // An integer value was assigned to an enum-typed variable without casting. WARNING_MAX, }; diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 49f5303ae6..17886181d5 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -541,7 +541,7 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position & for (int c = p_position.character; c >= 0; c--) { start_pos = c; char32_t ch = line[c]; - bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_'; + bool valid_char = is_ascii_identifier_char(ch); if (!valid_char) { break; } @@ -550,7 +550,7 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position & int end_pos = p_position.character; for (int c = p_position.character; c < line.length(); c++) { char32_t ch = line[c]; - bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_'; + bool valid_char = is_ascii_identifier_char(ch); if (!valid_char) { break; } diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp index 33c1c834f1..14337e87da 100644 --- a/modules/gdscript/language_server/gdscript_language_server.cpp +++ b/modules/gdscript/language_server/gdscript_language_server.cpp @@ -45,17 +45,20 @@ GDScriptLanguageServer::GDScriptLanguageServer() { void GDScriptLanguageServer::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_ENTER_TREE: { start(); - break; - case NOTIFICATION_EXIT_TREE: + } break; + + case NOTIFICATION_EXIT_TREE: { stop(); - break; + } break; + case NOTIFICATION_INTERNAL_PROCESS: { if (started && !use_thread) { protocol.poll(); } } break; + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { String host = String(_EDITOR_GET("network/language_server/remote_host")); int port = (int)_EDITOR_GET("network/language_server/remote_port"); diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index ed879b088a..d20b243616 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -63,7 +63,7 @@ void GDScriptWorkspace::apply_new_signal(Object *obj, String function, PackedStr String function_signature = "func " + function; String source = script->get_source_code(); - if (source.find(function_signature) != -1) { + if (source.contains(function_signature)) { return; } @@ -380,7 +380,7 @@ Error GDScriptWorkspace::initialize() { symbol.children.push_back(symbol_arg); } - if (data.qualifiers.find("vararg") != -1) { + if (data.qualifiers.contains("vararg")) { params += params.is_empty() ? "..." : ", ..."; } diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp index 8e503a9df9..a63f9df918 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/modules/gdscript/language_server/lsp.hpp @@ -1940,7 +1940,7 @@ static String marked_documentation(const String &p_bbcode) { line = "\t" + line.substr(code_block_indent, line.length()); } - if (in_code_block && line.find("[/codeblock]") != -1) { + if (in_code_block && line.contains("[/codeblock]")) { line = "\n"; in_code_block = false; } diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index 47772b8039..c2bb2caa29 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -73,23 +73,21 @@ void init_autoloads() { RES res = ResourceLoader::load(info.path); ERR_CONTINUE_MSG(res.is_null(), "Can't autoload: " + info.path); Node *n = nullptr; - if (res->is_class("PackedScene")) { - Ref<PackedScene> ps = res; - n = ps->instantiate(); - } else if (res->is_class("Script")) { - Ref<Script> script_res = res; - StringName ibt = script_res->get_instance_base_type(); + Ref<PackedScene> scn = res; + Ref<Script> script = res; + if (scn.is_valid()) { + n = scn->instantiate(); + } else if (script.is_valid()) { + StringName ibt = script->get_instance_base_type(); bool valid_type = ClassDB::is_parent_class(ibt, "Node"); ERR_CONTINUE_MSG(!valid_type, "Script does not inherit a Node: " + info.path); Object *obj = ClassDB::instantiate(ibt); - ERR_CONTINUE_MSG(obj == nullptr, - "Cannot instance script for autoload, expected 'Node' inheritance, got: " + - String(ibt)); + ERR_CONTINUE_MSG(!obj, "Cannot instance script for autoload, expected 'Node' inheritance, got: " + String(ibt) + "."); n = Object::cast_to<Node>(obj); - n->set_script(script_res); + n->set_script(script); } ERR_CONTINUE_MSG(!n, "Path in autoload not a node or script: " + info.path); @@ -134,12 +132,14 @@ GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_l if (do_init_languages) { init_language(p_source_dir); } +#ifdef DEBUG_ENABLED // Enable all warnings for GDScript, so we can test them. ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/enable", true); for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) { String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower(); ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/" + warning, true); } +#endif // Enable printing to show results _print_line_enabled = true; @@ -153,6 +153,21 @@ GDScriptTestRunner::~GDScriptTestRunner() { } } +#ifndef DEBUG_ENABLED +static String strip_warnings(const String &p_expected) { + // On release builds we don't have warnings. Here we remove them from the output before comparison + // so it doesn't fail just because of difference in warnings. + String expected_no_warnings; + for (String line : p_expected.split("\n")) { + if (line.begins_with(">> ")) { + continue; + } + expected_no_warnings += line + "\n"; + } + return expected_no_warnings.strip_edges() + "\n"; +} +#endif + int GDScriptTestRunner::run_tests() { if (!make_tests()) { FAIL("An error occurred while making the tests."); @@ -170,6 +185,9 @@ int GDScriptTestRunner::run_tests() { GDScriptTest::TestResult result = test.run_test(); String expected = FileAccess::get_file_as_string(test.get_output_file()); +#ifndef DEBUG_ENABLED + expected = strip_warnings(expected); +#endif INFO(test.get_source_file()); if (!result.passed) { INFO(expected); @@ -233,6 +251,22 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) { } } else { if (next.get_extension().to_lower() == "gd") { +#ifndef DEBUG_ENABLED + // On release builds, skip tests marked as debug only. + Error open_err = OK; + FileAccessRef script_file(FileAccess::open(current_dir.plus_file(next), FileAccess::READ, &open_err)); + if (open_err != OK) { + ERR_PRINT(vformat(R"(Couldn't open test file "%s".)", next)); + next = dir->get_next(); + continue; + } else { + if (script_file->get_line() == "#debug-only") { + next = dir->get_next(); + continue; + } + } +#endif + String out_file = next.get_basename() + ".out"; if (!is_generating && !dir->file_exists(out_file)) { ERR_FAIL_V_MSG(false, "Could not find output file for " + next); @@ -387,6 +421,10 @@ bool GDScriptTest::check_output(const String &p_output) const { String got = p_output.strip_edges(); // TODO: may be hacky. got += "\n"; // Make sure to insert newline for CI static checks. +#ifndef DEBUG_ENABLED + expected = strip_warnings(expected); +#endif + return got == expected; } @@ -469,6 +507,7 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { return result; } +#ifdef DEBUG_ENABLED StringBuilder warning_string; for (const GDScriptWarning &E : parser.get_warnings()) { const GDScriptWarning warning = E; @@ -482,6 +521,7 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { warning_string.append("\n"); } result.output += warning_string.as_string(); +#endif // Test compiling. GDScriptCompiler compiler; diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.gd new file mode 100644 index 0000000000..928c886650 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.gd @@ -0,0 +1,10 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } +enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 } + +# Different enum types can't be assigned without casting. +var class_var: MyEnum = MyEnum.ENUM_VALUE_1 + +func test(): + print(class_var) + class_var = MyOtherEnum.OTHER_ENUM_VALUE_2 + print(class_var) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out new file mode 100644 index 0000000000..fde7e92f8c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a value of type "MyOtherEnum (enum)" to a target of type "MyEnum (enum)". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.gd new file mode 100644 index 0000000000..03a1711d7b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.gd @@ -0,0 +1,8 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } +enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 } + +# Different enum types can't be assigned without casting. +var class_var: MyEnum = MyOtherEnum.OTHER_ENUM_VALUE_1 + +func test(): + print(class_var) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out new file mode 100644 index 0000000000..b1710c798d --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Value of type "MyOtherEnum (enum)" cannot be assigned to a variable of type "MyEnum (enum)". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.gd new file mode 100644 index 0000000000..d08d3dd7b2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.gd @@ -0,0 +1,8 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } +enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 } + +func test(): + var local_var: MyEnum = MyEnum.ENUM_VALUE_1 + print(local_var) + local_var = MyOtherEnum.OTHER_ENUM_VALUE_2 + print(local_var) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out new file mode 100644 index 0000000000..fde7e92f8c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a value of type "MyOtherEnum (enum)" to a target of type "MyEnum (enum)". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.gd new file mode 100644 index 0000000000..ca6d892218 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.gd @@ -0,0 +1,6 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } +enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 } + +func test(): + var local_var: MyEnum = MyOtherEnum.OTHER_ENUM_VALUE_1 + print(local_var) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out new file mode 100644 index 0000000000..b1710c798d --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Value of type "MyOtherEnum (enum)" cannot be assigned to a variable of type "MyEnum (enum)". diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.gd new file mode 100644 index 0000000000..edb785c8b6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.gd @@ -0,0 +1,13 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } + +var class_var: int = MyEnum.ENUM_VALUE_1 + +func test(): + print(class_var) + class_var = MyEnum.ENUM_VALUE_2 + print(class_var) + + var local_var: int = MyEnum.ENUM_VALUE_1 + print(local_var) + local_var = MyEnum.ENUM_VALUE_2 + print(local_var) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.out b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.out new file mode 100644 index 0000000000..5f53802c33 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.out @@ -0,0 +1,5 @@ +GDTEST_OK +0 +1 +0 +1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.gd new file mode 100644 index 0000000000..726e4fd413 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.gd @@ -0,0 +1,13 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } + +var class_var: MyEnum = 0 as MyEnum + +func test(): + print(class_var) + class_var = 1 as MyEnum + print(class_var) + + var local_var: MyEnum = 0 as MyEnum + print(local_var) + local_var = 1 as MyEnum + print(local_var) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.out b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.out new file mode 100644 index 0000000000..5f53802c33 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.out @@ -0,0 +1,5 @@ +GDTEST_OK +0 +1 +0 +1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.gd new file mode 100644 index 0000000000..798912c987 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.gd @@ -0,0 +1,14 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } +enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 } + +var class_var: MyEnum = MyOtherEnum.OTHER_ENUM_VALUE_1 as MyEnum + +func test(): + print(class_var) + class_var = MyOtherEnum.OTHER_ENUM_VALUE_2 as MyEnum + print(class_var) + + var local_var: MyEnum = MyOtherEnum.OTHER_ENUM_VALUE_1 as MyEnum + print(local_var) + local_var = MyOtherEnum.OTHER_ENUM_VALUE_2 as MyEnum + print(local_var) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.out b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.out new file mode 100644 index 0000000000..5f53802c33 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.out @@ -0,0 +1,5 @@ +GDTEST_OK +0 +1 +0 +1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.gd new file mode 100644 index 0000000000..2bfb318c3c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.gd @@ -0,0 +1,13 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } + +var class_var: MyEnum = MyEnum.ENUM_VALUE_1 + +func test(): + print(class_var) + class_var = MyEnum.ENUM_VALUE_2 + print(class_var) + + var local_var: MyEnum = MyEnum.ENUM_VALUE_1 + print(local_var) + local_var = MyEnum.ENUM_VALUE_2 + print(local_var) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.out b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.out new file mode 100644 index 0000000000..5f53802c33 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.out @@ -0,0 +1,5 @@ +GDTEST_OK +0 +1 +0 +1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.gd new file mode 100644 index 0000000000..7022d14566 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.gd @@ -0,0 +1,21 @@ +# Enum is equivalent to int for comparisons and operations. +enum MyEnum { + ZERO, + ONE, + TWO, +} + +enum OtherEnum { + ZERO, + ONE, + TWO, +} + +func test(): + print(MyEnum.ZERO == OtherEnum.ZERO) + print(MyEnum.ZERO == 1) + print(MyEnum.ZERO != OtherEnum.ONE) + print(MyEnum.ZERO != 0) + + print(MyEnum.ONE + OtherEnum.TWO) + print(2 - MyEnum.ONE) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.out b/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.out new file mode 100644 index 0000000000..c8f34c11db --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.out @@ -0,0 +1,7 @@ +GDTEST_OK +true +false +true +false +3 +1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.gd new file mode 100644 index 0000000000..885d70408a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.gd @@ -0,0 +1,13 @@ +enum MyEnum { + ZERO, + ONE, + TWO, +} + +func test(): + for key in MyEnum.keys(): + prints(key, MyEnum[key]) + + # https://github.com/godotengine/godot/issues/55491 + for key in MyEnum: + prints(key, MyEnum[key]) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.out b/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.out new file mode 100644 index 0000000000..d29f53109c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.out @@ -0,0 +1,7 @@ +GDTEST_OK +ZERO 0 +ONE 1 +TWO 2 +ZERO 0 +ONE 1 +TWO 2 diff --git a/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.gd b/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.gd new file mode 100644 index 0000000000..2be1024214 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.gd @@ -0,0 +1,15 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } + +# Assigning int value to enum-typed variable without explicit cast causes a warning. +# While it is valid it may be a mistake in the assignment. +var class_var: MyEnum = 0 + +func test(): + print(class_var) + class_var = 1 + print(class_var) + + var local_var: MyEnum = 0 + print(local_var) + local_var = 1 + print(local_var) diff --git a/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.out b/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.out new file mode 100644 index 0000000000..eef13bbff8 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.out @@ -0,0 +1,21 @@ +GDTEST_OK +>> WARNING +>> Line: 5 +>> INT_ASSIGNED_TO_ENUM +>> Integer used when an enum value is expected. If this is intended cast the integer to the enum type. +>> WARNING +>> Line: 9 +>> INT_ASSIGNED_TO_ENUM +>> Integer used when an enum value is expected. If this is intended cast the integer to the enum type. +>> WARNING +>> Line: 12 +>> INT_ASSIGNED_TO_ENUM +>> Integer used when an enum value is expected. If this is intended cast the integer to the enum type. +>> WARNING +>> Line: 14 +>> INT_ASSIGNED_TO_ENUM +>> Integer used when an enum value is expected. If this is intended cast the integer to the enum type. +0 +1 +0 +1 diff --git a/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd index 10780b5379..7b3c112fe9 100644 --- a/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd +++ b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd @@ -1,3 +1,4 @@ +#debug-only func test(): var node := Node.new() var inside_tree = node.is_inside_tree diff --git a/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out index e585c374e2..fe48ade26b 100644 --- a/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out +++ b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out @@ -2,5 +2,5 @@ GDTEST_RUNTIME_ERROR >> SCRIPT ERROR >> on function: test() >> runtime/errors/callable_call_after_free_object.gd ->> 5 +>> 6 >> Attempt to call function 'null::is_inside_tree (Callable)' on a null instance. diff --git a/modules/gltf/doc_classes/GLTFAccessor.xml b/modules/gltf/doc_classes/GLTFAccessor.xml index ae81cae81a..b73a4f8c5f 100644 --- a/modules/gltf/doc_classes/GLTFAccessor.xml +++ b/modules/gltf/doc_classes/GLTFAccessor.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="GLTFAccessor" inherits="Resource" version="4.0"> +<class name="GLTFAccessor" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/gltf/doc_classes/GLTFAnimation.xml b/modules/gltf/doc_classes/GLTFAnimation.xml index 70480c2b38..e2991170a4 100644 --- a/modules/gltf/doc_classes/GLTFAnimation.xml +++ b/modules/gltf/doc_classes/GLTFAnimation.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="GLTFAnimation" inherits="Resource" version="4.0"> +<class name="GLTFAnimation" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/gltf/doc_classes/GLTFBufferView.xml b/modules/gltf/doc_classes/GLTFBufferView.xml index f58aa46508..00b6ccbe7d 100644 --- a/modules/gltf/doc_classes/GLTFBufferView.xml +++ b/modules/gltf/doc_classes/GLTFBufferView.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="GLTFBufferView" inherits="Resource" version="4.0"> +<class name="GLTFBufferView" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/gltf/doc_classes/GLTFCamera.xml b/modules/gltf/doc_classes/GLTFCamera.xml index 3682df5951..9b9eff6141 100644 --- a/modules/gltf/doc_classes/GLTFCamera.xml +++ b/modules/gltf/doc_classes/GLTFCamera.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="GLTFCamera" inherits="Resource" version="4.0"> +<class name="GLTFCamera" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml index ed7c018cb9..7adabdc605 100644 --- a/modules/gltf/doc_classes/GLTFDocument.xml +++ b/modules/gltf/doc_classes/GLTFDocument.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="GLTFDocument" inherits="Resource" version="4.0"> +<class name="GLTFDocument" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/gltf/doc_classes/GLTFDocumentExtension.xml b/modules/gltf/doc_classes/GLTFDocumentExtension.xml index 390bd3b30b..03b4380ff4 100644 --- a/modules/gltf/doc_classes/GLTFDocumentExtension.xml +++ b/modules/gltf/doc_classes/GLTFDocumentExtension.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="GLTFDocumentExtension" inherits="Resource" version="4.0"> +<class name="GLTFDocumentExtension" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/gltf/doc_classes/GLTFDocumentExtensionConvertImporterMesh.xml b/modules/gltf/doc_classes/GLTFDocumentExtensionConvertImporterMesh.xml index 452eec5f4f..70268fc0c9 100644 --- a/modules/gltf/doc_classes/GLTFDocumentExtensionConvertImporterMesh.xml +++ b/modules/gltf/doc_classes/GLTFDocumentExtensionConvertImporterMesh.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="GLTFDocumentExtensionConvertImporterMesh" inherits="GLTFDocumentExtension" version="4.0"> +<class name="GLTFDocumentExtensionConvertImporterMesh" inherits="GLTFDocumentExtension" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/gltf/doc_classes/GLTFLight.xml b/modules/gltf/doc_classes/GLTFLight.xml index b4f03cd1ed..354cd48a06 100644 --- a/modules/gltf/doc_classes/GLTFLight.xml +++ b/modules/gltf/doc_classes/GLTFLight.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="GLTFLight" inherits="Resource" version="4.0"> +<class name="GLTFLight" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/gltf/doc_classes/GLTFMesh.xml b/modules/gltf/doc_classes/GLTFMesh.xml index 58853217e2..bac351cc20 100644 --- a/modules/gltf/doc_classes/GLTFMesh.xml +++ b/modules/gltf/doc_classes/GLTFMesh.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="GLTFMesh" inherits="Resource" version="4.0"> +<class name="GLTFMesh" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/gltf/doc_classes/GLTFNode.xml b/modules/gltf/doc_classes/GLTFNode.xml index f27965ea07..e933e6046a 100644 --- a/modules/gltf/doc_classes/GLTFNode.xml +++ b/modules/gltf/doc_classes/GLTFNode.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="GLTFNode" inherits="Resource" version="4.0"> +<class name="GLTFNode" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/gltf/doc_classes/GLTFSkeleton.xml b/modules/gltf/doc_classes/GLTFSkeleton.xml index 037c3545a6..dad985e886 100644 --- a/modules/gltf/doc_classes/GLTFSkeleton.xml +++ b/modules/gltf/doc_classes/GLTFSkeleton.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="GLTFSkeleton" inherits="Resource" version="4.0"> +<class name="GLTFSkeleton" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/gltf/doc_classes/GLTFSkin.xml b/modules/gltf/doc_classes/GLTFSkin.xml index ad4f017584..b6a2bdb957 100644 --- a/modules/gltf/doc_classes/GLTFSkin.xml +++ b/modules/gltf/doc_classes/GLTFSkin.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="GLTFSkin" inherits="Resource" version="4.0"> +<class name="GLTFSkin" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/gltf/doc_classes/GLTFSpecGloss.xml b/modules/gltf/doc_classes/GLTFSpecGloss.xml index 6b8f86ed1c..8433cf8dd7 100644 --- a/modules/gltf/doc_classes/GLTFSpecGloss.xml +++ b/modules/gltf/doc_classes/GLTFSpecGloss.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="GLTFSpecGloss" inherits="Resource" version="4.0"> +<class name="GLTFSpecGloss" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml index 6d03d0ecf8..a59fa900bc 100644 --- a/modules/gltf/doc_classes/GLTFState.xml +++ b/modules/gltf/doc_classes/GLTFState.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="GLTFState" inherits="Resource" version="4.0"> +<class name="GLTFState" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/gltf/doc_classes/GLTFTexture.xml b/modules/gltf/doc_classes/GLTFTexture.xml index 7c88d2318e..c0bc424168 100644 --- a/modules/gltf/doc_classes/GLTFTexture.xml +++ b/modules/gltf/doc_classes/GLTFTexture.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="GLTFTexture" inherits="Resource" version="4.0"> +<class name="GLTFTexture" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/gltf/editor_scene_exporter_gltf_plugin.cpp b/modules/gltf/editor_scene_exporter_gltf_plugin.cpp index fd5741605c..601c70791c 100644 --- a/modules/gltf/editor_scene_exporter_gltf_plugin.cpp +++ b/modules/gltf/editor_scene_exporter_gltf_plugin.cpp @@ -30,10 +30,12 @@ #if TOOLS_ENABLED #include "editor_scene_exporter_gltf_plugin.h" + #include "core/config/project_settings.h" #include "core/error/error_list.h" #include "core/object/object.h" #include "core/templates/vector.h" +#include "editor/editor_file_dialog.h" #include "editor/editor_file_system.h" #include "gltf_document.h" #include "gltf_state.h" @@ -51,10 +53,9 @@ bool SceneExporterGLTFPlugin::has_main_screen() const { return false; } -SceneExporterGLTFPlugin::SceneExporterGLTFPlugin(EditorNode *p_node) { - editor = p_node; +SceneExporterGLTFPlugin::SceneExporterGLTFPlugin() { file_export_lib = memnew(EditorFileDialog); - editor->get_gui_base()->add_child(file_export_lib); + EditorNode::get_singleton()->get_gui_base()->add_child(file_export_lib); file_export_lib->connect("file_selected", callable_mp(this, &SceneExporterGLTFPlugin::_gltf2_dialog_action)); file_export_lib->set_title(TTR("Export Library")); file_export_lib->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); @@ -68,9 +69,9 @@ SceneExporterGLTFPlugin::SceneExporterGLTFPlugin(EditorNode *p_node) { } void SceneExporterGLTFPlugin::_gltf2_dialog_action(String p_file) { - Node *root = editor->get_tree()->get_edited_scene_root(); + Node *root = EditorNode::get_singleton()->get_tree()->get_edited_scene_root(); if (!root) { - editor->show_accept(TTR("This operation can't be done without a scene."), TTR("OK")); + EditorNode::get_singleton()->show_accept(TTR("This operation can't be done without a scene."), TTR("OK")); return; } List<String> deps; @@ -91,9 +92,9 @@ void SceneExporterGLTFPlugin::_gltf2_dialog_action(String p_file) { } void SceneExporterGLTFPlugin::convert_scene_to_gltf2() { - Node *root = editor->get_tree()->get_edited_scene_root(); + Node *root = EditorNode::get_singleton()->get_tree()->get_edited_scene_root(); if (!root) { - editor->show_accept(TTR("This operation can't be done without a scene."), TTR("OK")); + EditorNode::get_singleton()->show_accept(TTR("This operation can't be done without a scene."), TTR("OK")); return; } String filename = String(root->get_scene_file_path().get_file().get_basename()); diff --git a/modules/gltf/editor_scene_exporter_gltf_plugin.h b/modules/gltf/editor_scene_exporter_gltf_plugin.h index e6b15e73c4..c2c3f5710c 100644 --- a/modules/gltf/editor_scene_exporter_gltf_plugin.h +++ b/modules/gltf/editor_scene_exporter_gltf_plugin.h @@ -33,13 +33,11 @@ #if TOOLS_ENABLED #include "editor/editor_plugin.h" - #include "editor_scene_importer_gltf.h" class SceneExporterGLTFPlugin : public EditorPlugin { GDCLASS(SceneExporterGLTFPlugin, EditorPlugin); - EditorNode *editor = nullptr; EditorFileDialog *file_export_lib = nullptr; void _gltf2_dialog_action(String p_file); void convert_scene_to_gltf2(); @@ -47,7 +45,7 @@ class SceneExporterGLTFPlugin : public EditorPlugin { public: virtual String get_name() const override; bool has_main_screen() const override; - SceneExporterGLTFPlugin(class EditorNode *p_node); + SceneExporterGLTFPlugin(); }; #endif // TOOLS_ENABLED #endif // EDITOR_SCENE_EXPORTER_GLTF_PLUGIN_H diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 51608273a1..2c42879bd3 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -50,13 +50,13 @@ #include "core/io/file_access.h" #include "core/io/file_access_memory.h" #include "core/io/json.h" +#include "core/io/stream_peer.h" #include "core/math/disjoint_set.h" #include "core/math/vector2.h" #include "core/variant/dictionary.h" #include "core/variant/typed_array.h" #include "core/variant/variant.h" #include "core/version.h" -#include "core/version_hash.gen.h" #include "drivers/png/png_driver_common.h" #include "editor/import/resource_importer_scene.h" #include "scene/2d/node_2d.h" @@ -84,6 +84,41 @@ #include <cstdint> #include <limits> +static Ref<ImporterMesh> _mesh_to_importer_mesh(Ref<Mesh> p_mesh) { + Ref<ImporterMesh> importer_mesh; + importer_mesh.instantiate(); + if (p_mesh.is_null()) { + return importer_mesh; + } + + Ref<ArrayMesh> array_mesh = p_mesh; + if (p_mesh->get_blend_shape_count()) { + ArrayMesh::BlendShapeMode shape_mode = ArrayMesh::BLEND_SHAPE_MODE_NORMALIZED; + if (array_mesh.is_valid()) { + shape_mode = array_mesh->get_blend_shape_mode(); + } + importer_mesh->set_blend_shape_mode(shape_mode); + for (int morph_i = 0; morph_i < p_mesh->get_blend_shape_count(); morph_i++) { + importer_mesh->add_blend_shape(p_mesh->get_blend_shape_name(morph_i)); + } + } + for (int32_t surface_i = 0; surface_i < p_mesh->get_surface_count(); surface_i++) { + Array array = p_mesh->surface_get_arrays(surface_i); + Ref<Material> mat = p_mesh->surface_get_material(surface_i); + String mat_name; + if (mat.is_valid()) { + mat_name = mat->get_name(); + } else { + // Assign default material when no material is assigned. + mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); + } + importer_mesh->add_surface(p_mesh->surface_get_primitive_type(surface_i), + array, p_mesh->surface_get_blend_shape_arrays(surface_i), p_mesh->surface_get_lods(surface_i), mat, + mat_name, p_mesh->surface_get_format(surface_i)); + } + return importer_mesh; +} + Error GLTFDocument::_serialize(Ref<GLTFState> state, const String &p_path) { if (!state->buffers.size()) { state->buffers.push_back(Vector<uint8_t>()); @@ -3373,9 +3408,9 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) { orm_texture_index = _set_texture(state, orm_texture); } if (has_ao) { - Dictionary ot; - ot["index"] = orm_texture_index; - d["occlusionTexture"] = ot; + Dictionary occt; + occt["index"] = orm_texture_index; + d["occlusionTexture"] = occt; } if (has_roughness || has_metalness) { mrt["index"] = orm_texture_index; @@ -5038,39 +5073,16 @@ GLTFMeshIndex GLTFDocument::_convert_mesh_to_gltf(Ref<GLTFState> state, MeshInst if (p_mesh_instance->get_mesh().is_null()) { return -1; } - Ref<ImporterMesh> current_mesh; - current_mesh.instantiate(); + + Ref<Mesh> import_mesh = p_mesh_instance->get_mesh(); + Ref<ImporterMesh> current_mesh = _mesh_to_importer_mesh(import_mesh); Vector<float> blend_weights; - { - Ref<Mesh> import_mesh = p_mesh_instance->get_mesh(); - Ref<ArrayMesh> import_array_mesh = p_mesh_instance->get_mesh(); - if (import_mesh->get_blend_shape_count()) { - ArrayMesh::BlendShapeMode shape_mode = ArrayMesh::BLEND_SHAPE_MODE_NORMALIZED; - if (import_array_mesh.is_valid()) { - shape_mode = import_array_mesh->get_blend_shape_mode(); - } - current_mesh->set_blend_shape_mode(shape_mode); - for (int morph_i = 0; morph_i < import_mesh->get_blend_shape_count(); morph_i++) { - current_mesh->add_blend_shape(import_mesh->get_blend_shape_name(morph_i)); - } - } - for (int32_t surface_i = 0; surface_i < import_mesh->get_surface_count(); surface_i++) { - Array array = import_mesh->surface_get_arrays(surface_i); - Ref<Material> mat = import_mesh->surface_get_material(surface_i); - String mat_name; - if (mat.is_valid()) { - mat_name = mat->get_name(); - } - current_mesh->add_surface(import_mesh->surface_get_primitive_type(surface_i), - array, import_mesh->surface_get_blend_shape_arrays(surface_i), import_mesh->surface_get_lods(surface_i), mat, - mat_name, import_mesh->surface_get_format(surface_i)); - } - int32_t blend_count = import_mesh->get_blend_shape_count(); - blend_weights.resize(blend_count); - for (int32_t blend_i = 0; blend_i < blend_count; blend_i++) { - blend_weights.write[blend_i] = 0.0f; - } + int32_t blend_count = import_mesh->get_blend_shape_count(); + blend_weights.resize(blend_count); + for (int32_t blend_i = 0; blend_i < blend_count; blend_i++) { + blend_weights.write[blend_i] = 0.0f; } + Ref<GLTFMesh> gltf_mesh; gltf_mesh.instantiate(); Array instance_materials; @@ -5322,14 +5334,31 @@ void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeInd if (meshes.size() != 2) { return; } - Ref<Material> mat; - if (csg->get_material_override().is_valid()) { - mat = csg->get_material_override(); + + Ref<ImporterMesh> mesh; + mesh.instantiate(); + { + Ref<Mesh> csg_mesh = csg->get_meshes()[1]; + + for (int32_t surface_i = 0; surface_i < csg_mesh->get_surface_count(); surface_i++) { + Array array = csg_mesh->surface_get_arrays(surface_i); + Ref<Material> mat = csg_mesh->surface_get_material(surface_i); + String mat_name; + if (mat.is_valid()) { + mat_name = mat->get_name(); + } else { + // Assign default material when no material is assigned. + mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); + } + mesh->add_surface(csg_mesh->surface_get_primitive_type(surface_i), + array, csg_mesh->surface_get_blend_shape_arrays(surface_i), csg_mesh->surface_get_lods(surface_i), mat, + mat_name, csg_mesh->surface_get_format(surface_i)); + } } + Ref<GLTFMesh> gltf_mesh; gltf_mesh.instantiate(); - Ref<ImporterMesh> array_mesh = csg->get_meshes()[1]; - gltf_mesh->set_mesh(array_mesh); + gltf_mesh->set_mesh(mesh); GLTFMeshIndex mesh_i = state->meshes.size(); state->meshes.push_back(gltf_mesh); gltf_node->mesh = mesh_i; @@ -5395,8 +5424,6 @@ void GLTFDocument::_convert_grid_map_to_gltf(GridMap *p_grid_map, GLTFNodeIndex Vector3 cell_location = cells[k]; int32_t cell = p_grid_map->get_cell_item( Vector3(cell_location.x, cell_location.y, cell_location.z)); - ImporterMeshInstance3D *import_mesh_node = memnew(ImporterMeshInstance3D); - import_mesh_node->set_mesh(p_grid_map->get_mesh_library()->get_item_mesh(cell)); Transform3D cell_xform; cell_xform.basis.set_orthogonal_index( p_grid_map->get_cell_item_orientation( @@ -5408,7 +5435,7 @@ void GLTFDocument::_convert_grid_map_to_gltf(GridMap *p_grid_map, GLTFNodeIndex Vector3(cell_location.x, cell_location.y, cell_location.z))); Ref<GLTFMesh> gltf_mesh; gltf_mesh.instantiate(); - gltf_mesh = import_mesh_node; + gltf_mesh->set_mesh(_mesh_to_importer_mesh(p_grid_map->get_mesh_library()->get_item_mesh(cell))); new_gltf_node->mesh = state->meshes.size(); state->meshes.push_back(gltf_mesh); new_gltf_node->xform = cell_xform * p_grid_map->get_transform(); @@ -6277,7 +6304,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state p_track.rotation_track.values.write[key_i] = rotation; } } else if (track_type == Animation::TYPE_VALUE) { - if (path.find(":position") != -1) { + if (path.contains(":position")) { p_track.position_track.times = times; p_track.position_track.interpolation = gltf_interpolation; @@ -6288,7 +6315,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state Vector3 position = p_animation->track_get_key_value(p_track_i, key_i); p_track.position_track.values.write[key_i] = position; } - } else if (path.find(":rotation") != -1) { + } else if (path.contains(":rotation")) { p_track.rotation_track.times = times; p_track.rotation_track.interpolation = gltf_interpolation; @@ -6299,7 +6326,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state Vector3 rotation_radian = p_animation->track_get_key_value(p_track_i, key_i); p_track.rotation_track.values.write[key_i] = Quaternion(rotation_radian); } - } else if (path.find(":scale") != -1) { + } else if (path.contains(":scale")) { p_track.scale_track.times = times; p_track.scale_track.interpolation = gltf_interpolation; @@ -6312,7 +6339,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state } } } else if (track_type == Animation::TYPE_BEZIER) { - if (path.find("/scale") != -1) { + if (path.contains("/scale")) { const int32_t keys = p_animation->track_get_key_time(p_track_i, key_count - 1) * BAKE_FPS; if (!p_track.scale_track.times.size()) { Vector<real_t> new_times; @@ -6333,16 +6360,16 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state for (int32_t key_i = 0; key_i < keys; key_i++) { Vector3 bezier_track = p_track.scale_track.values[key_i]; - if (path.find("/scale:x") != -1) { + if (path.contains("/scale:x")) { bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); - } else if (path.find("/scale:y") != -1) { + } else if (path.contains("/scale:y")) { bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); - } else if (path.find("/scale:z") != -1) { + } else if (path.contains("/scale:z")) { bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); } p_track.scale_track.values.write[key_i] = bezier_track; } - } else if (path.find("/position") != -1) { + } else if (path.contains("/position")) { const int32_t keys = p_animation->track_get_key_time(p_track_i, key_count - 1) * BAKE_FPS; if (!p_track.position_track.times.size()) { Vector<real_t> new_times; @@ -6359,11 +6386,11 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state for (int32_t key_i = 0; key_i < keys; key_i++) { Vector3 bezier_track = p_track.position_track.values[key_i]; - if (path.find("/position:x") != -1) { + if (path.contains("/position:x")) { bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); - } else if (path.find("/position:y") != -1) { + } else if (path.contains("/position:y")) { bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); - } else if (path.find("/position:z") != -1) { + } else if (path.contains("/position:z")) { bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); } p_track.position_track.values.write[key_i] = bezier_track; @@ -6384,7 +6411,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, continue; } String orig_track_path = animation->track_get_path(track_i); - if (String(orig_track_path).find(":position") != -1) { + if (String(orig_track_path).contains(":position")) { const Vector<String> node_suffix = String(orig_track_path).split(":position"); const NodePath path = node_suffix[0]; const Node *node = ap->get_parent()->get_node_or_null(path); @@ -6400,7 +6427,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, gltf_animation->get_tracks().insert(node_index, track); } } - } else if (String(orig_track_path).find(":rotation_degrees") != -1) { + } else if (String(orig_track_path).contains(":rotation_degrees")) { const Vector<String> node_suffix = String(orig_track_path).split(":rotation_degrees"); const NodePath path = node_suffix[0]; const Node *node = ap->get_parent()->get_node_or_null(path); @@ -6416,7 +6443,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, gltf_animation->get_tracks().insert(node_index, track); } } - } else if (String(orig_track_path).find(":scale") != -1) { + } else if (String(orig_track_path).contains(":scale")) { const Vector<String> node_suffix = String(orig_track_path).split(":scale"); const NodePath path = node_suffix[0]; const Node *node = ap->get_parent()->get_node_or_null(path); @@ -6432,7 +6459,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, gltf_animation->get_tracks().insert(node_index, track); } } - } else if (String(orig_track_path).find(":transform") != -1) { + } else if (String(orig_track_path).contains(":transform")) { const Vector<String> node_suffix = String(orig_track_path).split(":transform"); const NodePath path = node_suffix[0]; const Node *node = ap->get_parent()->get_node_or_null(path); @@ -6443,7 +6470,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, gltf_animation->get_tracks().insert(transform_track_i.key, track); } } - } else if (String(orig_track_path).find(":blend_shapes/") != -1) { + } else if (String(orig_track_path).contains(":blend_shapes/")) { const Vector<String> node_suffix = String(orig_track_path).split(":blend_shapes/"); const NodePath path = node_suffix[0]; const String suffix = node_suffix[1]; @@ -6499,7 +6526,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, } tracks[mesh_index] = track; } - } else if (String(orig_track_path).find(":") != -1) { + } else if (String(orig_track_path).contains(":")) { //Process skeleton const Vector<String> node_suffix = String(orig_track_path).split(":"); const String node = node_suffix[0]; @@ -6529,7 +6556,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, gltf_animation->get_tracks()[node_i] = track; } } - } else if (String(orig_track_path).find(":") == -1) { + } else if (!String(orig_track_path).contains(":")) { ERR_CONTINUE(!ap->get_parent()); Node *godot_node = ap->get_parent()->get_node_or_null(orig_track_path); for (const KeyValue<GLTFNodeIndex, Node *> &scene_node_i : state->scene_nodes) { @@ -6654,8 +6681,8 @@ Error GLTFDocument::_serialize_version(Ref<GLTFState> state) { Dictionary asset; asset["version"] = version; - String hash = VERSION_HASH; - asset["generator"] = String(VERSION_FULL_NAME) + String("@") + (hash.length() == 0 ? String("unknown") : hash); + String hash = String(VERSION_HASH); + asset["generator"] = String(VERSION_FULL_NAME) + String("@") + (hash.is_empty() ? String("unknown") : hash); state->json["asset"] = asset; ERR_FAIL_COND_V(!asset.has("version"), Error::FAILED); ERR_FAIL_COND_V(!state->json.has("asset"), Error::FAILED); diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml index 9c28421a46..407ce961c8 100644 --- a/modules/gridmap/doc_classes/GridMap.xml +++ b/modules/gridmap/doc_classes/GridMap.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="GridMap" inherits="Node3D" version="4.0"> +<class name="GridMap" inherits="Node3D" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Node for 3D tile-based maps. </brief_description> @@ -79,6 +79,13 @@ Returns an array of [Vector3] with the non-empty cell coordinates in the grid map. </description> </method> + <method name="get_used_cells_by_item" qualifiers="const"> + <return type="Array" /> + <argument index="0" name="item" type="int" /> + <description> + Returns an array of all cells with the given item index specified in [code]item[/code]. + </description> + </method> <method name="make_baked_meshes"> <return type="void" /> <argument index="0" name="gen_lightmap_uv" type="bool" default="false" /> @@ -110,15 +117,6 @@ Optionally, the item's orientation can be passed. For valid orientation values, see [method Basis.get_orthogonal_index]. </description> </method> - <method name="set_clip"> - <return type="void" /> - <argument index="0" name="enabled" type="bool" /> - <argument index="1" name="clipabove" type="bool" default="true" /> - <argument index="2" name="floor" type="int" default="0" /> - <argument index="3" name="axis" type="int" enum="Vector3.Axis" default="0" /> - <description> - </description> - </method> <method name="set_collision_layer_value"> <return type="void" /> <argument index="0" name="layer_number" type="int" /> diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index 67deedf839..02fe4d93de 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -703,8 +703,8 @@ void GridMap::_notification(int p_what) { RS::get_singleton()->instance_set_scenario(baked_meshes[i].instance, get_world_3d()->get_scenario()); RS::get_singleton()->instance_set_transform(baked_meshes[i].instance, get_global_transform()); } - } break; + case NOTIFICATION_TRANSFORM_CHANGED: { Transform3D new_xform = get_global_transform(); if (new_xform == last_transform) { @@ -721,6 +721,7 @@ void GridMap::_notification(int p_what) { RS::get_singleton()->instance_set_transform(baked_meshes[i].instance, get_global_transform()); } } break; + case NOTIFICATION_EXIT_WORLD: { for (const KeyValue<OctantKey, Octant *> &E : octant_map) { _octant_exit_world(E.key); @@ -732,8 +733,8 @@ void GridMap::_notification(int p_what) { for (int i = 0; i < baked_meshes.size(); i++) { RS::get_singleton()->instance_set_scenario(baked_meshes[i].instance, RID()); } - } break; + case NOTIFICATION_VISIBILITY_CHANGED: { _update_visibility(); } break; @@ -813,6 +814,7 @@ void GridMap::_update_octants_callback() { } while (to_delete.front()) { + memdelete(octant_map[to_delete.front()->get()]); octant_map.erase(to_delete.front()->get()); to_delete.pop_front(); } @@ -872,11 +874,10 @@ void GridMap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_center_z", "enable"), &GridMap::set_center_z); ClassDB::bind_method(D_METHOD("get_center_z"), &GridMap::get_center_z); - ClassDB::bind_method(D_METHOD("set_clip", "enabled", "clipabove", "floor", "axis"), &GridMap::set_clip, DEFVAL(true), DEFVAL(0), DEFVAL(Vector3::AXIS_X)); - ClassDB::bind_method(D_METHOD("clear"), &GridMap::clear); ClassDB::bind_method(D_METHOD("get_used_cells"), &GridMap::get_used_cells); + ClassDB::bind_method(D_METHOD("get_used_cells_by_item", "item"), &GridMap::get_used_cells_by_item); ClassDB::bind_method(D_METHOD("get_meshes"), &GridMap::get_meshes); ClassDB::bind_method(D_METHOD("get_bake_meshes"), &GridMap::get_bake_meshes); @@ -906,28 +907,6 @@ void GridMap::_bind_methods() { ADD_SIGNAL(MethodInfo("cell_size_changed", PropertyInfo(Variant::VECTOR3, "cell_size"))); } -void GridMap::set_clip(bool p_enabled, bool p_clip_above, int p_floor, Vector3::Axis p_axis) { - if (!p_enabled && !clip) { - return; - } - if (clip && p_enabled && clip_floor == p_floor && p_clip_above == clip_above && p_axis == clip_axis) { - return; - } - - clip = p_enabled; - clip_floor = p_floor; - clip_axis = p_axis; - clip_above = p_clip_above; - - //make it all update - for (KeyValue<OctantKey, Octant *> &E : octant_map) { - Octant *g = E.value; - g->dirty = true; - } - awaiting_update = true; - _update_octants_callback(); -} - void GridMap::set_cell_scale(float p_scale) { cell_scale = p_scale; _recreate_octant_data(); @@ -949,6 +928,18 @@ Array GridMap::get_used_cells() const { return a; } +Array GridMap::get_used_cells_by_item(int p_item) const { + Array a; + for (const KeyValue<IndexKey, Cell> &E : cell_map) { + if (E.value.item == p_item) { + Vector3 p(E.key.x, E.key.y, E.key.z); + a.push_back(p); + } + } + + return a; +} + Array GridMap::get_meshes() const { if (mesh_library.is_null()) { return Array(); diff --git a/modules/gridmap/grid_map.h b/modules/gridmap/grid_map.h index 6cdc3b178d..b09cabfe25 100644 --- a/modules/gridmap/grid_map.h +++ b/modules/gridmap/grid_map.h @@ -150,14 +150,8 @@ class GridMap : public Node3D { bool center_z = true; float cell_scale = 1.0; - bool clip = false; - bool clip_above = true; - int clip_floor = 0; - bool recreating_octants = false; - Vector3::Axis clip_axis = Vector3::AXIS_Z; - Ref<MeshLibrary> mesh_library; Map<OctantKey, Octant *> octant_map; @@ -260,12 +254,11 @@ public: Vector3i world_to_map(const Vector3 &p_world_position) const; Vector3 map_to_world(const Vector3i &p_map_position) const; - void set_clip(bool p_enabled, bool p_clip_above = true, int p_floor = 0, Vector3::Axis p_axis = Vector3::AXIS_X); - void set_cell_scale(float p_scale); float get_cell_scale() const; Array get_used_cells() const; + Array get_used_cells_by_item(int p_item) const; Array get_meshes() const; diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp index 84510fc71e..a05905cbc3 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -30,6 +30,7 @@ #include "grid_map_editor_plugin.h" #include "core/input/input.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/plugins/node_3d_editor_plugin.h" @@ -62,17 +63,6 @@ void GridMapEditor::_menu_option(int p_option) { floor->set_value(floor->get_value() + 1); } break; - case MENU_OPTION_CLIP_DISABLED: - case MENU_OPTION_CLIP_ABOVE: - case MENU_OPTION_CLIP_BELOW: { - clip_mode = ClipMode(p_option - MENU_OPTION_CLIP_DISABLED); - for (int i = 0; i < 3; i++) { - int index = options->get_popup()->get_item_index(MENU_OPTION_CLIP_DISABLED + i); - options->get_popup()->set_item_checked(index, i == clip_mode); - } - - _update_clip(); - } break; case MENU_OPTION_X_AXIS: case MENU_OPTION_Y_AXIS: case MENU_OPTION_Z_AXIS: { @@ -97,7 +87,6 @@ void GridMapEditor::_menu_option(int p_option) { } edit_axis = Vector3::Axis(new_axis); update_grid(); - _update_clip(); } break; case MENU_OPTION_CURSOR_ROTATE_Y: { @@ -923,7 +912,7 @@ void GridMapEditor::edit(GridMap *p_gridmap) { _update_selection_transform(); _update_paste_indicator(); - spatial_editor = Object::cast_to<Node3DEditorPlugin>(editor->get_editor_plugin_screen()); + spatial_editor = Object::cast_to<Node3DEditorPlugin>(EditorNode::get_singleton()->get_editor_plugin_screen()); if (!node) { set_process(false); @@ -942,24 +931,12 @@ void GridMapEditor::edit(GridMap *p_gridmap) { set_process(true); - clip_mode = p_gridmap->has_meta("_editor_clip_") ? ClipMode(p_gridmap->get_meta("_editor_clip_").operator int()) : CLIP_DISABLED; - _draw_grids(node->get_cell_size()); update_grid(); - _update_clip(); node->connect("cell_size_changed", callable_mp(this, &GridMapEditor::_draw_grids)); } -void GridMapEditor::_update_clip() { - node->set_meta("_editor_clip_", clip_mode); - if (clip_mode == CLIP_DISABLED) { - node->set_clip(false); - } else { - node->set_clip(true, clip_mode == CLIP_ABOVE, edit_floor[edit_axis], edit_axis); - } -} - void GridMapEditor::update_grid() { grid_xform.origin.x -= 1; // Force update in hackish way. @@ -1146,7 +1123,6 @@ void GridMapEditor::_floor_changed(float p_value) { edit_floor[edit_axis] = p_value; node->set_meta("_editor_floor_", Vector3(edit_floor[0], edit_floor[1], edit_floor[2])); update_grid(); - _update_clip(); _update_selection_transform(); } @@ -1159,9 +1135,8 @@ void GridMapEditor::_bind_methods() { ClassDB::bind_method("_set_selection", &GridMapEditor::_set_selection); } -GridMapEditor::GridMapEditor(EditorNode *p_editor) { - editor = p_editor; - undo_redo = p_editor->get_undo_redo(); +GridMapEditor::GridMapEditor() { + undo_redo = EditorNode::get_singleton()->get_undo_redo(); int mw = EDITOR_DEF("editors/grid_map/palette_min_width", 230); Control *ec = memnew(Control); @@ -1198,11 +1173,6 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { options->get_popup()->add_item(TTR("Previous Floor"), MENU_OPTION_PREV_LEVEL, Key::Q); options->get_popup()->add_item(TTR("Next Floor"), MENU_OPTION_NEXT_LEVEL, Key::E); options->get_popup()->add_separator(); - options->get_popup()->add_radio_check_item(TTR("Clip Disabled"), MENU_OPTION_CLIP_DISABLED); - options->get_popup()->set_item_checked(options->get_popup()->get_item_index(MENU_OPTION_CLIP_DISABLED), true); - options->get_popup()->add_radio_check_item(TTR("Clip Above"), MENU_OPTION_CLIP_ABOVE); - options->get_popup()->add_radio_check_item(TTR("Clip Below"), MENU_OPTION_CLIP_BELOW); - options->get_popup()->add_separator(); options->get_popup()->add_radio_check_item(TTR("Edit X Axis"), MENU_OPTION_X_AXIS, Key::Z); options->get_popup()->add_radio_check_item(TTR("Edit Y Axis"), MENU_OPTION_Y_AXIS, Key::X); options->get_popup()->add_radio_check_item(TTR("Edit Z Axis"), MENU_OPTION_Z_AXIS, Key::C); @@ -1456,15 +1426,17 @@ GridMapEditor::~GridMapEditor() { } void GridMapEditorPlugin::_notification(int p_what) { - if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { - switch ((int)EditorSettings::get_singleton()->get("editors/grid_map/editor_side")) { - case 0: { // Left. - Node3DEditor::get_singleton()->move_control_to_left_panel(grid_map_editor); - } break; - case 1: { // Right. - Node3DEditor::get_singleton()->move_control_to_right_panel(grid_map_editor); - } break; - } + switch (p_what) { + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + switch ((int)EditorSettings::get_singleton()->get("editors/grid_map/editor_side")) { + case 0: { // Left. + Node3DEditor::get_singleton()->move_control_to_left_panel(grid_map_editor); + } break; + case 1: { // Right. + Node3DEditor::get_singleton()->move_control_to_right_panel(grid_map_editor); + } break; + } + } break; } } @@ -1489,13 +1461,11 @@ void GridMapEditorPlugin::make_visible(bool p_visible) { } } -GridMapEditorPlugin::GridMapEditorPlugin(EditorNode *p_node) { - editor = p_node; - +GridMapEditorPlugin::GridMapEditorPlugin() { EDITOR_DEF("editors/grid_map/editor_side", 1); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/grid_map/editor_side", PROPERTY_HINT_ENUM, "Left,Right")); - grid_map_editor = memnew(GridMapEditor(editor)); + grid_map_editor = memnew(GridMapEditor); switch ((int)EditorSettings::get_singleton()->get("editors/grid_map/editor_side")) { case 0: { // Left. Node3DEditor::get_singleton()->add_control_to_left_panel(grid_map_editor); diff --git a/modules/gridmap/grid_map_editor_plugin.h b/modules/gridmap/grid_map_editor_plugin.h index 37298a1d80..c44c4ca7e0 100644 --- a/modules/gridmap/grid_map_editor_plugin.h +++ b/modules/gridmap/grid_map_editor_plugin.h @@ -31,9 +31,11 @@ #ifndef GRID_MAP_EDITOR_PLUGIN_H #define GRID_MAP_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "grid_map.h" +#include "scene/gui/item_list.h" +#include "scene/gui/slider.h" +#include "scene/gui/spin_box.h" class Node3DEditorPlugin; @@ -53,12 +55,6 @@ class GridMapEditor : public VBoxContainer { INPUT_PASTE, }; - enum ClipMode { - CLIP_DISABLED, - CLIP_ABOVE, - CLIP_BELOW - }; - enum DisplayMode { DISPLAY_THUMBNAIL, DISPLAY_LIST @@ -92,7 +88,6 @@ class GridMapEditor : public VBoxContainer { GridMap *node = nullptr; MeshLibrary *last_mesh_library; - ClipMode clip_mode = CLIP_DISABLED; Transform3D grid_xform; Transform3D edit_grid_xform; @@ -157,9 +152,6 @@ class GridMapEditor : public VBoxContainer { MENU_OPTION_NEXT_LEVEL, MENU_OPTION_PREV_LEVEL, MENU_OPTION_LOCK_VIEW, - MENU_OPTION_CLIP_DISABLED, - MENU_OPTION_CLIP_ABOVE, - MENU_OPTION_CLIP_BELOW, MENU_OPTION_X_AXIS, MENU_OPTION_Y_AXIS, MENU_OPTION_Z_AXIS, @@ -189,8 +181,6 @@ class GridMapEditor : public VBoxContainer { ItemList *mesh_library_palette; Label *info_message; - EditorNode *editor; - void update_grid(); // Change which and where the grid is displayed void _draw_grids(const Vector3 &cell_size); void _configure(); @@ -200,7 +190,6 @@ class GridMapEditor : public VBoxContainer { void _item_selected_cbk(int idx); void _update_cursor_transform(); void _update_cursor_instance(); - void _update_clip(); void _update_theme(); void _text_changed(const String &p_text); @@ -236,8 +225,7 @@ public: EditorPlugin::AfterGUIInput forward_spatial_input_event(Camera3D *p_camera, const Ref<InputEvent> &p_event); void edit(GridMap *p_gridmap); - GridMapEditor() {} - GridMapEditor(EditorNode *p_editor); + GridMapEditor(); ~GridMapEditor(); }; @@ -245,7 +233,6 @@ class GridMapEditorPlugin : public EditorPlugin { GDCLASS(GridMapEditorPlugin, EditorPlugin); GridMapEditor *grid_map_editor; - EditorNode *editor; protected: void _notification(int p_what); @@ -258,7 +245,7 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - GridMapEditorPlugin(EditorNode *p_node); + GridMapEditorPlugin(); ~GridMapEditorPlugin(); }; diff --git a/modules/lightmapper_rd/lm_blendseams.glsl b/modules/lightmapper_rd/lm_blendseams.glsl index 374c48082e..70164e27c4 100644 --- a/modules/lightmapper_rd/lm_blendseams.glsl +++ b/modules/lightmapper_rd/lm_blendseams.glsl @@ -11,7 +11,7 @@ triangles = "#define MODE_TRIANGLES"; #include "lm_common_inc.glsl" -layout(push_constant, binding = 0, std430) uniform Params { +layout(push_constant, std430) uniform Params { uint base_index; uint slice; vec2 uv_offset; @@ -78,7 +78,7 @@ void main() { #include "lm_common_inc.glsl" -layout(push_constant, binding = 0, std430) uniform Params { +layout(push_constant, std430) uniform Params { uint base_index; uint slice; vec2 uv_offset; diff --git a/modules/lightmapper_rd/lm_compute.glsl b/modules/lightmapper_rd/lm_compute.glsl index 7bb8346c47..0b6b72a310 100644 --- a/modules/lightmapper_rd/lm_compute.glsl +++ b/modules/lightmapper_rd/lm_compute.glsl @@ -70,7 +70,7 @@ layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly image2DArray de layout(set = 1, binding = 1) uniform texture2DArray source_light; #endif -layout(push_constant, binding = 0, std430) uniform Params { +layout(push_constant, std430) uniform Params { ivec2 atlas_size; // x used for light probe mode total probes uint ray_count; uint ray_to; diff --git a/modules/lightmapper_rd/lm_raster.glsl b/modules/lightmapper_rd/lm_raster.glsl index a86968a4f3..875c3e967a 100644 --- a/modules/lightmapper_rd/lm_raster.glsl +++ b/modules/lightmapper_rd/lm_raster.glsl @@ -14,7 +14,7 @@ layout(location = 4) flat out uvec3 vertex_indices; layout(location = 5) flat out vec3 face_normal; layout(location = 6) flat out uint fragment_action; -layout(push_constant, binding = 0, std430) uniform Params { +layout(push_constant, std430) uniform Params { vec2 atlas_size; vec2 uv_offset; vec3 to_cell_size; @@ -69,7 +69,7 @@ void main() { #include "lm_common_inc.glsl" -layout(push_constant, binding = 0, std430) uniform Params { +layout(push_constant, std430) uniform Params { vec2 atlas_size; vec2 uv_offset; vec3 to_cell_size; diff --git a/modules/meshoptimizer/register_types.cpp b/modules/meshoptimizer/register_types.cpp index 57a1b697be..597c12ed23 100644 --- a/modules/meshoptimizer/register_types.cpp +++ b/modules/meshoptimizer/register_types.cpp @@ -38,6 +38,9 @@ void register_meshoptimizer_types() { SurfaceTool::simplify_with_attrib_func = meshopt_simplifyWithAttributes; SurfaceTool::simplify_scale_func = meshopt_simplifyScale; SurfaceTool::simplify_sloppy_func = meshopt_simplifySloppy; + SurfaceTool::generate_remap_func = meshopt_generateVertexRemap; + SurfaceTool::remap_vertex_func = meshopt_remapVertexBuffer; + SurfaceTool::remap_index_func = meshopt_remapIndexBuffer; } void unregister_meshoptimizer_types() { @@ -45,4 +48,7 @@ void unregister_meshoptimizer_types() { SurfaceTool::simplify_func = nullptr; SurfaceTool::simplify_scale_func = nullptr; SurfaceTool::simplify_sloppy_func = nullptr; + SurfaceTool::generate_remap_func = nullptr; + SurfaceTool::remap_vertex_func = nullptr; + SurfaceTool::remap_index_func = nullptr; } diff --git a/modules/minimp3/doc_classes/AudioStreamMP3.xml b/modules/minimp3/doc_classes/AudioStreamMP3.xml index e4f56614ee..f5f7d3ef17 100644 --- a/modules/minimp3/doc_classes/AudioStreamMP3.xml +++ b/modules/minimp3/doc_classes/AudioStreamMP3.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="AudioStreamMP3" inherits="AudioStream" version="4.0"> +<class name="AudioStreamMP3" inherits="AudioStream" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> MP3 audio stream driver. </brief_description> diff --git a/modules/mobile_vr/doc_classes/MobileVRInterface.xml b/modules/mobile_vr/doc_classes/MobileVRInterface.xml index df099f2c98..db186079b0 100644 --- a/modules/mobile_vr/doc_classes/MobileVRInterface.xml +++ b/modules/mobile_vr/doc_classes/MobileVRInterface.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="MobileVRInterface" inherits="XRInterface" version="4.0"> +<class name="MobileVRInterface" inherits="XRInterface" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Generic mobile VR implementation. </brief_description> diff --git a/modules/modules_builders.py b/modules/modules_builders.py index 2243162555..13d5a2075a 100644 --- a/modules/modules_builders.py +++ b/modules/modules_builders.py @@ -14,13 +14,10 @@ def generate_modules_enabled(target, source, env): def generate_modules_tests(target, source, env): import os - import glob with open(target[0].path, "w") as f: - for name, path in env.module_list.items(): - headers = glob.glob(os.path.join(path, "tests", "*.h")) - for h in headers: - f.write('#include "%s"\n' % (os.path.normpath(h))) + for header in source: + f.write('#include "%s"\n' % (os.path.normpath(header.path))) if __name__ == "__main__": diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 26436e3ec0..085ab9a467 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -3245,6 +3245,8 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) { CRASH_COND(!valid); #endif + GD_MONO_SCOPE_THREAD_ATTACH; + if (native) { StringName native_name = NATIVE_GDMONOCLASS_NAME(native); if (!ClassDB::is_parent_class(p_this->get_class_name(), native_name)) { @@ -3257,8 +3259,6 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) { } } - GD_MONO_SCOPE_THREAD_ATTACH; - Callable::CallError unchecked_error; return _create_instance(nullptr, 0, p_this, Object::cast_to<RefCounted>(p_this) != nullptr, unchecked_error); } diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 2be588cac4..d6cd9e6e57 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -53,8 +53,9 @@ class CSharpLanguage; #ifdef NO_SAFE_CAST template <typename TScriptInstance, typename TScriptLanguage> TScriptInstance *cast_script_instance(ScriptInstance *p_inst) { - if (!p_inst) + if (!p_inst) { return nullptr; + } return p_inst->get_language() == TScriptLanguage::get_singleton() ? static_cast<TScriptInstance *>(p_inst) : nullptr; } #else @@ -103,8 +104,6 @@ private: bool valid = false; bool reload_invalidated = false; - bool builtin; - GDMonoClass *base = nullptr; GDMonoClass *native = nullptr; GDMonoClass *script_class = nullptr; diff --git a/modules/mono/doc_classes/CSharpScript.xml b/modules/mono/doc_classes/CSharpScript.xml index 14c62b4bb0..f8293fb107 100644 --- a/modules/mono/doc_classes/CSharpScript.xml +++ b/modules/mono/doc_classes/CSharpScript.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="CSharpScript" inherits="Script" version="4.0"> +<class name="CSharpScript" inherits="Script" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A script implemented in the C# programming language (Mono-enabled builds only). </brief_description> diff --git a/modules/mono/doc_classes/GodotSharp.xml b/modules/mono/doc_classes/GodotSharp.xml index a148072245..9de6b48e9e 100644 --- a/modules/mono/doc_classes/GodotSharp.xml +++ b/modules/mono/doc_classes/GodotSharp.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="GodotSharp" inherits="Object" version="4.0"> +<class name="GodotSharp" inherits="Object" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Bridge between Godot and the Mono runtime (Mono-enabled builds only). </brief_description> diff --git a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs index ce2b378623..7a4641dbbc 100644 --- a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs +++ b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs @@ -47,9 +47,13 @@ namespace GodotTools.OpenVisualStudio if (dte == null) { // Open a new instance + dte = TryVisualStudioLaunch("VisualStudio.DTE.17.0"); - var visualStudioDteType = Type.GetTypeFromProgID("VisualStudio.DTE.16.0", throwOnError: true); - dte = (DTE)Activator.CreateInstance(visualStudioDteType); + if (dte == null) + { + // Launch of VS 2022 failed, fallback to 2019 + dte = TryVisualStudioLaunch("VisualStudio.DTE.16.0"); + } dte.UserControl = true; @@ -133,6 +137,21 @@ namespace GodotTools.OpenVisualStudio return 0; } + private static DTE TryVisualStudioLaunch(string version) + { + try + { + var visualStudioDteType = Type.GetTypeFromProgID(version, throwOnError: true); + var dte = (DTE)Activator.CreateInstance(visualStudioDteType); + + return dte; + } + catch (COMException) + { + return null; + } + } + private static DTE FindInstanceEditingSolution(string solutionPath) { if (GetRunningObjectTable(0, out IRunningObjectTable pprot) != 0) @@ -164,7 +183,7 @@ namespace GodotTools.OpenVisualStudio continue; // The digits after the colon are the process ID - if (!Regex.IsMatch(ppszDisplayName, "!VisualStudio.DTE.16.0:[0-9]")) + if (!Regex.IsMatch(ppszDisplayName, "!VisualStudio.DTE.1[6-7].0:[0-9]")) continue; if (pprot.GetObject(moniker[0], out object ppunkObject) == 0) diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs index 37e6a34977..ed758cc137 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs @@ -537,7 +537,6 @@ MONO_AOT_MODE_LAST = 1000, { var iosArchs = new[] { - "armv7", "arm64" }; diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 1b4ab0ef4b..07128770b7 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -279,8 +279,9 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf xml_output.append("["); pos = brk_pos + 1; } else if (tag.begins_with("method ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ")) { - String link_target = tag.substr(tag.find(" ") + 1, tag.length()); - String link_tag = tag.substr(0, tag.find(" ")); + const int tag_end = tag.find(" "); + const String link_tag = tag.substr(0, tag_end); + const String link_target = tag.substr(tag_end + 1, tag.length()).lstrip(" "); Vector<String> link_target_parts = link_target.split("."); @@ -360,12 +361,38 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf } } } else if (link_tag == "signal") { - // We do not declare signals in any way in C#, so there is nothing to reference - xml_output.append("<c>"); - xml_output.append(link_target); - xml_output.append("</c>"); + if (!target_itype || !target_itype->is_object_type) { + if (OS::get_singleton()->is_stdout_verbose()) { + if (target_itype) { + OS::get_singleton()->print("Cannot resolve signal reference for non-Godot.Object type in documentation: %s\n", link_target.utf8().get_data()); + } else { + OS::get_singleton()->print("Cannot resolve type from signal reference in documentation: %s\n", link_target.utf8().get_data()); + } + } + + // TODO Map what we can + xml_output.append("<c>"); + xml_output.append(link_target); + xml_output.append("</c>"); + } else { + const SignalInterface *target_isignal = target_itype->find_signal_by_name(target_cname); + + if (target_isignal) { + xml_output.append("<see cref=\"" BINDINGS_NAMESPACE "."); + xml_output.append(target_itype->proxy_name); + xml_output.append("."); + xml_output.append(target_isignal->proxy_name); + xml_output.append("\"/>"); + } else { + ERR_PRINT("Cannot resolve signal reference in documentation: '" + link_target + "'."); + + xml_output.append("<c>"); + xml_output.append(link_target); + xml_output.append("</c>"); + } + } } else if (link_tag == "enum") { - StringName search_cname = !target_itype ? target_cname : StringName(target_itype->name + "." + (String)target_cname); + const StringName search_cname = !target_itype ? target_cname : StringName(target_itype->name + "." + (String)target_cname); const Map<StringName, TypeInterface>::Element *enum_match = enum_types.find(search_cname); @@ -401,7 +428,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf xml_output.append(link_target); xml_output.append("</c>"); } else if (!target_itype && target_cname == name_cache.type_at_GlobalScope) { - String target_name = (String)target_cname; + const String target_name = (String)target_cname; // Try to find as a global constant const ConstantInterface *target_iconst = find_constant_by_name(target_name, global_constants); @@ -438,7 +465,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf } } } else { - String target_name = (String)target_cname; + const String target_name = (String)target_cname; // Try to find the constant in the current class const ConstantInterface *target_iconst = find_constant_by_name(target_name, target_itype->constants); @@ -690,11 +717,11 @@ void BindingsGenerator::_apply_prefix_to_enum_constants(BindingsGenerator::EnumI continue; } - if (parts[curr_prefix_length][0] >= '0' && parts[curr_prefix_length][0] <= '9') { + if (is_digit(parts[curr_prefix_length][0])) { // The name of enum constants may begin with a numeric digit when strip from the enum prefix, // so we make the prefix for this constant one word shorter in those cases. for (curr_prefix_length = curr_prefix_length - 1; curr_prefix_length > 0; curr_prefix_length--) { - if (parts[curr_prefix_length][0] < '0' || parts[curr_prefix_length][0] > '9') { + if (!is_digit(parts[curr_prefix_length][0])) { break; } } @@ -2122,8 +2149,9 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { } output.append("#ifdef TOOLS_ENABLED\n"); - for (const InternalCall &internal_call : editor_custom_icalls) + for (const InternalCall &internal_call : editor_custom_icalls) { ADD_INTERNAL_CALL_REGISTRATION(internal_call); + } output.append("#endif // TOOLS_ENABLED\n"); for (const InternalCall &internal_call : method_icalls) { diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index 2e6ce3a952..5460f018f0 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -366,6 +366,16 @@ class BindingsGenerator { return nullptr; } + const MethodInterface *find_method_by_proxy_name(const String &p_proxy_name) const { + for (const MethodInterface &E : methods) { + if (E.proxy_name == p_proxy_name) { + return &E; + } + } + + return nullptr; + } + const PropertyInterface *find_property_by_name(const StringName &p_cname) const { for (const PropertyInterface &E : properties) { if (E.cname == p_cname) { @@ -386,8 +396,18 @@ class BindingsGenerator { return nullptr; } - const MethodInterface *find_method_by_proxy_name(const String &p_proxy_name) const { - for (const MethodInterface &E : methods) { + const SignalInterface *find_signal_by_name(const StringName &p_cname) const { + for (const SignalInterface &E : signals_) { + if (E.cname == p_cname) { + return &E; + } + } + + return nullptr; + } + + const SignalInterface *find_signal_by_proxy_name(const String &p_proxy_name) const { + for (const SignalInterface &E : signals_) { if (E.proxy_name == p_proxy_name) { return &E; } diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index 3c02ea0e8e..f7f710f3f1 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -34,6 +34,7 @@ #include <unistd.h> // access #endif +#include "core/config/project_settings.h" #include "core/os/os.h" #include "core/version.h" #include "editor/debugger/editor_debugger_node.h" diff --git a/modules/mono/editor_templates/CharacterBody2D/basic_movement.cs b/modules/mono/editor_templates/CharacterBody2D/basic_movement.cs index 6e9547ce9b..2ca81ab7cd 100644 --- a/modules/mono/editor_templates/CharacterBody2D/basic_movement.cs +++ b/modules/mono/editor_templates/CharacterBody2D/basic_movement.cs @@ -6,36 +6,36 @@ using System; public partial class _CLASS_ : _BASE_ { public const float Speed = 300.0f; - public const float JumpForce = -400.0f; + public const float JumpVelocity = -400.0f; // Get the gravity from the project settings to be synced with RigidDynamicBody nodes. public float gravity = (float)ProjectSettings.GetSetting("physics/2d/default_gravity"); public override void _PhysicsProcess(float delta) { - Vector2 motionVelocity = MotionVelocity; + Vector2 velocity = Velocity; // Add the gravity. if (!IsOnFloor()) - motionVelocity.y += gravity * delta; + velocity.y += gravity * delta; // Handle Jump. if (Input.IsActionJustPressed("ui_accept") && IsOnFloor()) - motionVelocity.y = JumpForce; + velocity.y = JumpVelocity; // Get the input direction and handle the movement/deceleration. // As good practice, you should replace UI actions with custom gameplay actions. Vector2 direction = Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down"); if (direction != Vector2.Zero) { - motionVelocity.x = direction.x * Speed; + velocity.x = direction.x * Speed; } else { - motionVelocity.x = Mathf.MoveToward(MotionVelocity.x, 0, Speed); + velocity.x = Mathf.MoveToward(Velocity.x, 0, Speed); } - MotionVelocity = motionVelocity; + Velocity = velocity; MoveAndSlide(); } } diff --git a/modules/mono/editor_templates/CharacterBody3D/basic_movement.cs b/modules/mono/editor_templates/CharacterBody3D/basic_movement.cs index 13be4bbcb1..a6935fe497 100644 --- a/modules/mono/editor_templates/CharacterBody3D/basic_movement.cs +++ b/modules/mono/editor_templates/CharacterBody3D/basic_movement.cs @@ -6,22 +6,22 @@ using System; public partial class _CLASS_ : _BASE_ { public const float Speed = 5.0f; - public const float JumpForce = 4.5f; + public const float JumpVelocity = 4.5f; // Get the gravity from the project settings to be synced with RigidDynamicBody nodes. public float gravity = (float)ProjectSettings.GetSetting("physics/3d/default_gravity"); public override void _PhysicsProcess(float delta) { - Vector3 motionVelocity = MotionVelocity; + Vector3 velocity = Velocity; // Add the gravity. if (!IsOnFloor()) - motionVelocity.y -= gravity * delta; + velocity.y -= gravity * delta; // Handle Jump. if (Input.IsActionJustPressed("ui_accept") && IsOnFloor()) - motionVelocity.y = JumpForce; + velocity.y = JumpVelocity; // Get the input direction and handle the movement/deceleration. // As good practice, you should replace UI actions with custom gameplay actions. @@ -29,16 +29,16 @@ public partial class _CLASS_ : _BASE_ Vector3 direction = Transform.basis.Xform(new Vector3(inputDir.x, 0, inputDir.y)).Normalized(); if (direction != Vector3.Zero) { - motionVelocity.x = direction.x * Speed; - motionVelocity.z = direction.z * Speed; + velocity.x = direction.x * Speed; + velocity.z = direction.z * Speed; } else { - motionVelocity.x = Mathf.MoveToward(MotionVelocity.x, 0, Speed); - motionVelocity.z = Mathf.MoveToward(MotionVelocity.z, 0, Speed); + velocity.x = Mathf.MoveToward(Velocity.x, 0, Speed); + velocity.z = Mathf.MoveToward(Velocity.z, 0, Speed); } - MotionVelocity = motionVelocity; + Velocity = velocity; MoveAndSlide(); } } diff --git a/modules/mono/editor_templates/VisualShaderNodeCustom/basic.cs b/modules/mono/editor_templates/VisualShaderNodeCustom/basic.cs index 00fdc9968e..a1b93e7daa 100644 --- a/modules/mono/editor_templates/VisualShaderNodeCustom/basic.cs +++ b/modules/mono/editor_templates/VisualShaderNodeCustom/basic.cs @@ -55,11 +55,6 @@ public partial class VisualShaderNode_CLASS_ : _BASE_ return 0; } - public override string _GetGlobalCode(Shader.Mode mode) - { - return ""; - } - public override string _GetCode(Godot.Collections.Array inputVars, Godot.Collections.Array outputVars, Shader.Mode mode, VisualShader.Type type) { return ""; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs index bfe9600084..ce213da6a7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs @@ -180,6 +180,24 @@ namespace Godot } /// <summary> + /// Cubic interpolates between two values by a normalized value with pre and post values. + /// </summary> + /// <param name="from">The start value for interpolation.</param> + /// <param name="to">The destination value for interpolation.</param> + /// <param name="pre">The value which before "from" value for interpolation.</param> + /// <param name="post">The value which after "to" value for interpolation.</param> + /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The resulting value of the interpolation.</returns> + public static real_t CubicInterpolate(real_t from, real_t to, real_t pre, real_t post, real_t weight) + { + return 0.5f * + ((from * 2.0f) + + (-pre + to) * weight + + (2.0f * pre - 5.0f * from + 4.0f * to - post) * (weight * weight) + + (-pre + 3.0f * from - 3.0f * to + post) * (weight * weight * weight)); + } + + /// <summary> /// Converts an angle expressed in degrees to radians. /// </summary> /// <param name="deg">An angle expressed in degrees.</param> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index eba0ea9a79..a1f058ffe5 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -266,7 +266,7 @@ namespace Godot /// <returns>The capitalized string.</returns> public static string Capitalize(this string instance) { - string aux = instance.Replace("_", " ").ToLower(); + string aux = instance.CamelcaseToUnderscore(true).Replace("_", " ").Trim(); string cap = string.Empty; for (int i = 0; i < aux.GetSliceCount(" "); i++) @@ -284,6 +284,51 @@ namespace Godot return cap; } + private static string CamelcaseToUnderscore(this string instance, bool lowerCase) + { + string newString = string.Empty; + int startIndex = 0; + + for (int i = 1; i < instance.Length; i++) + { + bool isUpper = char.IsUpper(instance[i]); + bool isNumber = char.IsDigit(instance[i]); + + bool areNext2Lower = false; + bool isNextLower = false; + bool isNextNumber = false; + bool wasPrecedentUpper = char.IsUpper(instance[i - 1]); + bool wasPrecedentNumber = char.IsDigit(instance[i - 1]); + + if (i + 2 < instance.Length) + { + areNext2Lower = char.IsLower(instance[i + 1]) && char.IsLower(instance[i + 2]); + } + + if (i + 1 < instance.Length) + { + isNextLower = char.IsLower(instance[i + 1]); + isNextNumber = char.IsDigit(instance[i + 1]); + } + + bool condA = isUpper && !wasPrecedentUpper && !wasPrecedentNumber; + bool condB = wasPrecedentUpper && isUpper && areNext2Lower; + bool condC = isNumber && !wasPrecedentNumber; + bool canBreakNumberLetter = isNumber && !wasPrecedentNumber && isNextLower; + bool canBreakLetterNumber = !isNumber && wasPrecedentNumber && (isNextLower || isNextNumber); + + bool shouldSplit = condA || condB || condC || canBreakNumberLetter || canBreakLetterNumber; + if (shouldSplit) + { + newString += instance.Substring(startIndex, i - startIndex) + "_"; + startIndex = i; + } + } + + newString += instance.Substring(startIndex, instance.Length - startIndex); + return lowerCase ? newString.ToLower() : newString; + } + /// <summary> /// Performs a case-sensitive comparison to another string, return -1 if less, 0 if equal and +1 if greater. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs index 1f5282e88f..8679f28132 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs @@ -204,20 +204,10 @@ namespace Godot /// <returns>The interpolated vector.</returns> public Vector2 CubicInterpolate(Vector2 b, Vector2 preA, Vector2 postB, real_t weight) { - Vector2 p0 = preA; - Vector2 p1 = this; - Vector2 p2 = b; - Vector2 p3 = postB; - - real_t t = weight; - real_t t2 = t * t; - real_t t3 = t2 * t; - - return 0.5f * ( - (p1 * 2.0f) + - ((-p0 + p2) * t) + - (((2.0f * p0) - (5.0f * p1) + (4 * p2) - p3) * t2) + - ((-p0 + (3.0f * p1) - (3.0f * p2) + p3) * t3) + return new Vector2 + ( + Mathf.CubicInterpolate(x, b.x, preA.x, postB.x, weight), + Mathf.CubicInterpolate(y, b.y, preA.y, postB.y, weight) ); } @@ -512,24 +502,24 @@ namespace Godot /// Returns the result of the spherical linear interpolation between /// this vector and <paramref name="to"/> by amount <paramref name="weight"/>. /// - /// Note: Both vectors must be normalized. + /// This method also handles interpolating the lengths if the input vectors have different lengths. + /// For the special case of one or both input vectors having zero length, this method behaves like [method lerp]. /// </summary> - /// <param name="to">The destination vector for interpolation. Must be normalized.</param> + /// <param name="to">The destination vector for interpolation.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The resulting vector of the interpolation.</returns> public Vector2 Slerp(Vector2 to, real_t weight) { -#if DEBUG - if (!IsNormalized()) - { - throw new InvalidOperationException("Vector2.Slerp: From vector is not normalized."); - } - if (!to.IsNormalized()) - { - throw new InvalidOperationException($"Vector2.Slerp: `{nameof(to)}` is not normalized."); + real_t startLengthSquared = LengthSquared(); + real_t endLengthSquared = to.LengthSquared(); + if (startLengthSquared == 0.0 || endLengthSquared == 0.0) { + // Zero length vectors have no angle, so the best we can do is either lerp or throw an error. + return Lerp(to, weight); } -#endif - return Rotated(AngleTo(to) * weight); + real_t startLength = Mathf.Sqrt(startLengthSquared); + real_t resultLength = Mathf.Lerp(startLength, Mathf.Sqrt(endLengthSquared), weight); + real_t angle = AngleTo(to); + return Rotated(angle * weight) * (resultLength / startLength); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs index 433a5d9dc9..1e60fb9523 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs @@ -195,19 +195,11 @@ namespace Godot /// <returns>The interpolated vector.</returns> public Vector3 CubicInterpolate(Vector3 b, Vector3 preA, Vector3 postB, real_t weight) { - Vector3 p0 = preA; - Vector3 p1 = this; - Vector3 p2 = b; - Vector3 p3 = postB; - - real_t t = weight; - real_t t2 = t * t; - real_t t3 = t2 * t; - - return 0.5f * ( - (p1 * 2.0f) + ((-p0 + p2) * t) + - (((2.0f * p0) - (5.0f * p1) + (4f * p2) - p3) * t2) + - ((-p0 + (3.0f * p1) - (3.0f * p2) + p3) * t3) + return new Vector3 + ( + Mathf.CubicInterpolate(x, b.x, preA.x, postB.x, weight), + Mathf.CubicInterpolate(y, b.y, preA.y, postB.y, weight), + Mathf.CubicInterpolate(z, b.z, preA.z, postB.z, weight) ); } @@ -549,25 +541,24 @@ namespace Godot /// Returns the result of the spherical linear interpolation between /// this vector and <paramref name="to"/> by amount <paramref name="weight"/>. /// - /// Note: Both vectors must be normalized. + /// This method also handles interpolating the lengths if the input vectors have different lengths. + /// For the special case of one or both input vectors having zero length, this method behaves like [method lerp]. /// </summary> - /// <param name="to">The destination vector for interpolation. Must be normalized.</param> + /// <param name="to">The destination vector for interpolation.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The resulting vector of the interpolation.</returns> public Vector3 Slerp(Vector3 to, real_t weight) { -#if DEBUG - if (!IsNormalized()) - { - throw new InvalidOperationException("Vector3.Slerp: From vector is not normalized."); - } - if (!to.IsNormalized()) - { - throw new InvalidOperationException($"Vector3.Slerp: `{nameof(to)}` is not normalized."); + real_t startLengthSquared = LengthSquared(); + real_t endLengthSquared = to.LengthSquared(); + if (startLengthSquared == 0.0 || endLengthSquared == 0.0) { + // Zero length vectors have no angle, so the best we can do is either lerp or throw an error. + return Lerp(to, weight); } -#endif - real_t theta = AngleTo(to); - return Rotated(Cross(to), theta * weight); + real_t startLength = Mathf.Sqrt(startLengthSquared); + real_t resultLength = Mathf.Lerp(startLength, Mathf.Sqrt(endLengthSquared), weight); + real_t angle = AngleTo(to); + return Rotated(Cross(to).Normalized(), angle * weight) * (resultLength / startLength); } /// <summary> diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp index c0cd18e29d..7c2cb2e260 100644 --- a/modules/mono/godotsharp_dirs.cpp +++ b/modules/mono/godotsharp_dirs.cpp @@ -36,7 +36,7 @@ #ifdef TOOLS_ENABLED #include "core/version.h" -#include "editor/editor_settings.h" +#include "editor/editor_paths.h" #endif #ifdef ANDROID_ENABLED diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 6f542a67e7..4cd4772d2c 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -52,10 +52,6 @@ #include "gd_mono_marshal.h" #include "gd_mono_utils.h" -#ifdef TOOLS_ENABLED -#include "main/main.h" -#endif - #ifdef ANDROID_ENABLED #include "android_mono_config.h" #include "support/android_support.h" @@ -143,7 +139,7 @@ void gd_mono_debug_init() { if (Engine::get_singleton()->is_editor_hint() || ProjectSettings::get_singleton()->get_resource_path().is_empty() || - Main::is_project_manager()) { + Engine::get_singleton()->is_project_manager_hint()) { if (da_args.size() == 0) { return; } @@ -155,8 +151,9 @@ void gd_mono_debug_init() { .utf8(); } #else - if (da_args.length() == 0) + if (da_args.length() == 0) { return; // Exported games don't use the project settings to setup the debugger agent + } #endif // Debugging enabled @@ -230,8 +227,9 @@ void GDMono::add_mono_shared_libs_dir_to_path() { path_value += mono_reg_info.bin_dir; } #else - if (DirAccess::exists(bundled_bin_dir)) + if (DirAccess::exists(bundled_bin_dir)) { path_value += bundled_bin_dir; + } #endif // TOOLS_ENABLED #else @@ -423,7 +421,7 @@ void GDMono::initialize_load_assemblies() { bool tool_assemblies_loaded = _load_tools_assemblies(); CRASH_COND_MSG(!tool_assemblies_loaded, "Mono: Failed to load '" TOOLS_ASM_NAME "' assemblies."); - if (Main::is_project_manager()) { + if (Engine::get_singleton()->is_project_manager_hint()) { return; } #endif @@ -815,7 +813,7 @@ bool GDMono::_load_core_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, c // For the editor and the editor player we want to load it from a specific path to make sure we can keep it up to date // If running the project manager, load it from the prebuilt API directory - String assembly_dir = !Main::is_project_manager() + String assembly_dir = !Engine::get_singleton()->is_project_manager_hint() ? GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config) : GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config); @@ -848,7 +846,7 @@ bool GDMono::_load_editor_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, // For the editor and the editor player we want to load it from a specific path to make sure we can keep it up to date // If running the project manager, load it from the prebuilt API directory - String assembly_dir = !Main::is_project_manager() + String assembly_dir = !Engine::get_singleton()->is_project_manager_hint() ? GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config) : GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config); @@ -932,7 +930,7 @@ void GDMono::_load_api_assemblies() { // this time update them from the prebuilt assemblies directory before trying to load them again. // Shouldn't happen. The project manager loads the prebuilt API assemblies - CRASH_COND_MSG(Main::is_project_manager(), "Failed to load one of the prebuilt API assemblies."); + CRASH_COND_MSG(Engine::get_singleton()->is_project_manager_hint(), "Failed to load one of the prebuilt API assemblies."); // 1. Unload the scripts domain Error domain_unload_err = _unload_scripts_domain(); @@ -1273,8 +1271,9 @@ GDMono::~GDMono() { print_verbose("Mono: Finalizing scripts domain..."); - if (mono_domain_get() != root_domain) + if (mono_domain_get() != root_domain) { mono_domain_set(root_domain, true); + } finalizing_scripts_domain = true; diff --git a/modules/mono/mono_gd/support/android_support.cpp b/modules/mono/mono_gd/support/android_support.cpp index eb8bbab948..4797d5dae1 100644 --- a/modules/mono/mono_gd/support/android_support.cpp +++ b/modules/mono/mono_gd/support/android_support.cpp @@ -134,8 +134,9 @@ String determine_app_native_lib_dir() { } String get_app_native_lib_dir() { - if (app_native_lib_dir_cache.is_empty()) + if (app_native_lib_dir_cache.is_empty()) { app_native_lib_dir_cache = determine_app_native_lib_dir(); + } return app_native_lib_dir_cache; } @@ -144,10 +145,11 @@ int gd_mono_convert_dl_flags(int flags) { int lflags = flags & MONO_DL_LOCAL ? 0 : RTLD_GLOBAL; - if (flags & MONO_DL_LAZY) + if (flags & MONO_DL_LAZY) { lflags |= RTLD_LAZY; - else + } else { lflags |= RTLD_NOW; + } return lflags; } @@ -164,8 +166,9 @@ void *godot_dl_handle = nullptr; void *try_dlopen(const String &p_so_path, int p_flags) { if (!FileAccess::exists(p_so_path)) { - if (OS::get_singleton()->is_stdout_verbose()) + if (OS::get_singleton()->is_stdout_verbose()) { OS::get_singleton()->print("Cannot find shared library: '%s'\n", p_so_path.utf8().get_data()); + } return nullptr; } @@ -174,13 +177,15 @@ void *try_dlopen(const String &p_so_path, int p_flags) { void *handle = dlopen(p_so_path.utf8().get_data(), lflags); if (!handle) { - if (OS::get_singleton()->is_stdout_verbose()) + if (OS::get_singleton()->is_stdout_verbose()) { OS::get_singleton()->print("Failed to open shared library: '%s'. Error: '%s'\n", p_so_path.utf8().get_data(), dlerror()); + } return nullptr; } - if (OS::get_singleton()->is_stdout_verbose()) + if (OS::get_singleton()->is_stdout_verbose()) { OS::get_singleton()->print("Successfully loaded shared library: '%s'\n", p_so_path.utf8().get_data()); + } return handle; } @@ -217,20 +222,23 @@ void *gd_mono_android_dlopen(const char *p_name, int p_flags, char **r_err, void void *gd_mono_android_dlsym(void *p_handle, const char *p_name, char **r_err, void *p_user_data) { void *sym_addr = dlsym(p_handle, p_name); - if (sym_addr) + if (sym_addr) { return sym_addr; + } if (p_handle == mono_dl_handle && godot_dl_handle) { // Looking up for '__Internal' P/Invoke. We want to search in both the Mono and Godot shared libraries. // This is needed to resolve the monodroid P/Invoke functions that are defined at the bottom of the file. sym_addr = dlsym(godot_dl_handle, p_name); - if (sym_addr) + if (sym_addr) { return sym_addr; + } } - if (r_err) + if (r_err) { *r_err = str_format_new("%s\n", dlerror()); + } return nullptr; } @@ -239,8 +247,9 @@ void *gd_mono_android_dlclose(void *p_handle, void *p_user_data) { dlclose(p_handle); // Not sure if this ever happens. Does Mono close the handle for the main module? - if (p_handle == mono_dl_handle) + if (p_handle == mono_dl_handle) { mono_dl_handle = nullptr; + } return nullptr; } @@ -292,13 +301,15 @@ MonoBoolean _gd_mono_init_cert_store() { ScopedLocalRef<jobject> certStoreLocal(env, env->CallStaticObjectMethod(keyStoreClass, getInstance, androidCAStoreString.get())); - if (jni_exception_check(env)) + if (jni_exception_check(env)) { return 0; + } env->CallVoidMethod(certStoreLocal, load, nullptr); - if (jni_exception_check(env)) + if (jni_exception_check(env)) { return 0; + } certStore = env->NewGlobalRef(certStoreLocal); @@ -309,8 +320,9 @@ MonoArray *_gd_mono_android_cert_store_lookup(MonoString *p_alias) { // The JNI code is the equivalent of: // // Certificate certificate = certStore.getCertificate(alias); - // if (certificate == null) + // if (certificate == null) { // return null; + // } // return certificate.getEncoded(); MonoError mono_error; @@ -340,8 +352,9 @@ MonoArray *_gd_mono_android_cert_store_lookup(MonoString *p_alias) { ScopedLocalRef<jobject> certificate(env, env->CallObjectMethod(certStore, getCertificate, js_alias.get())); - if (!certificate) + if (!certificate) { return nullptr; + } ScopedLocalRef<jbyteArray> encoded(env, (jbyteArray)env->CallObjectMethod(certificate, getEncoded)); jsize encodedLength = env->GetArrayLength(encoded); @@ -374,11 +387,13 @@ void initialize() { void cleanup() { // This is called after shutting down the Mono runtime - if (mono_dl_handle) + if (mono_dl_handle) { gd_mono_android_dlclose(mono_dl_handle, nullptr); + } - if (godot_dl_handle) + if (godot_dl_handle) { gd_mono_android_dlclose(godot_dl_handle, nullptr); + } JNIEnv *env = get_jni_env(); @@ -431,8 +446,9 @@ GD_PINVOKE_EXPORT mono_bool _monodroid_get_network_interface_up_state(const char // // NetworkInterface.getByName(p_ifname).isUp() - if (!r_is_up || !p_ifname || strlen(p_ifname) == 0) + if (!r_is_up || !p_ifname || strlen(p_ifname) == 0) { return 0; + } *r_is_up = 0; @@ -450,8 +466,9 @@ GD_PINVOKE_EXPORT mono_bool _monodroid_get_network_interface_up_state(const char ScopedLocalRef<jstring> js_ifname(env, env->NewStringUTF(p_ifname)); ScopedLocalRef<jobject> networkInterface(env, env->CallStaticObjectMethod(networkInterfaceClass, getByName, js_ifname.get())); - if (!networkInterface) + if (!networkInterface) { return 0; + } *r_is_up = (mono_bool)env->CallBooleanMethod(networkInterface, isUp); @@ -463,8 +480,9 @@ GD_PINVOKE_EXPORT mono_bool _monodroid_get_network_interface_supports_multicast( // // NetworkInterface.getByName(p_ifname).supportsMulticast() - if (!r_supports_multicast || !p_ifname || strlen(p_ifname) == 0) + if (!r_supports_multicast || !p_ifname || strlen(p_ifname) == 0) { return 0; + } *r_supports_multicast = 0; @@ -482,8 +500,9 @@ GD_PINVOKE_EXPORT mono_bool _monodroid_get_network_interface_supports_multicast( ScopedLocalRef<jstring> js_ifname(env, env->NewStringUTF(p_ifname)); ScopedLocalRef<jobject> networkInterface(env, env->CallStaticObjectMethod(networkInterfaceClass, getByName, js_ifname.get())); - if (!networkInterface) + if (!networkInterface) { return 0; + } *r_supports_multicast = (mono_bool)env->CallBooleanMethod(networkInterface, supportsMulticast); @@ -528,8 +547,9 @@ static void interop_get_active_network_dns_servers(char **r_dns_servers, int *dn ScopedLocalRef<jobject> connectivityManager(env, env->CallObjectMethod(applicationContext, getSystemService, connectivityServiceString.get())); - if (!connectivityManager) + if (!connectivityManager) { return; + } ScopedLocalRef<jclass> connectivityManagerClass(env, env->FindClass("android/net/ConnectivityManager")); ERR_FAIL_NULL(connectivityManagerClass); @@ -539,8 +559,9 @@ static void interop_get_active_network_dns_servers(char **r_dns_servers, int *dn ScopedLocalRef<jobject> activeNetwork(env, env->CallObjectMethod(connectivityManager, getActiveNetwork)); - if (!activeNetwork) + if (!activeNetwork) { return; + } jmethodID getLinkProperties = env->GetMethodID(connectivityManagerClass, "getLinkProperties", "(Landroid/net/Network;)Landroid/net/LinkProperties;"); @@ -548,8 +569,9 @@ static void interop_get_active_network_dns_servers(char **r_dns_servers, int *dn ScopedLocalRef<jobject> linkProperties(env, env->CallObjectMethod(connectivityManager, getLinkProperties, activeNetwork.get())); - if (!linkProperties) + if (!linkProperties) { return; + } ScopedLocalRef<jclass> linkPropertiesClass(env, env->FindClass("android/net/LinkProperties")); ERR_FAIL_NULL(linkPropertiesClass); @@ -559,8 +581,9 @@ static void interop_get_active_network_dns_servers(char **r_dns_servers, int *dn ScopedLocalRef<jobject> dnsServers(env, env->CallObjectMethod(linkProperties, getDnsServers)); - if (!dnsServers) + if (!dnsServers) { return; + } ScopedLocalRef<jclass> listClass(env, env->FindClass("java/util/List")); ERR_FAIL_NULL(listClass); @@ -570,11 +593,13 @@ static void interop_get_active_network_dns_servers(char **r_dns_servers, int *dn int dnsServersCount = env->CallIntMethod(dnsServers, listSize); - if (dnsServersCount > dns_servers_len) + if (dnsServersCount > dns_servers_len) { dnsServersCount = dns_servers_len; + } - if (dnsServersCount <= 0) + if (dnsServersCount <= 0) { return; + } jmethodID listGet = env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;"); ERR_FAIL_NULL(listGet); @@ -587,8 +612,9 @@ static void interop_get_active_network_dns_servers(char **r_dns_servers, int *dn for (int i = 0; i < dnsServersCount; i++) { ScopedLocalRef<jobject> dnsServer(env, env->CallObjectMethod(dnsServers, listGet, (jint)i)); - if (!dnsServer) + if (!dnsServer) { continue; + } ScopedLocalRef<jstring> hostAddress(env, (jstring)env->CallObjectMethod(dnsServer, getHostAddress)); const char *host_address = env->GetStringUTFChars(hostAddress, 0); @@ -603,8 +629,9 @@ static void interop_get_active_network_dns_servers(char **r_dns_servers, int *dn } GD_PINVOKE_EXPORT int32_t _monodroid_get_dns_servers(void **r_dns_servers_array) { - if (!r_dns_servers_array) + if (!r_dns_servers_array) { return -1; + } *r_dns_servers_array = nullptr; @@ -661,13 +688,15 @@ GD_PINVOKE_EXPORT const char *_monodroid_timezone_get_default_id() { ScopedLocalRef<jobject> defaultTimeZone(env, env->CallStaticObjectMethod(timeZoneClass, getDefault)); - if (!defaultTimeZone) + if (!defaultTimeZone) { return nullptr; + } ScopedLocalRef<jstring> defaultTimeZoneID(env, (jstring)env->CallObjectMethod(defaultTimeZone, getID)); - if (!defaultTimeZoneID) + if (!defaultTimeZoneID) { return nullptr; + } const char *default_time_zone_id = env->GetStringUTFChars(defaultTimeZoneID, 0); diff --git a/modules/mono/mono_gd/support/ios_support.mm b/modules/mono/mono_gd/support/ios_support.mm index e66b88db32..df97dfba49 100644 --- a/modules/mono/mono_gd/support/ios_support.mm +++ b/modules/mono/mono_gd/support/ios_support.mm @@ -94,8 +94,9 @@ GD_PINVOKE_EXPORT const char *xamarin_get_locale_country_code() { GD_PINVOKE_EXPORT void xamarin_log(const uint16_t *p_unicode_message) { int length = 0; const uint16_t *ptr = p_unicode_message; - while (*ptr++) + while (*ptr++) { length += sizeof(uint16_t); + } NSString *msg = [[NSString alloc] initWithBytes:p_unicode_message length:length encoding:NSUTF16LittleEndianStringEncoding]; os_log_info(OS_LOG_DEFAULT, "%{public}@", msg); diff --git a/modules/mono/utils/mono_reg_utils.cpp b/modules/mono/utils/mono_reg_utils.cpp index f388661207..8e37e6943c 100644 --- a/modules/mono/utils/mono_reg_utils.cpp +++ b/modules/mono/utils/mono_reg_utils.cpp @@ -60,8 +60,9 @@ REGSAM _get_bitness_sam() { LONG _RegOpenKey(HKEY hKey, LPCWSTR lpSubKey, PHKEY phkResult) { LONG res = RegOpenKeyExW(hKey, lpSubKey, 0, KEY_READ, phkResult); - if (res != ERROR_SUCCESS) + if (res != ERROR_SUCCESS) { res = RegOpenKeyExW(hKey, lpSubKey, 0, KEY_READ | _get_bitness_sam(), phkResult); + } return res; } @@ -92,31 +93,37 @@ LONG _find_mono_in_reg(const String &p_subkey, MonoRegInfo &r_info, bool p_old_r HKEY hKey; LONG res = _RegOpenKey(HKEY_LOCAL_MACHINE, (LPCWSTR)(p_subkey.utf16().get_data()), &hKey); - if (res != ERROR_SUCCESS) + if (res != ERROR_SUCCESS) { goto cleanup; + } if (!p_old_reg) { res = _RegKeyQueryString(hKey, "Version", r_info.version); - if (res != ERROR_SUCCESS) + if (res != ERROR_SUCCESS) { goto cleanup; + } } res = _RegKeyQueryString(hKey, "SdkInstallRoot", r_info.install_root_dir); - if (res != ERROR_SUCCESS) + if (res != ERROR_SUCCESS) { goto cleanup; + } res = _RegKeyQueryString(hKey, "FrameworkAssemblyDirectory", r_info.assembly_dir); - if (res != ERROR_SUCCESS) + if (res != ERROR_SUCCESS) { goto cleanup; + } res = _RegKeyQueryString(hKey, "MonoConfigDir", r_info.config_dir); - if (res != ERROR_SUCCESS) + if (res != ERROR_SUCCESS) { goto cleanup; + } - if (r_info.install_root_dir.ends_with("\\")) + if (r_info.install_root_dir.ends_with("\\")) { r_info.bin_dir = r_info.install_root_dir + "bin"; - else + } else { r_info.bin_dir = r_info.install_root_dir + "\\bin"; + } cleanup: RegCloseKey(hKey); @@ -129,8 +136,9 @@ LONG _find_mono_in_reg_old(const String &p_subkey, MonoRegInfo &r_info) { HKEY hKey; LONG res = _RegOpenKey(HKEY_LOCAL_MACHINE, (LPCWSTR)(p_subkey.utf16().get_data()), &hKey); - if (res != ERROR_SUCCESS) + if (res != ERROR_SUCCESS) { goto cleanup; + } res = _RegKeyQueryString(hKey, "DefaultCLR", default_clr); @@ -147,11 +155,13 @@ cleanup: MonoRegInfo find_mono() { MonoRegInfo info; - if (_find_mono_in_reg("Software\\Mono", info) == ERROR_SUCCESS) + if (_find_mono_in_reg("Software\\Mono", info) == ERROR_SUCCESS) { return info; + } - if (_find_mono_in_reg_old("Software\\Novell\\Mono", info) == ERROR_SUCCESS) + if (_find_mono_in_reg_old("Software\\Novell\\Mono", info) == ERROR_SUCCESS) { return info; + } return MonoRegInfo(); } @@ -212,13 +222,15 @@ String find_msbuild_tools_path() { HKEY hKey; LONG res = _RegOpenKey(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\MSBuild\\ToolsVersions\\14.0", &hKey); - if (res != ERROR_SUCCESS) + if (res != ERROR_SUCCESS) { goto cleanup; + } res = _RegKeyQueryString(hKey, "MSBuildToolsPath", msbuild_tools_path); - if (res != ERROR_SUCCESS) + if (res != ERROR_SUCCESS) { goto cleanup; + } cleanup: RegCloseKey(hKey); diff --git a/modules/mono/utils/path_utils.cpp b/modules/mono/utils/path_utils.cpp index 89851fc4d3..15a0b28181 100644 --- a/modules/mono/utils/path_utils.cpp +++ b/modules/mono/utils/path_utils.cpp @@ -57,8 +57,9 @@ String cwd() { Char16String buffer; buffer.resize((int)expected_size); - if (::GetCurrentDirectoryW(expected_size, (wchar_t *)buffer.ptrw()) == 0) + if (::GetCurrentDirectoryW(expected_size, (wchar_t *)buffer.ptrw()) == 0) { return "."; + } String result; if (result.parse_utf16(buffer.ptr())) { @@ -95,8 +96,9 @@ String realpath(const String &p_path) { FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); - if (hFile == INVALID_HANDLE_VALUE) + if (hFile == INVALID_HANDLE_VALUE) { return p_path; + } const DWORD expected_size = ::GetFinalPathNameByHandleW(hFile, nullptr, 0, FILE_NAME_NORMALIZED); @@ -177,8 +179,9 @@ String relative_to_impl(const String &p_path, const String &p_relative_to) { #ifdef WINDOWS_ENABLED String get_drive_letter(const String &p_norm_path) { int idx = p_norm_path.find(":/"); - if (idx != -1 && idx < p_norm_path.find("/")) + if (idx != -1 && idx < p_norm_path.find("/")) { return p_norm_path.substr(0, idx + 1); + } return String(); } #endif diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp index 76c31a5f42..1151df6390 100644 --- a/modules/navigation/nav_map.cpp +++ b/modules/navigation/nav_map.cpp @@ -83,12 +83,9 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p continue; } - // For each point cast a face and check the distance between the origin/destination - for (size_t point_id = 0; point_id < p.points.size(); point_id++) { - const Vector3 p1 = p.points[point_id].pos; - const Vector3 p2 = p.points[(point_id + 1) % p.points.size()].pos; - const Vector3 p3 = p.points[(point_id + 2) % p.points.size()].pos; - const Face3 face(p1, p2, p3); + // For each face check the distance between the origin/destination + for (size_t point_id = 2; point_id < p.points.size(); point_id++) { + const Face3 face(p.points[0].pos, p.points[point_id - 1].pos, p.points[point_id].pos); Vector3 point = face.get_closest_point_to(p_origin); float distance_to_point = point.distance_to(p_origin); @@ -214,7 +211,7 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p end_poly = reachable_end; end_d = 1e20; for (size_t point_id = 2; point_id < end_poly->points.size(); point_id++) { - Face3 f(end_poly->points[point_id - 2].pos, end_poly->points[point_id - 1].pos, end_poly->points[point_id].pos); + Face3 f(end_poly->points[0].pos, end_poly->points[point_id - 1].pos, end_poly->points[point_id].pos); Vector3 spoint = f.get_closest_point_to(p_destination); float dpoint = spoint.distance_to(p_destination); if (dpoint < end_d) { @@ -370,13 +367,12 @@ Vector3 NavMap::get_closest_point_to_segment(const Vector3 &p_from, const Vector Vector3 closest_point; real_t closest_point_d = 1e20; - // Find the initial poly and the end poly on this map. for (size_t i(0); i < polygons.size(); i++) { const gd::Polygon &p = polygons[i]; - // For each point cast a face and check the distance to the segment + // For each face check the distance to the segment for (size_t point_id = 2; point_id < p.points.size(); point_id += 1) { - const Face3 f(p.points[point_id - 2].pos, p.points[point_id - 1].pos, p.points[point_id].pos); + const Face3 f(p.points[0].pos, p.points[point_id - 1].pos, p.points[point_id].pos); Vector3 inters; if (f.intersects_segment(p_from, p_to, &inters)) { const real_t d = closest_point_d = p_from.distance_to(inters); @@ -416,82 +412,42 @@ Vector3 NavMap::get_closest_point_to_segment(const Vector3 &p_from, const Vector } Vector3 NavMap::get_closest_point(const Vector3 &p_point) const { - // TODO this is really not optimal, please redesign the API to directly return all this data - - Vector3 closest_point; - real_t closest_point_d = 1e20; - - // Find the initial poly and the end poly on this map. - for (size_t i(0); i < polygons.size(); i++) { - const gd::Polygon &p = polygons[i]; - - // For each point cast a face and check the distance to the point - for (size_t point_id = 2; point_id < p.points.size(); point_id += 1) { - const Face3 f(p.points[point_id - 2].pos, p.points[point_id - 1].pos, p.points[point_id].pos); - const Vector3 inters = f.get_closest_point_to(p_point); - const real_t d = inters.distance_to(p_point); - if (d < closest_point_d) { - closest_point = inters; - closest_point_d = d; - } - } - } - - return closest_point; + gd::ClosestPointQueryResult cp = get_closest_point_info(p_point); + return cp.point; } Vector3 NavMap::get_closest_point_normal(const Vector3 &p_point) const { - // TODO this is really not optimal, please redesign the API to directly return all this data - - Vector3 closest_point; - Vector3 closest_point_normal; - real_t closest_point_d = 1e20; - - // Find the initial poly and the end poly on this map. - for (size_t i(0); i < polygons.size(); i++) { - const gd::Polygon &p = polygons[i]; - - // For each point cast a face and check the distance to the point - for (size_t point_id = 2; point_id < p.points.size(); point_id += 1) { - const Face3 f(p.points[point_id - 2].pos, p.points[point_id - 1].pos, p.points[point_id].pos); - const Vector3 inters = f.get_closest_point_to(p_point); - const real_t d = inters.distance_to(p_point); - if (d < closest_point_d) { - closest_point = inters; - closest_point_normal = f.get_plane().normal; - closest_point_d = d; - } - } - } - - return closest_point_normal; + gd::ClosestPointQueryResult cp = get_closest_point_info(p_point); + return cp.normal; } RID NavMap::get_closest_point_owner(const Vector3 &p_point) const { - // TODO this is really not optimal, please redesign the API to directly return all this data + gd::ClosestPointQueryResult cp = get_closest_point_info(p_point); + return cp.owner; +} - Vector3 closest_point; - RID closest_point_owner; - real_t closest_point_d = 1e20; +gd::ClosestPointQueryResult NavMap::get_closest_point_info(const Vector3 &p_point) const { + gd::ClosestPointQueryResult result; + real_t closest_point_ds = 1e20; - // Find the initial poly and the end poly on this map. for (size_t i(0); i < polygons.size(); i++) { const gd::Polygon &p = polygons[i]; - // For each point cast a face and check the distance to the point + // For each face check the distance to the point for (size_t point_id = 2; point_id < p.points.size(); point_id += 1) { - const Face3 f(p.points[point_id - 2].pos, p.points[point_id - 1].pos, p.points[point_id].pos); + const Face3 f(p.points[0].pos, p.points[point_id - 1].pos, p.points[point_id].pos); const Vector3 inters = f.get_closest_point_to(p_point); - const real_t d = inters.distance_to(p_point); - if (d < closest_point_d) { - closest_point = inters; - closest_point_owner = p.owner->get_self(); - closest_point_d = d; + const real_t ds = inters.distance_squared_to(p_point); + if (ds < closest_point_ds) { + result.point = inters; + result.normal = f.get_plane().normal; + result.owner = p.owner->get_self(); + closest_point_ds = ds; } } } - return closest_point_owner; + return result; } void NavMap::add_region(NavRegion *p_region) { diff --git a/modules/navigation/nav_map.h b/modules/navigation/nav_map.h index 1802f4e907..f46297a7ce 100644 --- a/modules/navigation/nav_map.h +++ b/modules/navigation/nav_map.h @@ -104,6 +104,7 @@ public: Vector3 get_closest_point_to_segment(const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision) const; Vector3 get_closest_point(const Vector3 &p_point) const; Vector3 get_closest_point_normal(const Vector3 &p_point) const; + gd::ClosestPointQueryResult get_closest_point_info(const Vector3 &p_point) const; RID get_closest_point_owner(const Vector3 &p_point) const; void add_region(NavRegion *p_region); diff --git a/modules/navigation/nav_utils.h b/modules/navigation/nav_utils.h index a6f51a4698..0c02885b10 100644 --- a/modules/navigation/nav_utils.h +++ b/modules/navigation/nav_utils.h @@ -32,6 +32,7 @@ #define NAV_UTILS_H #include "core/math/vector3.h" +#include "core/templates/vector.h" #include <vector> @@ -131,6 +132,12 @@ struct NavigationPoly { } }; +struct ClosestPointQueryResult { + Vector3 point; + Vector3 normal; + RID owner; +}; + } // namespace gd #endif // NAV_UTILS_H diff --git a/modules/navigation/navigation_mesh_editor_plugin.cpp b/modules/navigation/navigation_mesh_editor_plugin.cpp index 6db3cbc1a3..511490ba07 100644 --- a/modules/navigation/navigation_mesh_editor_plugin.cpp +++ b/modules/navigation/navigation_mesh_editor_plugin.cpp @@ -33,6 +33,7 @@ #include "core/io/marshalls.h" #include "core/io/resource_saver.h" +#include "editor/editor_node.h" #include "navigation_mesh_generator.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/gui/box_container.h" @@ -45,10 +46,12 @@ void NavigationMeshEditor::_node_removed(Node *p_node) { } } -void NavigationMeshEditor::_notification(int p_option) { - if (p_option == NOTIFICATION_ENTER_TREE) { - button_bake->set_icon(get_theme_icon(SNAME("Bake"), SNAME("EditorIcons"))); - button_reset->set_icon(get_theme_icon(SNAME("Reload"), SNAME("EditorIcons"))); +void NavigationMeshEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + button_bake->set_icon(get_theme_icon(SNAME("Bake"), SNAME("EditorIcons"))); + button_reset->set_icon(get_theme_icon(SNAME("Reload"), SNAME("EditorIcons"))); + } break; } } @@ -139,10 +142,9 @@ void NavigationMeshEditorPlugin::make_visible(bool p_visible) { } } -NavigationMeshEditorPlugin::NavigationMeshEditorPlugin(EditorNode *p_node) { - editor = p_node; +NavigationMeshEditorPlugin::NavigationMeshEditorPlugin() { navigation_mesh_editor = memnew(NavigationMeshEditor); - editor->get_main_control()->add_child(navigation_mesh_editor); + EditorNode::get_singleton()->get_main_control()->add_child(navigation_mesh_editor); add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, navigation_mesh_editor->bake_hbox); navigation_mesh_editor->hide(); navigation_mesh_editor->bake_hbox->hide(); diff --git a/modules/navigation/navigation_mesh_editor_plugin.h b/modules/navigation/navigation_mesh_editor_plugin.h index 6517a7be5b..d581b453b3 100644 --- a/modules/navigation/navigation_mesh_editor_plugin.h +++ b/modules/navigation/navigation_mesh_editor_plugin.h @@ -33,7 +33,6 @@ #ifdef TOOLS_ENABLED -#include "editor/editor_node.h" #include "editor/editor_plugin.h" class NavigationRegion3D; @@ -58,7 +57,7 @@ class NavigationMeshEditor : public Control { protected: void _node_removed(Node *p_node); static void _bind_methods(); - void _notification(int p_option); + void _notification(int p_what); public: void edit(NavigationRegion3D *p_nav_region); @@ -70,7 +69,6 @@ class NavigationMeshEditorPlugin : public EditorPlugin { GDCLASS(NavigationMeshEditorPlugin, EditorPlugin); NavigationMeshEditor *navigation_mesh_editor; - EditorNode *editor; public: virtual String get_name() const override { return "NavigationMesh"; } @@ -79,7 +77,7 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - NavigationMeshEditorPlugin(EditorNode *p_node); + NavigationMeshEditorPlugin(); ~NavigationMeshEditorPlugin(); }; diff --git a/modules/navigation/navigation_mesh_generator.cpp b/modules/navigation/navigation_mesh_generator.cpp index 77fe9cdd19..61c3cefc7a 100644 --- a/modules/navigation/navigation_mesh_generator.cpp +++ b/modules/navigation/navigation_mesh_generator.cpp @@ -50,7 +50,6 @@ #ifdef TOOLS_ENABLED #include "editor/editor_node.h" -#include "editor/editor_settings.h" #endif #include "modules/modules_enabled.gen.h" // For csg, gridmap. @@ -140,12 +139,12 @@ void NavigationMeshGenerator::_add_faces(const PackedVector3Array &p_faces, cons } } -void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transform, Node *p_node, Vector<float> &p_vertices, Vector<int> &p_indices, NavigationMesh::ParsedGeometryType p_generate_from, uint32_t p_collision_mask, bool p_recurse_children) { +void NavigationMeshGenerator::_parse_geometry(const Transform3D &p_navmesh_transform, Node *p_node, Vector<float> &p_vertices, Vector<int> &p_indices, NavigationMesh::ParsedGeometryType p_generate_from, uint32_t p_collision_mask, bool p_recurse_children) { if (Object::cast_to<MeshInstance3D>(p_node) && p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) { MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(p_node); Ref<Mesh> mesh = mesh_instance->get_mesh(); if (mesh.is_valid()) { - _add_mesh(mesh, p_accumulated_transform * mesh_instance->get_transform(), p_vertices, p_indices); + _add_mesh(mesh, p_navmesh_transform * mesh_instance->get_global_transform(), p_vertices, p_indices); } } @@ -159,7 +158,7 @@ void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transfor n = multimesh->get_instance_count(); } for (int i = 0; i < n; i++) { - _add_mesh(mesh, p_accumulated_transform * multimesh->get_instance_transform(i), p_vertices, p_indices); + _add_mesh(mesh, p_navmesh_transform * multimesh_instance->get_global_transform() * multimesh->get_instance_transform(i), p_vertices, p_indices); } } } @@ -171,7 +170,7 @@ void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transfor if (!meshes.is_empty()) { Ref<Mesh> mesh = meshes[1]; if (mesh.is_valid()) { - _add_mesh(mesh, p_accumulated_transform * csg_shape->get_transform(), p_vertices, p_indices); + _add_mesh(mesh, p_navmesh_transform * csg_shape->get_global_transform(), p_vertices, p_indices); } } } @@ -186,7 +185,7 @@ void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transfor if (Object::cast_to<CollisionShape3D>(child)) { CollisionShape3D *col_shape = Object::cast_to<CollisionShape3D>(child); - Transform3D transform = p_accumulated_transform * static_body->get_transform() * col_shape->get_transform(); + Transform3D transform = p_navmesh_transform * static_body->get_global_transform() * col_shape->get_transform(); Ref<Mesh> mesh; Ref<Shape3D> s = col_shape->get_shape(); @@ -270,11 +269,11 @@ void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transfor if (gridmap) { if (p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) { Array meshes = gridmap->get_meshes(); - Transform3D xform = gridmap->get_transform(); + Transform3D xform = gridmap->get_global_transform(); for (int i = 0; i < meshes.size(); i += 2) { Ref<Mesh> mesh = meshes[i + 1]; if (mesh.is_valid()) { - _add_mesh(mesh, p_accumulated_transform * xform * (Transform3D)meshes[i], p_vertices, p_indices); + _add_mesh(mesh, p_navmesh_transform * xform * (Transform3D)meshes[i], p_vertices, p_indices); } } } @@ -364,14 +363,9 @@ void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transfor } #endif - if (Object::cast_to<Node3D>(p_node)) { - Node3D *spatial = Object::cast_to<Node3D>(p_node); - p_accumulated_transform = p_accumulated_transform * spatial->get_transform(); - } - if (p_recurse_children) { for (int i = 0; i < p_node->get_child_count(); i++) { - _parse_geometry(p_accumulated_transform, p_node->get_child(i), p_vertices, p_indices, p_generate_from, p_collision_mask, p_recurse_children); + _parse_geometry(p_navmesh_transform, p_node->get_child(i), p_vertices, p_indices, p_generate_from, p_collision_mask, p_recurse_children); } } } @@ -616,7 +610,7 @@ void NavigationMeshGenerator::bake(Ref<NavigationMesh> p_nav_mesh, Node *p_node) p_node->get_tree()->get_nodes_in_group(p_nav_mesh->get_source_group_name(), &parse_nodes); } - Transform3D navmesh_xform = Object::cast_to<Node3D>(p_node)->get_transform().affine_inverse(); + Transform3D navmesh_xform = Object::cast_to<Node3D>(p_node)->get_global_transform().affine_inverse(); for (Node *E : parse_nodes) { NavigationMesh::ParsedGeometryType geometry_type = p_nav_mesh->get_parsed_geometry_type(); uint32_t collision_mask = p_nav_mesh->get_collision_mask(); diff --git a/modules/navigation/navigation_mesh_generator.h b/modules/navigation/navigation_mesh_generator.h index 1ffdd39aee..21f7a4941b 100644 --- a/modules/navigation/navigation_mesh_generator.h +++ b/modules/navigation/navigation_mesh_generator.h @@ -52,7 +52,7 @@ protected: static void _add_vertex(const Vector3 &p_vec3, Vector<float> &p_vertices); static void _add_mesh(const Ref<Mesh> &p_mesh, const Transform3D &p_xform, Vector<float> &p_vertices, Vector<int> &p_indices); static void _add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform, Vector<float> &p_vertices, Vector<int> &p_indices); - static void _parse_geometry(Transform3D p_accumulated_transform, Node *p_node, Vector<float> &p_vertices, Vector<int> &p_indices, NavigationMesh::ParsedGeometryType p_generate_from, uint32_t p_collision_mask, bool p_recurse_children); + static void _parse_geometry(const Transform3D &p_navmesh_transform, Node *p_node, Vector<float> &p_vertices, Vector<int> &p_indices, NavigationMesh::ParsedGeometryType p_generate_from, uint32_t p_collision_mask, bool p_recurse_children); static void _convert_detail_mesh_to_native_navigation_mesh(const rcPolyMeshDetail *p_detail_mesh, Ref<NavigationMesh> p_nav_mesh); static void _build_recast_navigation_mesh( diff --git a/modules/ogg/doc_classes/OGGPacketSequence.xml b/modules/ogg/doc_classes/OGGPacketSequence.xml index deac5b67e2..bff3691ce0 100644 --- a/modules/ogg/doc_classes/OGGPacketSequence.xml +++ b/modules/ogg/doc_classes/OGGPacketSequence.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="OGGPacketSequence" inherits="Resource" version="4.0"> +<class name="OGGPacketSequence" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A sequence of OGG packets. </brief_description> diff --git a/modules/ogg/doc_classes/OGGPacketSequencePlayback.xml b/modules/ogg/doc_classes/OGGPacketSequencePlayback.xml index 86dee15567..11fc1f4cb6 100644 --- a/modules/ogg/doc_classes/OGGPacketSequencePlayback.xml +++ b/modules/ogg/doc_classes/OGGPacketSequencePlayback.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="OGGPacketSequencePlayback" inherits="RefCounted" version="4.0"> +<class name="OGGPacketSequencePlayback" inherits="RefCounted" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/ogg/ogg_packet_sequence.cpp b/modules/ogg/ogg_packet_sequence.cpp index 65058f088e..da52ecfdd5 100644 --- a/modules/ogg/ogg_packet_sequence.cpp +++ b/modules/ogg/ogg_packet_sequence.cpp @@ -162,6 +162,7 @@ bool OGGPacketSequencePlayback::next_ogg_packet(ogg_packet **p_packet) const { } uint32_t OGGPacketSequencePlayback::seek_page_internal(int64_t granule, uint32_t after_page_inclusive, uint32_t before_page_inclusive) { + // FIXME: This function needs better corner case handling. if (before_page_inclusive == after_page_inclusive) { return before_page_inclusive; } @@ -169,7 +170,8 @@ uint32_t OGGPacketSequencePlayback::seek_page_internal(int64_t granule, uint32_t // Complicating the bisection search algorithm, the middle page might not have a packet that ends on it, // which means it might not have a correct granule position. Find a nearby page that does have a packet ending on it. uint32_t bisection_page = -1; - for (uint32_t test_page = actual_middle_page; test_page <= before_page_inclusive; test_page++) { + // Don't include before_page_inclusive because that always succeeds and will cause infinite recursion later. + for (uint32_t test_page = actual_middle_page; test_page < before_page_inclusive; test_page++) { if (ogg_packet_sequence->page_data[test_page].size() > 0) { bisection_page = test_page; break; diff --git a/modules/ogg/ogg_packet_sequence.h b/modules/ogg/ogg_packet_sequence.h index 7e56b14a24..73e3cb4fff 100644 --- a/modules/ogg/ogg_packet_sequence.h +++ b/modules/ogg/ogg_packet_sequence.h @@ -104,7 +104,7 @@ class OGGPacketSequencePlayback : public RefCounted { mutable ogg_packet *packet; - uint64_t data_version; + uint64_t data_version = 0; mutable int64_t packetno = 0; diff --git a/modules/opensimplex/doc_classes/NoiseTexture.xml b/modules/opensimplex/doc_classes/NoiseTexture.xml index 16fea228b1..497735ccf3 100644 --- a/modules/opensimplex/doc_classes/NoiseTexture.xml +++ b/modules/opensimplex/doc_classes/NoiseTexture.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="NoiseTexture" inherits="Texture2D" version="4.0"> +<class name="NoiseTexture" inherits="Texture2D" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> [OpenSimplexNoise] filled texture. </brief_description> diff --git a/modules/opensimplex/doc_classes/OpenSimplexNoise.xml b/modules/opensimplex/doc_classes/OpenSimplexNoise.xml index 604b07b645..51dd83efc3 100644 --- a/modules/opensimplex/doc_classes/OpenSimplexNoise.xml +++ b/modules/opensimplex/doc_classes/OpenSimplexNoise.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="OpenSimplexNoise" inherits="Resource" version="4.0"> +<class name="OpenSimplexNoise" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Noise generator based on Open Simplex. </brief_description> diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub new file mode 100644 index 0000000000..37a8f3909a --- /dev/null +++ b/modules/openxr/SCsub @@ -0,0 +1,87 @@ +#!/usr/bin/env python + +Import("env") +Import("env_modules") + +env_openxr = env_modules.Clone() + +################################################# +# Add in our Khronos OpenXR loader + +thirdparty_obj = [] +thirdparty_dir = "#thirdparty/openxr" + +env_openxr.Prepend( + CPPPATH=[ + thirdparty_dir, + thirdparty_dir + "/include", + thirdparty_dir + "/src", + thirdparty_dir + "/src/common", + thirdparty_dir + "/src/external/jsoncpp/include", + thirdparty_dir + "/src/loader", + ] +) + +# may need to check and set: +# - XR_USE_TIMESPEC + +env_thirdparty = env_openxr.Clone() +env_thirdparty.disable_warnings() +env_thirdparty.AppendUnique(CPPDEFINES=["DISABLE_STD_FILESYSTEM"]) + +if env["platform"] == "android": + # may need to set OPENXR_ANDROID_VERSION_SUFFIX + env_thirdparty.AppendUnique(CPPDEFINES=["XR_OS_ANDROID", "XR_USE_PLATFORM_ANDROID"]) + + # may need to include java parts of the openxr loader +elif env["platform"] == "linuxbsd": + env_thirdparty.AppendUnique(CPPDEFINES=["XR_OS_LINUX", "XR_USE_PLATFORM_XLIB"]) + # FIXME: Review what needs to be set for Android and macOS. + env_thirdparty.AppendUnique(CPPDEFINES=["HAVE_SECURE_GETENV"]) +elif env["platform"] == "windows": + env_thirdparty.AppendUnique(CPPDEFINES=["XR_OS_WINDOWS", "NOMINMAX", "XR_USE_PLATFORM_WIN32"]) + +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/xr_generated_dispatch_table.c") + +# add in common files (hope these don't clash with us) +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/common/filesystem_utils.cpp") +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/common/object_info.cpp") + +# add in external jsoncpp dependency +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/external/jsoncpp/src/lib_json/json_reader.cpp") +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/external/jsoncpp/src/lib_json/json_value.cpp") +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/external/jsoncpp/src/lib_json/json_writer.cpp") + +# add in load +if env["platform"] == "android": + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/android_utilities.cpp") + +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/api_layer_interface.cpp") +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/loader_core.cpp") +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/loader_instance.cpp") +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/loader_logger_recorders.cpp") +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/loader_logger.cpp") +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/manifest_file.cpp") +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/runtime_interface.cpp") +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/xr_generated_loader.cpp") + +env.modules_sources += thirdparty_obj + +################################################# +# And include our module source + +module_obj = [] + +env_openxr.add_source_files(module_obj, "*.cpp") +env_openxr.add_source_files(module_obj, "action_map/*.cpp") + +# We're a little more targetted with our extensions +if env["platform"] == "android": + env_openxr.add_source_files(module_obj, "extensions/openxr_android_extension.cpp") +if env["vulkan"]: + env_openxr.add_source_files(module_obj, "extensions/openxr_vulkan_extension.cpp") + +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/openxr/action_map/openxr_action.cpp b/modules/openxr/action_map/openxr_action.cpp new file mode 100644 index 0000000000..59ee3f4292 --- /dev/null +++ b/modules/openxr/action_map/openxr_action.cpp @@ -0,0 +1,91 @@ +/*************************************************************************/ +/* openxr_action.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "openxr_action.h" + +void OpenXRAction::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_localized_name", "localized_name"), &OpenXRAction::set_localized_name); + ClassDB::bind_method(D_METHOD("get_localized_name"), &OpenXRAction::get_localized_name); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "localized_name"), "set_localized_name", "get_localized_name"); + + ClassDB::bind_method(D_METHOD("set_action_type", "action_type"), &OpenXRAction::set_action_type); + ClassDB::bind_method(D_METHOD("get_action_type"), &OpenXRAction::get_action_type); + ADD_PROPERTY(PropertyInfo(Variant::INT, "action_type", PROPERTY_HINT_ENUM, "bool,float,vector2,pose"), "set_action_type", "get_action_type"); + + ClassDB::bind_method(D_METHOD("set_toplevel_paths", "toplevel_paths"), &OpenXRAction::set_toplevel_paths); + ClassDB::bind_method(D_METHOD("get_toplevel_paths"), &OpenXRAction::get_toplevel_paths); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "toplevel_paths", PROPERTY_HINT_ARRAY_TYPE, "STRING"), "set_toplevel_paths", "get_toplevel_paths"); + + BIND_ENUM_CONSTANT(OPENXR_ACTION_BOOL); + BIND_ENUM_CONSTANT(OPENXR_ACTION_FLOAT); + BIND_ENUM_CONSTANT(OPENXR_ACTION_VECTOR2); + BIND_ENUM_CONSTANT(OPENXR_ACTION_POSE); +} + +Ref<OpenXRAction> OpenXRAction::new_action(const char *p_name, const char *p_localized_name, const ActionType p_action_type, const char *p_toplevel_paths) { + // This is a helper function to help build our default action sets + + Ref<OpenXRAction> action; + action.instantiate(); + action->set_name(String(p_name)); + action->set_localized_name(String(p_localized_name)); + action->set_action_type(p_action_type); + action->parse_toplevel_paths(String(p_toplevel_paths)); + + return action; +} + +void OpenXRAction::set_localized_name(const String p_localized_name) { + localized_name = p_localized_name; +} + +String OpenXRAction::get_localized_name() const { + return localized_name; +} + +void OpenXRAction::set_action_type(const OpenXRAction::ActionType p_action_type) { + action_type = p_action_type; +} + +OpenXRAction::ActionType OpenXRAction::get_action_type() const { + return action_type; +} + +void OpenXRAction::set_toplevel_paths(const PackedStringArray p_toplevel_paths) { + toplevel_paths = p_toplevel_paths; +} + +PackedStringArray OpenXRAction::get_toplevel_paths() const { + return toplevel_paths; +} + +void OpenXRAction::parse_toplevel_paths(const String p_toplevel_paths) { + toplevel_paths = p_toplevel_paths.split(",", false); +} diff --git a/modules/openxr/action_map/openxr_action.h b/modules/openxr/action_map/openxr_action.h new file mode 100644 index 0000000000..e2cfe79e64 --- /dev/null +++ b/modules/openxr/action_map/openxr_action.h @@ -0,0 +1,74 @@ +/*************************************************************************/ +/* openxr_action.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef OPENXR_ACTION_H +#define OPENXR_ACTION_H + +#include "core/io/resource.h" + +class OpenXRAction : public Resource { + GDCLASS(OpenXRAction, Resource); + +public: + enum ActionType { + OPENXR_ACTION_BOOL, + OPENXR_ACTION_FLOAT, + OPENXR_ACTION_VECTOR2, + OPENXR_ACTION_POSE, + OPENXR_ACTION_HAPTIC, + }; + +private: + String localized_name; + ActionType action_type = OPENXR_ACTION_FLOAT; + + PackedStringArray toplevel_paths; + +protected: + static void _bind_methods(); + +public: + static Ref<OpenXRAction> new_action(const char *p_name, const char *p_localized_name, const ActionType p_action_type, const char *p_toplevel_paths); + + void set_localized_name(const String p_localized_name); + String get_localized_name() const; + + void set_action_type(const ActionType p_action_type); + ActionType get_action_type() const; + + void set_toplevel_paths(const PackedStringArray p_toplevel_paths); + PackedStringArray get_toplevel_paths() const; + + void parse_toplevel_paths(const String p_toplevel_paths); +}; + +VARIANT_ENUM_CAST(OpenXRAction::ActionType); + +#endif // !OPENXR_ACTION_H diff --git a/modules/openxr/action_map/openxr_action_map.cpp b/modules/openxr/action_map/openxr_action_map.cpp new file mode 100644 index 0000000000..5391f9569a --- /dev/null +++ b/modules/openxr/action_map/openxr_action_map.cpp @@ -0,0 +1,261 @@ +/*************************************************************************/ +/* openxr_action_map.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "openxr_action_map.h" + +void OpenXRActionMap::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_action_sets", "action_sets"), &OpenXRActionMap::set_action_sets); + ClassDB::bind_method(D_METHOD("get_action_sets"), &OpenXRActionMap::get_action_sets); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "action_sets", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRActionSet", PROPERTY_USAGE_NO_EDITOR), "set_action_sets", "get_action_sets"); + + ClassDB::bind_method(D_METHOD("add_action_set", "action_set"), &OpenXRActionMap::add_action_set); + ClassDB::bind_method(D_METHOD("remove_action_set", "action_set"), &OpenXRActionMap::remove_action_set); + + ClassDB::bind_method(D_METHOD("set_interaction_profiles", "interaction_profiles"), &OpenXRActionMap::set_interaction_profiles); + ClassDB::bind_method(D_METHOD("get_interaction_profiles"), &OpenXRActionMap::get_interaction_profiles); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "interaction_profiles", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRInteractionProfile", PROPERTY_USAGE_NO_EDITOR), "set_interaction_profiles", "get_interaction_profiles"); + + ClassDB::bind_method(D_METHOD("add_interaction_profile", "interaction_profile"), &OpenXRActionMap::add_interaction_profile); + ClassDB::bind_method(D_METHOD("remove_interaction_profile", "interaction_profile"), &OpenXRActionMap::remove_interaction_profile); + + ClassDB::bind_method(D_METHOD("create_default_action_sets"), &OpenXRActionMap::create_default_action_sets); +} + +void OpenXRActionMap::set_action_sets(Array p_action_sets) { + action_sets = p_action_sets; +} + +Array OpenXRActionMap::get_action_sets() const { + return action_sets; +} + +void OpenXRActionMap::add_action_set(Ref<OpenXRActionSet> p_action_set) { + ERR_FAIL_COND(p_action_set.is_null()); + + if (action_sets.find(p_action_set) == -1) { + action_sets.push_back(p_action_set); + } +} + +void OpenXRActionMap::remove_action_set(Ref<OpenXRActionSet> p_action_set) { + int idx = action_sets.find(p_action_set); + if (idx != -1) { + action_sets.remove_at(idx); + } +} + +void OpenXRActionMap::set_interaction_profiles(Array p_interaction_profiles) { + interaction_profiles = p_interaction_profiles; +} + +Array OpenXRActionMap::get_interaction_profiles() const { + return interaction_profiles; +} + +void OpenXRActionMap::add_interaction_profile(Ref<OpenXRInteractionProfile> p_interaction_profile) { + ERR_FAIL_COND(p_interaction_profile.is_null()); + + if (interaction_profiles.find(p_interaction_profile) == -1) { + interaction_profiles.push_back(p_interaction_profile); + } +} + +void OpenXRActionMap::remove_interaction_profile(Ref<OpenXRInteractionProfile> p_interaction_profile) { + int idx = interaction_profiles.find(p_interaction_profile); + if (idx != -1) { + interaction_profiles.remove_at(idx); + } +} + +void OpenXRActionMap::create_default_action_sets() { + // Note, if you make changes here make sure to delete your default_action_map.tres file of it will load an old version. + + // Create our Godot action set + Ref<OpenXRActionSet> action_set = OpenXRActionSet::new_action_set("godot", "Godot action set"); + add_action_set(action_set); + + // Create our actions + Ref<OpenXRAction> trigger = action_set->add_new_action("trigger", "Trigger", OpenXRAction::OPENXR_ACTION_FLOAT, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> trigger_click = action_set->add_new_action("trigger_click", "Trigger click", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> trigger_touch = action_set->add_new_action("trigger_touch", "Trigger touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> grip = action_set->add_new_action("grip", "Grip", OpenXRAction::OPENXR_ACTION_FLOAT, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> grip_click = action_set->add_new_action("grip_click", "Grip click", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> grip_touch = action_set->add_new_action("grip_touch", "Grip touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> primary = action_set->add_new_action("primary", "Primary joystick/thumbstick/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> primary_click = action_set->add_new_action("primary_click", "Primary joystick/thumbstick/trackpad click", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> primary_touch = action_set->add_new_action("primary_touch", "Primary joystick/thumbstick/trackpad touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> secondary = action_set->add_new_action("secondary", "Secondary joystick/thumbstick/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> secondary_click = action_set->add_new_action("secondary_click", "Secondary joystick/thumbstick/trackpad click", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> secondary_touch = action_set->add_new_action("secondary_touch", "Secondary joystick/thumbstick/trackpad touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> menu_button = action_set->add_new_action("menu_button", "Menu button", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> select_button = action_set->add_new_action("select_button", "Select button", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> ax_button = action_set->add_new_action("ax_button", "A/X button", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> ax_touch = action_set->add_new_action("ax_touch", "A/X touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> by_button = action_set->add_new_action("by_button", "B/Y button", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> by_touch = action_set->add_new_action("by_touch", "B/Y touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> default_pose = action_set->add_new_action("default_pose", "Default pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> aim_pose = action_set->add_new_action("aim_pose", "Aim pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> grip_pose = action_set->add_new_action("grip_pose", "Grip pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> haptic = action_set->add_new_action("haptic", "Haptic", OpenXRAction::OPENXR_ACTION_HAPTIC, "/user/hand/left,/user/hand/right"); + + // Create our interaction profiles + Ref<OpenXRInteractionProfile> profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/khr/simple_controller"); + profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); + profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click,/user/hand/right/input/menu/click"); + profile->add_new_binding(select_button, "/user/hand/left/input/select/click,/user/hand/right/input/select/click"); + // generic has no support for triggers, grip, A/B buttons, nor joystick/trackpad inputs + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + + // Create our Vive controller profile + profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/htc/vive_controller"); + profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); + profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click,/user/hand/right/input/menu/click"); + profile->add_new_binding(select_button, "/user/hand/left/input/system/click,/user/hand/right/input/system/click"); + // wmr controller has no a/b/x/y buttons + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/click,/user/hand/right/input/trigger/click"); + profile->add_new_binding(grip, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); // OpenXR will convert bool to float + profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); + // primary on our vive controller is our trackpad + profile->add_new_binding(primary, "/user/hand/left/input/trackpad,/user/hand/right/input/trackpad"); + profile->add_new_binding(primary_click, "/user/hand/left/input/trackpad/click,/user/hand/right/input/trackpad/click"); + profile->add_new_binding(primary_touch, "/user/hand/left/input/trackpad/touch,/user/hand/right/input/trackpad/touch"); + // vive controllers have no secondary input + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + + // Create our WMR controller profile + profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/microsoft/motion_controller"); + profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); + profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + // wmr controllers have no select button we can use + profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click,/user/hand/right/input/menu/click"); + // wmr controller has no a/b/x/y buttons + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); // OpenXR will conver float to bool + profile->add_new_binding(grip, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); // OpenXR will convert bool to float + profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); + // primary on our wmr controller is our thumbstick, no touch + profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick"); + profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click"); + // secondary on our wmr controller is our trackpad + profile->add_new_binding(secondary, "/user/hand/left/input/trackpad,/user/hand/right/input/trackpad"); + profile->add_new_binding(secondary_click, "/user/hand/left/input/trackpad/click,/user/hand/right/input/trackpad/click"); + profile->add_new_binding(secondary_touch, "/user/hand/left/input/trackpad/touch,/user/hand/right/input/trackpad/touch"); + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + + // Create our HP MR controller profile + profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/hp/mixed_reality_controller"); + profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); + profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + // hpmr controllers have no select button we can use + profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click,/user/hand/right/input/menu/click"); + // hpmr controllers only register click, not touch, on our a/b/x/y buttons + profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand + profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(grip, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); + profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); + // primary on our hpmr controller is our thumbstick + profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick"); + profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click"); + // No secondary on our hpmr controller + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + + // Create our Meta touch controller profile + profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/oculus/touch_controller"); + profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); + profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + // touch controllers have no select button we can use + profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click,/user/hand/right/input/system/click"); // right hand system click may not be available + profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand + profile->add_new_binding(ax_touch, "/user/hand/left/input/x/touch,/user/hand/right/input/a/touch"); + profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand + profile->add_new_binding(by_touch, "/user/hand/left/input/y/touch,/user/hand/right/input/b/touch"); + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); // should be converted to boolean + profile->add_new_binding(trigger_touch, "/user/hand/left/input/trigger/touch,/user/hand/right/input/trigger/touch"); + profile->add_new_binding(grip, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); // should be converted to boolean + profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); + // primary on our touch controller is our thumbstick + profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick"); + profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click"); + profile->add_new_binding(primary_touch, "/user/hand/left/input/thumbstick/touch,/user/hand/right/input/thumbstick/touch"); + // touch controller has no secondary input + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + + // Create our Valve index controller profile + profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/valve/index_controller"); + profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); + profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + // index controllers have no select button we can use + profile->add_new_binding(menu_button, "/user/hand/left/input/system/click,/user/hand/right/input/system/click"); + profile->add_new_binding(ax_button, "/user/hand/left/input/a/click,/user/hand/right/input/a/click"); // a on both controllers + profile->add_new_binding(ax_touch, "/user/hand/left/input/a/touch,/user/hand/right/input/a/touch"); + profile->add_new_binding(by_button, "/user/hand/left/input/b/click,/user/hand/right/input/b/click"); // b on both controllers + profile->add_new_binding(by_touch, "/user/hand/left/input/b/touch,/user/hand/right/input/b/touch"); + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/click,/user/hand/right/input/trigger/click"); + profile->add_new_binding(trigger_touch, "/user/hand/left/input/trigger/touch,/user/hand/right/input/trigger/touch"); + profile->add_new_binding(grip, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); + profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); // this should do a float to bool conversion + // primary on our index controller is our thumbstick + profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick"); + profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click"); + profile->add_new_binding(primary_touch, "/user/hand/left/input/thumbstick/touch,/user/hand/right/input/thumbstick/touch"); + // secondary on our index controller is our trackpad + profile->add_new_binding(secondary, "/user/hand/left/input/trackpad,/user/hand/right/input/trackpad"); + profile->add_new_binding(secondary_click, "/user/hand/left/input/trackpad/force,/user/hand/right/input/trackpad/force"); // not sure if this will work but doesn't seem to support click... + profile->add_new_binding(secondary_touch, "/user/hand/left/input/trackpad/touch,/user/hand/right/input/trackpad/touch"); + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); +} + +void OpenXRActionMap::create_editor_action_sets() { + // TODO implement +} + +OpenXRActionMap::~OpenXRActionMap() { + action_sets.clear(); + interaction_profiles.clear(); +} diff --git a/modules/basis_universal/texture_basisu.h b/modules/openxr/action_map/openxr_action_map.h index 8c8be68254..866e170468 100644 --- a/modules/basis_universal/texture_basisu.h +++ b/modules/openxr/action_map/openxr_action_map.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* texture_basisu.h */ +/* openxr_action_map.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,53 +28,41 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef BASIS_UNIVERSAL_TEXTURE_BASISU_H -#define BASIS_UNIVERSAL_TEXTURE_BASISU_H +#ifndef OPENXR_ACTION_SETS_H +#define OPENXR_ACTION_SETS_H -#include "scene/resources/texture.h" +#include "core/io/resource.h" -#ifdef TOOLS_ENABLED -#include <encoder/basisu_comp.h> -#endif +#include "openxr_action_set.h" +#include "openxr_interaction_profile.h" -#include <transcoder/basisu_transcoder.h> +class OpenXRActionMap : public Resource { + GDCLASS(OpenXRActionMap, Resource); -#if 0 -class TextureBasisU : public Texture { - GDCLASS(TextureBasisU, Texture); - RES_BASE_EXTENSION("butex"); - - RID texture; - Size2 tex_size; - - uint32_t flags = FLAGS_DEFAULT; - - Vector<uint8_t> data; +private: + Array action_sets; + Array interaction_profiles; +protected: static void _bind_methods(); public: + void set_action_sets(Array p_action_sets); + Array get_action_sets() const; - virtual int get_width() const; - virtual int get_height() const; - virtual RID get_rid() const; - virtual bool has_alpha() const; + void add_action_set(Ref<OpenXRActionSet> p_action_set); + void remove_action_set(Ref<OpenXRActionSet> p_action_set); - virtual void set_flags(uint32_t p_flags); - virtual uint32_t get_flags() const; + void set_interaction_profiles(Array p_interaction_profiles); + Array get_interaction_profiles() const; + void add_interaction_profile(Ref<OpenXRInteractionProfile> p_interaction_profile); + void remove_interaction_profile(Ref<OpenXRInteractionProfile> p_interaction_profile); - Error import(const Ref<Image> &p_img); + void create_default_action_sets(); + void create_editor_action_sets(); - void set_basisu_data(const Vector<uint8_t>& p_data); - - Vector<uint8_t> get_basisu_data() const; - String get_img_path() const; - - TextureBasisU(); - ~TextureBasisU(); + ~OpenXRActionMap(); }; -#endif - -#endif // BASIS_UNIVERSAL_TEXTURE_BASISU_H +#endif // !OPENXR_ACTION_SETS_H diff --git a/modules/openxr/action_map/openxr_action_set.cpp b/modules/openxr/action_map/openxr_action_set.cpp new file mode 100644 index 0000000000..465a709b60 --- /dev/null +++ b/modules/openxr/action_map/openxr_action_set.cpp @@ -0,0 +1,111 @@ +/*************************************************************************/ +/* openxr_action_set.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "openxr_action_set.h" + +void OpenXRActionSet::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_localized_name", "localized_name"), &OpenXRActionSet::set_localized_name); + ClassDB::bind_method(D_METHOD("get_localized_name"), &OpenXRActionSet::get_localized_name); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "localized_name"), "set_localized_name", "get_localized_name"); + + ClassDB::bind_method(D_METHOD("set_priority", "priority"), &OpenXRActionSet::set_priority); + ClassDB::bind_method(D_METHOD("get_priority"), &OpenXRActionSet::get_priority); + ADD_PROPERTY(PropertyInfo(Variant::INT, "priority"), "set_priority", "get_priority"); + + ClassDB::bind_method(D_METHOD("set_actions", "actions"), &OpenXRActionSet::set_actions); + ClassDB::bind_method(D_METHOD("get_actions"), &OpenXRActionSet::get_actions); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "actions", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRAction", PROPERTY_USAGE_NO_EDITOR), "set_actions", "get_actions"); + + ClassDB::bind_method(D_METHOD("add_action", "action"), &OpenXRActionSet::add_action); + ClassDB::bind_method(D_METHOD("remove_action", "action"), &OpenXRActionSet::remove_action); +} + +Ref<OpenXRActionSet> OpenXRActionSet::new_action_set(const char *p_name, const char *p_localized_name, const int p_priority) { + // This is a helper function to help build our default action sets + + Ref<OpenXRActionSet> action_set; + action_set.instantiate(); + action_set->set_name(String(p_name)); + action_set->set_localized_name(p_localized_name); + action_set->set_priority(p_priority); + + return action_set; +} + +void OpenXRActionSet::set_localized_name(const String p_localized_name) { + localized_name = p_localized_name; +} + +String OpenXRActionSet::get_localized_name() const { + return localized_name; +} + +void OpenXRActionSet::set_priority(const int p_priority) { + priority = p_priority; +} + +int OpenXRActionSet::get_priority() const { + return priority; +} + +void OpenXRActionSet::set_actions(Array p_actions) { + actions = p_actions; +} + +Array OpenXRActionSet::get_actions() const { + return actions; +} + +void OpenXRActionSet::add_action(Ref<OpenXRAction> p_action) { + ERR_FAIL_COND(p_action.is_null()); + + if (actions.find(p_action) == -1) { + actions.push_back(p_action); + } +} + +void OpenXRActionSet::remove_action(Ref<OpenXRAction> p_action) { + int idx = actions.find(p_action); + if (idx != -1) { + actions.remove_at(idx); + } +} + +Ref<OpenXRAction> OpenXRActionSet::add_new_action(const char *p_name, const char *p_localized_name, const OpenXRAction::ActionType p_action_type, const char *p_toplevel_paths) { + // This is a helper function to help build our default action sets + + Ref<OpenXRAction> new_action = OpenXRAction::new_action(p_name, p_localized_name, p_action_type, p_toplevel_paths); + add_action(new_action); + return new_action; +} + +OpenXRActionSet::~OpenXRActionSet() { + actions.clear(); +} diff --git a/modules/openxr/action_map/openxr_action_set.h b/modules/openxr/action_map/openxr_action_set.h new file mode 100644 index 0000000000..012a088b1c --- /dev/null +++ b/modules/openxr/action_map/openxr_action_set.h @@ -0,0 +1,70 @@ +/*************************************************************************/ +/* openxr_action_set.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef OPENXR_ACTION_SET_H +#define OPENXR_ACTION_SET_H + +#include "core/io/resource.h" + +#include "openxr_action.h" + +class OpenXRActionSet : public Resource { + GDCLASS(OpenXRActionSet, Resource); + +private: + String localized_name; + int priority = 0; + + Array actions; + +protected: + static void _bind_methods(); + +public: + static Ref<OpenXRActionSet> new_action_set(const char *p_name, const char *p_localized_name, const int p_priority = 0); + + void set_localized_name(const String p_localized_name); + String get_localized_name() const; + + void set_priority(const int p_priority); + int get_priority() const; + + void set_actions(Array p_actions); + Array get_actions() const; + + void add_action(Ref<OpenXRAction> p_action); + void remove_action(Ref<OpenXRAction> p_action); + + Ref<OpenXRAction> add_new_action(const char *p_name, const char *p_localized_name, const OpenXRAction::ActionType p_action_type, const char *p_toplevel_paths); + + ~OpenXRActionSet(); +}; + +#endif // !OPENXR_ACTION_SET_H diff --git a/modules/openxr/action_map/openxr_interaction_profile.cpp b/modules/openxr/action_map/openxr_interaction_profile.cpp new file mode 100644 index 0000000000..bc33814f17 --- /dev/null +++ b/modules/openxr/action_map/openxr_interaction_profile.cpp @@ -0,0 +1,136 @@ +/*************************************************************************/ +/* openxr_interaction_profile.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "openxr_interaction_profile.h" + +void OpenXRIPBinding::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_action", "action"), &OpenXRIPBinding::set_action); + ClassDB::bind_method(D_METHOD("get_action"), &OpenXRIPBinding::get_action); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "action", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRAction"), "set_action", "get_action"); + + ClassDB::bind_method(D_METHOD("set_paths", "paths"), &OpenXRIPBinding::set_paths); + ClassDB::bind_method(D_METHOD("get_paths"), &OpenXRIPBinding::get_paths); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "paths", PROPERTY_HINT_ARRAY_TYPE, "STRING"), "set_paths", "get_paths"); +} + +Ref<OpenXRIPBinding> OpenXRIPBinding::new_binding(const Ref<OpenXRAction> p_action, const char *p_paths) { + // This is a helper function to help build our default action sets + + Ref<OpenXRIPBinding> binding; + binding.instantiate(); + binding->set_action(p_action); + binding->parse_paths(String(p_paths)); + + return binding; +} + +void OpenXRIPBinding::set_action(const Ref<OpenXRAction> p_action) { + action = p_action; +} + +Ref<OpenXRAction> OpenXRIPBinding::get_action() const { + return action; +} + +void OpenXRIPBinding::set_paths(const PackedStringArray p_paths) { + paths = p_paths; +} + +PackedStringArray OpenXRIPBinding::get_paths() const { + return paths; +} + +void OpenXRIPBinding::parse_paths(const String p_paths) { + paths = p_paths.split(",", false); +} + +OpenXRIPBinding::~OpenXRIPBinding() { + action.unref(); +} + +void OpenXRInteractionProfile::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_interaction_profile_path", "interaction_profile_path"), &OpenXRInteractionProfile::set_interaction_profile_path); + ClassDB::bind_method(D_METHOD("get_interaction_profile_path"), &OpenXRInteractionProfile::get_interaction_profile_path); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "interaction_profile_path"), "set_interaction_profile_path", "get_interaction_profile_path"); + + ClassDB::bind_method(D_METHOD("set_bindings", "bindings"), &OpenXRInteractionProfile::set_bindings); + ClassDB::bind_method(D_METHOD("get_bindings"), &OpenXRInteractionProfile::get_bindings); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "bindings", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRIPBinding", PROPERTY_USAGE_NO_EDITOR), "set_bindings", "get_bindings"); +} + +Ref<OpenXRInteractionProfile> OpenXRInteractionProfile::new_profile(const char *p_input_profile_path) { + Ref<OpenXRInteractionProfile> profile; + profile.instantiate(); + profile->set_interaction_profile_path(String(p_input_profile_path)); + + return profile; +} + +void OpenXRInteractionProfile::set_interaction_profile_path(const String p_input_profile_path) { + interaction_profile_path = p_input_profile_path; +} + +String OpenXRInteractionProfile::get_interaction_profile_path() const { + return interaction_profile_path; +} + +void OpenXRInteractionProfile::set_bindings(Array p_bindings) { + bindings = p_bindings; +} + +Array OpenXRInteractionProfile::get_bindings() const { + return bindings; +} + +void OpenXRInteractionProfile::add_binding(Ref<OpenXRIPBinding> p_binding) { + ERR_FAIL_COND(p_binding.is_null()); + + if (bindings.find(p_binding) == -1) { + bindings.push_back(p_binding); + } +} + +void OpenXRInteractionProfile::remove_binding(Ref<OpenXRIPBinding> p_binding) { + int idx = bindings.find(p_binding); + if (idx != -1) { + bindings.remove_at(idx); + } +} + +void OpenXRInteractionProfile::add_new_binding(const Ref<OpenXRAction> p_action, const char *p_paths) { + // This is a helper function to help build our default action sets + + Ref<OpenXRIPBinding> binding = OpenXRIPBinding::new_binding(p_action, p_paths); + add_binding(binding); +} + +OpenXRInteractionProfile::~OpenXRInteractionProfile() { + bindings.clear(); +} diff --git a/modules/openxr/action_map/openxr_interaction_profile.h b/modules/openxr/action_map/openxr_interaction_profile.h new file mode 100644 index 0000000000..abbc429e7d --- /dev/null +++ b/modules/openxr/action_map/openxr_interaction_profile.h @@ -0,0 +1,89 @@ +/*************************************************************************/ +/* openxr_interaction_profile.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef OPENXR_INTERACTION_PROFILE_H +#define OPENXR_INTERACTION_PROFILE_H + +#include "core/io/resource.h" + +#include "openxr_action.h" + +class OpenXRIPBinding : public Resource { + GDCLASS(OpenXRIPBinding, Resource); + +private: + Ref<OpenXRAction> action; + PackedStringArray paths; + +protected: + static void _bind_methods(); + +public: + static Ref<OpenXRIPBinding> new_binding(const Ref<OpenXRAction> p_action, const char *p_paths); + + void set_action(const Ref<OpenXRAction> p_action); + Ref<OpenXRAction> get_action() const; + + void set_paths(const PackedStringArray p_paths); + PackedStringArray get_paths() const; + + void parse_paths(const String p_paths); + + ~OpenXRIPBinding(); +}; + +class OpenXRInteractionProfile : public Resource { + GDCLASS(OpenXRInteractionProfile, Resource); + +private: + String interaction_profile_path; + Array bindings; + +protected: + static void _bind_methods(); + +public: + static Ref<OpenXRInteractionProfile> new_profile(const char *p_input_profile_path); + + void set_interaction_profile_path(const String p_input_profile_path); + String get_interaction_profile_path() const; + + void set_bindings(Array p_bindings); + Array get_bindings() const; + + void add_binding(Ref<OpenXRIPBinding> p_binding); + void remove_binding(Ref<OpenXRIPBinding> p_binding); + + void add_new_binding(const Ref<OpenXRAction> p_action, const char *p_paths); + + ~OpenXRInteractionProfile(); +}; + +#endif // !OPENXR_INTERACTION_PROFILE_H diff --git a/modules/openxr/config.py b/modules/openxr/config.py new file mode 100644 index 0000000000..f91cb1359f --- /dev/null +++ b/modules/openxr/config.py @@ -0,0 +1,27 @@ +def can_build(env, platform): + if ( + platform == "linuxbsd" or platform == "windows" + ): # or platform == "android" -- temporarily disabled android support + return env["openxr"] + else: + # not supported on these platforms + return False + + +def configure(env): + pass + + +def get_doc_classes(): + return [ + "OpenXRInterface", + "OpenXRAction", + "OpenXRActionSet", + "OpenXRActionMap", + "OpenXRInteractionProfile", + "OpenXRIPBinding", + ] + + +def get_doc_path(): + return "doc_classes" diff --git a/modules/openxr/doc_classes/OpenXRAction.xml b/modules/openxr/doc_classes/OpenXRAction.xml new file mode 100644 index 0000000000..6ff8c1ad26 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRAction.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OpenXRAction" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + An OpenXR action. + </brief_description> + <description> + This resource defines an OpenXR action. Actions can be used both for inputs (buttons/joystick/trigger/etc) and outputs (haptics). + OpenXR performs automatic conversion between action type and input type whenever possible. An analogue trigger bound to a boolean action will thus return [code]false[/core] if the trigger is depressed and [code]true[/code] if pressed fully. + Actions are not directly bound to specific devices, instead OpenXR recognises a limited number of top level paths that identify devices by usage. We can restrict which devices an action can be bound to by these top level paths. For instance an action that should only be used for hand held controllers can have the top level paths "/user/hand/left" and "/user/hand/right" associated with them. See the [url=https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#semantic-path-reserved]reserved path section in the OpenXR specification[/url] for more info on the top level paths. + Note that the name of the resource is used to register the action with. + </description> + <tutorials> + </tutorials> + <members> + <member name="action_type" type="int" setter="set_action_type" getter="get_action_type" enum="OpenXRAction.ActionType" default="1"> + The type of action. + </member> + <member name="localized_name" type="String" setter="set_localized_name" getter="get_localized_name" default=""""> + The localised description of this action. + </member> + <member name="toplevel_paths" type="PackedStringArray" setter="set_toplevel_paths" getter="get_toplevel_paths" default="PackedStringArray()"> + A collections of toplevel paths to which this action can be bound. + </member> + </members> + <constants> + <constant name="OPENXR_ACTION_BOOL" value="0" enum="ActionType"> + This action provides a boolean value. + </constant> + <constant name="OPENXR_ACTION_FLOAT" value="1" enum="ActionType"> + This action provides a float value between [code]0.0[/code] and [code]1.0[/code] for any analogue input such as triggers. + </constant> + <constant name="OPENXR_ACTION_VECTOR2" value="2" enum="ActionType"> + This action provides a vector2 value and can be bound to embedded trackpads and joysticks + </constant> + <constant name="OPENXR_ACTION_POSE" value="3" enum="ActionType"> + </constant> + </constants> +</class> diff --git a/modules/openxr/doc_classes/OpenXRActionMap.xml b/modules/openxr/doc_classes/OpenXRActionMap.xml new file mode 100644 index 0000000000..f1def8aad8 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRActionMap.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OpenXRActionMap" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + Collection of [OpenXRActionSet] and [OpenXRInteractionProfile] resources for the OpenXR module. + </brief_description> + <description> + OpenXR uses an action system similar to Godots Input map system to bind inputs and outputs on various types of XR controllers to named actions. OpenXR specifies more detail on these inputs and outputs than Godot supports. + Another important distinction is that OpenXR offers no control over these bindings. The bindings we register are suggestions, it is up to the XR runtime to offer users the ability to change these bindings. This allows the XR runtime to fill in the gaps if new hardware becomes available. + The action map therefor needs to be loaded at startup and can't be changed afterwards. This resource is a container for the entire action map. + </description> + <tutorials> + </tutorials> + <methods> + <method name="add_action_set"> + <return type="void" /> + <argument index="0" name="action_set" type="OpenXRActionSet" /> + <description> + Add an action set. + </description> + </method> + <method name="add_interaction_profile"> + <return type="void" /> + <argument index="0" name="interaction_profile" type="OpenXRInteractionProfile" /> + <description> + Add an interaction profile. + </description> + </method> + <method name="create_default_action_sets"> + <return type="void" /> + <description> + Setup this action set with our default actions. + </description> + </method> + <method name="remove_action_set"> + <return type="void" /> + <argument index="0" name="action_set" type="OpenXRActionSet" /> + <description> + Remove an action set. + </description> + </method> + <method name="remove_interaction_profile"> + <return type="void" /> + <argument index="0" name="interaction_profile" type="OpenXRInteractionProfile" /> + <description> + Remove an interaction profile. + </description> + </method> + </methods> + <members> + <member name="action_sets" type="Array" setter="set_action_sets" getter="get_action_sets" default="[]"> + </member> + <member name="interaction_profiles" type="Array" setter="set_interaction_profiles" getter="get_interaction_profiles" default="[]"> + </member> + </members> +</class> diff --git a/modules/openxr/doc_classes/OpenXRActionSet.xml b/modules/openxr/doc_classes/OpenXRActionSet.xml new file mode 100644 index 0000000000..5a87de463e --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRActionSet.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OpenXRActionSet" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + Collection of [OpenXRAction] resources that make up an action set. + </brief_description> + <description> + Action sets in OpenXR define a collection of actions that can be activated in unison. This allows games to easily change between different states that require different inputs or need to reinterpret inputs. For instance we could have an action set that is active when a menu is open, an action set that is active when the player is freely walking around and an action set that is active when the player is controlling a vehicle. + Action sets can contain the same actions, or actions with the same name, if such action sets are active at the same time the action set with the highest priority defines which binding is active. + Note that the name of the resource is used to identify the action set within OpenXR. + </description> + <tutorials> + </tutorials> + <methods> + <method name="add_action"> + <return type="void" /> + <argument index="0" name="action" type="OpenXRAction" /> + <description> + Add an action to this action set. + </description> + </method> + <method name="remove_action"> + <return type="void" /> + <argument index="0" name="action" type="OpenXRAction" /> + <description> + Remove an action from this action set. + </description> + </method> + </methods> + <members> + <member name="actions" type="Array" setter="set_actions" getter="get_actions" default="[]"> + Collection of actions for this action set. + </member> + <member name="localized_name" type="String" setter="set_localized_name" getter="get_localized_name" default=""""> + The localised name of this action set. + </member> + <member name="priority" type="int" setter="set_priority" getter="get_priority" default="0"> + The priority for this action set. + </member> + </members> +</class> diff --git a/modules/openxr/doc_classes/OpenXRIPBinding.xml b/modules/openxr/doc_classes/OpenXRIPBinding.xml new file mode 100644 index 0000000000..3fdcde5eb5 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRIPBinding.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OpenXRIPBinding" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + Defines a binding between an [OpenXRAction] and an XR input or output. + </brief_description> + <description> + This binding resource binds an OpenXR action to inputs or outputs. As most controllers have left hand and right versions that are handled by the same interaction profile we can specify multiple bindings. For instance an action "Fire" could be bound to both "/user/hand/left/input/trigger" and "/user/hand/right/input/trigger". + </description> + <tutorials> + </tutorials> + <members> + <member name="action" type="OpenXRAction" setter="set_action" getter="get_action"> + Action that is bound to these paths. + </member> + <member name="paths" type="PackedStringArray" setter="set_paths" getter="get_paths" default="PackedStringArray()"> + Paths that define the inputs or outputs bound on the device. + </member> + </members> +</class> diff --git a/modules/openxr/doc_classes/OpenXRInteractionProfile.xml b/modules/openxr/doc_classes/OpenXRInteractionProfile.xml new file mode 100644 index 0000000000..a8629caae4 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRInteractionProfile.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OpenXRInteractionProfile" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + Suggested bindings object for OpenXR. + </brief_description> + <description> + This object stores suggested bindings for an interaction profile. Interaction profiles define the meta data for a tracked XR device such as an XR controller. + For more information see the [url=https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#semantic-path-interaction-profiles]interaction profiles info in the OpenXR specification[/url]. + </description> + <tutorials> + </tutorials> + <members> + <member name="bindings" type="Array" setter="set_bindings" getter="get_bindings" default="[]"> + Action bindings for this interaction profile. + </member> + <member name="interaction_profile_path" type="String" setter="set_interaction_profile_path" getter="get_interaction_profile_path" default=""""> + The interaction profile path identifying the XR device. + </member> + </members> +</class> diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml new file mode 100644 index 0000000000..1160061e04 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRInterface.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OpenXRInterface" inherits="XRInterface" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + Our OpenXR interface. + </brief_description> + <description> + The OpenXR interface allows Godot to interact with OpenXR runtimes and make it possible to create XR experiences and games. + Due to the needs of OpenXR this interface works slightly different then other plugin based XR interfaces. It needs to be initialised when Godot starts. You need to enable OpenXR, settings for this can be found in your games project settings under the XR heading. You do need to mark a viewport for use with XR in order for Godot to know which render result should be output to the headset. + </description> + <tutorials> + <link title="OpenXR documentation">$DOCS_URL/tutorials/vr/openxr/index.html</link> + </tutorials> +</class> diff --git a/modules/openxr/extensions/openxr_android_extension.cpp b/modules/openxr/extensions/openxr_android_extension.cpp new file mode 100644 index 0000000000..3bd4db169c --- /dev/null +++ b/modules/openxr/extensions/openxr_android_extension.cpp @@ -0,0 +1,71 @@ +/*************************************************************************/ +/* openxr_android_extension.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "openxr_android_extension.h" + +#include <openxr/openxr.h> +#include <openxr/openxr_platform.h> + +OpenXRAndroidExtension *OpenXRAndroidExtension::singleton = nullptr; + +OpenXRAndroidExtension *OpenXRAndroidExtension::get_singleton() { + return singleton; +} + +OpenXRAndroidExtension::OpenXRAndroidExtension(OpenXRAPI *p_openxr_api) : + OpenXRExtensionWrapper(p_openxr_api) { + singleton = this; + + request_extensions[XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME] = nullptr; // must be available + + // Initialize the loader + PFN_xrInitializeLoaderKHR xrInitializeLoaderKHR; + result = xrGetInstanceProcAddr(XR_NULL_HANDLE, "xrInitializeLoaderKHR", (PFN_xrVoidFunction *)(&xrInitializeLoaderKHR)); + ERR_FAIL_COND_MSG(XR_FAILED(result), "Failed to retrieve pointer to xrInitializeLoaderKHR"); + + // TODO fix this code, this is still code from GDNative! + JNIEnv *env = android_api->godot_android_get_env(); + JavaVM *vm; + env->GetJavaVM(&vm); + jobject activity_object = env->NewGlobalRef(android_api->godot_android_get_activity()); + + XrLoaderInitInfoAndroidKHR loader_init_info_android = { + .type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR, + .next = nullptr, + .applicationVM = vm, + .applicationContext = activity_object + }; + xrInitializeLoaderKHR((const XrLoaderInitInfoBaseHeaderKHR *)&loader_init_info_android); + ERR_FAIL_COND_MSG(XR_FAILED(result), "Failed to call xrInitializeLoaderKHR"); +} + +OpenXRAndroidExtension::~OpenXRAndroidExtension() { + singleton = nullptr; +} diff --git a/modules/openxr/extensions/openxr_android_extension.h b/modules/openxr/extensions/openxr_android_extension.h new file mode 100644 index 0000000000..e102197a55 --- /dev/null +++ b/modules/openxr/extensions/openxr_android_extension.h @@ -0,0 +1,47 @@ +/*************************************************************************/ +/* openxr_android_extension.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef OPENXR_ANDROID_EXTENSION_H +#define OPENXR_ANDROID_EXTENSION_H + +#include "openxr_extension_wrapper.h" + +class OpenXRAndroidExtension : public OpenXRExtensionWrapper { +public: + static OpenXRAndroidExtension *get_singleton(); + + OpenXRAndroidExtension(OpenXRAPI *p_openxr_api); + virtual ~OpenXRAndroidExtension() override; + +private: + static OpenXRAndroidExtension *singleton; +}; + +#endif // !OPENXR_ANDROID_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_composition_layer_provider.h b/modules/openxr/extensions/openxr_composition_layer_provider.h new file mode 100644 index 0000000000..019dffa2a8 --- /dev/null +++ b/modules/openxr/extensions/openxr_composition_layer_provider.h @@ -0,0 +1,45 @@ +/*************************************************************************/ +/* openxr_composition_layer_provider.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef OPENXR_COMPOSITION_LAYER_PROVIDER_H +#define OPENXR_COMPOSITION_LAYER_PROVIDER_H + +#include <openxr/openxr.h> + +// Interface for OpenXR extensions that provide a composition layer. +class OpenXRCompositionLayerProvider { +public: + // TODO changed to normal method definition for now + // CI complains until we implement this, haven't ported it yet from plugin + // virtual XrCompositionLayerBaseHeader *get_composition_layer() = 0; + XrCompositionLayerBaseHeader *get_composition_layer() { return nullptr; }; +}; + +#endif // OPENXR_COMPOSITION_LAYER_PROVIDER_H diff --git a/modules/openxr/extensions/openxr_extension_wrapper.h b/modules/openxr/extensions/openxr_extension_wrapper.h new file mode 100644 index 0000000000..5242ee6063 --- /dev/null +++ b/modules/openxr/extensions/openxr_extension_wrapper.h @@ -0,0 +1,106 @@ +/*************************************************************************/ +/* openxr_extension_wrapper.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef OPENXR_EXTENSION_WRAPPER_H +#define OPENXR_EXTENSION_WRAPPER_H + +#include "core/error/error_macros.h" +#include "core/math/camera_matrix.h" +#include "core/templates/map.h" +#include "core/templates/rid.h" + +#include "thirdparty/openxr/src/common/xr_linear.h" +#include <openxr/openxr.h> + +class OpenXRAPI; + +class OpenXRExtensionWrapper { +protected: + OpenXRAPI *openxr_api; + + // Store extension we require. + // If bool pointer is a nullptr this means this extension is mandatory and initialisation will fail if it is not available + // If bool pointer is set, value will be set to true or false depending on whether extension is available + Map<const char *, bool *> request_extensions; + +public: + virtual Map<const char *, bool *> get_request_extensions() { + return request_extensions; + } + + // These functions allow an extension to add entries to a struct chain. + // `p_next_pointer` points to the last struct that was created for this chain + // and should be used as the value for the `pNext` pointer in the first struct you add. + // You should return the pointer to the last struct you define as your result. + // If you are not adding any structs, just return `p_next_pointer`. + // See existing extensions for examples of this implementation. + virtual void *set_system_properties_and_get_next_pointer(void *p_next_pointer) { return p_next_pointer; } + virtual void *set_session_create_and_get_next_pointer(void *p_next_pointer) { return p_next_pointer; } + virtual void *set_swapchain_create_info_and_get_next_pointer(void *p_next_pointer) { return p_next_pointer; } + + virtual void on_instance_created(const XrInstance p_instance) {} + virtual void on_instance_destroyed() {} + virtual void on_session_created(const XrSession p_instance) {} + virtual void on_process() {} + virtual void on_pre_render() {} + virtual void on_session_destroyed() {} + + virtual void on_state_idle() {} + virtual void on_state_ready() {} + virtual void on_state_synchronized() {} + virtual void on_state_visible() {} + virtual void on_state_focused() {} + virtual void on_state_stopping() {} + virtual void on_state_loss_pending() {} + virtual void on_state_exiting() {} + + // Returns true if the event was handled, false otherwise. + virtual bool on_event_polled(const XrEventDataBuffer &event) { + return false; + } + + OpenXRExtensionWrapper(OpenXRAPI *p_openxr_api) { openxr_api = p_openxr_api; }; + virtual ~OpenXRExtensionWrapper() = default; +}; + +class OpenXRGraphicsExtensionWrapper : public OpenXRExtensionWrapper { +public: + virtual void get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) = 0; + virtual String get_swapchain_format_name(int64_t p_swapchain_format) const = 0; + virtual bool get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) = 0; + virtual void cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) = 0; + virtual bool create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, CameraMatrix &r_camera_matrix) = 0; + virtual bool copy_render_target_to_image(RID p_from_render_target, void *p_swapchain_graphics_data, int p_image_index) = 0; + + OpenXRGraphicsExtensionWrapper(OpenXRAPI *p_openxr_api) : + OpenXRExtensionWrapper(p_openxr_api){}; +}; + +#endif // ~OPENXR_EXTENSION_WRAPPER_H diff --git a/modules/openxr/extensions/openxr_vulkan_extension.cpp b/modules/openxr/extensions/openxr_vulkan_extension.cpp new file mode 100644 index 0000000000..c7c840fdf3 --- /dev/null +++ b/modules/openxr/extensions/openxr_vulkan_extension.cpp @@ -0,0 +1,750 @@ +/*************************************************************************/ +/* openxr_vulkan_extension.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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/print_string.h" + +#include "modules/openxr/extensions/openxr_vulkan_extension.h" +#include "modules/openxr/openxr_api.h" +#include "modules/openxr/openxr_util.h" +#include "servers/rendering/renderer_rd/renderer_storage_rd.h" +#include "servers/rendering/rendering_server_globals.h" +#include "servers/rendering_server.h" + +// need to include Vulkan so we know of type definitions +#define XR_USE_GRAPHICS_API_VULKAN + +#ifdef WINDOWS_ENABLED +// Including windows.h here is absolutely evil, we shouldn't be doing this outside of platform +// however due to the way the openxr headers are put together, we have no choice. +#include <windows.h> +#endif + +// include platform dependent structs +#include <openxr/openxr_platform.h> + +PFN_xrGetVulkanGraphicsRequirements2KHR xrGetVulkanGraphicsRequirements2KHR_ptr = nullptr; +PFN_xrCreateVulkanInstanceKHR xrCreateVulkanInstanceKHR_ptr = nullptr; +PFN_xrGetVulkanGraphicsDevice2KHR xrGetVulkanGraphicsDevice2KHR_ptr = nullptr; +PFN_xrCreateVulkanDeviceKHR xrCreateVulkanDeviceKHR_ptr = nullptr; + +OpenXRVulkanExtension::OpenXRVulkanExtension(OpenXRAPI *p_openxr_api) : + OpenXRGraphicsExtensionWrapper(p_openxr_api) { + VulkanContext::set_vulkan_hooks(this); + + request_extensions[XR_KHR_VULKAN_ENABLE2_EXTENSION_NAME] = nullptr; // must be available + + ERR_FAIL_NULL(openxr_api); +} + +OpenXRVulkanExtension::~OpenXRVulkanExtension() { + VulkanContext::set_vulkan_hooks(nullptr); +} + +void OpenXRVulkanExtension::on_instance_created(const XrInstance p_instance) { + XrResult result; + + ERR_FAIL_NULL(openxr_api); + + // Obtain pointers to functions we're accessing here, they are (not yet) part of core. + result = xrGetInstanceProcAddr(p_instance, "xrGetVulkanGraphicsRequirements2KHR", (PFN_xrVoidFunction *)&xrGetVulkanGraphicsRequirements2KHR_ptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to xrGetVulkanGraphicsRequirements2KHR entry point [", openxr_api->get_error_string(result), "]"); + } + + result = xrGetInstanceProcAddr(p_instance, "xrCreateVulkanInstanceKHR", (PFN_xrVoidFunction *)&xrCreateVulkanInstanceKHR_ptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to xrCreateVulkanInstanceKHR entry point [", openxr_api->get_error_string(result), "]"); + } + + result = xrGetInstanceProcAddr(p_instance, "xrGetVulkanGraphicsDevice2KHR", (PFN_xrVoidFunction *)&xrGetVulkanGraphicsDevice2KHR_ptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to xrGetVulkanGraphicsDevice2KHR entry point [", openxr_api->get_error_string(result), "]"); + } + + result = xrGetInstanceProcAddr(p_instance, "xrCreateVulkanDeviceKHR", (PFN_xrVoidFunction *)&xrCreateVulkanDeviceKHR_ptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to xrCreateVulkanDeviceKHR entry point [", openxr_api->get_error_string(result), "]"); + } +} + +XrResult OpenXRVulkanExtension::xrGetVulkanGraphicsRequirements2KHR(XrInstance p_instance, XrSystemId p_system_id, XrGraphicsRequirementsVulkanKHR *p_graphics_requirements) { + ERR_FAIL_NULL_V(xrGetVulkanGraphicsRequirements2KHR_ptr, XR_ERROR_HANDLE_INVALID); + + return (*xrGetVulkanGraphicsRequirements2KHR_ptr)(p_instance, p_system_id, p_graphics_requirements); +} + +bool OpenXRVulkanExtension::check_graphics_api_support(XrVersion p_desired_version) { + ERR_FAIL_NULL_V(openxr_api, false); + + XrGraphicsRequirementsVulkan2KHR vulkan_requirements = { + XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN2_KHR, // type + nullptr, // next + 0, // minApiVersionSupported + 0 // maxApiVersionSupported + }; + + XrResult result = xrGetVulkanGraphicsRequirements2KHR(openxr_api->get_instance(), openxr_api->get_system_id(), &vulkan_requirements); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get vulkan graphics requirements [", openxr_api->get_error_string(result), "]"); + return false; + } + + // #ifdef DEBUG + print_line("OpenXR: XrGraphicsRequirementsVulkan2KHR:"); + print_line(" - minApiVersionSupported: ", OpenXRUtil::make_xr_version_string(vulkan_requirements.minApiVersionSupported)); + print_line(" - maxApiVersionSupported: ", OpenXRUtil::make_xr_version_string(vulkan_requirements.maxApiVersionSupported)); + // #endif + + if (p_desired_version < vulkan_requirements.minApiVersionSupported) { + print_line("OpenXR: Requested Vulkan version does not meet the minimum version this runtime supports."); + print_line("- desired_version ", OpenXRUtil::make_xr_version_string(p_desired_version)); + print_line("- minApiVersionSupported ", OpenXRUtil::make_xr_version_string(vulkan_requirements.minApiVersionSupported)); + print_line("- maxApiVersionSupported ", OpenXRUtil::make_xr_version_string(vulkan_requirements.maxApiVersionSupported)); + return false; + } + + if (p_desired_version > vulkan_requirements.maxApiVersionSupported) { + print_line("OpenXR: Requested Vulkan version exceeds the maximum version this runtime has been tested on and is known to support."); + print_line("- desired_version ", OpenXRUtil::make_xr_version_string(p_desired_version)); + print_line("- minApiVersionSupported ", OpenXRUtil::make_xr_version_string(vulkan_requirements.minApiVersionSupported)); + print_line("- maxApiVersionSupported ", OpenXRUtil::make_xr_version_string(vulkan_requirements.maxApiVersionSupported)); + } + + return true; +} + +XrResult OpenXRVulkanExtension::xrCreateVulkanInstanceKHR(XrInstance p_instance, const XrVulkanInstanceCreateInfoKHR *p_create_info, VkInstance *r_vulkan_instance, VkResult *r_vulkan_result) { + ERR_FAIL_NULL_V(xrCreateVulkanInstanceKHR_ptr, XR_ERROR_HANDLE_INVALID); + + return (*xrCreateVulkanInstanceKHR_ptr)(p_instance, p_create_info, r_vulkan_instance, r_vulkan_result); +} + +bool OpenXRVulkanExtension::create_vulkan_instance(const VkInstanceCreateInfo *p_vulkan_create_info, VkInstance *r_instance) { + // get the vulkan version we are creating + uint32_t vulkan_version = p_vulkan_create_info->pApplicationInfo->apiVersion; + uint32_t major_version = VK_VERSION_MAJOR(vulkan_version); + uint32_t minor_version = VK_VERSION_MINOR(vulkan_version); + uint32_t patch_version = VK_VERSION_PATCH(vulkan_version); + XrVersion desired_version = XR_MAKE_VERSION(major_version, minor_version, patch_version); + + // check if this is supported + if (!check_graphics_api_support(desired_version)) { + return false; + } + + XrVulkanInstanceCreateInfoKHR xr_vulkan_instance_info = { + XR_TYPE_VULKAN_INSTANCE_CREATE_INFO_KHR, // type + nullptr, // next + openxr_api->get_system_id(), // systemId + 0, // createFlags + vkGetInstanceProcAddr, // pfnGetInstanceProcAddr + p_vulkan_create_info, // vulkanCreateInfo + nullptr, // vulkanAllocator + }; + + VkResult vk_result = VK_SUCCESS; + XrResult result = xrCreateVulkanInstanceKHR(openxr_api->get_instance(), &xr_vulkan_instance_info, &vulkan_instance, &vk_result); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to create vulkan instance [", openxr_api->get_error_string(result), "]"); + return false; + } + + ERR_FAIL_COND_V_MSG(vk_result == VK_ERROR_INCOMPATIBLE_DRIVER, false, + "Cannot find a compatible Vulkan installable client driver (ICD).\n\n" + "vkCreateInstance Failure"); + ERR_FAIL_COND_V_MSG(vk_result == VK_ERROR_EXTENSION_NOT_PRESENT, false, + "Cannot find a specified extension library.\n" + "Make sure your layers path is set appropriately.\n" + "vkCreateInstance Failure"); + ERR_FAIL_COND_V_MSG(vk_result, false, + "vkCreateInstance failed.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n" + "vkCreateInstance Failure"); + + *r_instance = vulkan_instance; + + return true; +} + +XrResult OpenXRVulkanExtension::xrGetVulkanGraphicsDevice2KHR(XrInstance p_instance, const XrVulkanGraphicsDeviceGetInfoKHR *p_get_info, VkPhysicalDevice *r_vulkan_physical_device) { + ERR_FAIL_NULL_V(xrGetVulkanGraphicsDevice2KHR_ptr, XR_ERROR_HANDLE_INVALID); + + return (*xrGetVulkanGraphicsDevice2KHR_ptr)(p_instance, p_get_info, r_vulkan_physical_device); +} + +bool OpenXRVulkanExtension::get_physical_device(VkPhysicalDevice *r_device) { + ERR_FAIL_NULL_V(openxr_api, false); + + XrVulkanGraphicsDeviceGetInfoKHR get_info = { + XR_TYPE_VULKAN_GRAPHICS_DEVICE_GET_INFO_KHR, // type + nullptr, // next + openxr_api->get_system_id(), // systemId + vulkan_instance, // vulkanInstance + }; + + XrResult result = xrGetVulkanGraphicsDevice2KHR(openxr_api->get_instance(), &get_info, &vulkan_physical_device); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to obtain vulkan physical device [", openxr_api->get_error_string(result), "]"); + return false; + } + + *r_device = vulkan_physical_device; + + return true; +} + +XrResult OpenXRVulkanExtension::xrCreateVulkanDeviceKHR(XrInstance p_instance, const XrVulkanDeviceCreateInfoKHR *p_create_info, VkDevice *r_device, VkResult *r_result) { + ERR_FAIL_NULL_V(xrCreateVulkanDeviceKHR_ptr, XR_ERROR_HANDLE_INVALID); + + return (*xrCreateVulkanDeviceKHR_ptr)(p_instance, p_create_info, r_device, r_result); +} + +bool OpenXRVulkanExtension::create_vulkan_device(const VkDeviceCreateInfo *p_device_create_info, VkDevice *r_device) { + ERR_FAIL_NULL_V(openxr_api, false); + + // the first entry in our queue list should be the one we need to remember... + vulkan_queue_family_index = p_device_create_info->pQueueCreateInfos[0].queueFamilyIndex; + vulkan_queue_index = 0; // ?? + + XrVulkanDeviceCreateInfoKHR create_info = { + XR_TYPE_VULKAN_DEVICE_CREATE_INFO_KHR, // type + nullptr, // next + openxr_api->get_system_id(), // systemId + 0, // createFlags + vkGetInstanceProcAddr, // pfnGetInstanceProcAddr + vulkan_physical_device, // vulkanPhysicalDevice + p_device_create_info, // vulkanCreateInfo + nullptr // vulkanAllocator + }; + + VkResult vk_result = VK_SUCCESS; + XrResult result = xrCreateVulkanDeviceKHR(openxr_api->get_instance(), &create_info, &vulkan_device, &vk_result); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to create vulkan device [", openxr_api->get_error_string(result), "]"); + return false; + } + + if (vk_result != VK_SUCCESS) { + print_line("OpenXR: Failed to create vulkan device [vulkan error", vk_result, "]"); + } + + *r_device = vulkan_device; + + return true; +} + +XrGraphicsBindingVulkanKHR OpenXRVulkanExtension::graphics_binding_vulkan; + +void *OpenXRVulkanExtension::set_session_create_and_get_next_pointer(void *p_next_pointer) { + graphics_binding_vulkan.type = XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR; + graphics_binding_vulkan.next = p_next_pointer; + graphics_binding_vulkan.instance = vulkan_instance; + graphics_binding_vulkan.physicalDevice = vulkan_physical_device; + graphics_binding_vulkan.device = vulkan_device; + graphics_binding_vulkan.queueFamilyIndex = vulkan_queue_family_index; + graphics_binding_vulkan.queueIndex = vulkan_queue_index; + + return &graphics_binding_vulkan; +} + +void OpenXRVulkanExtension::get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) { + // We might want to do more here especially if we keep things in linear color space + // Possibly add in R10G10B10A2 as an option if we're using the mobile renderer. + p_usable_swap_chains.push_back(VK_FORMAT_R8G8B8A8_SRGB); + p_usable_swap_chains.push_back(VK_FORMAT_B8G8R8A8_SRGB); + p_usable_swap_chains.push_back(VK_FORMAT_R8G8B8A8_UINT); + p_usable_swap_chains.push_back(VK_FORMAT_B8G8R8A8_UINT); +} + +bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) { + XrSwapchainImageVulkanKHR *images = nullptr; + + RenderingServer *rendering_server = RenderingServer::get_singleton(); + ERR_FAIL_NULL_V(rendering_server, false); + RenderingDevice *rendering_device = rendering_server->get_rendering_device(); + ERR_FAIL_NULL_V(rendering_device, false); + + uint32_t swapchain_length; + XrResult result = xrEnumerateSwapchainImages(p_swapchain, 0, &swapchain_length, nullptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get swapchaim image count [", openxr_api->get_error_string(result), "]"); + return false; + } + + images = (XrSwapchainImageVulkanKHR *)memalloc(sizeof(XrSwapchainImageVulkanKHR) * swapchain_length); + ERR_FAIL_NULL_V_MSG(images, false, "OpenXR Couldn't allocate memory for swap chain image"); + + for (uint64_t i = 0; i < swapchain_length; i++) { + images[i].type = XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR; + images[i].next = nullptr; + images[i].image = VK_NULL_HANDLE; + } + + result = xrEnumerateSwapchainImages(p_swapchain, swapchain_length, &swapchain_length, (XrSwapchainImageBaseHeader *)images); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get swapchaim images [", openxr_api->get_error_string(result), "]"); + memfree(images); + return false; + } + + // SwapchainGraphicsData *data = (SwapchainGraphicsData *)memalloc(sizeof(SwapchainGraphicsData)); + SwapchainGraphicsData *data = memnew(SwapchainGraphicsData); + if (data == nullptr) { + print_line("OpenXR: Failed to allocate memory for swapchain data"); + memfree(images); + return false; + } + *r_swapchain_graphics_data = data; + data->is_multiview = (p_array_size > 1); + + RenderingDevice::DataFormat format = RenderingDevice::DATA_FORMAT_R8G8B8A8_SRGB; + RenderingDevice::TextureSamples samples = RenderingDevice::TEXTURE_SAMPLES_1; + uint64_t usage_flags = RenderingDevice::TEXTURE_USAGE_SAMPLING_BIT | RenderingDevice::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT; + + switch (p_swapchain_format) { + case VK_FORMAT_R8G8B8A8_SRGB: + // Even though this is an sRGB framebuffer format we're using UNORM here. + // The reason here is because Godot does a linear to sRGB conversion while + // with the sRGB format, this conversion would be doubled by the hardware. + // This also means we're reading the values as is for our preview on screen. + // The OpenXR runtime however is still treating this as an sRGB format and + // will thus do an sRGB -> Linear conversion as expected. + // format = RenderingDevice::DATA_FORMAT_R8G8B8A8_SRGB; + format = RenderingDevice::DATA_FORMAT_R8G8B8A8_UNORM; + break; + case VK_FORMAT_B8G8R8A8_SRGB: + // format = RenderingDevice::DATA_FORMAT_B8G8R8A8_SRGB; + format = RenderingDevice::DATA_FORMAT_B8G8R8A8_UNORM; + break; + case VK_FORMAT_R8G8B8A8_UINT: + format = RenderingDevice::DATA_FORMAT_R8G8B8A8_UINT; + break; + case VK_FORMAT_B8G8R8A8_UINT: + format = RenderingDevice::DATA_FORMAT_B8G8R8A8_UINT; + break; + default: + // continue with our default value + print_line("Unsupported swapchain format ", p_swapchain_format); + break; + } + + switch (p_sample_count) { + case 1: + samples = RenderingDevice::TEXTURE_SAMPLES_1; + break; + case 2: + samples = RenderingDevice::TEXTURE_SAMPLES_2; + break; + case 4: + samples = RenderingDevice::TEXTURE_SAMPLES_4; + break; + case 8: + samples = RenderingDevice::TEXTURE_SAMPLES_8; + break; + case 16: + samples = RenderingDevice::TEXTURE_SAMPLES_16; + break; + case 32: + samples = RenderingDevice::TEXTURE_SAMPLES_32; + break; + case 64: + samples = RenderingDevice::TEXTURE_SAMPLES_64; + break; + default: + // continue with our default value + print_line("Unsupported sample count ", p_sample_count); + break; + } + + Vector<RID> image_rids; + Vector<RID> framebuffers; + + // create Godot texture objects for each entry in our swapchain + for (uint64_t i = 0; i < swapchain_length; i++) { + RID image_rid = rendering_device->texture_create_from_extension( + p_array_size == 1 ? RenderingDevice::TEXTURE_TYPE_2D : RenderingDevice::TEXTURE_TYPE_2D_ARRAY, + format, + samples, + usage_flags, + (uint64_t)images[i].image, + p_width, + p_height, + 1, + p_array_size); + + image_rids.push_back(image_rid); + + { + Vector<RID> fb; + fb.push_back(image_rid); + + RID fb_rid = rendering_device->framebuffer_create(fb, RenderingDevice::INVALID_ID, p_array_size); + framebuffers.push_back(fb_rid); + } + } + + data->image_rids = image_rids; + data->framebuffers = framebuffers; + + memfree(images); + + return true; +} + +bool OpenXRVulkanExtension::create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, CameraMatrix &r_camera_matrix) { + // Even though this is a Vulkan renderer we're using OpenGL coordinate systems + XrMatrix4x4f matrix; + XrMatrix4x4f_CreateProjectionFov(&matrix, GRAPHICS_OPENGL, p_fov, (float)p_z_near, (float)p_z_far); + + for (int j = 0; j < 4; j++) { + for (int i = 0; i < 4; i++) { + r_camera_matrix.matrix[j][i] = matrix.m[j * 4 + i]; + } + } + + return true; +} + +bool OpenXRVulkanExtension::copy_render_target_to_image(RID p_from_render_target, void *p_swapchain_graphics_data, int p_image_index) { + SwapchainGraphicsData *data = (SwapchainGraphicsData *)p_swapchain_graphics_data; + ERR_FAIL_NULL_V(data, false); + ERR_FAIL_COND_V(p_from_render_target.is_null(), false); + ERR_FAIL_NULL_V(RendererStorageRD::base_singleton, false); + + RID source_image = RendererStorageRD::base_singleton->render_target_get_rd_texture(p_from_render_target); + ERR_FAIL_COND_V(source_image.is_null(), false); + + RID depth_image; // TODO implement + + ERR_FAIL_INDEX_V(p_image_index, data->framebuffers.size(), false); + RID fb = data->framebuffers[p_image_index]; + ERR_FAIL_COND_V(fb.is_null(), false); + + // Our vulkan extension can only be used in conjunction with our vulkan renderer. + // We need access to the effects object in order to have access to our copy logic. + // Breaking all the rules but there is no nice way to do this. + EffectsRD *effects = RendererStorageRD::base_singleton->get_effects(); + ERR_FAIL_NULL_V(effects, false); + effects->copy_to_fb_rect(source_image, fb, Rect2i(), false, false, false, false, depth_image, data->is_multiview); + + return true; +} + +void OpenXRVulkanExtension::cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) { + if (*p_swapchain_graphics_data == nullptr) { + return; + } + + SwapchainGraphicsData *data = (SwapchainGraphicsData *)*p_swapchain_graphics_data; + + RenderingServer *rendering_server = RenderingServer::get_singleton(); + ERR_FAIL_NULL(rendering_server); + RenderingDevice *rendering_device = rendering_server->get_rendering_device(); + ERR_FAIL_NULL(rendering_device); + + for (int i = 0; i < data->image_rids.size(); i++) { + // This should clean up our RIDs and associated texture objects but shouldn't destroy the images, they are owned by our XrSwapchain + rendering_device->free(data->image_rids[i]); + } + data->image_rids.clear(); + + for (int i = 0; i < data->framebuffers.size(); i++) { + // This should clean up our RIDs and associated texture objects but shouldn't destroy the images, they are owned by our XrSwapchain + rendering_device->free(data->framebuffers[i]); + } + data->framebuffers.clear(); + + memdelete(data); + *p_swapchain_graphics_data = nullptr; +} + +#define ENUM_TO_STRING_CASE(e) \ + case e: { \ + return String(#e); \ + } break; + +String OpenXRVulkanExtension::get_swapchain_format_name(int64_t p_swapchain_format) const { + // This really should be in vulkan_context... + VkFormat format = VkFormat(p_swapchain_format); + switch (format) { + ENUM_TO_STRING_CASE(VK_FORMAT_UNDEFINED) + ENUM_TO_STRING_CASE(VK_FORMAT_R4G4_UNORM_PACK8) + ENUM_TO_STRING_CASE(VK_FORMAT_R4G4B4A4_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_B4G4R4A4_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R5G6B5_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_B5G6R5_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R5G5B5A1_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_B5G5R5A1_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_A1R5G5B5_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R8_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8_SRGB) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8_SRGB) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8_SRGB) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8_SRGB) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8A8_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8A8_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8A8_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8A8_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8A8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8A8_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8A8_SRGB) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8A8_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8A8_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8A8_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8A8_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8A8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8A8_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8A8_SRGB) + ENUM_TO_STRING_CASE(VK_FORMAT_A8B8G8R8_UNORM_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A8B8G8R8_SNORM_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A8B8G8R8_USCALED_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A8B8G8R8_SSCALED_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A8B8G8R8_UINT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A8B8G8R8_SINT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A8B8G8R8_SRGB_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2R10G10B10_UNORM_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2R10G10B10_SNORM_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2R10G10B10_USCALED_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2R10G10B10_SSCALED_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2R10G10B10_UINT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2R10G10B10_SINT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2B10G10R10_UNORM_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2B10G10R10_SNORM_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2B10G10R10_USCALED_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2B10G10R10_SSCALED_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2B10G10R10_UINT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2B10G10R10_SINT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_R16_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16A16_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16A16_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16A16_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16A16_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16A16_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16A16_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16A16_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32B32_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32B32_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32B32_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32B32A32_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32B32A32_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32B32A32_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64B64_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64B64_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64B64_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64B64A64_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64B64A64_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64B64A64_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_B10G11R11_UFLOAT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_E5B9G9R9_UFLOAT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_D16_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_X8_D24_UNORM_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_D32_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_S8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_D16_UNORM_S8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_D24_UNORM_S8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_D32_SFLOAT_S8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_BC1_RGB_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC1_RGB_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC1_RGBA_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC1_RGBA_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC2_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC2_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC3_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC3_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC4_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC4_SNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC5_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC5_SNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC6H_UFLOAT_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC6H_SFLOAT_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC7_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC7_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_EAC_R11_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_EAC_R11_SNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_EAC_R11G11_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_EAC_R11G11_SNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_4x4_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_4x4_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_5x4_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_5x4_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_5x5_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_5x5_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_6x5_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_6x5_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_6x6_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_6x6_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x5_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x5_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x6_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x6_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x8_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x8_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x5_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x5_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x6_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x6_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x8_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x8_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x10_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x10_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_12x10_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_12x10_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_12x12_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_12x12_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_G8B8G8R8_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8G8_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G8_B8R8_2PLANE_420_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G8_B8R8_2PLANE_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R10X6_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R10X6G10X6_UNORM_2PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R12X4_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R12X4G12X4_UNORM_2PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G16B16G16R16_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_B16G16R16G16_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G16_B16R16_2PLANE_420_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G16_B16R16_2PLANE_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_G8_B8R8_2PLANE_444_UNORM_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_G16_B16R16_2PLANE_444_UNORM_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_A4R4G4B4_UNORM_PACK16_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_MAX_ENUM) + default: { + return String("Swapchain format ") + String::num_int64(int64_t(p_swapchain_format)); + } break; + } +} diff --git a/modules/openxr/extensions/openxr_vulkan_extension.h b/modules/openxr/extensions/openxr_vulkan_extension.h new file mode 100644 index 0000000000..cf55ae264f --- /dev/null +++ b/modules/openxr/extensions/openxr_vulkan_extension.h @@ -0,0 +1,93 @@ +/*************************************************************************/ +/* openxr_vulkan_extension.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef OPENXR_VULKAN_EXTENSION_H +#define OPENXR_VULKAN_EXTENSION_H + +#include "core/templates/vector.h" +#include "openxr_extension_wrapper.h" + +#include "drivers/vulkan/vulkan_context.h" + +// Forward declare these so we don't need OpenXR headers where-ever this is included +// Including OpenXR at this point gives loads and loads of compile issues especially +// on Windows because windows.h is EVIL and really shouldn't be included outside of platform +// but we really don't have a choice in the matter + +struct XrGraphicsRequirementsVulkanKHR; +struct XrVulkanInstanceCreateInfoKHR; +struct XrVulkanGraphicsDeviceGetInfoKHR; +struct XrVulkanDeviceCreateInfoKHR; +struct XrGraphicsBindingVulkanKHR; + +class OpenXRVulkanExtension : public OpenXRGraphicsExtensionWrapper, VulkanHooks { +public: + OpenXRVulkanExtension(OpenXRAPI *p_openxr_api); + virtual ~OpenXRVulkanExtension() override; + + virtual void on_instance_created(const XrInstance p_instance) override; + virtual void *set_session_create_and_get_next_pointer(void *p_next_pointer) override; + + virtual bool create_vulkan_instance(const VkInstanceCreateInfo *p_vulkan_create_info, VkInstance *r_instance) override; + virtual bool get_physical_device(VkPhysicalDevice *r_device) override; + virtual bool create_vulkan_device(const VkDeviceCreateInfo *p_device_create_info, VkDevice *r_device) override; + + virtual void get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) override; + virtual String get_swapchain_format_name(int64_t p_swapchain_format) const override; + virtual bool get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) override; + virtual void cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) override; + virtual bool create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, CameraMatrix &r_camera_matrix) override; + virtual bool copy_render_target_to_image(RID p_from_render_target, void *p_swapchain_graphics_data, int p_image_index) override; + +private: + static OpenXRVulkanExtension *singleton; + static XrGraphicsBindingVulkanKHR graphics_binding_vulkan; // declaring this as static so we don't need to know its size and we only need it once when creating our session + + struct SwapchainGraphicsData { + bool is_multiview; + Vector<RID> image_rids; + Vector<RID> framebuffers; + }; + + bool check_graphics_api_support(XrVersion p_desired_version); + + VkInstance vulkan_instance; + VkPhysicalDevice vulkan_physical_device; + VkDevice vulkan_device; + uint32_t vulkan_queue_family_index; + uint32_t vulkan_queue_index; + + XrResult xrGetVulkanGraphicsRequirements2KHR(XrInstance p_instance, XrSystemId p_system_id, XrGraphicsRequirementsVulkanKHR *p_graphics_requirements); + XrResult xrCreateVulkanInstanceKHR(XrInstance p_instance, const XrVulkanInstanceCreateInfoKHR *p_create_info, VkInstance *r_vulkan_instance, VkResult *r_vulkan_result); + XrResult xrGetVulkanGraphicsDevice2KHR(XrInstance p_instance, const XrVulkanGraphicsDeviceGetInfoKHR *p_get_info, VkPhysicalDevice *r_vulkan_physical_device); + XrResult xrCreateVulkanDeviceKHR(XrInstance p_instance, const XrVulkanDeviceCreateInfoKHR *p_create_info, VkDevice *r_device, VkResult *r_result); +}; + +#endif // !OPENXR_VULKAN_EXTENSION_H diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp new file mode 100644 index 0000000000..e3da214cc8 --- /dev/null +++ b/modules/openxr/openxr_api.cpp @@ -0,0 +1,2171 @@ +/*************************************************************************/ +/* openxr_api.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "openxr_api.h" +#include "openxr_util.h" + +#include "core/config/engine.h" +#include "core/config/project_settings.h" +#include "core/os/memory.h" +#include "core/version.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif + +#ifdef ANDROID_ENABLED +#include "extensions/openxr_android_extension.h" +#endif + +#ifdef VULKAN_ENABLED +#include "extensions/openxr_vulkan_extension.h" +#endif + +OpenXRAPI *OpenXRAPI::singleton = nullptr; + +void OpenXRAPI::setup_global_defs() { + // As OpenXRAPI is not constructed if OpenXR is not enabled, we register our project and editor settings here + + // Project settings + GLOBAL_DEF_BASIC("xr/openxr/enabled", false); + GLOBAL_DEF_BASIC("xr/openxr/default_action_map", "res://default_action_map.tres"); + ProjectSettings::get_singleton()->set_custom_property_info("xr/openxr/default_action_map", PropertyInfo(Variant::STRING, "xr/openxr/default_action_map", PROPERTY_HINT_FILE, "*.tres")); + + GLOBAL_DEF_BASIC("xr/openxr/form_factor", "0"); + ProjectSettings::get_singleton()->set_custom_property_info("xr/openxr/form_factor", PropertyInfo(Variant::INT, "xr/openxr/form_factor", PROPERTY_HINT_ENUM, "Head mounted,Handheld")); + + GLOBAL_DEF_BASIC("xr/openxr/view_configuration", "1"); + ProjectSettings::get_singleton()->set_custom_property_info("xr/openxr/view_configuration", PropertyInfo(Variant::INT, "xr/openxr/view_configuration", PROPERTY_HINT_ENUM, "Mono,Stereo")); // "Mono,Stereo,Quad,Observer" + + GLOBAL_DEF_BASIC("xr/openxr/reference_space", "1"); + ProjectSettings::get_singleton()->set_custom_property_info("xr/openxr/reference_space", PropertyInfo(Variant::INT, "xr/openxr/reference_space", PROPERTY_HINT_ENUM, "Local,Stage")); + +#ifdef TOOLS_ENABLED + // Disabled for now, using XR inside of the editor we'll be working on during the coming months. + + // editor settings (it seems we're too early in the process when setting up rendering, to access editor settings...) + // EDITOR_DEF_RST("xr/openxr/in_editor", false); + // GLOBAL_DEF("xr/openxr/in_editor", false); +#endif +} + +bool OpenXRAPI::openxr_is_enabled() { + // @TODO we need an overrule switch so we can force enable openxr, i.e run "godot --openxr_enabled" + + if (Engine::get_singleton()->is_editor_hint()) { +#ifdef TOOLS_ENABLED + // Disabled for now, using XR inside of the editor we'll be working on during the coming months. + return false; + + // bool enabled = GLOBAL_GET("xr/openxr/in_editor"); // EDITOR_GET("xr/openxr/in_editor"); + // return enabled; +#else + // we should never get here, editor hint won't be true if the editor isn't compiled in. + return false; +#endif + } else { + bool enabled = GLOBAL_GET("xr/openxr/enabled"); + return enabled; + } +} + +OpenXRAPI *OpenXRAPI::get_singleton() { + if (singleton != nullptr) { + // already constructed, return our singleton + return singleton; + } else if (openxr_is_enabled()) { + // construct our singleton and return it + singleton = memnew(OpenXRAPI); + return singleton; + } else { + // not enabled, don't instantiate, return nullptr + return nullptr; + } +} + +String OpenXRAPI::get_default_action_map_resource_name() { + String name = GLOBAL_GET("xr/openxr/default_action_map"); + + return name; +} + +String OpenXRAPI::get_error_string(XrResult result) { + if (XR_SUCCEEDED(result)) { + return String("Succeeded"); + } + + if (instance == XR_NULL_HANDLE) { + Array args; + args.push_back(Variant(result)); + return String("Error code {0}").format(args); + } + + char resultString[XR_MAX_RESULT_STRING_SIZE]; + xrResultToString(instance, result, resultString); + + return String(resultString); +} + +String OpenXRAPI::get_swapchain_format_name(int64_t p_swapchain_format) const { + // This is rendering engine dependend... + if (graphics_extension) { + return graphics_extension->get_swapchain_format_name(p_swapchain_format); + } + + return String("Swapchain format ") + String::num_int64(int64_t(p_swapchain_format)); +} + +bool OpenXRAPI::load_layer_properties() { + // This queries additional layers that are available and can be initialised when we create our OpenXR instance + if (layer_properties != nullptr) { + // already retrieved this + return true; + } + + // Note, instance is not yet setup so we can't use get_error_string to retrieve our error + XrResult result = xrEnumerateApiLayerProperties(0, &num_layer_properties, nullptr); + ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate number of api layer properties"); + + layer_properties = (XrApiLayerProperties *)memalloc(sizeof(XrApiLayerProperties) * num_layer_properties); + ERR_FAIL_NULL_V(layer_properties, false); + for (uint32_t i = 0; i < num_layer_properties; i++) { + layer_properties[i].type = XR_TYPE_API_LAYER_PROPERTIES; + layer_properties[i].next = nullptr; + } + + result = xrEnumerateApiLayerProperties(num_layer_properties, &num_layer_properties, layer_properties); + ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate api layer properties"); + +#ifdef DEBUG + for (uint32_t i = 0; i < num_layer_properties; i++) { + print_line("OpenXR: Found OpenXR layer ", layer_properties[i].layerName); + } +#endif + + return true; +} + +bool OpenXRAPI::load_supported_extensions() { + // This queries supported extensions that are available and can be initialised when we create our OpenXR instance + + if (supported_extensions != nullptr) { + // already retrieved this + return true; + } + + // Note, instance is not yet setup so we can't use get_error_string to retrieve our error + XrResult result = xrEnumerateInstanceExtensionProperties(nullptr, 0, &num_supported_extensions, nullptr); + ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate number of extension properties"); + + supported_extensions = (XrExtensionProperties *)memalloc(sizeof(XrExtensionProperties) * num_supported_extensions); + ERR_FAIL_NULL_V(supported_extensions, false); + + // set our types + for (uint32_t i = 0; i < num_supported_extensions; i++) { + supported_extensions[i].type = XR_TYPE_EXTENSION_PROPERTIES; + supported_extensions[i].next = nullptr; + } + result = xrEnumerateInstanceExtensionProperties(nullptr, num_supported_extensions, &num_supported_extensions, supported_extensions); + ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate extension properties"); + +#ifdef DEBUG + for (uint32_t i = 0; i < num_supported_extensions; i++) { + print_line("OpenXR: Found OpenXR extension ", supported_extensions[i].extensionName); + } +#endif + + return true; +} + +bool OpenXRAPI::is_extension_supported(const char *p_extension) const { + for (uint32_t i = 0; i < num_supported_extensions; i++) { + if (strcmp(supported_extensions[i].extensionName, p_extension)) { + return true; + } + } + + return false; +} + +void OpenXRAPI::copy_string_to_char_buffer(const String p_string, char *p_buffer, int p_buffer_len) { + CharString char_string = p_string.utf8(); + int len = char_string.length(); + if (len < p_buffer_len - 1) { + // was having weird CI issues with strcpy so.... + memcpy(p_buffer, char_string.get_data(), len); + p_buffer[len] = '\0'; + } else { + memcpy(p_buffer, char_string.get_data(), p_buffer_len - 1); + p_buffer[p_buffer_len - 1] = '\0'; + } +} + +bool OpenXRAPI::create_instance() { + // Create our OpenXR instance, this will query any registered extension wrappers for extensions we need to enable. + + // Append the extensions requested by the registered extension wrappers. + Map<const char *, bool *> requested_extensions; + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + Map<const char *, bool *> wrapper_request_extensions = wrapper->get_request_extensions(); + + // requested_extensions.insert(wrapper_request_extensions.begin(), wrapper_request_extensions.end()); + for (auto &requested_extension : wrapper_request_extensions) { + requested_extensions[requested_extension.key] = requested_extension.value; + } + } + + // Check which extensions are supported + enabled_extensions.clear(); + for (auto &requested_extension : requested_extensions) { + if (!is_extension_supported(requested_extension.key)) { + if (requested_extension.value == nullptr) { + // nullptr means this is a manditory extension so we fail + ERR_FAIL_V_MSG(false, "OpenXR: OpenXR Runtime does not support OpenGL extension!"); + } else { + // set this extension as not supported + *requested_extension.value = false; + } + } else if (requested_extension.value != nullptr) { + // set this extension as supported + *requested_extension.value = true; + + // and record that we want to enable it + enabled_extensions.push_back(requested_extension.key); + } else { + // record that we want to enable this + enabled_extensions.push_back(requested_extension.key); + } + } + + // Get our project name + String project_name = GLOBAL_GET("application/config/name"); + + // Create our OpenXR instance + XrApplicationInfo application_info{ + "", // applicationName, we'll set this down below + 1, // applicationVersion, we don't currently have this + "Godot Game Engine", // engineName + VERSION_MAJOR * 10000 + VERSION_MINOR * 100 + VERSION_PATCH, // engineVersion 4.0 -> 40000, 4.0.1 -> 40001, 4.1 -> 40100, etc. + XR_CURRENT_API_VERSION // apiVersion + }; + + XrInstanceCreateInfo instance_create_info = { + XR_TYPE_INSTANCE_CREATE_INFO, // type + nullptr, // next + 0, // createFlags + application_info, // applicationInfo + 0, // enabledApiLayerCount, need to find out if we need support for this? + nullptr, // enabledApiLayerNames + uint32_t(enabled_extensions.size()), // enabledExtensionCount + enabled_extensions.ptr() // enabledExtensionNames + }; + + copy_string_to_char_buffer(project_name, instance_create_info.applicationInfo.applicationName, XR_MAX_APPLICATION_NAME_SIZE); + + XrResult result = xrCreateInstance(&instance_create_info, &instance); + ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "Failed to create XR instance."); + + // from this point on we can use get_error_string to get more info about our errors... + + XrInstanceProperties instanceProps = { + XR_TYPE_INSTANCE_PROPERTIES, // type; + nullptr, // next + 0, // runtimeVersion, from here will be set by our get call + "" // runtimeName + }; + result = xrGetInstanceProperties(instance, &instanceProps); + if (XR_FAILED(result)) { + // not fatal probably + print_line("OpenXR: Failed to get XR instance properties [", get_error_string(result), "]"); + } else { + print_line("OpenXR: Running on OpenXR runtime: ", instanceProps.runtimeName, " ", OpenXRUtil::make_xr_version_string(instanceProps.runtimeVersion)); + } + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_instance_created(instance); + } + + return true; +} + +bool OpenXRAPI::get_system_info() { + // Retrieve basic OpenXR system info based on the form factor we desire + + // Retrieve the system for our form factor, fails if form factor is not available + XrSystemGetInfo system_get_info = { + XR_TYPE_SYSTEM_GET_INFO, // type; + nullptr, // next + form_factor // formFactor + }; + + XrResult result = xrGetSystem(instance, &system_get_info, &system_id); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get system for our form factor [", get_error_string(result), "]"); + return false; + } + + // obtain info about our system, writing this out completely to make CI on Linux happy.. + void *next_pointer = nullptr; + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + void *np = wrapper->set_system_properties_and_get_next_pointer(next_pointer); + if (np != nullptr) { + next_pointer = np; + } + } + + XrSystemProperties system_properties = { + XR_TYPE_SYSTEM_PROPERTIES, // type + next_pointer, // next + 0, // systemId, from here will be set by our get call + 0, // vendorId + "", // systemName + { + 0, // maxSwapchainImageHeight + 0, // maxSwapchainImageWidth + 0, // maxLayerCount + }, // graphicsProperties + { + false, // orientationTracking + false // positionTracking + } // trackingProperties + }; + + result = xrGetSystemProperties(instance, system_id, &system_properties); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get System properties [", get_error_string(result), "]"); + return false; + } + + // remember this state, we'll use it later + system_name = String(system_properties.systemName); + vendor_id = system_properties.vendorId; + graphics_properties = system_properties.graphicsProperties; + tracking_properties = system_properties.trackingProperties; + + return true; +} + +bool OpenXRAPI::load_supported_view_configuration_types() { + // This queries the supported configuration types, likely there will only be one chosing between Mono (phone AR) and Stereo (HMDs) + + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); + + if (supported_view_configuration_types != nullptr) { + // free previous results + memfree(supported_view_configuration_types); + supported_view_configuration_types = nullptr; + } + + XrResult result = xrEnumerateViewConfigurations(instance, system_id, 0, &num_view_configuration_types, nullptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get view configuration count [", get_error_string(result), "]"); + return false; + } + + supported_view_configuration_types = (XrViewConfigurationType *)memalloc(sizeof(XrViewConfigurationType) * num_view_configuration_types); + ERR_FAIL_NULL_V(supported_view_configuration_types, false); + + result = xrEnumerateViewConfigurations(instance, system_id, num_view_configuration_types, &num_view_configuration_types, supported_view_configuration_types); + ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerateview configurations"); + +#ifdef DEBUG + for (uint32_t i = 0; i < num_view_configuration_types; i++) { + print_line("OpenXR: Found supported view configuration ", OpenXRUtil::get_view_configuration_name(supported_view_configuration_types[i])); + } +#endif + + return true; +} + +bool OpenXRAPI::is_view_configuration_supported(XrViewConfigurationType p_configuration_type) const { + ERR_FAIL_NULL_V(supported_view_configuration_types, false); + + for (uint32_t i = 0; i < num_view_configuration_types; i++) { + if (supported_view_configuration_types[i] == p_configuration_type) { + return true; + } + } + + return false; +} + +bool OpenXRAPI::load_supported_view_configuration_views(XrViewConfigurationType p_configuration_type) { + // This loads our view configuration for each view so for a stereo HMD, we'll get two entries (that are likely identical) + // The returned data supplies us with the recommended render target size + + if (!is_view_configuration_supported(p_configuration_type)) { + print_line("OpenXR: View configuration ", OpenXRUtil::get_view_configuration_name(view_configuration), " is not supported."); + return false; + } + + if (view_configuration_views != nullptr) { + // free previous results + memfree(view_configuration_views); + view_configuration_views = nullptr; + } + + XrResult result = xrEnumerateViewConfigurationViews(instance, system_id, p_configuration_type, 0, &view_count, nullptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get view configuration count [", get_error_string(result), "]"); + return false; + } + + view_configuration_views = (XrViewConfigurationView *)memalloc(sizeof(XrViewConfigurationView) * view_count); + ERR_FAIL_NULL_V(view_configuration_views, false); + + for (uint32_t i = 0; i < view_count; i++) { + view_configuration_views[i].type = XR_TYPE_VIEW_CONFIGURATION_VIEW; + view_configuration_views[i].next = NULL; + } + + result = xrEnumerateViewConfigurationViews(instance, system_id, p_configuration_type, view_count, &view_count, view_configuration_views); + ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate view configurations"); + +#ifdef DEBUG + for (uint32_t i = 0; i < view_count; i++) { + print_line("OpenXR: Found supported view configuration view"); + print_line(" - width: ", view_configuration_views[i].maxImageRectWidth); + print_line(" - height: ", view_configuration_views[i].maxImageRectHeight); + print_line(" - sample count: ", view_configuration_views[i].maxSwapchainSampleCount); + print_line(" - recommended render width: ", view_configuration_views[i].recommendedImageRectWidth); + print_line(" - recommended render height: ", view_configuration_views[i].recommendedImageRectHeight); + print_line(" - recommended render sample count: ", view_configuration_views[i].recommendedSwapchainSampleCount); + } +#endif + + return true; +} + +void OpenXRAPI::destroy_instance() { + if (view_configuration_views != nullptr) { + memfree(view_configuration_views); + view_configuration_views = nullptr; + } + + if (supported_view_configuration_types != nullptr) { + memfree(supported_view_configuration_types); + supported_view_configuration_types = nullptr; + } + + if (instance != XR_NULL_HANDLE) { + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_instance_destroyed(); + } + + xrDestroyInstance(instance); + instance = XR_NULL_HANDLE; + } + enabled_extensions.clear(); +} + +bool OpenXRAPI::create_session() { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); + ERR_FAIL_COND_V(session != XR_NULL_HANDLE, false); + + void *next_pointer = nullptr; + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + void *np = wrapper->set_session_create_and_get_next_pointer(next_pointer); + if (np != nullptr) { + next_pointer = np; + } + } + + XrSessionCreateInfo session_create_info = { + XR_TYPE_SESSION_CREATE_INFO, // type + next_pointer, // next + 0, // createFlags + system_id // systemId + }; + + XrResult result = xrCreateSession(instance, &session_create_info, &session); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to create session [", get_error_string(result), "]"); + return false; + } + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_session_created(session); + } + + return true; +} + +bool OpenXRAPI::load_supported_reference_spaces() { + // loads the supported reference spaces for our OpenXR session + + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + + if (supported_reference_spaces != nullptr) { + // free previous results + memfree(supported_reference_spaces); + supported_reference_spaces = nullptr; + } + + XrResult result = xrEnumerateReferenceSpaces(session, 0, &num_reference_spaces, nullptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get reference space count [", get_error_string(result), "]"); + return false; + } + + supported_reference_spaces = (XrReferenceSpaceType *)memalloc(sizeof(XrReferenceSpaceType) * num_reference_spaces); + ERR_FAIL_NULL_V(supported_reference_spaces, false); + + result = xrEnumerateReferenceSpaces(session, num_reference_spaces, &num_reference_spaces, supported_reference_spaces); + ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate reference spaces"); + + // #ifdef DEBUG + for (uint32_t i = 0; i < num_reference_spaces; i++) { + print_line("OpenXR: Found supported reference space ", OpenXRUtil::get_reference_space_name(supported_reference_spaces[i])); + } + // #endif + + return true; +} + +bool OpenXRAPI::is_reference_space_supported(XrReferenceSpaceType p_reference_space) { + ERR_FAIL_NULL_V(supported_reference_spaces, false); + + for (uint32_t i = 0; i < num_reference_spaces; i++) { + if (supported_reference_spaces[i] == p_reference_space) { + return true; + } + } + + return false; +} + +bool OpenXRAPI::setup_spaces() { + XrResult result; + + XrPosef identityPose = { + { 0.0, 0.0, 0.0, 1.0 }, + { 0.0, 0.0, 0.0 } + }; + + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + + // create play space + { + if (!is_reference_space_supported(reference_space)) { + print_line("OpenXR: reference space ", OpenXRUtil::get_reference_space_name(reference_space), " is not supported."); + return false; + } + + XrReferenceSpaceCreateInfo play_space_create_info = { + XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type + nullptr, // next + reference_space, // referenceSpaceType + identityPose // poseInReferenceSpace + }; + + result = xrCreateReferenceSpace(session, &play_space_create_info, &play_space); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to create play space [", get_error_string(result), "]"); + return false; + } + } + + // create view space + { + if (!is_reference_space_supported(XR_REFERENCE_SPACE_TYPE_VIEW)) { + print_line("OpenXR: reference space XR_REFERENCE_SPACE_TYPE_VIEW is not supported."); + return false; + } + + XrReferenceSpaceCreateInfo view_space_create_info = { + XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type + nullptr, // next + XR_REFERENCE_SPACE_TYPE_VIEW, // referenceSpaceType + identityPose // poseInReferenceSpace + }; + + result = xrCreateReferenceSpace(session, &view_space_create_info, &view_space); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to create view space [", get_error_string(result), "]"); + return false; + } + } + + return true; +} + +bool OpenXRAPI::load_supported_swapchain_formats() { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + + if (supported_swapchain_formats != nullptr) { + // free previous results + memfree(supported_swapchain_formats); + supported_swapchain_formats = nullptr; + } + + XrResult result = xrEnumerateSwapchainFormats(session, 0, &num_swapchain_formats, nullptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get swapchain format count [", get_error_string(result), "]"); + return false; + } + + supported_swapchain_formats = (int64_t *)memalloc(sizeof(int64_t) * num_swapchain_formats); + ERR_FAIL_NULL_V(supported_swapchain_formats, false); + + result = xrEnumerateSwapchainFormats(session, num_swapchain_formats, &num_swapchain_formats, supported_swapchain_formats); + ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate swapchain formats"); + + // #ifdef DEBUG + for (uint32_t i = 0; i < num_swapchain_formats; i++) { + print_line("OpenXR: Found supported swapchain format ", get_swapchain_format_name(supported_swapchain_formats[i])); + } + // #endif + + return true; +} + +bool OpenXRAPI::is_swapchain_format_supported(int64_t p_swapchain_format) { + ERR_FAIL_NULL_V(supported_swapchain_formats, false); + + for (uint32_t i = 0; i < num_swapchain_formats; i++) { + if (supported_swapchain_formats[i] == p_swapchain_format) { + return true; + } + } + + return false; +} + +bool OpenXRAPI::create_main_swapchain() { + ERR_FAIL_NULL_V(graphics_extension, false); + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + + /* + TODO: We need to improve on this, for now we're taking our old approach of creating our main swapchains and substituting + those for the ones Godot normally creates. + This however means we can only use swapchains for our main XR view. + + It would have been nicer if we could override the swapchain creation in Godot with ours but we have a timing issue here. + We can't create XR swapchains until after our XR session is fully instantiated, yet Godot creates its swapchain much earlier. + + Also Godot only creates a swapchain for the main output. + OpenXR will require us to create swapchains as the render target for additional viewports if we want to use the layer system + to optimise text rendering and background rendering as OpenXR may choose to re-use the results for reprojection while we're + already rendering the next frame. + + Finally an area we need to expand upon is that Foveated rendering is only enabled for the swap chain we create, + as we render 3D content into internal buffers that are copied into the swapchain, we don't get any of the performance gains + until such time as we implement VRS. + */ + + // Build a vector with swapchain formats we want to use, from best fit to worst + Vector<int64_t> usable_swapchain_formats; + int64_t swapchain_format_to_use = 0; + + graphics_extension->get_usable_swapchain_formats(usable_swapchain_formats); + + // now find out which one is supported + for (int i = 0; i < usable_swapchain_formats.size() && swapchain_format_to_use == 0; i++) { + if (is_swapchain_format_supported(usable_swapchain_formats[i])) { + swapchain_format_to_use = usable_swapchain_formats[i]; + } + } + + if (swapchain_format_to_use == 0) { + swapchain_format_to_use = usable_swapchain_formats[0]; // just use the first one and hope for the best... + print_line("Couldn't find usable swap chain format, using", get_swapchain_format_name(swapchain_format_to_use), "instead."); + } else { + print_line("Using swap chain format:", get_swapchain_format_name(swapchain_format_to_use)); + } + + Size2 recommended_size = get_recommended_target_size(); + + if (!create_swapchain(swapchain_format_to_use, recommended_size.width, recommended_size.height, view_configuration_views[0].recommendedSwapchainSampleCount, view_count, swapchain, &swapchain_graphics_data)) { + return false; + } + + views = (XrView *)memalloc(sizeof(XrView) * view_count); + ERR_FAIL_NULL_V_MSG(views, false, "OpenXR Couldn't allocate memory for views"); + + projection_views = (XrCompositionLayerProjectionView *)memalloc(sizeof(XrCompositionLayerProjectionView) * view_count); + ERR_FAIL_NULL_V_MSG(projection_views, false, "OpenXR Couldn't allocate memory for projection views"); + + for (uint32_t i = 0; i < view_count; i++) { + views[i].type = XR_TYPE_VIEW; + views[i].next = NULL; + + projection_views[i].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW; + projection_views[i].next = NULL; + projection_views[i].subImage.swapchain = swapchain; + projection_views[i].subImage.imageArrayIndex = i; + projection_views[i].subImage.imageRect.offset.x = 0; + projection_views[i].subImage.imageRect.offset.y = 0; + projection_views[i].subImage.imageRect.extent.width = recommended_size.width; + projection_views[i].subImage.imageRect.extent.height = recommended_size.height; + }; + + return true; +}; + +void OpenXRAPI::destroy_session() { + if (running && session != XR_NULL_HANDLE) { + xrEndSession(session); + } + + if (graphics_extension) { + graphics_extension->cleanup_swapchain_graphics_data(&swapchain_graphics_data); + } + + if (views != nullptr) { + memfree(views); + views = nullptr; + } + + if (projection_views != nullptr) { + memfree(projection_views); + projection_views = nullptr; + } + + if (swapchain != XR_NULL_HANDLE) { + xrDestroySwapchain(swapchain); + swapchain = XR_NULL_HANDLE; + } + + if (supported_swapchain_formats != nullptr) { + memfree(supported_swapchain_formats); + supported_swapchain_formats = nullptr; + } + + // destroy our spaces + if (play_space != XR_NULL_HANDLE) { + xrDestroySpace(play_space); + play_space = XR_NULL_HANDLE; + } + if (view_space != XR_NULL_HANDLE) { + xrDestroySpace(view_space); + view_space = XR_NULL_HANDLE; + } + + if (supported_reference_spaces != nullptr) { + // free previous results + memfree(supported_reference_spaces); + supported_reference_spaces = nullptr; + } + + if (session != XR_NULL_HANDLE) { + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_session_destroyed(); + } + + xrDestroySession(session); + session = XR_NULL_HANDLE; + } +} + +bool OpenXRAPI::create_swapchain(int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, XrSwapchain &r_swapchain, void **r_swapchain_graphics_data) { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + ERR_FAIL_NULL_V(graphics_extension, false); + + XrResult result; + + void *next_pointer = nullptr; + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + void *np = wrapper->set_swapchain_create_info_and_get_next_pointer(next_pointer); + if (np != nullptr) { + next_pointer = np; + } + } + + XrSwapchainCreateInfo swapchain_create_info = { + XR_TYPE_SWAPCHAIN_CREATE_INFO, // type + next_pointer, // next + 0, // createFlags + XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT, // usageFlags + p_swapchain_format, // format + p_sample_count, // sampleCount + p_width, // width + p_height, // height + 1, // faceCount + p_array_size, // arraySize + 1 // mipCount + }; + + XrSwapchain new_swapchain; + result = xrCreateSwapchain(session, &swapchain_create_info, &new_swapchain); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get swapchain [", get_error_string(result), "]"); + return false; + } + + if (!graphics_extension->get_swapchain_image_data(new_swapchain, p_swapchain_format, p_width, p_height, p_sample_count, p_array_size, r_swapchain_graphics_data)) { + xrDestroySwapchain(new_swapchain); + return false; + } + + r_swapchain = new_swapchain; + + return true; +} + +bool OpenXRAPI::on_state_idle() { +#ifdef DEBUG + print_line("On state idle"); +#endif + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_idle(); + } + + return true; +} + +bool OpenXRAPI::on_state_ready() { +#ifdef DEBUG + print_line("On state ready"); +#endif + + // begin session + XrSessionBeginInfo session_begin_info = { + XR_TYPE_SESSION_BEGIN_INFO, // type + nullptr, // next + view_configuration // primaryViewConfigurationType + }; + + XrResult result = xrBeginSession(session, &session_begin_info); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to begin session [", get_error_string(result), "]"); + return false; + } + + // This is when we create our swapchain, this can be a "long" time after Godot finishes, we can deal with this for now + // but once we want to provide Viewports for additional layers where OpenXR requires us to create further swapchains, + // we'll be creating those viewport WAY before we reach this point. + // We may need to implement a wait in our init in main.cpp polling our events until the session is ready. + // That will be very very ugly + // The other possibility is to create a separate OpenXRViewport type specifically for this goal as part of our OpenXR module + + if (!create_main_swapchain()) { + return false; + } + + // we're running + running = true; + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_ready(); + } + + // TODO emit signal + + // TODO Tell android + + return true; +} + +bool OpenXRAPI::on_state_synchronized() { +#ifdef DEBUG + print_line("On state synchronized"); +#endif + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_synchronized(); + } + + return true; +} + +bool OpenXRAPI::on_state_visible() { +#ifdef DEBUG + print_line("On state visible"); +#endif + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_visible(); + } + + // TODO emit signal + + return true; +} + +bool OpenXRAPI::on_state_focused() { +#ifdef DEBUG + print_line("On state focused"); +#endif + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_focused(); + } + + // TODO emit signal + + return true; +} + +bool OpenXRAPI::on_state_stopping() { +#ifdef DEBUG + print_line("On state stopping"); +#endif + + // TODO emit signal + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_stopping(); + } + + if (running) { + XrResult result = xrEndSession(session); + if (XR_FAILED(result)) { + // we only report this.. + print_line("OpenXR: Failed to end session [", get_error_string(result), "]"); + } + + running = false; + } + + // TODO further cleanup + + return true; +} + +bool OpenXRAPI::on_state_loss_pending() { +#ifdef DEBUG + print_line("On state loss pending"); +#endif + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_loss_pending(); + } + + // TODO need to look into the correct action here, read up on the spec but we may need to signal Godot to exit (if it's not already exiting) + + return true; +} + +bool OpenXRAPI::on_state_exiting() { +#ifdef DEBUG + print_line("On state existing"); +#endif + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_exiting(); + } + + // TODO need to look into the correct action here, read up on the spec but we may need to signal Godot to exit (if it's not already exiting) + + return true; +} + +bool OpenXRAPI::is_initialized() { + return (instance != XR_NULL_HANDLE); +} + +bool OpenXRAPI::is_running() { + if (instance == XR_NULL_HANDLE) { + return false; + } + if (session == XR_NULL_HANDLE) { + return false; + } + + return running; +} + +bool OpenXRAPI::initialise(const String &p_rendering_driver) { + ERR_FAIL_COND_V_MSG(instance != XR_NULL_HANDLE, false, "OpenXR instance was already created"); + + if (p_rendering_driver == "vulkan") { +#ifdef VULKAN_ENABLED + graphics_extension = memnew(OpenXRVulkanExtension(this)); + register_extension_wrapper(graphics_extension); +#else + // shouldn't be possible... + ERR_FAIL_V(false); +#endif + } else if (p_rendering_driver == "opengl3") { +#ifdef OPENGL3_ENABLED + // graphics_extension = memnew(OpenXROpenGLExtension(this)); + // register_extension_wrapper(graphics_extension); + ERR_FAIL_V_MSG(false, "OpenXR: OpenGL is not supported at this time."); +#else + // shouldn't be possible... + ERR_FAIL_V(false); +#endif + } else { + ERR_FAIL_V_MSG(false, "OpenXR: Unsupported rendering device."); + } + + // initialise + if (!load_layer_properties()) { + destroy_instance(); + return false; + } + + if (!load_supported_extensions()) { + destroy_instance(); + return false; + } + + if (!create_instance()) { + destroy_instance(); + return false; + } + + if (!get_system_info()) { + destroy_instance(); + return false; + } + + if (!load_supported_view_configuration_types()) { + destroy_instance(); + return false; + } + + if (!load_supported_view_configuration_views(view_configuration)) { + destroy_instance(); + return false; + } + + return true; +} + +bool OpenXRAPI::initialise_session() { + if (!create_session()) { + destroy_session(); + return false; + } + + if (!load_supported_reference_spaces()) { + destroy_session(); + return false; + } + + if (!setup_spaces()) { + destroy_session(); + return false; + } + + if (!load_supported_swapchain_formats()) { + destroy_session(); + return false; + } + + return true; +} + +void OpenXRAPI::finish() { + destroy_session(); + + destroy_instance(); +} + +void OpenXRAPI::register_extension_wrapper(OpenXRExtensionWrapper *p_extension_wrapper) { + registered_extension_wrappers.push_back(p_extension_wrapper); +} + +Size2 OpenXRAPI::get_recommended_target_size() { + ERR_FAIL_NULL_V(view_configuration_views, Size2()); + + Size2 target_size; + + target_size.width = view_configuration_views[0].recommendedImageRectWidth; + target_size.height = view_configuration_views[0].recommendedImageRectHeight; + + return target_size; +} + +XRPose::TrackingConfidence OpenXRAPI::get_head_center(Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity) { + XrResult result; + + ERR_FAIL_COND_V(!running, XRPose::XR_TRACKING_CONFIDENCE_NONE); + + // xrWaitFrame not run yet + if (frame_state.predictedDisplayTime == 0) { + return XRPose::XR_TRACKING_CONFIDENCE_NONE; + } + + // Get timing for the next frame, as that is the current frame we're processing + XrTime display_time = get_next_frame_time(); + + XrSpaceVelocity velocity = { + XR_TYPE_SPACE_VELOCITY, // type + nullptr, // next + 0, // velocityFlags + { 0.0, 0.0, 0.0 }, // linearVelocity + { 0.0, 0.0, 0.0 } // angularVelocity + }; + + XrSpaceLocation location = { + XR_TYPE_SPACE_LOCATION, // type + &velocity, // next + 0, // locationFlags + { + { 0.0, 0.0, 0.0, 0.0 }, // orientation + { 0.0, 0.0, 0.0 } // position + } // pose + }; + + result = xrLocateSpace(view_space, play_space, display_time, &location); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to locate view space in play space [", get_error_string(result), "]"); + return XRPose::XR_TRACKING_CONFIDENCE_NONE; + } + + XRPose::TrackingConfidence confidence = transform_from_location(location, r_transform); + parse_velocities(velocity, r_linear_velocity, r_angular_velocity); + + if (head_pose_confidence != confidence) { + // prevent error spam + head_pose_confidence = confidence; + if (head_pose_confidence == XRPose::XR_TRACKING_CONFIDENCE_NONE) { + print_line("OpenXR head space location not valid (check tracking?)"); +#ifdef DEBUG + } else if (head_pose_confidence == XRPose::XR_TRACKING_CONFIDENCE_LOW) { + print_line("OpenVR Head pose now tracking with low confidence"); + } else { + print_line("OpenVR Head pose now tracking with high confidence"); +#endif + } + } + + return confidence; +} + +bool OpenXRAPI::get_view_transform(uint32_t p_view, Transform3D &r_transform) { + ERR_FAIL_COND_V(!running, false); + + // xrWaitFrame not run yet + if (frame_state.predictedDisplayTime == 0) { + return false; + } + + // we don't have valid view info + if (views == NULL || !view_pose_valid) { + return false; + } + + // Note, the timing of this is set right before rendering, which is what we need here. + r_transform = transform_from_pose(views[p_view].pose); + + return true; +} + +bool OpenXRAPI::get_view_projection(uint32_t p_view, double p_z_near, double p_z_far, CameraMatrix &p_camera_matrix) { + ERR_FAIL_COND_V(!running, false); + ERR_FAIL_NULL_V(graphics_extension, false); + + // xrWaitFrame not run yet + if (frame_state.predictedDisplayTime == 0) { + return false; + } + + // we don't have valid view info + if (views == NULL || !view_pose_valid) { + return false; + } + + return graphics_extension->create_projection_fov(views[p_view].fov, p_z_near, p_z_far, p_camera_matrix); +} + +bool OpenXRAPI::poll_events() { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); + + XrEventDataBuffer runtimeEvent; + runtimeEvent.type = XR_TYPE_EVENT_DATA_BUFFER; + runtimeEvent.next = nullptr; + // runtimeEvent.varying = ... + + XrResult pollResult = xrPollEvent(instance, &runtimeEvent); + while (pollResult == XR_SUCCESS) { + bool handled = false; + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + handled |= wrapper->on_event_polled(runtimeEvent); + } + switch (runtimeEvent.type) { + // case XR_TYPE_EVENT_DATA_EVENTS_LOST: { + // } break; + // case XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR: { + // } break; + // case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: { + // } break; + case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: { + XrEventDataSessionStateChanged *event = (XrEventDataSessionStateChanged *)&runtimeEvent; + + session_state = event->state; + if (session_state >= XR_SESSION_STATE_MAX_ENUM) { + print_line("OpenXR EVENT: session state changed to UNKNOWN -", session_state); + } else { + print_line("OpenXR EVENT: session state changed to", OpenXRUtil::get_session_state_name(session_state)); + + switch (session_state) { + case XR_SESSION_STATE_IDLE: + on_state_idle(); + break; + case XR_SESSION_STATE_READY: + on_state_ready(); + break; + case XR_SESSION_STATE_SYNCHRONIZED: + on_state_synchronized(); + break; + case XR_SESSION_STATE_VISIBLE: + on_state_visible(); + break; + case XR_SESSION_STATE_FOCUSED: + on_state_focused(); + break; + case XR_SESSION_STATE_STOPPING: + on_state_stopping(); + break; + case XR_SESSION_STATE_LOSS_PENDING: + on_state_loss_pending(); + break; + case XR_SESSION_STATE_EXITING: + on_state_exiting(); + break; + default: + break; + } + } + } break; + // case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: { + // } break; + // case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: { + // } break; + default: + if (!handled) { + print_line("OpenXR Unhandled event type", OpenXRUtil::get_structure_type_name(runtimeEvent.type)); + } + break; + } + + runtimeEvent.type = XR_TYPE_EVENT_DATA_BUFFER; + pollResult = xrPollEvent(instance, &runtimeEvent); + } + + if (pollResult == XR_EVENT_UNAVAILABLE) { + // processed all events in the queue + return true; + } else { + ERR_FAIL_V_MSG(false, "OpenXR: Failed to poll events!"); + } +} + +bool OpenXRAPI::process() { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); + + if (!poll_events()) { + return false; + } + + if (!running) { + return false; + } + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_process(); + } + + return true; +} + +bool OpenXRAPI::acquire_image(XrSwapchain p_swapchain, uint32_t &r_image_index) { + ERR_FAIL_COND_V(image_acquired, true); // this was not released when it should be, error out and re-use... + + XrResult result; + XrSwapchainImageAcquireInfo swapchain_image_acquire_info = { + XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO, // type + nullptr // next + }; + result = xrAcquireSwapchainImage(p_swapchain, &swapchain_image_acquire_info, &r_image_index); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to acquire swapchain image [", get_error_string(result), "]"); + return false; + } + + XrSwapchainImageWaitInfo swapchain_image_wait_info = { + XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO, // type + nullptr, // next + 17000000 // timeout in nanoseconds + }; + + result = xrWaitSwapchainImage(p_swapchain, &swapchain_image_wait_info); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to wait for swapchain image [", get_error_string(result), "]"); + return false; + } + + return true; +} + +bool OpenXRAPI::release_image(XrSwapchain p_swapchain) { + XrSwapchainImageReleaseInfo swapchain_image_release_info = { + XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO, // type + nullptr // next + }; + XrResult result = xrReleaseSwapchainImage(swapchain, &swapchain_image_release_info); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to release swapchain image! [", get_error_string(result), "]"); + return false; + } + + return true; +} + +void OpenXRAPI::pre_render() { + ERR_FAIL_COND(instance == XR_NULL_HANDLE); + + if (!running) { + return; + } + + // Waitframe does 2 important things in our process: + // 1) It provides us with predictive timing, telling us when OpenXR expects to display the frame we're about to commit + // 2) It will use the previous timing to pause our thread so that rendering starts as close to displaying as possible + // This must thus be called as close to when we start rendering as possible + XrFrameWaitInfo frame_wait_info = { XR_TYPE_FRAME_WAIT_INFO, nullptr }; + XrResult result = xrWaitFrame(session, &frame_wait_info, &frame_state); + if (XR_FAILED(result)) { + print_line("OpenXR: xrWaitFrame() was not successful [", get_error_string(result), "]"); + return; + } + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_pre_render(); + } + + // Get our view info for the frame we're about to render, note from the OpenXR manual: + // "Repeatedly calling xrLocateViews with the same time may not necessarily return the same result. Instead the prediction gets increasingly accurate as the function is called closer to the given time for which a prediction is made" + + // We're calling this "relatively" early, the positioning we're obtaining here will be used to do our frustum culling, + // occlusion culling, etc. There is however a technique that we can investigate in the future where after our entire + // Vulkan command buffer is build, but right before vkSubmitQueue is called, we call xrLocateViews one more time and + // update the view and projection matrix once more with a slightly more accurate predication and then submit the + // command queues. + + // That is not possible yet but worth investigating in the future. + + XrViewLocateInfo view_locate_info = { + XR_TYPE_VIEW_LOCATE_INFO, // type + nullptr, // next + view_configuration, // viewConfigurationType + frame_state.predictedDisplayTime, // displayTime + play_space // space + }; + XrViewState view_state = { + XR_TYPE_VIEW_STATE, // type + nullptr, // next + 0 // viewStateFlags + }; + uint32_t view_count_output; + result = xrLocateViews(session, &view_locate_info, &view_state, view_count, &view_count_output, views); + if (XR_FAILED(result)) { + print_line("OpenXR: Couldn't locate views [", get_error_string(result), "]"); + return; + } + + bool pose_valid = true; + for (uint64_t i = 0; i < view_count_output; i++) { + if ((view_state.viewStateFlags & XR_VIEW_STATE_ORIENTATION_VALID_BIT) == 0 || + (view_state.viewStateFlags & XR_VIEW_STATE_POSITION_VALID_BIT) == 0) { + pose_valid = false; + } + } + if (view_pose_valid != pose_valid) { + view_pose_valid = pose_valid; +#ifdef DEBUG + if (!view_pose_valid) { + print_line("OpenXR View pose became invalid"); + } else { + print_line("OpenXR View pose became valid"); + } +#endif + } + + // let's start our frame.. + XrFrameBeginInfo frame_begin_info = { + XR_TYPE_FRAME_BEGIN_INFO, // type + nullptr // next + }; + result = xrBeginFrame(session, &frame_begin_info); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to being frame [", get_error_string(result), "]"); + return; + } +} + +bool OpenXRAPI::pre_draw_viewport(RID p_render_target) { + if (!can_render()) { + return false; + } + + // TODO: at some point in time we may support multiple viewports in which case we need to handle that... + + return true; +} + +void OpenXRAPI::post_draw_viewport(RID p_render_target) { + if (!can_render()) { + return; + } + + // TODO: at some point in time we may support multiple viewports in which case we need to handle that... + + // TODO: if we can get PR 51179 to work properly we can change away from this approach and move this into get_external_texture or something + if (!image_acquired) { + if (!acquire_image(swapchain, image_index)) { + return; + } + image_acquired = true; + + // print_line("OpenXR: acquired image " + itos(image_index) + ", copying..."); + + // Copy our buffer into our swap chain (remove once PR 51179 is done) + graphics_extension->copy_render_target_to_image(p_render_target, swapchain_graphics_data, image_index); + } +}; + +void OpenXRAPI::end_frame() { + XrResult result; + + ERR_FAIL_COND(instance == XR_NULL_HANDLE); + + if (!running) { + return; + } + + if (frame_state.shouldRender && view_pose_valid && !image_acquired) { + print_line("OpenXR: No viewport was marked with use_xr, there is no rendered output!"); + } + + // must have: + // - shouldRender set to true + // - a valid view pose for projection_views[eye].pose to submit layer + // - an image to render + if (!frame_state.shouldRender || !view_pose_valid || !image_acquired) { + // submit 0 layers when we shouldn't render + XrFrameEndInfo frame_end_info = { + XR_TYPE_FRAME_END_INFO, // type + nullptr, // next + frame_state.predictedDisplayTime, // displayTime + XR_ENVIRONMENT_BLEND_MODE_OPAQUE, // environmentBlendMode + 0, // layerCount + nullptr // layers + }; + result = xrEndFrame(session, &frame_end_info); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to end frame! [", get_error_string(result), "]"); + return; + } + + // neither eye is rendered + return; + } + + // release our swapchain image if we acquired it + if (image_acquired) { + image_acquired = false; // whether we succeed or not, consider this released. + + release_image(swapchain); + } + + for (uint32_t eye = 0; eye < view_count; eye++) { + projection_views[eye].fov = views[eye].fov; + projection_views[eye].pose = views[eye].pose; + } + + Vector<const XrCompositionLayerBaseHeader *> layers_list; + + // Add composition layers from providers + for (OpenXRCompositionLayerProvider *provider : composition_layer_providers) { + XrCompositionLayerBaseHeader *layer = provider->get_composition_layer(); + if (layer) { + layers_list.push_back(layer); + } + } + + XrCompositionLayerProjection projection_layer = { + XR_TYPE_COMPOSITION_LAYER_PROJECTION, // type + nullptr, // next + layers_list.size() > 1 ? XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT | XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT : XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT, // layerFlags + play_space, // space + view_count, // viewCount + projection_views, // views + }; + layers_list.push_back((const XrCompositionLayerBaseHeader *)&projection_layer); + + XrFrameEndInfo frame_end_info = { + XR_TYPE_FRAME_END_INFO, // type + nullptr, // next + frame_state.predictedDisplayTime, // displayTime + XR_ENVIRONMENT_BLEND_MODE_OPAQUE, // environmentBlendMode + static_cast<uint32_t>(layers_list.size()), // layerCount + layers_list.ptr() // layers + }; + result = xrEndFrame(session, &frame_end_info); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to end frame! [", get_error_string(result), "]"); + return; + } +} + +OpenXRAPI::OpenXRAPI() { + // OpenXRAPI is only constructed if OpenXR is enabled. + // It will be constructed when the rendering device first accesses OpenXR (be it the Vulkan or OpenGL rendering system) + + if (Engine::get_singleton()->is_editor_hint()) { + // Enabled OpenXR in the editor? Adjust our settings for the editor + + } else { + // Load settings from project settings + int ff = GLOBAL_GET("xr/openxr/form_factor"); + switch (ff) { + case 0: { + form_factor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY; + } break; + case 1: { + form_factor = XR_FORM_FACTOR_HANDHELD_DISPLAY; + } break; + default: + break; + } + + int vc = GLOBAL_GET("xr/openxr/view_configuration"); + switch (vc) { + case 0: { + view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MONO; + } break; + case 1: { + view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; + } break; + /* we don't support quad and observer configurations (yet) + case 2: { + view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_QUAD_VARJO; + } break; + case 3: { + view_configuration = XR_VIEW_CONFIGURATION_TYPE_SECONDARY_MONO_FIRST_PERSON_OBSERVER_MSFT; + } break; + */ + default: + break; + } + + int rs = GLOBAL_GET("xr/openxr/reference_space"); + switch (rs) { + case 0: { + reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL; + } break; + case 1: { + reference_space = XR_REFERENCE_SPACE_TYPE_STAGE; + } break; + default: + break; + } + } + + // reset a few things that can't be done in our class definition + frame_state.predictedDisplayTime = 0; + frame_state.predictedDisplayPeriod = 0; + +#ifdef ANDROID_ENABLED + // our android wrapper will initialise our android loader at this point + register_extension_wrapper(memnew(OpenXRAndroidExtension(this))); +#endif +} + +OpenXRAPI::~OpenXRAPI() { + // cleanup our composition layer providers + for (OpenXRCompositionLayerProvider *provider : composition_layer_providers) { + memdelete(provider); + } + composition_layer_providers.clear(); + + // cleanup our extension wrappers + for (OpenXRExtensionWrapper *extension_wrapper : registered_extension_wrappers) { + memdelete(extension_wrapper); + } + registered_extension_wrappers.clear(); + + if (supported_extensions != nullptr) { + memfree(supported_extensions); + supported_extensions = nullptr; + } + + if (layer_properties != nullptr) { + memfree(layer_properties); + layer_properties = nullptr; + } +} + +Transform3D OpenXRAPI::transform_from_pose(const XrPosef &p_pose) { + Quaternion q(p_pose.orientation.x, p_pose.orientation.y, p_pose.orientation.z, p_pose.orientation.w); + Basis basis(q); + Vector3 origin(p_pose.position.x, p_pose.position.y, p_pose.position.z); + + return Transform3D(basis, origin); +} + +template <typename T> +XRPose::TrackingConfidence _transform_from_location(const T &p_location, Transform3D &r_transform) { + Basis basis; + Vector3 origin; + XRPose::TrackingConfidence confidence = XRPose::XR_TRACKING_CONFIDENCE_NONE; + const auto &pose = p_location.pose; + + // Check orientation + if (p_location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) { + Quaternion q(pose.orientation.x, pose.orientation.y, pose.orientation.z, pose.orientation.w); + r_transform.basis = Basis(q); + + if (p_location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT) { + // Fully valid orientation, so either 3DOF or 6DOF tracking with high confidence so default to HIGH_TRACKING + confidence = XRPose::XR_TRACKING_CONFIDENCE_HIGH; + } else { + // Orientation is being tracked but we're using old/predicted data, so low tracking confidence + confidence = XRPose::XR_TRACKING_CONFIDENCE_LOW; + } + } else { + r_transform.basis = Basis(); + } + + // Check location + if (p_location.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) { + r_transform.origin = Vector3(pose.position.x, pose.position.y, pose.position.z); + + if (!(p_location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT)) { + // Location is being tracked but we're using old/predicted data, so low tracking confidence + confidence = XRPose::XR_TRACKING_CONFIDENCE_LOW; + } else if (confidence == XRPose::XR_TRACKING_CONFIDENCE_NONE) { + // Position tracking without orientation tracking? + confidence = XRPose::XR_TRACKING_CONFIDENCE_HIGH; + } + } else { + // No tracking or 3DOF I guess.. + r_transform.origin = Vector3(); + } + + return confidence; +} + +XRPose::TrackingConfidence OpenXRAPI::transform_from_location(const XrSpaceLocation &p_location, Transform3D &r_transform) { + return _transform_from_location(p_location, r_transform); +} + +XRPose::TrackingConfidence OpenXRAPI::transform_from_location(const XrHandJointLocationEXT &p_location, Transform3D &r_transform) { + return _transform_from_location(p_location, r_transform); +} + +void OpenXRAPI::parse_velocities(const XrSpaceVelocity &p_velocity, Vector3 &r_linear_velocity, Vector3 r_angular_velocity) { + if (p_velocity.velocityFlags & XR_SPACE_VELOCITY_LINEAR_VALID_BIT) { + XrVector3f linear_velocity = p_velocity.linearVelocity; + r_linear_velocity = Vector3(linear_velocity.x, linear_velocity.y, linear_velocity.z); + } else { + r_linear_velocity = Vector3(); + } + if (p_velocity.velocityFlags & XR_SPACE_VELOCITY_ANGULAR_VALID_BIT) { + XrVector3f angular_velocity = p_velocity.angularVelocity; + r_angular_velocity = Vector3(angular_velocity.x, angular_velocity.y, angular_velocity.z); + } else { + r_angular_velocity = Vector3(); + } +} + +RID OpenXRAPI::path_create(const String p_name) { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, RID()); + + // Encoding our path as a RID is probably overkill but it does future proof this + // Note that we only do this for XrPaths that we access from outside of this class! + + Path new_path; + + print_line("Parsing path ", p_name); + + XrResult result = xrStringToPath(instance, p_name.utf8().get_data(), &new_path.path); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to get path for ", p_name, "! [", get_error_string(result), "]"); + return RID(); + } + + return xr_path_owner.make_rid(new_path); +} + +void OpenXRAPI::path_free(RID p_path) { + Path *path = xr_path_owner.get_or_null(p_path); + ERR_FAIL_NULL(path); + + // there is nothing to free here + + xr_path_owner.free(p_path); +} + +RID OpenXRAPI::action_set_create(const String p_name, const String p_localized_name, const int p_priority) { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, RID()); + ActionSet action_set; + + action_set.is_attached = false; + + // create our action set... + XrActionSetCreateInfo action_set_info = { + XR_TYPE_ACTION_SET_CREATE_INFO, // type + nullptr, // next + "", // actionSetName + "", // localizedActionSetName + uint32_t(p_priority) // priority + }; + + copy_string_to_char_buffer(p_name, action_set_info.actionSetName, XR_MAX_ACTION_SET_NAME_SIZE); + copy_string_to_char_buffer(p_localized_name, action_set_info.localizedActionSetName, XR_MAX_LOCALIZED_ACTION_SET_NAME_SIZE); + + print_line("Creating action set ", action_set_info.actionSetName, " - ", action_set_info.localizedActionSetName, " (", itos(action_set_info.priority), ")"); + + XrResult result = xrCreateActionSet(instance, &action_set_info, &action_set.handle); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to create action set ", p_name, "! [", get_error_string(result), "]"); + return RID(); + } + + return action_set_owner.make_rid(action_set); +} + +bool OpenXRAPI::action_set_attach(RID p_action_set) { + ActionSet *action_set = action_set_owner.get_or_null(p_action_set); + ERR_FAIL_NULL_V(action_set, false); + + if (action_set->is_attached) { + // already attached + return true; + } + + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + + // So according to the docs, once we attach our action set to our session it becomes read only.. + // https://www.khronos.org/registry/OpenXR/specs/1.0/man/html/xrAttachSessionActionSets.html + XrSessionActionSetsAttachInfo attach_info = { + XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO, // type + nullptr, // next + 1, // countActionSets, + &action_set->handle // actionSets + }; + + XrResult result = xrAttachSessionActionSets(session, &attach_info); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to attach action set! [", get_error_string(result), "]"); + return false; + } + + action_set->is_attached = true; + + return true; +} + +void OpenXRAPI::action_set_free(RID p_action_set) { + ActionSet *action_set = action_set_owner.get_or_null(p_action_set); + ERR_FAIL_NULL(action_set); + + if (action_set->handle != XR_NULL_HANDLE) { + xrDestroyActionSet(action_set->handle); + } + + action_set_owner.free(p_action_set); +} + +RID OpenXRAPI::action_create(RID p_action_set, const String p_name, const String p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<RID> &p_toplevel_paths) { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, RID()); + + Action action; + + ActionSet *action_set = action_set_owner.get_or_null(p_action_set); + ERR_FAIL_NULL_V(action_set, RID()); + ERR_FAIL_COND_V(action_set->handle == XR_NULL_HANDLE, RID()); + + switch (p_action_type) { + case OpenXRAction::OPENXR_ACTION_BOOL: + action.action_type = XR_ACTION_TYPE_BOOLEAN_INPUT; + break; + case OpenXRAction::OPENXR_ACTION_FLOAT: + action.action_type = XR_ACTION_TYPE_FLOAT_INPUT; + break; + case OpenXRAction::OPENXR_ACTION_VECTOR2: + action.action_type = XR_ACTION_TYPE_VECTOR2F_INPUT; + break; + case OpenXRAction::OPENXR_ACTION_POSE: + action.action_type = XR_ACTION_TYPE_POSE_INPUT; + break; + case OpenXRAction::OPENXR_ACTION_HAPTIC: + action.action_type = XR_ACTION_TYPE_VIBRATION_OUTPUT; + break; + default: + ERR_FAIL_V(RID()); + break; + } + + Vector<XrPath> toplevel_paths; + for (int i = 0; i < p_toplevel_paths.size(); i++) { + Path *xr_path = xr_path_owner.get_or_null(p_toplevel_paths[i]); + if (xr_path != nullptr && xr_path->path != XR_NULL_PATH) { + PathWithSpace path_with_space = { + xr_path->path, // toplevel_path + XR_NULL_HANDLE, // space + false // was_location_valid + }; + action.toplevel_paths.push_back(path_with_space); + + toplevel_paths.push_back(xr_path->path); + } + } + + XrActionCreateInfo action_info = { + XR_TYPE_ACTION_CREATE_INFO, // type + nullptr, // next + "", // actionName + action.action_type, // actionType + uint32_t(toplevel_paths.size()), // countSubactionPaths + toplevel_paths.ptr(), // subactionPaths + "" // localizedActionName + }; + + copy_string_to_char_buffer(p_name, action_info.actionName, XR_MAX_ACTION_NAME_SIZE); + copy_string_to_char_buffer(p_localized_name, action_info.localizedActionName, XR_MAX_LOCALIZED_ACTION_NAME_SIZE); + + print_line("Creating action ", action_info.actionName, action_info.localizedActionName, action_info.countSubactionPaths); + + XrResult result = xrCreateAction(action_set->handle, &action_info, &action.handle); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to create action ", p_name, "! [", get_error_string(result), "]"); + return RID(); + } + + return action_owner.make_rid(action); +} + +void OpenXRAPI::action_free(RID p_action) { + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_NULL(action); + + if (action->handle != XR_NULL_HANDLE) { + xrDestroyAction(action->handle); + } + + action_owner.free(p_action); +} + +bool OpenXRAPI::suggest_bindings(const String p_interaction_profile, const Vector<Binding> p_bindings) { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); + + XrPath interaction_profile; + Vector<XrActionSuggestedBinding> bindings; + + XrResult result = xrStringToPath(instance, p_interaction_profile.utf8().get_data(), &interaction_profile); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to get path for ", p_interaction_profile, "! [", get_error_string(result), "]"); + return false; + } + + for (int i = 0; i < p_bindings.size(); i++) { + XrActionSuggestedBinding binding; + + Action *action = action_owner.get_or_null(p_bindings[i].action); + if (action == nullptr || action->handle == XR_NULL_HANDLE) { + // just skip it + continue; + } + + binding.action = action->handle; + + result = xrStringToPath(instance, p_bindings[i].path.utf8().get_data(), &binding.binding); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to get path for ", p_bindings[i].path, "! [", get_error_string(result), "]"); + continue; + } + + bindings.push_back(binding); + } + + const XrInteractionProfileSuggestedBinding suggested_bindings = { + XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING, // type + nullptr, // next + interaction_profile, // interactionProfile + uint32_t(bindings.size()), // countSuggestedBindings + bindings.ptr() // suggestedBindings + }; + + result = xrSuggestInteractionProfileBindings(instance, &suggested_bindings); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to suggest bindings for ", p_interaction_profile, "! [", get_error_string(result), "]"); + // reporting is enough... + } + + return true; +} + +bool OpenXRAPI::sync_action_sets(const Vector<RID> p_active_sets) { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + + if (!running) { + return false; + } + + Vector<XrActiveActionSet> active_sets; + for (int i = 0; i < p_active_sets.size(); i++) { + ActionSet *action_set = action_set_owner.get_or_null(p_active_sets[i]); + if (action_set && action_set->handle != XR_NULL_HANDLE) { + XrActiveActionSet aset; + aset.actionSet = action_set->handle; + aset.subactionPath = XR_NULL_PATH; + active_sets.push_back(aset); + } + } + + ERR_FAIL_COND_V(active_sets.size() == 0, false); + + XrActionsSyncInfo sync_info = { + XR_TYPE_ACTIONS_SYNC_INFO, // type + nullptr, // next + uint32_t(active_sets.size()), // countActiveActionSets + active_sets.ptr() // activeActionSets + }; + + XrResult result = xrSyncActions(session, &sync_info); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to sync active action sets! [", get_error_string(result), "]"); + return false; + } + + return true; +} + +bool OpenXRAPI::get_action_bool(RID p_action, RID p_path) { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_NULL_V(action, false); + Path *path = xr_path_owner.get_or_null(p_path); + ERR_FAIL_NULL_V(path, false); + + if (!running) { + return false; + } + + ERR_FAIL_COND_V(action->action_type != XR_ACTION_TYPE_BOOLEAN_INPUT, false); + + XrActionStateGetInfo get_info = { + XR_TYPE_ACTION_STATE_GET_INFO, // type + nullptr, // next + action->handle, // action + path->path // subactionPath + }; + + XrActionStateBoolean result_state; + result_state.type = XR_TYPE_ACTION_STATE_BOOLEAN, + result_state.next = nullptr; + XrResult result = xrGetActionStateBoolean(session, &get_info, &result_state); + if (XR_FAILED(result)) { + print_line("OpenXR: couldn't get action boolean! [", get_error_string(result), "]"); + return false; + } + + return result_state.isActive && result_state.currentState; +} + +float OpenXRAPI::get_action_float(RID p_action, RID p_path) { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, 0.0); + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_NULL_V(action, 0.0); + Path *path = xr_path_owner.get_or_null(p_path); + ERR_FAIL_NULL_V(path, 0.0); + + if (!running) { + return 0.0; + } + + ERR_FAIL_COND_V(action->action_type != XR_ACTION_TYPE_FLOAT_INPUT, 0.0); + + XrActionStateGetInfo get_info = { + XR_TYPE_ACTION_STATE_GET_INFO, // type + nullptr, // next + action->handle, // action + path->path // subactionPath + }; + + XrActionStateFloat result_state; + result_state.type = XR_TYPE_ACTION_STATE_FLOAT, + result_state.next = nullptr; + XrResult result = xrGetActionStateFloat(session, &get_info, &result_state); + if (XR_FAILED(result)) { + print_line("OpenXR: couldn't get action float! [", get_error_string(result), "]"); + return 0.0; + } + + return result_state.isActive ? result_state.currentState : 0.0; +} + +Vector2 OpenXRAPI::get_action_vector2(RID p_action, RID p_path) { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, Vector2()); + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_NULL_V(action, Vector2()); + Path *path = xr_path_owner.get_or_null(p_path); + ERR_FAIL_NULL_V(path, Vector2()); + + if (!running) { + return Vector2(); + } + + ERR_FAIL_COND_V(action->action_type != XR_ACTION_TYPE_VECTOR2F_INPUT, Vector2()); + + XrActionStateGetInfo get_info = { + XR_TYPE_ACTION_STATE_GET_INFO, // type + nullptr, // next + action->handle, // action + path->path // subactionPath + }; + + XrActionStateVector2f result_state; + result_state.type = XR_TYPE_ACTION_STATE_VECTOR2F, + result_state.next = nullptr; + XrResult result = xrGetActionStateVector2f(session, &get_info, &result_state); + if (XR_FAILED(result)) { + print_line("OpenXR: couldn't get action vector2! [", get_error_string(result), "]"); + return Vector2(); + } + + return result_state.isActive ? Vector2(result_state.currentState.x, result_state.currentState.y) : Vector2(); +} + +XRPose::TrackingConfidence OpenXRAPI::get_action_pose(RID p_action, RID p_path, Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity) { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, XRPose::XR_TRACKING_CONFIDENCE_NONE); + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_NULL_V(action, XRPose::XR_TRACKING_CONFIDENCE_NONE); + Path *path = xr_path_owner.get_or_null(p_path); + ERR_FAIL_NULL_V(path, XRPose::XR_TRACKING_CONFIDENCE_NONE); + + if (!running) { + return XRPose::XR_TRACKING_CONFIDENCE_NONE; + } + + ERR_FAIL_COND_V(action->action_type != XR_ACTION_TYPE_POSE_INPUT, XRPose::XR_TRACKING_CONFIDENCE_NONE); + + uint64_t index = 0xFFFFFFFF; + uint64_t size = uint64_t(action->toplevel_paths.size()); + for (uint64_t i = 0; i < size && index == 0xFFFFFFFF; i++) { + if (action->toplevel_paths[i].toplevel_path == path->path) { + index = i; + } + } + + if (index == 0xFFFFFFFF) { + // couldn't find it? + return XRPose::XR_TRACKING_CONFIDENCE_NONE; + } + + if (action->toplevel_paths[index].space == XR_NULL_HANDLE) { + // if this is a pose we need to define spaces + + XrActionSpaceCreateInfo action_space_info = { + XR_TYPE_ACTION_SPACE_CREATE_INFO, // type + nullptr, // next + action->handle, // action + action->toplevel_paths[index].toplevel_path, // subactionPath + { + { 0.0, 0.0, 0.0, 1.0 }, // orientation + { 0.0, 0.0, 0.0 } // position + } // poseInActionSpace + }; + + XrSpace space; + XrResult result = xrCreateActionSpace(session, &action_space_info, &space); + if (XR_FAILED(result)) { + print_line("OpenXR: couldn't create action space! [", get_error_string(result), "]"); + return XRPose::XR_TRACKING_CONFIDENCE_NONE; + } + + action->toplevel_paths.ptrw()[index].space = space; + } + + XrTime display_time = get_next_frame_time(); + + XrSpaceVelocity velocity = { + XR_TYPE_SPACE_VELOCITY, // type + nullptr, // next + 0, // velocityFlags + { 0.0, 0.0, 0.0 }, // linearVelocity + { 0.0, 0.0, 0.0 } // angularVelocity + }; + + XrSpaceLocation location = { + XR_TYPE_SPACE_LOCATION, // type + &velocity, // next + 0, // locationFlags + { + { 0.0, 0.0, 0.0, 0.0 }, // orientation + { 0.0, 0.0, 0.0 } // position + } // pose + }; + + XrResult result = xrLocateSpace(action->toplevel_paths[index].space, play_space, display_time, &location); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to locate space! [", get_error_string(result), "]"); + return XRPose::XR_TRACKING_CONFIDENCE_NONE; + } + + XRPose::TrackingConfidence confidence = transform_from_location(location, r_transform); + parse_velocities(velocity, r_linear_velocity, r_angular_velocity); + + return confidence; +} + +bool OpenXRAPI::trigger_haptic_pulse(RID p_action, RID p_path, float p_frequency, float p_amplitude, XrDuration p_duration_ns) { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_NULL_V(action, false); + Path *path = xr_path_owner.get_or_null(p_path); + ERR_FAIL_NULL_V(path, false); + + if (!running) { + return false; + } + + ERR_FAIL_COND_V(action->action_type != XR_ACTION_TYPE_VIBRATION_OUTPUT, false); + + XrHapticActionInfo action_info = { + XR_TYPE_HAPTIC_ACTION_INFO, // type + nullptr, // next + action->handle, // action + path->path // subactionPath + }; + + XrHapticVibration vibration = { + XR_TYPE_HAPTIC_VIBRATION, // type + nullptr, // next + p_duration_ns, // duration + p_frequency, // frequency + p_amplitude, // amplitude + }; + + XrResult result = xrApplyHapticFeedback(session, &action_info, (const XrHapticBaseHeader *)&vibration); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to apply haptic feedback! [", get_error_string(result), "]"); + return false; + } + + return true; +} diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h new file mode 100644 index 0000000000..33b503543a --- /dev/null +++ b/modules/openxr/openxr_api.h @@ -0,0 +1,261 @@ +/*************************************************************************/ +/* openxr_api.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef OPENXR_DRIVER_H +#define OPENXR_DRIVER_H + +#include "core/error/error_macros.h" +#include "core/math/camera_matrix.h" +#include "core/math/transform_3d.h" +#include "core/math/vector2.h" +#include "core/os/memory.h" +#include "core/string/ustring.h" +#include "core/templates/map.h" +#include "core/templates/rid_owner.h" +#include "core/templates/vector.h" +#include "servers/xr/xr_pose.h" + +#include "thirdparty/openxr/src/common/xr_linear.h" +#include <openxr/openxr.h> + +#include "action_map/openxr_action.h" + +#include "extensions/openxr_composition_layer_provider.h" +#include "extensions/openxr_extension_wrapper.h" + +// Note, OpenXR code that we wrote for our plugin makes use of C++20 notation for initialising structs which ensures zeroing out unspecified members. +// Godot is currently restricted to C++17 which doesn't allow this notation. Make sure critical fields are set. + +// forward declarations, we don't want to include these fully +class OpenXRVulkanExtension; + +class OpenXRAPI { +private: + // our singleton + static OpenXRAPI *singleton; + + // layers + uint32_t num_layer_properties = 0; + XrApiLayerProperties *layer_properties = nullptr; + + // extensions + uint32_t num_supported_extensions = 0; + XrExtensionProperties *supported_extensions = nullptr; + Vector<OpenXRExtensionWrapper *> registered_extension_wrappers; + Vector<const char *> enabled_extensions; + + // composition layer providers + Vector<OpenXRCompositionLayerProvider *> composition_layer_providers; + + // view configuration + uint32_t num_view_configuration_types = 0; + XrViewConfigurationType *supported_view_configuration_types = nullptr; + + // reference spaces + uint32_t num_reference_spaces = 0; + XrReferenceSpaceType *supported_reference_spaces = nullptr; + + // swapchains (note these are platform dependent) + uint32_t num_swapchain_formats = 0; + int64_t *supported_swapchain_formats = nullptr; + + // configuration + XrFormFactor form_factor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY; + XrViewConfigurationType view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; + XrReferenceSpaceType reference_space = XR_REFERENCE_SPACE_TYPE_STAGE; + XrEnvironmentBlendMode environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; + + // state + XrInstance instance = XR_NULL_HANDLE; + XrSystemId system_id; + String system_name; + uint32_t vendor_id; + XrSystemTrackingProperties tracking_properties; + XrSession session = XR_NULL_HANDLE; + XrSessionState session_state = XR_SESSION_STATE_UNKNOWN; + bool running = false; + XrFrameState frame_state = { XR_TYPE_FRAME_STATE, NULL, 0, 0, false }; + + OpenXRGraphicsExtensionWrapper *graphics_extension = nullptr; + XrSystemGraphicsProperties graphics_properties; + void *swapchain_graphics_data = nullptr; + uint32_t image_index = 0; + bool image_acquired = false; + + uint32_t view_count = 0; + XrViewConfigurationView *view_configuration_views = nullptr; + XrView *views = nullptr; + XrCompositionLayerProjectionView *projection_views = nullptr; + XrSwapchain swapchain = XR_NULL_HANDLE; + + XrSpace play_space = XR_NULL_HANDLE; + XrSpace view_space = XR_NULL_HANDLE; + bool view_pose_valid = false; + XRPose::TrackingConfidence head_pose_confidence = XRPose::XR_TRACKING_CONFIDENCE_NONE; + + bool load_layer_properties(); + bool load_supported_extensions(); + bool is_extension_supported(const char *p_extension) const; + + // instance + bool create_instance(); + bool get_system_info(); + bool load_supported_view_configuration_types(); + bool is_view_configuration_supported(XrViewConfigurationType p_configuration_type) const; + bool load_supported_view_configuration_views(XrViewConfigurationType p_configuration_type); + void destroy_instance(); + + // session + bool create_session(); + bool load_supported_reference_spaces(); + bool is_reference_space_supported(XrReferenceSpaceType p_reference_space); + bool setup_spaces(); + bool load_supported_swapchain_formats(); + bool is_swapchain_format_supported(int64_t p_swapchain_format); + bool create_main_swapchain(); + void destroy_session(); + + // swapchains + bool create_swapchain(int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, XrSwapchain &r_swapchain, void **r_swapchain_graphics_data); + bool acquire_image(XrSwapchain p_swapchain, uint32_t &r_image_index); + bool release_image(XrSwapchain p_swapchain); + + // action map + struct Path { + XrPath path; + }; + RID_Owner<Path, true> xr_path_owner; + + struct ActionSet { + bool is_attached; + XrActionSet handle; + }; + RID_Owner<ActionSet, true> action_set_owner; + + struct PathWithSpace { + XrPath toplevel_path; + XrSpace space; + bool was_location_valid; + }; + + struct Action { + XrActionType action_type; + Vector<PathWithSpace> toplevel_paths; + XrAction handle; + }; + RID_Owner<Action, true> action_owner; + + // state changes + bool poll_events(); + bool on_state_idle(); + bool on_state_ready(); + bool on_state_synchronized(); + bool on_state_visible(); + bool on_state_focused(); + bool on_state_stopping(); + bool on_state_loss_pending(); + bool on_state_exiting(); + + // convencience + void copy_string_to_char_buffer(const String p_string, char *p_buffer, int p_buffer_len); + +protected: + friend class OpenXRVulkanExtension; + + XrInstance get_instance() const { return instance; }; + XrSystemId get_system_id() const { return system_id; }; + XrSession get_session() const { return session; }; + + // helper method to convert an XrPosef to a Transform3D + Transform3D transform_from_pose(const XrPosef &p_pose); + + // helper method to get a valid Transform3D from an openxr space location + XRPose::TrackingConfidence transform_from_location(const XrSpaceLocation &p_location, Transform3D &r_transform); + XRPose::TrackingConfidence transform_from_location(const XrHandJointLocationEXT &p_location, Transform3D &r_transform); + void parse_velocities(const XrSpaceVelocity &p_velocity, Vector3 &r_linear_velocity, Vector3 r_angular_velocity); + +public: + static void setup_global_defs(); + static bool openxr_is_enabled(); + static OpenXRAPI *get_singleton(); + + String get_error_string(XrResult result); + String get_swapchain_format_name(int64_t p_swapchain_format) const; + + void register_extension_wrapper(OpenXRExtensionWrapper *p_extension_wrapper); + + bool is_initialized(); + bool is_running(); + bool initialise(const String &p_rendering_driver); + bool initialise_session(); + void finish(); + + XrTime get_next_frame_time() { return frame_state.predictedDisplayTime + frame_state.predictedDisplayPeriod; }; + bool can_render() { return instance != XR_NULL_HANDLE && session != XR_NULL_HANDLE && running && view_pose_valid && frame_state.shouldRender; }; + + Size2 get_recommended_target_size(); + XRPose::TrackingConfidence get_head_center(Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity); + bool get_view_transform(uint32_t p_view, Transform3D &r_transform); + bool get_view_projection(uint32_t p_view, double p_z_near, double p_z_far, CameraMatrix &p_camera_matrix); + bool process(); + + void pre_render(); + bool pre_draw_viewport(RID p_render_target); + void post_draw_viewport(RID p_render_target); + void end_frame(); + + // action map + String get_default_action_map_resource_name(); + RID path_create(const String p_name); + void path_free(RID p_path); + RID action_set_create(const String p_name, const String p_localized_name, const int p_priority); + bool action_set_attach(RID p_action_set); + void action_set_free(RID p_action_set); + RID action_create(RID p_action_set, const String p_name, const String p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<RID> &p_toplevel_paths); + void action_free(RID p_action); + + struct Binding { + RID action; + String path; + }; + bool suggest_bindings(const String p_interaction_profile, const Vector<Binding> p_bindings); + + bool sync_action_sets(const Vector<RID> p_active_sets); + bool get_action_bool(RID p_action, RID p_path); + float get_action_float(RID p_action, RID p_path); + Vector2 get_action_vector2(RID p_action, RID p_path); + XRPose::TrackingConfidence get_action_pose(RID p_action, RID p_path, Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity); + bool trigger_haptic_pulse(RID p_action, RID p_path, float p_frequency, float p_amplitude, XrDuration p_duration_ns); + + OpenXRAPI(); + ~OpenXRAPI(); +}; + +#endif // !OPENXR_DRIVER_H diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp new file mode 100644 index 0000000000..394f634687 --- /dev/null +++ b/modules/openxr/openxr_interface.cpp @@ -0,0 +1,663 @@ +/*************************************************************************/ +/* openxr_interface.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "openxr_interface.h" + +#include "core/io/resource_loader.h" +#include "core/io/resource_saver.h" +#include "servers/rendering/rendering_server_globals.h" + +void OpenXRInterface::_bind_methods() { + // todo +} + +StringName OpenXRInterface::get_name() const { + return StringName("OpenXR"); +}; + +uint32_t OpenXRInterface::get_capabilities() const { + return XRInterface::XR_VR + XRInterface::XR_STEREO; +}; + +XRInterface::TrackingStatus OpenXRInterface::get_tracking_status() const { + return tracking_state; +} + +void OpenXRInterface::_load_action_map() { + ERR_FAIL_NULL(openxr_api); + + // This may seem a bit duplicitous to a little bit of background info here. + // OpenXRActionMap (with all its sub resource classes) is a class that allows us to configure and store an action map in. + // This gives the user the ability to edit the action map in a UI and customise the actions. + // OpenXR however requires us to submit an action map and it takes over from that point and we can no longer change it. + // This system does that push and we store the info needed to then work with this action map going forward. + + // Within our openxr device we maintain a number of classes that wrap the relevant OpenXR objects for this. + // Within OpenXRInterface we have a few internal classes that keep track of what we've created. + // This allow us to process the relevant actions each frame. + + // just in case clean up + free_action_sets(); + free_trackers(); + + Ref<OpenXRActionMap> action_map; + if (Engine::get_singleton()->is_editor_hint()) { +#ifdef TOOLS_ENABLED + action_map.instantiate(); + action_map->create_editor_action_sets(); +#endif + } else { + String default_tres_name = openxr_api->get_default_action_map_resource_name(); + + // Check if we can load our default + if (ResourceLoader::exists(default_tres_name)) { + action_map = ResourceLoader::load(default_tres_name); + } + + // Check if we need to create default action set + if (action_map.is_null()) { + action_map.instantiate(); + action_map->create_default_action_sets(); +#ifdef TOOLS_ENABLED + // Save our action sets so our user can + action_map->set_path(default_tres_name, true); + ResourceSaver::save(default_tres_name, action_map); +#endif + } + } + + // process our action map + if (action_map.is_valid()) { + Map<Ref<OpenXRAction>, RID> action_rids; + + Array action_sets = action_map->get_action_sets(); + for (int i = 0; i < action_sets.size(); i++) { + // Create our action set + Ref<OpenXRActionSet> xr_action_set = action_sets[i]; + ActionSet *action_set = create_action_set(xr_action_set->get_name(), xr_action_set->get_localized_name(), xr_action_set->get_priority()); + if (!action_set) { + continue; + } + + // Now create our actions for these + Array actions = xr_action_set->get_actions(); + for (int j = 0; j < actions.size(); j++) { + Ref<OpenXRAction> xr_action = actions[j]; + + PackedStringArray toplevel_paths = xr_action->get_toplevel_paths(); + Vector<RID> toplevel_rids; + Vector<Tracker *> trackers; + + for (int k = 0; k < toplevel_paths.size(); k++) { + Tracker *tracker = get_tracker(toplevel_paths[k]); + if (tracker) { + toplevel_rids.push_back(tracker->path_rid); + trackers.push_back(tracker); + } + } + + Action *action = create_action(action_set, xr_action->get_name(), xr_action->get_localized_name(), xr_action->get_action_type(), toplevel_rids); + if (action) { + // we link our actions back to our trackers so we know which actions to check when we're processing our trackers + for (int t = 0; t < trackers.size(); t++) { + link_action_to_tracker(trackers[t], action); + } + + // add this to our map for creating our interaction profiles + action_rids[xr_action] = action->action_rid; + } + } + } + + // now do our suggestions + Array interaction_profiles = action_map->get_interaction_profiles(); + for (int i = 0; i < interaction_profiles.size(); i++) { + Vector<OpenXRAPI::Binding> bindings; + Ref<OpenXRInteractionProfile> xr_interaction_profile = interaction_profiles[i]; + + Array xr_bindings = xr_interaction_profile->get_bindings(); + for (int j = 0; j < xr_bindings.size(); j++) { + Ref<OpenXRIPBinding> xr_binding = xr_bindings[j]; + Ref<OpenXRAction> xr_action = xr_binding->get_action(); + OpenXRAPI::Binding binding; + + if (action_rids.has(xr_action)) { + binding.action = action_rids[xr_action]; + } else { + print_line("Action ", xr_action->get_name(), " isn't part of an action set!"); + continue; + } + + PackedStringArray xr_paths = xr_binding->get_paths(); + for (int k = 0; k < xr_paths.size(); k++) { + binding.path = xr_paths[k]; + bindings.push_back(binding); + } + } + + openxr_api->suggest_bindings(xr_interaction_profile->get_interaction_profile_path(), bindings); + } + } +} + +OpenXRInterface::ActionSet *OpenXRInterface::create_action_set(const String &p_action_set_name, const String &p_localized_name, const int p_priority) { + ERR_FAIL_NULL_V(openxr_api, nullptr); + + // find if it already exists + for (int i = 0; i < action_sets.size(); i++) { + if (action_sets[i]->action_set_name == p_action_set_name) { + // already exists in this set + return nullptr; + } + } + + ActionSet *action_set = memnew(ActionSet); + action_set->action_set_name = p_action_set_name; + action_set->is_active = true; + action_set->action_set_rid = openxr_api->action_set_create(p_action_set_name, p_localized_name, p_priority); + action_sets.push_back(action_set); + + return action_set; +} + +void OpenXRInterface::free_action_sets() { + ERR_FAIL_NULL(openxr_api); + + for (int i = 0; i < action_sets.size(); i++) { + ActionSet *action_set = action_sets[i]; + + openxr_api->path_free(action_set->action_set_rid); + free_actions(action_set); + + memfree(action_set); + } + action_sets.clear(); +} + +OpenXRInterface::Action *OpenXRInterface::create_action(ActionSet *p_action_set, const String &p_action_name, const String &p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<RID> p_toplevel_paths) { + ERR_FAIL_NULL_V(openxr_api, nullptr); + + for (int i = 0; i < p_action_set->actions.size(); i++) { + if (p_action_set->actions[i]->action_name == p_action_name) { + // already exists in this set + return nullptr; + } + } + + Action *action = memnew(Action); + action->action_name = p_action_name; + action->action_type = p_action_type; + action->action_rid = openxr_api->action_create(p_action_set->action_set_rid, p_action_name, p_localized_name, p_action_type, p_toplevel_paths); + p_action_set->actions.push_back(action); + + return action; +} + +OpenXRInterface::Action *OpenXRInterface::find_action(const String &p_action_name) { + // We just find the first action by this name + + for (int i = 0; i < action_sets.size(); i++) { + for (int j = 0; j < action_sets[i]->actions.size(); j++) { + if (action_sets[i]->actions[j]->action_name == p_action_name) { + return action_sets[i]->actions[j]; + } + } + } + + // not found + return nullptr; +} + +void OpenXRInterface::free_actions(ActionSet *p_action_set) { + ERR_FAIL_NULL(openxr_api); + + for (int i = 0; i < p_action_set->actions.size(); i++) { + Action *action = p_action_set->actions[i]; + + openxr_api->action_free(action->action_rid); + + memdelete(action); + } + p_action_set->actions.clear(); +} + +OpenXRInterface::Tracker *OpenXRInterface::get_tracker(const String &p_path_name) { + XRServer *xr_server = XRServer::get_singleton(); + ERR_FAIL_NULL_V(xr_server, nullptr); + ERR_FAIL_NULL_V(openxr_api, nullptr); + + Tracker *tracker = nullptr; + for (int i = 0; i < trackers.size(); i++) { + tracker = trackers[i]; + if (tracker->path_name == p_path_name) { + return tracker; + } + } + + // create our positional tracker + Ref<XRPositionalTracker> positional_tracker; + positional_tracker.instantiate(); + + // We have standardised some names to make things nicer to the user so lets recognise the toplevel paths related to these. + if (p_path_name == "/user/hand/left") { + positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); + positional_tracker->set_tracker_name("left_hand"); + positional_tracker->set_tracker_desc("Left hand controller"); + positional_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_LEFT); + } else if (p_path_name == "/user/hand/right") { + positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); + positional_tracker->set_tracker_name("right_hand"); + positional_tracker->set_tracker_desc("Right hand controller"); + positional_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_RIGHT); + } else { + positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); + positional_tracker->set_tracker_name(p_path_name); + positional_tracker->set_tracker_desc(p_path_name); + } + xr_server->add_tracker(positional_tracker); + + // create a new entry + tracker = memnew(Tracker); + tracker->path_name = p_path_name; + tracker->path_rid = openxr_api->path_create(p_path_name); + tracker->positional_tracker = positional_tracker; + trackers.push_back(tracker); + + return tracker; +} + +OpenXRInterface::Tracker *OpenXRInterface::find_tracker(const String &p_positional_tracker_name) { + for (int i = 0; i < trackers.size(); i++) { + Tracker *tracker = trackers[i]; + if (tracker->positional_tracker.is_valid() && tracker->positional_tracker->get_tracker_name() == p_positional_tracker_name) { + return tracker; + } + } + + return nullptr; +} + +void OpenXRInterface::link_action_to_tracker(Tracker *p_tracker, Action *p_action) { + if (p_tracker->actions.find(p_action) == -1) { + p_tracker->actions.push_back(p_action); + } +} + +void OpenXRInterface::handle_tracker(Tracker *p_tracker) { + ERR_FAIL_NULL(openxr_api); + ERR_FAIL_COND(p_tracker->positional_tracker.is_null()); + + // handle all the actions + for (int i = 0; i < p_tracker->actions.size(); i++) { + Action *action = p_tracker->actions[i]; + switch (action->action_type) { + case OpenXRAction::OPENXR_ACTION_BOOL: { + bool pressed = openxr_api->get_action_bool(action->action_rid, p_tracker->path_rid); + p_tracker->positional_tracker->set_input(action->action_name, Variant(pressed)); + } break; + case OpenXRAction::OPENXR_ACTION_FLOAT: { + real_t value = openxr_api->get_action_float(action->action_rid, p_tracker->path_rid); + p_tracker->positional_tracker->set_input(action->action_name, Variant(value)); + } break; + case OpenXRAction::OPENXR_ACTION_VECTOR2: { + Vector2 value = openxr_api->get_action_vector2(action->action_rid, p_tracker->path_rid); + p_tracker->positional_tracker->set_input(action->action_name, Variant(value)); + } break; + case OpenXRAction::OPENXR_ACTION_POSE: { + Transform3D transform; + Vector3 linear, angular; + XRPose::TrackingConfidence confidence = openxr_api->get_action_pose(action->action_rid, p_tracker->path_rid, transform, linear, angular); + if (confidence != XRPose::XR_TRACKING_CONFIDENCE_NONE) { + String name; + // We can't have dual action names in OpenXR hence we added _pose, but default, aim and grip and default pose action names in Godot so rename them on the tracker. + // NOTE need to decide on whether we should keep the naming convention or rename it on Godots side + if (action->action_name == "default_pose") { + name = "default"; + } else if (action->action_name == "aim_pose") { + name = "aim"; + } else if (action->action_name == "grip_pose") { + name = "grip"; + } else { + name = action->action_name; + } + p_tracker->positional_tracker->set_pose(name, transform, linear, angular, confidence); + } + } break; + default: { + // not yet supported + } break; + } + } +} + +void OpenXRInterface::trigger_haptic_pulse(const String &p_action_name, const StringName &p_tracker_name, double p_frequency, double p_amplitude, double p_duration_sec, double p_delay_sec) { + ERR_FAIL_NULL(openxr_api); + Action *action = find_action(p_action_name); + ERR_FAIL_NULL(action); + Tracker *tracker = find_tracker(p_tracker_name); + ERR_FAIL_NULL(tracker); + + // TODO OpenXR does not support delay, so we may need to add support for that somehow... + + XrDuration duration = XrDuration(p_duration_sec * 1000000000.0); // seconds -> nanoseconds + + openxr_api->trigger_haptic_pulse(action->action_rid, tracker->path_rid, p_frequency, p_amplitude, duration); +} + +void OpenXRInterface::free_trackers() { + XRServer *xr_server = XRServer::get_singleton(); + ERR_FAIL_NULL(xr_server); + ERR_FAIL_NULL(openxr_api); + + for (int i = 0; i < trackers.size(); i++) { + Tracker *tracker = trackers[i]; + + openxr_api->path_free(tracker->path_rid); + xr_server->remove_tracker(tracker->positional_tracker); + tracker->positional_tracker.unref(); + + memdelete(tracker); + } + trackers.clear(); +} + +bool OpenXRInterface::initialise_on_startup() const { + if (openxr_api == nullptr) { + return false; + } else if (!openxr_api->is_initialized()) { + return false; + } else { + return true; + } +} + +bool OpenXRInterface::is_initialized() const { + return initialized; +}; + +bool OpenXRInterface::initialize() { + XRServer *xr_server = XRServer::get_singleton(); + ERR_FAIL_NULL_V(xr_server, false); + + if (openxr_api == nullptr) { + return false; + } else if (!openxr_api->is_initialized()) { + return false; + } else if (initialized) { + return true; + } + + // load up our action sets before setting up our session, note that our profiles are suggestions, OpenXR takes ownership of (re)binding + _load_action_map(); + + if (!openxr_api->initialise_session()) { + return false; + } + + // we must create a tracker for our head + head.instantiate(); + head->set_tracker_type(XRServer::TRACKER_HEAD); + head->set_tracker_name("head"); + head->set_tracker_desc("Players head"); + xr_server->add_tracker(head); + + // attach action sets + for (int i = 0; i < action_sets.size(); i++) { + openxr_api->action_set_attach(action_sets[i]->action_set_rid); + } + + // make this our primary interface + xr_server->set_primary_interface(this); + + initialized = true; + + return initialized; +} + +void OpenXRInterface::uninitialize() { + // Our OpenXR driver will clean itself up properly when Godot exits, so we just do some basic stuff here + + // end the session if we need to? + + // cleanup stuff + free_action_sets(); + free_trackers(); + + XRServer *xr_server = XRServer::get_singleton(); + if (xr_server) { + if (head.is_valid()) { + xr_server->remove_tracker(head); + + head.unref(); + } + } + + initialized = false; +} + +bool OpenXRInterface::supports_play_area_mode(XRInterface::PlayAreaMode p_mode) { + return false; +} + +XRInterface::PlayAreaMode OpenXRInterface::get_play_area_mode() const { + return XRInterface::XR_PLAY_AREA_UNKNOWN; +} + +bool OpenXRInterface::set_play_area_mode(XRInterface::PlayAreaMode p_mode) { + return false; +} + +Size2 OpenXRInterface::get_render_target_size() { + if (openxr_api == nullptr) { + return Size2(); + } else { + return openxr_api->get_recommended_target_size(); + } +} + +uint32_t OpenXRInterface::get_view_count() { + // TODO set this based on our configuration + return 2; +} + +void OpenXRInterface::_set_default_pos(Transform3D &p_transform, double p_world_scale, uint64_t p_eye) { + p_transform = Transform3D(); + + // if we're not tracking, don't put our head on the floor... + p_transform.origin.y = 1.5 * p_world_scale; + + // overkill but.. + if (p_eye == 1) { + p_transform.origin.x = 0.03 * p_world_scale; + } else if (p_eye == 2) { + p_transform.origin.x = -0.03 * p_world_scale; + } +} + +Transform3D OpenXRInterface::get_camera_transform() { + XRServer *xr_server = XRServer::get_singleton(); + ERR_FAIL_NULL_V(xr_server, Transform3D()); + + Transform3D hmd_transform; + double world_scale = xr_server->get_world_scale(); + + // head_transform should be updated in process + + hmd_transform.basis = head_transform.basis; + hmd_transform.origin = head_transform.origin * world_scale; + + return hmd_transform; +} + +Transform3D OpenXRInterface::get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) { + XRServer *xr_server = XRServer::get_singleton(); + ERR_FAIL_NULL_V(xr_server, Transform3D()); + + Transform3D t; + if (openxr_api && openxr_api->get_view_transform(p_view, t)) { + // update our cached value if we have a valid transform + transform_for_view[p_view] = t; + } else { + // reuse cached value + t = transform_for_view[p_view]; + } + + // Apply our world scale + double world_scale = xr_server->get_world_scale(); + t.origin *= world_scale; + + return p_cam_transform * xr_server->get_reference_frame() * t; +} + +CameraMatrix OpenXRInterface::get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) { + CameraMatrix cm; + + if (openxr_api) { + if (openxr_api->get_view_projection(p_view, p_z_near, p_z_far, cm)) { + return cm; + } + } + + // Failed to get from our OpenXR device? Default to some sort of sensible camera matrix.. + cm.set_for_hmd(p_view + 1, 1.0, 6.0, 14.5, 4.0, 1.5, p_z_near, p_z_far); + + return cm; +} + +void OpenXRInterface::process() { + if (openxr_api) { + // do our normal process + if (openxr_api->process()) { + Transform3D t; + Vector3 linear_velocity; + Vector3 angular_velocity; + XRPose::TrackingConfidence confidence = openxr_api->get_head_center(t, linear_velocity, angular_velocity); + if (confidence != XRPose::XR_TRACKING_CONFIDENCE_NONE) { + // Only update our transform if we have one to update it with + // note that poses are stored without world scale and reference frame applied! + head_transform = t; + head_linear_velocity = linear_velocity; + head_angular_velocity = angular_velocity; + } + } + + // handle our action sets.... + Vector<RID> active_sets; + for (int i = 0; i < action_sets.size(); i++) { + if (action_sets[i]->is_active) { + active_sets.push_back(action_sets[i]->action_set_rid); + } + } + + if (openxr_api->sync_action_sets(active_sets)) { + for (int i = 0; i < trackers.size(); i++) { + handle_tracker(trackers[i]); + } + } + } + + if (head.is_valid()) { + // TODO figure out how to get our velocities + + head->set_pose("default", head_transform, head_linear_velocity, head_angular_velocity); + + // TODO set confidence on pose once we support tracking this.. + } +} + +void OpenXRInterface::pre_render() { + if (openxr_api) { + openxr_api->pre_render(); + } +} + +bool OpenXRInterface::pre_draw_viewport(RID p_render_target) { + if (openxr_api) { + return openxr_api->pre_draw_viewport(p_render_target); + } else { + // don't render + return false; + } +} + +Vector<BlitToScreen> OpenXRInterface::post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) { + Vector<BlitToScreen> blit_to_screen; + + // If separate HMD we should output one eye to screen + if (p_screen_rect != Rect2()) { + BlitToScreen blit; + + blit.render_target = p_render_target; + blit.multi_view.use_layer = true; + blit.multi_view.layer = 0; + blit.lens_distortion.apply = false; + + Size2 render_size = get_render_target_size(); + Rect2 dst_rect = p_screen_rect; + float new_height = dst_rect.size.x * (render_size.y / render_size.x); + if (new_height > dst_rect.size.y) { + dst_rect.position.y = (0.5 * dst_rect.size.y) - (0.5 * new_height); + dst_rect.size.y = new_height; + } else { + float new_width = dst_rect.size.y * (render_size.x / render_size.y); + + dst_rect.position.x = (0.5 * dst_rect.size.x) - (0.5 * new_width); + dst_rect.size.x = new_width; + } + + blit.dst_rect = dst_rect; + blit_to_screen.push_back(blit); + } + + if (openxr_api) { + openxr_api->post_draw_viewport(p_render_target); + } + + return blit_to_screen; +} + +void OpenXRInterface::end_frame() { + if (openxr_api) { + openxr_api->end_frame(); + } +} + +OpenXRInterface::OpenXRInterface() { + openxr_api = OpenXRAPI::get_singleton(); + + // while we don't have head tracking, don't put the headset on the floor... + _set_default_pos(head_transform, 1.0, 0); + _set_default_pos(transform_for_view[0], 1.0, 1); + _set_default_pos(transform_for_view[1], 1.0, 2); +} + +OpenXRInterface::~OpenXRInterface() { + openxr_api = nullptr; +} diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h new file mode 100644 index 0000000000..ede7d481d2 --- /dev/null +++ b/modules/openxr/openxr_interface.h @@ -0,0 +1,129 @@ +/*************************************************************************/ +/* openxr_interface.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef OPENXR_INTERFACE_H +#define OPENXR_INTERFACE_H + +#include "servers/xr/xr_interface.h" +#include "servers/xr/xr_positional_tracker.h" + +#include "action_map/openxr_action_map.h" +#include "openxr_api.h" + +class OpenXRInterface : public XRInterface { + GDCLASS(OpenXRInterface, XRInterface); + +private: + OpenXRAPI *openxr_api = nullptr; + bool initialized = false; + XRInterface::TrackingStatus tracking_state; + + // At a minimum we need a tracker for our head + Ref<XRPositionalTracker> head; + Transform3D head_transform; + Vector3 head_linear_velocity; + Vector3 head_angular_velocity; + Transform3D transform_for_view[2]; // We currently assume 2, but could be 4 for VARJO which we do not support yet + + void _load_action_map(); + + struct Action { + String action_name; + OpenXRAction::ActionType action_type; + RID action_rid; + }; + struct ActionSet { + String action_set_name; + bool is_active; + RID action_set_rid; + Vector<Action *> actions; + }; + struct Tracker { + String path_name; + RID path_rid; + Ref<XRPositionalTracker> positional_tracker; + Vector<Action *> actions; + }; + + Vector<ActionSet *> action_sets; + Vector<Tracker *> trackers; + + ActionSet *create_action_set(const String &p_action_set_name, const String &p_localized_name, const int p_priority); + void free_action_sets(); + + Action *create_action(ActionSet *p_action_set, const String &p_action_name, const String &p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<RID> p_toplevel_paths); + Action *find_action(const String &p_action_name); + void free_actions(ActionSet *p_action_set); + + Tracker *get_tracker(const String &p_path_name); + Tracker *find_tracker(const String &p_positional_tracker_name); + void link_action_to_tracker(Tracker *p_tracker, Action *p_action); + void handle_tracker(Tracker *p_tracker); + void free_trackers(); + + void _set_default_pos(Transform3D &p_transform, double p_world_scale, uint64_t p_eye); + +protected: + static void _bind_methods(); + +public: + virtual StringName get_name() const override; + virtual uint32_t get_capabilities() const override; + + virtual TrackingStatus get_tracking_status() const override; + + bool initialise_on_startup() const; + virtual bool is_initialized() const override; + virtual bool initialize() override; + virtual void uninitialize() override; + + virtual void trigger_haptic_pulse(const String &p_action_name, const StringName &p_tracker_name, double p_frequency, double p_amplitude, double p_duration_sec, double p_delay_sec = 0) override; + + virtual bool supports_play_area_mode(XRInterface::PlayAreaMode p_mode) override; + virtual XRInterface::PlayAreaMode get_play_area_mode() const override; + virtual bool set_play_area_mode(XRInterface::PlayAreaMode p_mode) override; + + virtual Size2 get_render_target_size() override; + virtual uint32_t get_view_count() override; + virtual Transform3D get_camera_transform() override; + virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) override; + virtual CameraMatrix get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) override; + + virtual void process() override; + virtual void pre_render() override; + bool pre_draw_viewport(RID p_render_target) override; + virtual Vector<BlitToScreen> post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) override; + virtual void end_frame() override; + + OpenXRInterface(); + ~OpenXRInterface(); +}; + +#endif // !OPENXR_INTERFACE_H diff --git a/modules/openxr/openxr_util.cpp b/modules/openxr/openxr_util.cpp new file mode 100644 index 0000000000..e515336daa --- /dev/null +++ b/modules/openxr/openxr_util.cpp @@ -0,0 +1,291 @@ +/*************************************************************************/ +/* openxr_util.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "openxr_util.h" + +#define ENUM_TO_STRING_CASE(e) \ + case e: { \ + return String(#e); \ + } break; + +// TODO see if we can generate this code further using the xml file with meta data supplied by OpenXR + +String OpenXRUtil::get_view_configuration_name(XrViewConfigurationType p_view_configuration) { + switch (p_view_configuration) { + ENUM_TO_STRING_CASE(XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MONO) + ENUM_TO_STRING_CASE(XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO) + ENUM_TO_STRING_CASE(XR_VIEW_CONFIGURATION_TYPE_PRIMARY_QUAD_VARJO) + ENUM_TO_STRING_CASE(XR_VIEW_CONFIGURATION_TYPE_SECONDARY_MONO_FIRST_PERSON_OBSERVER_MSFT) + ENUM_TO_STRING_CASE(XR_VIEW_CONFIGURATION_TYPE_MAX_ENUM) + default: { + return String("View Configuration ") + String::num_int64(int64_t(p_view_configuration)); + } break; + } +} + +String OpenXRUtil::get_reference_space_name(XrReferenceSpaceType p_reference_space) { + switch (p_reference_space) { + ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_VIEW) + ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_LOCAL) + ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_STAGE) + ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_UNBOUNDED_MSFT) + ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_COMBINED_EYE_VARJO) + ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_MAX_ENUM) + default: { + return String("Reference space ") + String::num_int64(int64_t(p_reference_space)); + } break; + } +} + +String OpenXRUtil::get_structure_type_name(XrStructureType p_structure_type) { + switch (p_structure_type) { + ENUM_TO_STRING_CASE(XR_TYPE_UNKNOWN) + ENUM_TO_STRING_CASE(XR_TYPE_API_LAYER_PROPERTIES) + ENUM_TO_STRING_CASE(XR_TYPE_EXTENSION_PROPERTIES) + ENUM_TO_STRING_CASE(XR_TYPE_INSTANCE_CREATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_GET_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_PROPERTIES) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW_LOCATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW) + ENUM_TO_STRING_CASE(XR_TYPE_SESSION_CREATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_CREATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_SESSION_BEGIN_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW_STATE) + ENUM_TO_STRING_CASE(XR_TYPE_FRAME_END_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_HAPTIC_VIBRATION) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_BUFFER) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_STATE_BOOLEAN) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_STATE_FLOAT) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_STATE_VECTOR2F) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_STATE_POSE) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_SET_CREATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_CREATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_INSTANCE_PROPERTIES) + ENUM_TO_STRING_CASE(XR_TYPE_FRAME_WAIT_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_PROJECTION) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_QUAD) + ENUM_TO_STRING_CASE(XR_TYPE_REFERENCE_SPACE_CREATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_SPACE_CREATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW_CONFIGURATION_VIEW) + ENUM_TO_STRING_CASE(XR_TYPE_SPACE_LOCATION) + ENUM_TO_STRING_CASE(XR_TYPE_SPACE_VELOCITY) + ENUM_TO_STRING_CASE(XR_TYPE_FRAME_STATE) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW_CONFIGURATION_PROPERTIES) + ENUM_TO_STRING_CASE(XR_TYPE_FRAME_BEGIN_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_EVENTS_LOST) + ENUM_TO_STRING_CASE(XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED) + ENUM_TO_STRING_CASE(XR_TYPE_INTERACTION_PROFILE_STATE) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_STATE_GET_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_HAPTIC_ACTION_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_ACTIONS_SYNC_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_BOUND_SOURCES_FOR_ACTION_ENUMERATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_CUBE_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_VULKAN_SWAPCHAIN_FORMAT_LIST_CREATE_INFO_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_PERF_SETTINGS_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_EQUIRECT_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_DEBUG_UTILS_MESSENGER_CALLBACK_DATA_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_DEBUG_UTILS_LABEL_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_OPENGL_XCB_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_OPENGL_WAYLAND_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_OPENGL_ES_ANDROID_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_D3D11_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_D3D11_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_REQUIREMENTS_D3D11_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_D3D12_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_D3D12_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_REQUIREMENTS_D3D12_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_EYE_GAZE_INTERACTION_PROPERTIES_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_EYE_GAZE_SAMPLE_TIME_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_VISIBILITY_MASK_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SESSION_CREATE_INFO_OVERLAY_EXTX) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_MAIN_SESSION_VISIBILITY_CHANGED_EXTX) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_COLOR_SCALE_BIAS_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SPATIAL_ANCHOR_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SPATIAL_ANCHOR_SPACE_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_IMAGE_LAYOUT_FB) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_ALPHA_BLEND_FB) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW_CONFIGURATION_DEPTH_RANGE_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_EGL_MNDX) + ENUM_TO_STRING_CASE(XR_TYPE_SPATIAL_GRAPH_NODE_SPACE_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_JOINT_LOCATIONS_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_JOINT_VELOCITIES_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_HAND_TRACKING_MESH_PROPERTIES_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_MESH_SPACE_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_MESH_UPDATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_MESH_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_POSE_TYPE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_SESSION_BEGIN_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_STATE_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_FRAME_STATE_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_FRAME_END_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_LAYER_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_SWAPCHAIN_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_CONTROLLER_MODEL_KEY_STATE_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_CONTROLLER_MODEL_NODE_PROPERTIES_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_CONTROLLER_MODEL_PROPERTIES_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_CONTROLLER_MODEL_NODE_STATE_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_CONTROLLER_MODEL_STATE_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW_CONFIGURATION_VIEW_FOV_EPIC) + ENUM_TO_STRING_CASE(XR_TYPE_HOLOGRAPHIC_WINDOW_ATTACHMENT_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_REPROJECTION_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_REPROJECTION_PLANE_OVERRIDE_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_ANDROID_SURFACE_SWAPCHAIN_CREATE_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_SECURE_CONTENT_FB) + ENUM_TO_STRING_CASE(XR_TYPE_INTERACTION_PROFILE_ANALOG_THRESHOLD_VALVE) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_JOINTS_MOTION_RANGE_INFO_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_VULKAN_INSTANCE_CREATE_INFO_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_VULKAN_DEVICE_CREATE_INFO_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_VULKAN_GRAPHICS_DEVICE_GET_INFO_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_OBSERVER_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_NEW_SCENE_COMPUTE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_VISUAL_MESH_COMPUTE_LOD_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_COMPONENTS_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_COMPONENTS_GET_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_COMPONENT_LOCATIONS_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_COMPONENTS_LOCATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_OBJECTS_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_COMPONENT_PARENT_FILTER_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_OBJECT_TYPES_FILTER_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_PLANES_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_PLANE_ALIGNMENT_FILTER_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESHES_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESH_BUFFERS_GET_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESH_BUFFERS_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESH_VERTEX_BUFFER_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESH_INDICES_UINT32_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESH_INDICES_UINT16_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SERIALIZED_SCENE_FRAGMENT_DATA_GET_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_DESERIALIZE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_DISPLAY_REFRESH_RATE_CHANGED_FB) + ENUM_TO_STRING_CASE(XR_TYPE_VIVE_TRACKER_PATHS_HTCX) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_VIVE_TRACKER_CONNECTED_HTCX) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_COLOR_SPACE_PROPERTIES_FB) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_TRACKING_MESH_FB) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_TRACKING_SCALE_FB) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_TRACKING_AIM_STATE_FB) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_TRACKING_CAPSULES_STATE_FB) + ENUM_TO_STRING_CASE(XR_TYPE_FOVEATION_PROFILE_CREATE_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_CREATE_INFO_FOVEATION_FB) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_STATE_FOVEATION_FB) + ENUM_TO_STRING_CASE(XR_TYPE_FOVEATION_LEVEL_PROFILE_CREATE_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_TRIANGLE_MESH_CREATE_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_PASSTHROUGH_PROPERTIES_FB) + ENUM_TO_STRING_CASE(XR_TYPE_PASSTHROUGH_CREATE_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB) + ENUM_TO_STRING_CASE(XR_TYPE_GEOMETRY_INSTANCE_CREATE_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_GEOMETRY_INSTANCE_TRANSFORM_FB) + ENUM_TO_STRING_CASE(XR_TYPE_PASSTHROUGH_STYLE_FB) + ENUM_TO_STRING_CASE(XR_TYPE_PASSTHROUGH_COLOR_MAP_MONO_TO_RGBA_FB) + ENUM_TO_STRING_CASE(XR_TYPE_PASSTHROUGH_COLOR_MAP_MONO_TO_MONO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_PASSTHROUGH_STATE_CHANGED_FB) + ENUM_TO_STRING_CASE(XR_TYPE_BINDING_MODIFICATIONS_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW_LOCATE_FOVEATED_RENDERING_VARJO) + ENUM_TO_STRING_CASE(XR_TYPE_FOVEATED_VIEW_CONFIGURATION_VIEW_VARJO) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_FOVEATED_RENDERING_PROPERTIES_VARJO) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_DEPTH_TEST_VARJO) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_MARKER_TRACKING_PROPERTIES_VARJO) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_MARKER_TRACKING_UPDATE_VARJO) + ENUM_TO_STRING_CASE(XR_TYPE_MARKER_SPACE_CREATE_INFO_VARJO) + ENUM_TO_STRING_CASE(XR_TYPE_SPATIAL_ANCHOR_PERSISTENCE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SPATIAL_ANCHOR_FROM_PERSISTED_ANCHOR_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_FOVEATION_VULKAN_FB) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_STATE_ANDROID_SURFACE_DIMENSIONS_FB) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_STATE_SAMPLER_OPENGL_ES_FB) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_STATE_SAMPLER_VULKAN_FB) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_SPACE_WARP_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_SPACE_WARP_PROPERTIES_FB) + ENUM_TO_STRING_CASE(XR_STRUCTURE_TYPE_MAX_ENUM) + default: { + return String("Structure type ") + String::num_int64(int64_t(p_structure_type)); + } break; + } +} + +String OpenXRUtil::get_session_state_name(XrSessionState p_session_state) { + switch (p_session_state) { + ENUM_TO_STRING_CASE(XR_SESSION_STATE_UNKNOWN) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_IDLE) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_READY) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_SYNCHRONIZED) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_VISIBLE) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_FOCUSED) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_STOPPING) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_LOSS_PENDING) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_EXITING) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_MAX_ENUM) + default: { + return String("Session state ") + String::num_int64(int64_t(p_session_state)); + } break; + } +} + +String OpenXRUtil::make_xr_version_string(XrVersion p_version) { + String version; + + version += String::num_int64(XR_VERSION_MAJOR(p_version)); + version += String("."); + version += String::num_int64(XR_VERSION_MINOR(p_version)); + version += String("."); + version += String::num_int64(XR_VERSION_PATCH(p_version)); + + return version; +} diff --git a/modules/openxr/openxr_util.h b/modules/openxr/openxr_util.h new file mode 100644 index 0000000000..1261268376 --- /dev/null +++ b/modules/openxr/openxr_util.h @@ -0,0 +1,46 @@ +/*************************************************************************/ +/* openxr_util.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef OPENXR_UTIL_H +#define OPENXR_UTIL_H + +#include "core/string/ustring.h" +#include <openxr/openxr.h> + +class OpenXRUtil { +public: + static String get_view_configuration_name(XrViewConfigurationType p_view_configuration); + static String get_reference_space_name(XrReferenceSpaceType p_reference_space); + static String get_structure_type_name(XrStructureType p_structure_type); + static String get_session_state_name(XrSessionState p_session_state); + static String make_xr_version_string(XrVersion p_version); +}; + +#endif // !OPENXR_UTIL_H diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp new file mode 100644 index 0000000000..86ff368619 --- /dev/null +++ b/modules/openxr/register_types.cpp @@ -0,0 +1,91 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "register_types.h" +#include "main/main.h" + +#include "openxr_interface.h" + +#include "action_map/openxr_action.h" +#include "action_map/openxr_action_map.h" +#include "action_map/openxr_action_set.h" +#include "action_map/openxr_interaction_profile.h" + +OpenXRAPI *openxr_api = nullptr; +Ref<OpenXRInterface> openxr_interface; + +void preregister_openxr_types() { + // For now we create our openxr device here. If we merge it with openxr_interface we'll create that here soon. + + OpenXRAPI::setup_global_defs(); + openxr_api = OpenXRAPI::get_singleton(); + if (openxr_api) { + if (!openxr_api->initialise(Main::get_rendering_driver_name())) { + return; + } + } +} + +void register_openxr_types() { + GDREGISTER_CLASS(OpenXRInterface); + + GDREGISTER_CLASS(OpenXRAction); + GDREGISTER_CLASS(OpenXRActionSet); + GDREGISTER_CLASS(OpenXRActionMap); + GDREGISTER_CLASS(OpenXRIPBinding); + GDREGISTER_CLASS(OpenXRInteractionProfile); + + XRServer *xr_server = XRServer::get_singleton(); + if (xr_server) { + openxr_interface.instantiate(); + xr_server->add_interface(openxr_interface); + + if (openxr_interface->initialise_on_startup()) { + openxr_interface->initialize(); + } + } +} + +void unregister_openxr_types() { + if (openxr_interface.is_valid()) { + // unregister our interface from the XR server + if (XRServer::get_singleton()) { + XRServer::get_singleton()->remove_interface(openxr_interface); + } + + // and release + openxr_interface.unref(); + } + + if (openxr_api) { + openxr_api->finish(); + memdelete(openxr_api); + } +} diff --git a/modules/openxr/register_types.h b/modules/openxr/register_types.h new file mode 100644 index 0000000000..fb42770750 --- /dev/null +++ b/modules/openxr/register_types.h @@ -0,0 +1,40 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef OPENXR_REGISTER_TYPES_H +#define OPENXR_REGISTER_TYPES_H + +#define MODULE_OPENXR_HAS_PREREGISTER + +void preregister_openxr_types(); +void register_openxr_types(); +void unregister_openxr_types(); + +#endif // OPENXR_REGISTER_TYPES_H diff --git a/modules/raycast/raycast_occlusion_cull.cpp b/modules/raycast/raycast_occlusion_cull.cpp index dd5eef8b2b..2e0d17fb28 100644 --- a/modules/raycast/raycast_occlusion_cull.cpp +++ b/modules/raycast/raycast_occlusion_cull.cpp @@ -270,13 +270,14 @@ void RaycastOcclusionCull::scenario_set_instance(RID p_scenario, RID p_instance, OccluderInstance &instance = scenario.instances[p_instance]; + bool changed = false; + if (instance.removed) { instance.removed = false; scenario.removed_instances.erase(p_instance); + changed = true; // It was removed and re-added, we might have missed some changes } - bool changed = false; - if (instance.occluder != p_occluder) { Occluder *old_occluder = occluder_owner.get_or_null(instance.occluder); if (old_occluder) { diff --git a/modules/regex/doc_classes/RegEx.xml b/modules/regex/doc_classes/RegEx.xml index 2ae2e53b02..deabc5ccd3 100644 --- a/modules/regex/doc_classes/RegEx.xml +++ b/modules/regex/doc_classes/RegEx.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="RegEx" inherits="RefCounted" version="4.0"> +<class name="RegEx" inherits="RefCounted" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Class for searching text for patterns using regular expressions. </brief_description> diff --git a/modules/regex/doc_classes/RegExMatch.xml b/modules/regex/doc_classes/RegExMatch.xml index 20680b41fd..530a541ae8 100644 --- a/modules/regex/doc_classes/RegExMatch.xml +++ b/modules/regex/doc_classes/RegExMatch.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="RegExMatch" inherits="RefCounted" version="4.0"> +<class name="RegExMatch" inherits="RefCounted" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Contains the results of a [RegEx] search. </brief_description> diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub index d6a96282f3..5e5c284b57 100644 --- a/modules/text_server_adv/SCsub +++ b/modules/text_server_adv/SCsub @@ -49,6 +49,7 @@ if env["builtin_harfbuzz"]: "src/hb-aat-map.cc", "src/hb-blob.cc", "src/hb-buffer-serialize.cc", + "src/hb-buffer-verify.cc", "src/hb-buffer.cc", "src/hb-common.cc", #'src/hb-coretext.cc', @@ -62,7 +63,6 @@ if env["builtin_harfbuzz"]: #'src/hb-gobject-structs.cc', "src/hb-icu.cc", "src/hb-map.cc", - "src/hb-ms-feature-ranges.cc", "src/hb-number.cc", "src/hb-ot-cff1-table.cc", "src/hb-ot-cff2-table.cc", diff --git a/modules/text_server_adv/doc_classes/TextServerAdvanced.xml b/modules/text_server_adv/doc_classes/TextServerAdvanced.xml index eff4aa5fae..bf86eb6406 100644 --- a/modules/text_server_adv/doc_classes/TextServerAdvanced.xml +++ b/modules/text_server_adv/doc_classes/TextServerAdvanced.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="TextServerAdvanced" inherits="TextServer" version="4.0"> +<class name="TextServerAdvanced" inherits="TextServer" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Text Server using HarfBuzz, ICU and SIL Graphite to support BiDi, complex text layouts and contextual OpenType features. </brief_description> diff --git a/modules/text_server_adv/script_iterator.cpp b/modules/text_server_adv/script_iterator.cpp index 9e3d9138d0..06e934c67d 100644 --- a/modules/text_server_adv/script_iterator.cpp +++ b/modules/text_server_adv/script_iterator.cpp @@ -82,7 +82,7 @@ ScriptIterator::ScriptIterator(const String &p_string, int p_start, int p_length paren_stack[paren_sp].pair_index = ch; paren_stack[paren_sp].script_code = script_code; } else if (paren_sp >= 0) { - // If it's a close character, find the matching open on the stack, and use that script code. Any non-matching open characters above it on the stack will be poped. + // If it's a close character, find the matching open on the stack, and use that script code. Any non-matching open characters above it on the stack will be popped. UChar32 paired_ch = u_getBidiPairedBracket(ch); while (paren_sp >= 0 && paren_stack[paren_sp].pair_index != paired_ch) { paren_sp -= 1; diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index e0ee809331..1b4512dc60 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -303,22 +303,6 @@ _FORCE_INLINE_ bool is_connected_to_prev(char32_t p_chr, char32_t p_pchr) { return (prop != U_JT_RIGHT_JOINING) && (prop != U_JT_NON_JOINING) ? !is_ligature(p_pchr, p_chr) : false; } -_FORCE_INLINE_ bool is_control(char32_t p_char) { - return (p_char <= 0x001f) || (p_char >= 0x007f && p_char <= 0x009F); -} - -_FORCE_INLINE_ bool is_whitespace(char32_t p_char) { - return (p_char == 0x0020) || (p_char == 0x00A0) || (p_char == 0x1680) || (p_char >= 0x2000 && p_char <= 0x200a) || (p_char == 0x202f) || (p_char == 0x205f) || (p_char == 0x3000) || (p_char == 0x2028) || (p_char == 0x2029) || (p_char >= 0x0009 && p_char <= 0x000d) || (p_char == 0x0085); -} - -_FORCE_INLINE_ bool is_linebreak(char32_t p_char) { - return (p_char >= 0x000a && p_char <= 0x000d) || (p_char == 0x0085) || (p_char == 0x2028) || (p_char == 0x2029); -} - -_FORCE_INLINE_ bool is_underscore(char32_t p_char) { - return (p_char == 0x005F); -} - /*************************************************************************/ String TextServerAdvanced::interface_name = "ICU / HarfBuzz / Graphite"; @@ -1113,12 +1097,14 @@ _FORCE_INLINE_ TextServerAdvanced::FontGlyph TextServerAdvanced::rasterize_bitma _FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontDataAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph) const { ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size), false); + int32_t glyph_index = p_glyph & 0xFFFFFF; // Remove subpixel shifts. + FontDataForSizeAdvanced *fd = p_font_data->cache[p_size]; if (fd->glyph_map.has(p_glyph)) { return fd->glyph_map[p_glyph].found; } - if (p_glyph == 0) { // Non graphical or invalid glyph, do not render. + if (glyph_index == 0) { // Non graphical or invalid glyph, do not render. fd->glyph_map[p_glyph] = FontGlyph(); return true; } @@ -1150,15 +1136,25 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontDataAdvanced *p_font_d } FT_Fixed v, h; - FT_Get_Advance(fd->face, p_glyph, flags, &h); - FT_Get_Advance(fd->face, p_glyph, flags | FT_LOAD_VERTICAL_LAYOUT, &v); + FT_Get_Advance(fd->face, glyph_index, flags, &h); + FT_Get_Advance(fd->face, glyph_index, flags | FT_LOAD_VERTICAL_LAYOUT, &v); - int error = FT_Load_Glyph(fd->face, p_glyph, flags); + int error = FT_Load_Glyph(fd->face, glyph_index, flags); if (error) { fd->glyph_map[p_glyph] = FontGlyph(); return false; } + if (!p_font_data->msdf) { + if ((p_font_data->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (p_font_data->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && p_size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) { + FT_Pos xshift = (int)((p_glyph >> 27) & 3) << 4; + FT_Outline_Translate(&fd->face->glyph->outline, xshift, 0); + } else if ((p_font_data->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (p_font_data->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && p_size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { + FT_Pos xshift = (int)((p_glyph >> 27) & 3) << 5; + FT_Outline_Translate(&fd->face->glyph->outline, xshift, 0); + } + } + if (!outline) { if (!p_font_data->msdf) { error = FT_Render_Glyph(fd->face->glyph, p_font_data->antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO); @@ -1860,6 +1856,24 @@ TextServer::Hinting TextServerAdvanced::font_get_hinting(RID p_font_rid) const { return fd->hinting; } +void TextServerAdvanced::font_set_subpixel_positioning(RID p_font_rid, TextServer::SubpixelPositioning p_subpixel) { + FontDataAdvanced *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + if (fd->subpixel_positioning != p_subpixel) { + fd->subpixel_positioning = p_subpixel; + } +} + +TextServer::SubpixelPositioning TextServerAdvanced::font_get_subpixel_positioning(RID p_font_rid) const { + FontDataAdvanced *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND_V(!fd, SUBPIXEL_POSITIONING_DISABLED); + + MutexLock lock(fd->mutex); + return fd->subpixel_positioning; +} + void TextServerAdvanced::font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) { FontDataAdvanced *fd = font_owner.get_or_null(p_font_rid); ERR_FAIL_COND(!fd); @@ -2277,6 +2291,8 @@ Vector2 TextServerAdvanced::font_get_glyph_advance(RID p_font_rid, int p_size, i if (fd->msdf) { return gl[p_glyph].advance * (float)p_size / (float)fd->msdf_source_size; + } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_DISABLED) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x > SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { + return gl[p_glyph].advance.round(); } else { return gl[p_glyph].advance; } @@ -2644,12 +2660,25 @@ void TextServerAdvanced::font_render_range(RID p_font_rid, const Vector2i &p_siz ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); for (char32_t i = p_start; i <= p_end; i++) { #ifdef MODULE_FREETYPE_ENABLED + int32_t idx = FT_Get_Char_Index(fd->cache[size]->face, i); if (fd->cache[size]->face) { - _ensure_glyph(fd, size, FT_Get_Char_Index(fd->cache[size]->face, i)); - continue; + if (fd->msdf) { + _ensure_glyph(fd, size, (int32_t)idx); + } else { + if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) { + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27)); + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27)); + _ensure_glyph(fd, size, (int32_t)idx | (2 << 27)); + _ensure_glyph(fd, size, (int32_t)idx | (3 << 27)); + } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27)); + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27)); + } else { + _ensure_glyph(fd, size, (int32_t)idx); + } + } } #endif - _ensure_glyph(fd, size, (int32_t)i); } } @@ -2660,7 +2689,26 @@ void TextServerAdvanced::font_render_glyph(RID p_font_rid, const Vector2i &p_siz MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - ERR_FAIL_COND(!_ensure_glyph(fd, size, p_index)); +#ifdef MODULE_FREETYPE_ENABLED + int32_t idx = p_index; + if (fd->cache[size]->face) { + if (fd->msdf) { + _ensure_glyph(fd, size, (int32_t)idx); + } else { + if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) { + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27)); + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27)); + _ensure_glyph(fd, size, (int32_t)idx | (2 << 27)); + _ensure_glyph(fd, size, (int32_t)idx | (3 << 27)); + } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27)); + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27)); + } else { + _ensure_glyph(fd, size, (int32_t)idx); + } + } + } +#endif } void TextServerAdvanced::font_draw_glyph(RID p_font_rid, RID p_canvas, int p_size, const Vector2 &p_pos, int32_t p_index, const Color &p_color) const { @@ -2670,11 +2718,26 @@ void TextServerAdvanced::font_draw_glyph(RID p_font_rid, RID p_canvas, int p_siz MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - if (!_ensure_glyph(fd, size, p_index)) { + + int32_t index = p_index; + +#ifdef MODULE_FREETYPE_ENABLED + if (!fd->msdf && fd->cache[size]->face) { + if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) { + int xshift = (int)(Math::floor(4 * (p_pos.x + 0.125)) - 4 * Math::floor(p_pos.x + 0.125)); + index = index | (xshift << 27); + } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { + int xshift = (int)(Math::floor(2 * (p_pos.x + 0.25)) - 2 * Math::floor(p_pos.x + 0.25)); + index = index | (xshift << 27); + } + } +#endif + + if (!_ensure_glyph(fd, size, index)) { return; // Invalid or non-graphical glyph, do not display errors, nothing to draw. } - const FontGlyph &gl = fd->cache[size]->glyph_map[p_index]; + const FontGlyph &gl = fd->cache[size]->glyph_map[index]; if (gl.found) { ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size()); @@ -2693,7 +2756,15 @@ void TextServerAdvanced::font_draw_glyph(RID p_font_rid, RID p_canvas, int p_siz Size2 csize = gl.rect.size * (float)p_size / (float)fd->msdf_source_size; RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, 0, fd->msdf_range); } else { - Point2 cpos = p_pos.floor(); + Point2 cpos = p_pos; + cpos.y = Math::floor(cpos.y); + if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) { + cpos.x = ((int)Math::floor(cpos.x + 0.125)); + } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { + cpos.x = ((int)Math::floor(cpos.x + 0.25)); + } else { + cpos.x = Math::floor(cpos.x); + } cpos += gl.rect.position; Size2 csize = gl.rect.size; RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false); @@ -2710,11 +2781,26 @@ void TextServerAdvanced::font_draw_glyph_outline(RID p_font_rid, RID p_canvas, i MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, Vector2i(p_size, p_outline_size)); ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - if (!_ensure_glyph(fd, size, p_index)) { + + int32_t index = p_index; + +#ifdef MODULE_FREETYPE_ENABLED + if (!fd->msdf && fd->cache[size]->face) { + if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) { + int xshift = (int)(Math::floor(4 * (p_pos.x + 0.125)) - 4 * Math::floor(p_pos.x + 0.125)); + index = index | (xshift << 27); + } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { + int xshift = (int)(Math::floor(2 * (p_pos.x + 0.25)) - 2 * Math::floor(p_pos.x + 0.25)); + index = index | (xshift << 27); + } + } +#endif + + if (!_ensure_glyph(fd, size, index)) { return; // Invalid or non-graphical glyph, do not display errors, nothing to draw. } - const FontGlyph &gl = fd->cache[size]->glyph_map[p_index]; + const FontGlyph &gl = fd->cache[size]->glyph_map[index]; if (gl.found) { ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size()); @@ -2733,7 +2819,15 @@ void TextServerAdvanced::font_draw_glyph_outline(RID p_font_rid, RID p_canvas, i Size2 csize = gl.rect.size * (float)p_size / (float)fd->msdf_source_size; RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, p_outline_size * 2, fd->msdf_range); } else { - Point2 cpos = p_pos.floor(); + Point2 cpos = p_pos; + cpos.y = Math::floor(cpos.y); + if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) { + cpos.x = ((int)Math::floor(cpos.x + 0.125)); + } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { + cpos.x = ((int)Math::floor(cpos.x + 0.25)); + } else { + cpos.x = Math::floor(cpos.x); + } cpos += gl.rect.position; Size2 csize = gl.rect.size; RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false); @@ -2901,7 +2995,7 @@ void TextServerAdvanced::font_set_global_oversampling(float p_oversampling) { List<RID> text_bufs; shaped_owner.get_owned_list(&text_bufs); for (const RID &E : text_bufs) { - invalidate(shaped_owner.get_or_null(E)); + invalidate(shaped_owner.get_or_null(E), false); } } } @@ -2936,7 +3030,7 @@ int TextServerAdvanced::_convert_pos_inv(const ShapedTextDataAdvanced *p_sd, int return limit; } -void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced *p_shaped) { +void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced *p_shaped, bool p_text) { p_shaped->valid = false; p_shaped->sort_valid = false; p_shaped->line_breaks_valid = false; @@ -2951,27 +3045,32 @@ void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced * p_shaped->glyphs_logical.clear(); p_shaped->overrun_trim_data = TrimData(); p_shaped->utf16 = Char16String(); - if (p_shaped->script_iter != nullptr) { - memdelete(p_shaped->script_iter); - p_shaped->script_iter = nullptr; - } for (int i = 0; i < p_shaped->bidi_iter.size(); i++) { ubidi_close(p_shaped->bidi_iter[i]); } p_shaped->bidi_iter.clear(); + + if (p_text) { + if (p_shaped->script_iter != nullptr) { + memdelete(p_shaped->script_iter); + p_shaped->script_iter = nullptr; + } + p_shaped->break_ops_valid = false; + p_shaped->js_ops_valid = false; + } } void TextServerAdvanced::full_copy(ShapedTextDataAdvanced *p_shaped) { ShapedTextDataAdvanced *parent = shaped_owner.get_or_null(p_shaped->parent); - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : parent->objects) { + for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : parent->objects) { if (E.value.pos >= p_shaped->start && E.value.pos < p_shaped->end) { p_shaped->objects[E.key] = E.value; } } - for (int k = 0; k < parent->spans.size(); k++) { - ShapedTextDataAdvanced::Span span = parent->spans[k]; + for (int i = 0; i < parent->spans.size(); i++) { + ShapedTextDataAdvanced::Span span = parent->spans[i]; if (span.start >= p_shaped->end || span.end <= p_shaped->start) { continue; } @@ -3004,7 +3103,7 @@ void TextServerAdvanced::shaped_text_clear(RID p_shaped) { sd->spans.clear(); sd->objects.clear(); sd->bidi_override.clear(); - invalidate(sd); + invalidate(sd, true); } void TextServerAdvanced::shaped_text_set_direction(RID p_shaped, TextServer::Direction p_direction) { @@ -3017,7 +3116,7 @@ void TextServerAdvanced::shaped_text_set_direction(RID p_shaped, TextServer::Dir full_copy(sd); } sd->direction = p_direction; - invalidate(sd); + invalidate(sd, false); } } @@ -3047,7 +3146,7 @@ void TextServerAdvanced::shaped_text_set_custom_punctuation(RID p_shaped, const full_copy(sd); } sd->custom_punct = p_punct; - invalidate(sd); + invalidate(sd, false); } } @@ -3070,7 +3169,7 @@ void TextServerAdvanced::shaped_text_set_bidi_override(RID p_shaped, const Array for (int i = 0; i < p_override.size(); i++) { sd->bidi_override.push_back(p_override[i]); } - invalidate(sd); + invalidate(sd, false); } void TextServerAdvanced::shaped_text_set_orientation(RID p_shaped, TextServer::Orientation p_orientation) { @@ -3083,7 +3182,7 @@ void TextServerAdvanced::shaped_text_set_orientation(RID p_shaped, TextServer::O full_copy(sd); } sd->orientation = p_orientation; - invalidate(sd); + invalidate(sd, false); } } @@ -3095,7 +3194,7 @@ void TextServerAdvanced::shaped_text_set_preserve_invalid(RID p_shaped, bool p_e ERR_FAIL_COND(sd->parent != RID()); if (sd->preserve_invalid != p_enabled) { sd->preserve_invalid = p_enabled; - invalidate(sd); + invalidate(sd, false); } } @@ -3117,7 +3216,7 @@ void TextServerAdvanced::shaped_text_set_preserve_control(RID p_shaped, bool p_e full_copy(sd); } sd->preserve_control = p_enabled; - invalidate(sd); + invalidate(sd, false); } } @@ -3137,7 +3236,41 @@ TextServer::Orientation TextServerAdvanced::shaped_text_get_orientation(RID p_sh return sd->orientation; } -bool TextServerAdvanced::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language) { +int TextServerAdvanced::shaped_get_span_count(RID p_shaped) const { + ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V(!sd, 0); + return sd->spans.size(); +} + +Variant TextServerAdvanced::shaped_get_span_meta(RID p_shaped, int p_index) const { + ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V(!sd, Variant()); + ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant()); + return sd->spans[p_index].meta; +} + +void TextServerAdvanced::shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features) { + ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND(!sd); + ERR_FAIL_INDEX(p_index, sd->spans.size()); + + ShapedTextDataAdvanced::Span &span = sd->spans.write[p_index]; + bool changed = (span.font_size != p_size) || (span.features != p_opentype_features) || (p_fonts.size() != span.fonts.size()); + if (!changed) { + for (int i = 0; i < p_fonts.size(); i++) { + changed = changed || (span.fonts[i] != p_fonts[i]); + } + } + if (changed) { + span.fonts = p_fonts; + span.font_size = p_size; + span.features = p_opentype_features; + + invalidate(sd, false); + } +} + +bool TextServerAdvanced::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); ERR_FAIL_COND_V(p_size <= 0, false); @@ -3162,11 +3295,12 @@ bool TextServerAdvanced::shaped_text_add_string(RID p_shaped, const String &p_te span.font_size = p_size; span.language = p_language; span.features = p_opentype_features; + span.meta = p_meta; sd->spans.push_back(span); sd->text += p_text; sd->end += p_text.length(); - invalidate(sd); + invalidate(sd, true); return true; } @@ -3196,13 +3330,13 @@ bool TextServerAdvanced::shaped_text_add_object(RID p_shaped, Variant p_key, con sd->text += String::chr(0xfffc).repeat(p_length); sd->end += p_length; sd->objects[p_key] = obj; - invalidate(sd); + invalidate(sd, true); return true; } bool TextServerAdvanced::shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -3222,7 +3356,7 @@ bool TextServerAdvanced::shaped_text_resize_object(RID p_shaped, Variant p_key, Glyph gl = sd->glyphs[i]; Variant key; if (gl.count == 1) { - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { + for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : sd->objects) { if (E.value.pos == gl.start) { key = E.key; break; @@ -3262,75 +3396,78 @@ bool TextServerAdvanced::shaped_text_resize_object(RID p_shaped, Variant p_key, sd->width += gl.advance * gl.repeat; } } + _realign(sd); + } + return true; +} - // Align embedded objects to baseline. - float full_ascent = sd->ascent; - float full_descent = sd->descent; - for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { - if ((E.value.pos >= sd->start) && (E.value.pos < sd->end)) { - if (sd->orientation == ORIENTATION_HORIZONTAL) { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.y = -sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.y = (-sd->ascent + sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.y = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.y = sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.y -= E.value.rect.size.y; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.y -= E.value.rect.size.y / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.y); - full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y); - } else { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.x = -sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.x = (-sd->ascent + sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.x = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.x = sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.x -= E.value.rect.size.x; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.x -= E.value.rect.size.x / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.x); - full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x); +void TextServerAdvanced::_realign(ShapedTextDataAdvanced *p_sd) const { + // Align embedded objects to baseline. + float full_ascent = p_sd->ascent; + float full_descent = p_sd->descent; + for (KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : p_sd->objects) { + if ((E.value.pos >= p_sd->start) && (E.value.pos < p_sd->end)) { + if (p_sd->orientation == ORIENTATION_HORIZONTAL) { + switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { + case INLINE_ALIGNMENT_TO_TOP: { + E.value.rect.position.y = -p_sd->ascent; + } break; + case INLINE_ALIGNMENT_TO_CENTER: { + E.value.rect.position.y = (-p_sd->ascent + p_sd->descent) / 2; + } break; + case INLINE_ALIGNMENT_TO_BASELINE: { + E.value.rect.position.y = 0; + } break; + case INLINE_ALIGNMENT_TO_BOTTOM: { + E.value.rect.position.y = p_sd->descent; + } break; + } + switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { + case INLINE_ALIGNMENT_BOTTOM_TO: { + E.value.rect.position.y -= E.value.rect.size.y; + } break; + case INLINE_ALIGNMENT_CENTER_TO: { + E.value.rect.position.y -= E.value.rect.size.y / 2; + } break; + case INLINE_ALIGNMENT_TOP_TO: { + // NOP + } break; } + full_ascent = MAX(full_ascent, -E.value.rect.position.y); + full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y); + } else { + switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { + case INLINE_ALIGNMENT_TO_TOP: { + E.value.rect.position.x = -p_sd->ascent; + } break; + case INLINE_ALIGNMENT_TO_CENTER: { + E.value.rect.position.x = (-p_sd->ascent + p_sd->descent) / 2; + } break; + case INLINE_ALIGNMENT_TO_BASELINE: { + E.value.rect.position.x = 0; + } break; + case INLINE_ALIGNMENT_TO_BOTTOM: { + E.value.rect.position.x = p_sd->descent; + } break; + } + switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { + case INLINE_ALIGNMENT_BOTTOM_TO: { + E.value.rect.position.x -= E.value.rect.size.x; + } break; + case INLINE_ALIGNMENT_CENTER_TO: { + E.value.rect.position.x -= E.value.rect.size.x / 2; + } break; + case INLINE_ALIGNMENT_TOP_TO: { + // NOP + } break; + } + full_ascent = MAX(full_ascent, -E.value.rect.position.x); + full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x); } } - sd->ascent = full_ascent; - sd->descent = full_descent; } - return true; + p_sd->ascent = full_ascent; + p_sd->descent = full_descent; } RID TextServerAdvanced::shaped_text_substr(RID p_shaped, int p_start, int p_length) const { @@ -3423,7 +3560,7 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S Variant key; bool find_embedded = false; if (gl.count == 1) { - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : p_sd->objects) { + for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : p_sd->objects) { if (E.value.pos == gl.start) { find_embedded = true; key = E.key; @@ -3466,72 +3603,7 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S } } - // Align embedded objects to baseline. - float full_ascent = p_new_sd->ascent; - float full_descent = p_new_sd->descent; - for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : p_new_sd->objects) { - if ((E.value.pos >= p_new_sd->start) && (E.value.pos < p_new_sd->end)) { - if (p_sd->orientation == ORIENTATION_HORIZONTAL) { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.y = -p_new_sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.y = (-p_new_sd->ascent + p_new_sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.y = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.y = p_new_sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.y -= E.value.rect.size.y; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.y -= E.value.rect.size.y / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.y); - full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y); - } else { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.x = -p_new_sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.x = (-p_new_sd->ascent + p_new_sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.x = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.x = p_new_sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.x -= E.value.rect.size.x; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.x -= E.value.rect.size.x / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.x); - full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x); - } - } - } - p_new_sd->ascent = full_ascent; - p_new_sd->descent = full_descent; + _realign(p_new_sd); } p_new_sd->valid = true; @@ -3597,7 +3669,7 @@ float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width, } justification_width = sd->width_trimmed; } else { - return sd->width; + return Math::ceil(sd->width); } } else { justification_width = sd->width; @@ -3700,7 +3772,7 @@ float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width, sd->width = justification_width; } - return justification_width; + return Math::ceil(justification_width); } float TextServerAdvanced::shaped_text_tab_align(RID p_shaped, const PackedFloat32Array &p_tab_stops) { @@ -3968,40 +4040,43 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) { const UChar *data = sd->utf16.ptr(); - HashMap<int, bool> breaks; - UErrorCode err = U_ZERO_ERROR; - int i = 0; - while (i < sd->spans.size()) { - String language = sd->spans[i].language; - int r_start = sd->spans[i].start; - while (i + 1 < sd->spans.size() && language == sd->spans[i + 1].language) { - i++; - } - int r_end = sd->spans[i].end; - UBreakIterator *bi = ubrk_open(UBRK_LINE, language.ascii().get_data(), data + _convert_pos_inv(sd, r_start), _convert_pos_inv(sd, r_end - r_start), &err); - if (U_FAILURE(err)) { - // No data loaded - use fallback. - for (int j = r_start; j < r_end; j++) { - char32_t c = sd->text[j - sd->start]; - if (is_whitespace(c)) { - breaks[j + 1] = false; - } - if (is_linebreak(c)) { - breaks[j + 1] = true; - } + if (!sd->break_ops_valid) { + sd->breaks.clear(); + UErrorCode err = U_ZERO_ERROR; + int i = 0; + while (i < sd->spans.size()) { + String language = sd->spans[i].language; + int r_start = sd->spans[i].start; + while (i + 1 < sd->spans.size() && language == sd->spans[i + 1].language) { + i++; } - } else { - while (ubrk_next(bi) != UBRK_DONE) { - int pos = _convert_pos(sd, ubrk_current(bi)) + r_start; - if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_HARD) && (ubrk_getRuleStatus(bi) < UBRK_LINE_HARD_LIMIT)) { - breaks[pos] = true; - } else if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_SOFT) && (ubrk_getRuleStatus(bi) < UBRK_LINE_SOFT_LIMIT)) { - breaks[pos] = false; + int r_end = sd->spans[i].end; + UBreakIterator *bi = ubrk_open(UBRK_LINE, language.ascii().get_data(), data + _convert_pos_inv(sd, r_start), _convert_pos_inv(sd, r_end - r_start), &err); + if (U_FAILURE(err)) { + // No data loaded - use fallback. + for (int j = r_start; j < r_end; j++) { + char32_t c = sd->text[j - sd->start]; + if (is_whitespace(c)) { + sd->breaks[j + 1] = false; + } + if (is_linebreak(c)) { + sd->breaks[j + 1] = true; + } + } + } else { + while (ubrk_next(bi) != UBRK_DONE) { + int pos = _convert_pos(sd, ubrk_current(bi)) + r_start; + if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_HARD) && (ubrk_getRuleStatus(bi) < UBRK_LINE_HARD_LIMIT)) { + sd->breaks[pos] = true; + } else if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_SOFT) && (ubrk_getRuleStatus(bi) < UBRK_LINE_SOFT_LIMIT)) { + sd->breaks[pos] = false; + } } } + ubrk_close(bi); + i++; } - ubrk_close(bi); - i++; + sd->break_ops_valid = true; } sd->sort_valid = false; @@ -4013,7 +4088,7 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) { int c_punct_size = sd->custom_punct.length(); const char32_t *c_punct = sd->custom_punct.ptr(); - for (i = 0; i < sd_size; i++) { + for (int i = 0; i < sd_size; i++) { if (sd_glyphs[i].count > 0) { char32_t c = ch[sd_glyphs[i].start - sd->start]; if (c == 0xfffc) { @@ -4040,8 +4115,8 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) { if (is_underscore(c)) { sd_glyphs[i].flags |= GRAPHEME_IS_UNDERSCORE; } - if (breaks.has(sd_glyphs[i].end)) { - if (breaks[sd_glyphs[i].end] && (is_linebreak(c))) { + if (sd->breaks.has(sd_glyphs[i].end)) { + if (sd->breaks[sd_glyphs[i].end] && (is_linebreak(c))) { sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_HARD; } else if (is_whitespace(c)) { sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_SOFT; @@ -4186,41 +4261,45 @@ bool TextServerAdvanced::shaped_text_update_justification_ops(RID p_shaped) { const UChar *data = sd->utf16.ptr(); int32_t data_size = sd->utf16.length(); - Map<int, bool> jstops; + if (!sd->js_ops_valid) { + sd->jstops.clear(); - // Use ICU word iterator and custom kashida detection. - UErrorCode err = U_ZERO_ERROR; - UBreakIterator *bi = ubrk_open(UBRK_WORD, "", data, data_size, &err); - if (U_FAILURE(err)) { - // No data - use fallback. - int limit = 0; - for (int i = 0; i < sd->text.length(); i++) { - if (is_whitespace(data[i])) { - int ks = _generate_kashida_justification_opportunies(sd->text, limit, i) + sd->start; - if (ks != -1) { - jstops[ks] = true; - } - limit = i + 1; + // Use ICU word iterator and custom kashida detection. + UErrorCode err = U_ZERO_ERROR; + UBreakIterator *bi = ubrk_open(UBRK_WORD, "", data, data_size, &err); + if (U_FAILURE(err)) { + // No data - use fallback. + int limit = 0; + for (int i = 0; i < sd->text.length(); i++) { + if (is_whitespace(data[i])) { + int ks = _generate_kashida_justification_opportunies(sd->text, limit, i) + sd->start; + if (ks != -1) { + sd->jstops[ks] = true; + } + limit = i + 1; + } } - } - int ks = _generate_kashida_justification_opportunies(sd->text, limit, sd->text.length()) + sd->start; - if (ks != -1) { - jstops[ks] = true; - } - } else { - int limit = 0; - while (ubrk_next(bi) != UBRK_DONE) { - if (ubrk_getRuleStatus(bi) != UBRK_WORD_NONE) { - int i = _convert_pos(sd, ubrk_current(bi)); - jstops[i + sd->start] = false; - int ks = _generate_kashida_justification_opportunies(sd->text, limit, i); - if (ks != -1) { - jstops[ks + sd->start] = true; - } - limit = i; + int ks = _generate_kashida_justification_opportunies(sd->text, limit, sd->text.length()) + sd->start; + if (ks != -1) { + sd->jstops[ks] = true; + } + } else { + int limit = 0; + while (ubrk_next(bi) != UBRK_DONE) { + if (ubrk_getRuleStatus(bi) != UBRK_WORD_NONE) { + int i = _convert_pos(sd, ubrk_current(bi)); + sd->jstops[i + sd->start] = false; + int ks = _generate_kashida_justification_opportunies(sd->text, limit, i); + if (ks != -1) { + sd->jstops[ks + sd->start] = true; + } + limit = i; + } } + ubrk_close(bi); } - ubrk_close(bi); + + sd->js_ops_valid = true; } sd->sort_valid = false; @@ -4228,18 +4307,18 @@ bool TextServerAdvanced::shaped_text_update_justification_ops(RID p_shaped) { Glyph *sd_glyphs = sd->glyphs.ptrw(); int sd_size = sd->glyphs.size(); - if (jstops.size() > 0) { + if (sd->jstops.size() > 0) { for (int i = 0; i < sd_size; i++) { if (sd_glyphs[i].count > 0) { char32_t c = sd->text[sd_glyphs[i].start - sd->start]; if (c == 0x0640) { sd_glyphs[i].flags |= GRAPHEME_IS_ELONGATION; } - if (jstops.has(sd_glyphs[i].start)) { + if (sd->jstops.has(sd_glyphs[i].start)) { if (c == 0xfffc) { continue; } - if (jstops[sd_glyphs[i].start]) { + if (sd->jstops[sd_glyphs[i].start]) { if (c != 0x0640) { if (sd_glyphs[i].font_rid != RID()) { Glyph gl = _shape_single_glyph(sd, 0x0640, HB_SCRIPT_ARABIC, HB_DIRECTION_RTL, sd->glyphs[i].font_rid, sd->glyphs[i].font_size); @@ -4312,6 +4391,7 @@ bool TextServerAdvanced::shaped_text_update_justification_ops(RID p_shaped) { Glyph TextServerAdvanced::_shape_single_glyph(ShapedTextDataAdvanced *p_sd, char32_t p_char, hb_script_t p_script, hb_direction_t p_direction, RID p_font, int p_font_size) { hb_font_t *hb_font = _font_get_hb_handle(p_font, p_font_size); + bool subpos = (font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_ONE_HALF) || (font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_ONE_QUARTER) || (font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_AUTO && p_font_size <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE); ERR_FAIL_COND_V(hb_font == nullptr, Glyph()); hb_buffer_clear_contents(p_sd->hb_buffer); @@ -4339,14 +4419,22 @@ Glyph TextServerAdvanced::_shape_single_glyph(ShapedTextDataAdvanced *p_sd, char if (glyph_count > 0) { float scale = font_get_scale(p_font, p_font_size); if (p_sd->orientation == ORIENTATION_HORIZONTAL) { - gl.advance = Math::round(glyph_pos[0].x_advance / (64.0 / scale)); + if (subpos) { + gl.advance = glyph_pos[0].x_advance / (64.0 / scale); + } else { + gl.advance = Math::round(glyph_pos[0].x_advance / (64.0 / scale)); + } } else { gl.advance = -Math::round(glyph_pos[0].y_advance / (64.0 / scale)); } gl.count = 1; gl.index = glyph_info[0].codepoint; - gl.x_off = Math::round(glyph_pos[0].x_offset / (64.0 / scale)); + if (subpos) { + gl.x_off = glyph_pos[0].x_offset / (64.0 / scale); + } else { + gl.x_off = Math::round(glyph_pos[0].x_offset / (64.0 / scale)); + } gl.y_off = -Math::round(glyph_pos[0].y_offset / (64.0 / scale)); if ((glyph_info[0].codepoint != 0) || !u_isgraph(p_char)) { @@ -4411,6 +4499,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int32_t p_star float scale = font_get_scale(f, fs); float sp_sp = font_get_spacing(f, fs, SPACING_SPACE); float sp_gl = font_get_spacing(f, fs, SPACING_GLYPH); + bool subpos = (font_get_subpixel_positioning(f) == SUBPIXEL_POSITIONING_ONE_HALF) || (font_get_subpixel_positioning(f) == SUBPIXEL_POSITIONING_ONE_QUARTER) || (font_get_subpixel_positioning(f) == SUBPIXEL_POSITIONING_AUTO && fs <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE); ERR_FAIL_COND(hb_font == nullptr); hb_buffer_clear_contents(p_sd->hb_buffer); @@ -4487,11 +4576,19 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int32_t p_star gl.index = glyph_info[i].codepoint; if (gl.index != 0) { if (p_sd->orientation == ORIENTATION_HORIZONTAL) { - gl.advance = Math::round(glyph_pos[i].x_advance / (64.0 / scale)); + if (subpos) { + gl.advance = glyph_pos[i].x_advance / (64.0 / scale); + } else { + gl.advance = Math::round(glyph_pos[i].x_advance / (64.0 / scale)); + } } else { gl.advance = -Math::round(glyph_pos[i].y_advance / (64.0 / scale)); } - gl.x_off = Math::round(glyph_pos[i].x_offset / (64.0 / scale)); + if (subpos) { + gl.x_off = glyph_pos[i].x_offset / (64.0 / scale); + } else { + gl.x_off = Math::round(glyph_pos[i].x_offset / (64.0 / scale)); + } gl.y_off = -Math::round(glyph_pos[i].y_offset / (64.0 / scale)); } if (sp_sp && is_whitespace(p_sd->text[glyph_info[i].cluster])) { @@ -4574,7 +4671,7 @@ bool TextServerAdvanced::shaped_text_shape(RID p_shaped) { return true; } - invalidate(sd); + invalidate(sd, false); if (sd->parent != RID()) { shaped_text_shape(sd->parent); ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(sd->parent); @@ -4733,70 +4830,7 @@ bool TextServerAdvanced::shaped_text_shape(RID p_shaped) { } } - // Align embedded objects to baseline. - float full_ascent = sd->ascent; - float full_descent = sd->descent; - for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { - if (sd->orientation == ORIENTATION_HORIZONTAL) { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.y = -sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.y = (-sd->ascent + sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.y = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.y = sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.y -= E.value.rect.size.y; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.y -= E.value.rect.size.y / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.y); - full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y); - } else { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.x = -sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.x = (-sd->ascent + sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.x = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.x = sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.x -= E.value.rect.size.x; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.x -= E.value.rect.size.x / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.x); - full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x); - } - } - sd->ascent = full_ascent; - sd->descent = full_descent; + _realign(sd); sd->valid = true; return sd->valid; } @@ -4863,7 +4897,7 @@ Array TextServerAdvanced::shaped_text_get_objects(RID p_shaped) const { ERR_FAIL_COND_V(!sd, ret); MutexLock lock(sd->mutex); - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { + for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : sd->objects) { ret.push_back(E.key); } @@ -4891,9 +4925,9 @@ Size2 TextServerAdvanced::shaped_text_get_size(RID p_shaped) const { const_cast<TextServerAdvanced *>(this)->shaped_text_shape(p_shaped); } if (sd->orientation == TextServer::ORIENTATION_HORIZONTAL) { - return Size2((sd->text_trimmed ? sd->width_trimmed : sd->width), sd->ascent + sd->descent); + return Size2((sd->text_trimmed ? sd->width_trimmed : sd->width), sd->ascent + sd->descent).ceil(); } else { - return Size2(sd->ascent + sd->descent, (sd->text_trimmed ? sd->width_trimmed : sd->width)); + return Size2(sd->ascent + sd->descent, (sd->text_trimmed ? sd->width_trimmed : sd->width)).ceil(); } } @@ -4927,7 +4961,7 @@ float TextServerAdvanced::shaped_text_get_width(RID p_shaped) const { if (!sd->valid) { const_cast<TextServerAdvanced *>(this)->shaped_text_shape(p_shaped); } - return (sd->text_trimmed ? sd->width_trimmed : sd->width); + return Math::ceil(sd->text_trimmed ? sd->width_trimmed : sd->width); } float TextServerAdvanced::shaped_text_get_underline_position(RID p_shaped) const { diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index f2ae24cae6..7841a15cd3 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -174,6 +174,7 @@ class TextServerAdvanced : public TextServer { int fixed_size = 0; bool force_autohinter = false; TextServer::Hinting hinting = TextServer::HINTING_LIGHT; + TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO; Dictionary variation_coordinates; float oversampling = 0.f; @@ -242,6 +243,21 @@ class TextServerAdvanced : public TextServer { // Shaped text cache data. struct ShapedTextDataAdvanced : public ShapedTextData { + struct Span { + int start = -1; + int end = -1; + + Vector<RID> fonts; + int font_size = 0; + + Variant embedded_key; + + String language; + Dictionary features; + Variant meta; + }; + Vector<Span> spans; + /* Intermediate data */ Char16String utf16; Vector<UBiDi *> bidi_iter; @@ -249,6 +265,11 @@ class TextServerAdvanced : public TextServer { ScriptIterator *script_iter = nullptr; hb_buffer_t *hb_buffer = nullptr; + HashMap<int, bool> jstops; + HashMap<int, bool> breaks; + bool break_ops_valid = false; + bool js_ops_valid = false; + ~ShapedTextDataAdvanced() { for (int i = 0; i < bidi_iter.size(); i++) { ubidi_close(bidi_iter[i]); @@ -268,6 +289,7 @@ class TextServerAdvanced : public TextServer { mutable RID_PtrOwner<FontDataAdvanced> font_owner; mutable RID_PtrOwner<ShapedTextDataAdvanced> shaped_owner; + void _realign(ShapedTextDataAdvanced *p_sd) const; int _convert_pos(const ShapedTextDataAdvanced *p_sd, int p_pos) const; int _convert_pos_inv(const ShapedTextDataAdvanced *p_sd, int p_pos) const; bool _shape_substr(ShapedTextDataAdvanced *p_new_sd, const ShapedTextDataAdvanced *p_sd, int p_start, int p_length) const; @@ -302,7 +324,7 @@ protected: static void _bind_methods(){}; void full_copy(ShapedTextDataAdvanced *p_shaped); - void invalidate(ShapedTextDataAdvanced *p_shaped); + void invalidate(ShapedTextDataAdvanced *p_shaped, bool p_text = false); public: virtual bool has_feature(Feature p_feature) const override; @@ -358,6 +380,9 @@ public: virtual void font_set_hinting(RID p_font_rid, TextServer::Hinting p_hinting) override; virtual TextServer::Hinting font_get_hinting(RID p_font_rid) const override; + virtual void font_set_subpixel_positioning(RID p_font_rid, SubpixelPositioning p_subpixel) override; + virtual SubpixelPositioning font_get_subpixel_positioning(RID p_font_rid) const override; + virtual void font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) override; virtual Dictionary font_get_variation_coordinates(RID p_font_rid) const override; @@ -482,10 +507,14 @@ public: virtual void shaped_text_set_preserve_control(RID p_shaped, bool p_enabled) override; virtual bool shaped_text_get_preserve_control(RID p_shaped) const override; - virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "") override; + virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant()) override; virtual bool shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int p_length = 1) override; virtual bool shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER) override; + virtual int shaped_get_span_count(RID p_shaped) const override; + virtual Variant shaped_get_span_meta(RID p_shaped, int p_index) const override; + virtual void shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary()) override; + virtual RID shaped_text_substr(RID p_shaped, int p_start, int p_length) const override; virtual RID shaped_text_get_parent(RID p_shaped) const override; diff --git a/modules/text_server_fb/doc_classes/TextServerFallback.xml b/modules/text_server_fb/doc_classes/TextServerFallback.xml index 8aadf2b882..76194a7bda 100644 --- a/modules/text_server_fb/doc_classes/TextServerFallback.xml +++ b/modules/text_server_fb/doc_classes/TextServerFallback.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="TextServerFallback" inherits="TextServer" version="4.0"> +<class name="TextServerFallback" inherits="TextServer" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Fallback implementation of the Text Server, without BiDi and complex text layout support. </brief_description> diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index 4a0415fbf9..e6f9bcf131 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -44,30 +44,6 @@ #endif /*************************************************************************/ -/* Character properties. */ -/*************************************************************************/ - -_FORCE_INLINE_ bool is_control(char32_t p_char) { - return (p_char <= 0x001f) || (p_char >= 0x007f && p_char <= 0x009F); -} - -_FORCE_INLINE_ bool is_whitespace(char32_t p_char) { - return (p_char == 0x0020) || (p_char == 0x00A0) || (p_char == 0x1680) || (p_char >= 0x2000 && p_char <= 0x200a) || (p_char == 0x202f) || (p_char == 0x205f) || (p_char == 0x3000) || (p_char == 0x2028) || (p_char == 0x2029) || (p_char >= 0x0009 && p_char <= 0x000d) || (p_char == 0x0085); -} - -_FORCE_INLINE_ bool is_linebreak(char32_t p_char) { - return (p_char >= 0x000a && p_char <= 0x000d) || (p_char == 0x0085) || (p_char == 0x2028) || (p_char == 0x2029); -} - -_FORCE_INLINE_ bool is_punct(char32_t p_char) { - return (p_char >= 0x0020 && p_char <= 0x002F) || (p_char >= 0x003A && p_char <= 0x0040) || (p_char >= 0x005B && p_char <= 0x005E) || (p_char == 0x0060) || (p_char >= 0x007B && p_char <= 0x007E) || (p_char >= 0x2000 && p_char <= 0x206F) || (p_char >= 0x3000 && p_char <= 0x303F); -} - -_FORCE_INLINE_ bool is_underscore(char32_t p_char) { - return (p_char == 0x005F); -} - -/*************************************************************************/ String TextServerFallback::interface_name = "Fallback"; uint32_t TextServerFallback::interface_features = 0; // Nothing is supported. @@ -91,7 +67,7 @@ void TextServerFallback::free(RID p_rid) { font_owner.free(p_rid); memdelete(fd); } else if (shaped_owner.owns(p_rid)) { - ShapedTextData *sd = shaped_owner.get_or_null(p_rid); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_rid); shaped_owner.free(p_rid); memdelete(sd); } @@ -572,12 +548,14 @@ _FORCE_INLINE_ TextServerFallback::FontGlyph TextServerFallback::rasterize_bitma _FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontDataFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph) const { ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size), false); + int32_t glyph_index = p_glyph & 0xFFFFFF; // Remove subpixel shifts. + FontDataForSizeFallback *fd = p_font_data->cache[p_size]; if (fd->glyph_map.has(p_glyph)) { return fd->glyph_map[p_glyph].found; } - if (p_glyph == 0) { // Non graphical or invalid glyph, do not render. + if (glyph_index == 0) { // Non graphical or invalid glyph, do not render. fd->glyph_map[p_glyph] = FontGlyph(); return true; } @@ -608,8 +586,6 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontDataFallback *p_font_d flags |= FT_LOAD_COLOR; } - int32_t glyph_index = FT_Get_Char_Index(fd->face, p_glyph); - FT_Fixed v, h; FT_Get_Advance(fd->face, glyph_index, flags, &h); FT_Get_Advance(fd->face, glyph_index, flags | FT_LOAD_VERTICAL_LAYOUT, &v); @@ -620,6 +596,16 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontDataFallback *p_font_d return false; } + if (!p_font_data->msdf) { + if ((p_font_data->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (p_font_data->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && p_size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) { + FT_Pos xshift = (int)((p_glyph >> 27) & 3) << 4; + FT_Outline_Translate(&fd->face->glyph->outline, xshift, 0); + } else if ((p_font_data->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (p_font_data->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && p_size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { + FT_Pos xshift = (int)((p_glyph >> 27) & 3) << 5; + FT_Outline_Translate(&fd->face->glyph->outline, xshift, 0); + } + } + if (!outline) { if (!p_font_data->msdf) { error = FT_Render_Glyph(fd->face->glyph, p_font_data->antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO); @@ -1040,6 +1026,24 @@ TextServer::Hinting TextServerFallback::font_get_hinting(RID p_font_rid) const { return fd->hinting; } +void TextServerFallback::font_set_subpixel_positioning(RID p_font_rid, TextServer::SubpixelPositioning p_subpixel) { + FontDataFallback *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + if (fd->subpixel_positioning != p_subpixel) { + fd->subpixel_positioning = p_subpixel; + } +} + +TextServer::SubpixelPositioning TextServerFallback::font_get_subpixel_positioning(RID p_font_rid) const { + FontDataFallback *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND_V(!fd, SUBPIXEL_POSITIONING_DISABLED); + + MutexLock lock(fd->mutex); + return fd->subpixel_positioning; +} + void TextServerFallback::font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) { FontDataFallback *fd = font_owner.get_or_null(p_font_rid); ERR_FAIL_COND(!fd); @@ -1457,6 +1461,8 @@ Vector2 TextServerFallback::font_get_glyph_advance(RID p_font_rid, int p_size, i if (fd->msdf) { return gl[p_glyph].advance * (float)p_size / (float)fd->msdf_source_size; + } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_DISABLED) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x > SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { + return gl[p_glyph].advance.round(); } else { return gl[p_glyph].advance; } @@ -1805,7 +1811,26 @@ void TextServerFallback::font_render_range(RID p_font_rid, const Vector2i &p_siz Vector2i size = _get_size_outline(fd, p_size); ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); for (char32_t i = p_start; i <= p_end; i++) { - _ensure_glyph(fd, size, (int32_t)i); +#ifdef MODULE_FREETYPE_ENABLED + int32_t idx = i; + if (fd->cache[size]->face) { + if (fd->msdf) { + _ensure_glyph(fd, size, (int32_t)idx); + } else { + if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) { + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27)); + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27)); + _ensure_glyph(fd, size, (int32_t)idx | (2 << 27)); + _ensure_glyph(fd, size, (int32_t)idx | (3 << 27)); + } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27)); + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27)); + } else { + _ensure_glyph(fd, size, (int32_t)idx); + } + } + } +#endif } } @@ -1816,7 +1841,26 @@ void TextServerFallback::font_render_glyph(RID p_font_rid, const Vector2i &p_siz MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - ERR_FAIL_COND(!_ensure_glyph(fd, size, p_index)); +#ifdef MODULE_FREETYPE_ENABLED + int32_t idx = p_index; + if (fd->cache[size]->face) { + if (fd->msdf) { + _ensure_glyph(fd, size, (int32_t)idx); + } else { + if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) { + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27)); + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27)); + _ensure_glyph(fd, size, (int32_t)idx | (2 << 27)); + _ensure_glyph(fd, size, (int32_t)idx | (3 << 27)); + } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27)); + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27)); + } else { + _ensure_glyph(fd, size, (int32_t)idx); + } + } + } +#endif } void TextServerFallback::font_draw_glyph(RID p_font_rid, RID p_canvas, int p_size, const Vector2 &p_pos, int32_t p_index, const Color &p_color) const { @@ -1826,11 +1870,26 @@ void TextServerFallback::font_draw_glyph(RID p_font_rid, RID p_canvas, int p_siz MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - if (!_ensure_glyph(fd, size, p_index)) { + + int32_t index = p_index; + +#ifdef MODULE_FREETYPE_ENABLED + if (!fd->msdf && fd->cache[size]->face) { + if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) { + int xshift = (int)(Math::floor(4 * (p_pos.x + 0.125)) - 4 * Math::floor(p_pos.x + 0.125)); + index = index | (xshift << 27); + } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { + int xshift = (int)(Math::floor(2 * (p_pos.x + 0.25)) - 2 * Math::floor(p_pos.x + 0.25)); + index = index | (xshift << 27); + } + } +#endif + + if (!_ensure_glyph(fd, size, index)) { return; // Invalid or non-graphical glyph, do not display errors, nothing to draw. } - const FontGlyph &gl = fd->cache[size]->glyph_map[p_index]; + const FontGlyph &gl = fd->cache[size]->glyph_map[index]; if (gl.found) { ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size()); @@ -1849,7 +1908,15 @@ void TextServerFallback::font_draw_glyph(RID p_font_rid, RID p_canvas, int p_siz Size2 csize = gl.rect.size * (float)p_size / (float)fd->msdf_source_size; RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, 0, fd->msdf_range); } else { - Point2 cpos = p_pos.floor(); + Point2 cpos = p_pos; + cpos.y = Math::floor(cpos.y); + if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) { + cpos.x = ((int)Math::floor(cpos.x + 0.125)); + } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { + cpos.x = ((int)Math::floor(cpos.x + 0.25)); + } else { + cpos.x = Math::floor(cpos.x); + } cpos += gl.rect.position; Size2 csize = gl.rect.size; RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false); @@ -1866,11 +1933,26 @@ void TextServerFallback::font_draw_glyph_outline(RID p_font_rid, RID p_canvas, i MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, Vector2i(p_size, p_outline_size)); ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - if (!_ensure_glyph(fd, size, p_index)) { + + int32_t index = p_index; + +#ifdef MODULE_FREETYPE_ENABLED + if (!fd->msdf && fd->cache[size]->face) { + if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) { + int xshift = (int)(Math::floor(4 * (p_pos.x + 0.125)) - 4 * Math::floor(p_pos.x + 0.125)); + index = index | (xshift << 27); + } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { + int xshift = (int)(Math::floor(2 * (p_pos.x + 0.25)) - 2 * Math::floor(p_pos.x + 0.25)); + index = index | (xshift << 27); + } + } +#endif + + if (!_ensure_glyph(fd, size, index)) { return; // Invalid or non-graphical glyph, do not display errors, nothing to draw. } - const FontGlyph &gl = fd->cache[size]->glyph_map[p_index]; + const FontGlyph &gl = fd->cache[size]->glyph_map[index]; if (gl.found) { ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size()); @@ -1889,7 +1971,15 @@ void TextServerFallback::font_draw_glyph_outline(RID p_font_rid, RID p_canvas, i Size2 csize = gl.rect.size * (float)p_size / (float)fd->msdf_source_size; RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, p_outline_size * 2, fd->msdf_range); } else { - Point2 cpos = p_pos.floor(); + Point2 cpos = p_pos; + cpos.y = Math::floor(cpos.y); + if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) { + cpos.x = ((int)Math::floor(cpos.x + 0.125)); + } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { + cpos.x = ((int)Math::floor(cpos.x + 0.25)); + } else { + cpos.x = Math::floor(cpos.x); + } cpos += gl.rect.position; Size2 csize = gl.rect.size; RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false); @@ -2061,7 +2151,7 @@ void TextServerFallback::font_set_global_oversampling(float p_oversampling) { /* Shaped text buffer interface */ /*************************************************************************/ -void TextServerFallback::invalidate(ShapedTextData *p_shaped) { +void TextServerFallback::invalidate(ShapedTextDataFallback *p_shaped) { p_shaped->valid = false; p_shaped->sort_valid = false; p_shaped->line_breaks_valid = false; @@ -2075,17 +2165,17 @@ void TextServerFallback::invalidate(ShapedTextData *p_shaped) { p_shaped->glyphs_logical.clear(); } -void TextServerFallback::full_copy(ShapedTextData *p_shaped) { - ShapedTextData *parent = shaped_owner.get_or_null(p_shaped->parent); +void TextServerFallback::full_copy(ShapedTextDataFallback *p_shaped) { + ShapedTextDataFallback *parent = shaped_owner.get_or_null(p_shaped->parent); - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : parent->objects) { + for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : parent->objects) { if (E.value.pos >= p_shaped->start && E.value.pos < p_shaped->end) { p_shaped->objects[E.key] = E.value; } } for (int k = 0; k < parent->spans.size(); k++) { - ShapedTextData::Span span = parent->spans[k]; + ShapedTextDataFallback::Span span = parent->spans[k]; if (span.start >= p_shaped->end || span.end <= p_shaped->start) { continue; } @@ -2099,7 +2189,7 @@ void TextServerFallback::full_copy(ShapedTextData *p_shaped) { RID TextServerFallback::create_shaped_text(TextServer::Direction p_direction, TextServer::Orientation p_orientation) { _THREAD_SAFE_METHOD_ - ShapedTextData *sd = memnew(ShapedTextData); + ShapedTextDataFallback *sd = memnew(ShapedTextDataFallback); sd->direction = p_direction; sd->orientation = p_orientation; @@ -2107,7 +2197,7 @@ RID TextServerFallback::create_shaped_text(TextServer::Direction p_direction, Te } void TextServerFallback::shaped_text_clear(RID p_shaped) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND(!sd); MutexLock lock(sd->mutex); @@ -2136,7 +2226,7 @@ TextServer::Direction TextServerFallback::shaped_text_get_inferred_direction(RID void TextServerFallback::shaped_text_set_custom_punctuation(RID p_shaped, const String &p_punct) { _THREAD_SAFE_METHOD_ - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND(!sd); if (sd->custom_punct != p_punct) { @@ -2150,13 +2240,13 @@ void TextServerFallback::shaped_text_set_custom_punctuation(RID p_shaped, const String TextServerFallback::shaped_text_get_custom_punctuation(RID p_shaped) const { _THREAD_SAFE_METHOD_ - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, String()); return sd->custom_punct; } void TextServerFallback::shaped_text_set_orientation(RID p_shaped, TextServer::Orientation p_orientation) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND(!sd); MutexLock lock(sd->mutex); @@ -2174,7 +2264,7 @@ void TextServerFallback::shaped_text_set_bidi_override(RID p_shaped, const Array } TextServer::Orientation TextServerFallback::shaped_text_get_orientation(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, TextServer::ORIENTATION_HORIZONTAL); MutexLock lock(sd->mutex); @@ -2182,7 +2272,7 @@ TextServer::Orientation TextServerFallback::shaped_text_get_orientation(RID p_sh } void TextServerFallback::shaped_text_set_preserve_invalid(RID p_shaped, bool p_enabled) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); MutexLock lock(sd->mutex); ERR_FAIL_COND(!sd); @@ -2196,7 +2286,7 @@ void TextServerFallback::shaped_text_set_preserve_invalid(RID p_shaped, bool p_e } bool TextServerFallback::shaped_text_get_preserve_invalid(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -2204,7 +2294,7 @@ bool TextServerFallback::shaped_text_get_preserve_invalid(RID p_shaped) const { } void TextServerFallback::shaped_text_set_preserve_control(RID p_shaped, bool p_enabled) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND(!sd); MutexLock lock(sd->mutex); @@ -2218,15 +2308,52 @@ void TextServerFallback::shaped_text_set_preserve_control(RID p_shaped, bool p_e } bool TextServerFallback::shaped_text_get_preserve_control(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); return sd->preserve_control; } -bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); +int TextServerFallback::shaped_get_span_count(RID p_shaped) const { + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V(!sd, 0); + return sd->spans.size(); +} + +Variant TextServerFallback::shaped_get_span_meta(RID p_shaped, int p_index) const { + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V(!sd, Variant()); + ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant()); + return sd->spans[p_index].meta; +} + +void TextServerFallback::shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features) { + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND(!sd); + ERR_FAIL_INDEX(p_index, sd->spans.size()); + + ShapedTextDataFallback::Span &span = sd->spans.write[p_index]; + span.fonts.clear(); + // Pre-sort fonts, push fonts with the language support first. + Vector<RID> fonts_no_match; + int font_count = p_fonts.size(); + for (int i = 0; i < font_count; i++) { + if (font_is_language_supported(p_fonts[i], span.language)) { + span.fonts.push_back(p_fonts[i]); + } else { + fonts_no_match.push_back(p_fonts[i]); + } + } + span.fonts.append_array(fonts_no_match); + span.font_size = p_size; + span.features = p_opentype_features; + + sd->valid = false; +} + +bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) { + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -2244,7 +2371,7 @@ bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_te full_copy(sd); } - ShapedTextData::Span span; + ShapedTextDataFallback::Span span; span.start = sd->text.length(); span.end = span.start + p_text.length(); @@ -2263,6 +2390,7 @@ bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_te ERR_FAIL_COND_V(span.fonts.is_empty(), false); span.font_size = p_size; span.language = p_language; + span.meta = p_meta; sd->spans.push_back(span); sd->text += p_text; @@ -2273,7 +2401,7 @@ bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_te } bool TextServerFallback::shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align, int p_length) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -2284,12 +2412,12 @@ bool TextServerFallback::shaped_text_add_object(RID p_shaped, Variant p_key, con full_copy(sd); } - ShapedTextData::Span span; + ShapedTextDataFallback::Span span; span.start = sd->start + sd->text.length(); span.end = span.start + p_length; span.embedded_key = p_key; - ShapedTextData::EmbeddedObject obj; + ShapedTextDataFallback::EmbeddedObject obj; obj.inline_align = p_inline_align; obj.rect.size = p_size; obj.pos = span.start; @@ -2304,7 +2432,7 @@ bool TextServerFallback::shaped_text_add_object(RID p_shaped, Variant p_key, con } bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -2324,7 +2452,7 @@ bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key, Glyph gl = sd->glyphs[i]; Variant key; if (gl.count == 1) { - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { + for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) { if (E.value.pos == gl.start) { key = E.key; break; @@ -2364,79 +2492,82 @@ bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key, sd->width += gl.advance * gl.repeat; } } + _realign(sd); + } + return true; +} - // Align embedded objects to baseline. - float full_ascent = sd->ascent; - float full_descent = sd->descent; - for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { - if ((E.value.pos >= sd->start) && (E.value.pos < sd->end)) { - if (sd->orientation == ORIENTATION_HORIZONTAL) { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.y = -sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.y = (-sd->ascent + sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.y = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.y = sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.y -= E.value.rect.size.y; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.y -= E.value.rect.size.y / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.y); - full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y); - } else { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.x = -sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.x = (-sd->ascent + sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.x = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.x = sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.x -= E.value.rect.size.x; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.x -= E.value.rect.size.x / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.x); - full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x); +void TextServerFallback::_realign(ShapedTextDataFallback *p_sd) const { + // Align embedded objects to baseline. + float full_ascent = p_sd->ascent; + float full_descent = p_sd->descent; + for (KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : p_sd->objects) { + if ((E.value.pos >= p_sd->start) && (E.value.pos < p_sd->end)) { + if (p_sd->orientation == ORIENTATION_HORIZONTAL) { + switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { + case INLINE_ALIGNMENT_TO_TOP: { + E.value.rect.position.y = -p_sd->ascent; + } break; + case INLINE_ALIGNMENT_TO_CENTER: { + E.value.rect.position.y = (-p_sd->ascent + p_sd->descent) / 2; + } break; + case INLINE_ALIGNMENT_TO_BASELINE: { + E.value.rect.position.y = 0; + } break; + case INLINE_ALIGNMENT_TO_BOTTOM: { + E.value.rect.position.y = p_sd->descent; + } break; + } + switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { + case INLINE_ALIGNMENT_BOTTOM_TO: { + E.value.rect.position.y -= E.value.rect.size.y; + } break; + case INLINE_ALIGNMENT_CENTER_TO: { + E.value.rect.position.y -= E.value.rect.size.y / 2; + } break; + case INLINE_ALIGNMENT_TOP_TO: { + // NOP + } break; + } + full_ascent = MAX(full_ascent, -E.value.rect.position.y); + full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y); + } else { + switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { + case INLINE_ALIGNMENT_TO_TOP: { + E.value.rect.position.x = -p_sd->ascent; + } break; + case INLINE_ALIGNMENT_TO_CENTER: { + E.value.rect.position.x = (-p_sd->ascent + p_sd->descent) / 2; + } break; + case INLINE_ALIGNMENT_TO_BASELINE: { + E.value.rect.position.x = 0; + } break; + case INLINE_ALIGNMENT_TO_BOTTOM: { + E.value.rect.position.x = p_sd->descent; + } break; + } + switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { + case INLINE_ALIGNMENT_BOTTOM_TO: { + E.value.rect.position.x -= E.value.rect.size.x; + } break; + case INLINE_ALIGNMENT_CENTER_TO: { + E.value.rect.position.x -= E.value.rect.size.x / 2; + } break; + case INLINE_ALIGNMENT_TOP_TO: { + // NOP + } break; } + full_ascent = MAX(full_ascent, -E.value.rect.position.x); + full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x); } } - sd->ascent = full_ascent; - sd->descent = full_descent; } - return true; + p_sd->ascent = full_ascent; + p_sd->descent = full_descent; } RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_length) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, RID()); MutexLock lock(sd->mutex); @@ -2450,7 +2581,7 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng ERR_FAIL_COND_V(sd->start > p_start || sd->end < p_start, RID()); ERR_FAIL_COND_V(sd->end < p_start + p_length, RID()); - ShapedTextData *new_sd = memnew(ShapedTextData); + ShapedTextDataFallback *new_sd = memnew(ShapedTextDataFallback); new_sd->parent = p_shaped; new_sd->start = p_start; new_sd->end = p_start + p_length; @@ -2476,7 +2607,7 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng Variant key; bool find_embedded = false; if (gl.count == 1) { - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { + for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) { if (E.value.pos == gl.start) { find_embedded = true; key = E.key; @@ -2520,7 +2651,7 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng // Align embedded objects to baseline. float full_ascent = new_sd->ascent; float full_descent = new_sd->descent; - for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : new_sd->objects) { + for (KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : new_sd->objects) { if ((E.value.pos >= new_sd->start) && (E.value.pos < new_sd->end)) { if (sd->orientation == ORIENTATION_HORIZONTAL) { switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { @@ -2590,7 +2721,7 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng } RID TextServerFallback::shaped_text_get_parent(RID p_shaped) const { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, RID()); MutexLock lock(sd->mutex); @@ -2598,7 +2729,7 @@ RID TextServerFallback::shaped_text_get_parent(RID p_shaped) const { } float TextServerFallback::shaped_text_fit_to_width(RID p_shaped, float p_width, uint16_t /*JustificationFlag*/ p_jst_flags) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); MutexLock lock(sd->mutex); @@ -2643,7 +2774,7 @@ float TextServerFallback::shaped_text_fit_to_width(RID p_shaped, float p_width, end_pos = sd->overrun_trim_data.trim_pos; justification_width = sd->width_trimmed; } else { - return sd->width; + return Math::ceil(sd->width); } } else { justification_width = sd->width; @@ -2703,11 +2834,11 @@ float TextServerFallback::shaped_text_fit_to_width(RID p_shaped, float p_width, sd->width = justification_width; } - return justification_width; + return Math::ceil(justification_width); } float TextServerFallback::shaped_text_tab_align(RID p_shaped, const PackedFloat32Array &p_tab_stops) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); MutexLock lock(sd->mutex); @@ -2763,7 +2894,7 @@ float TextServerFallback::shaped_text_tab_align(RID p_shaped, const PackedFloat3 } bool TextServerFallback::shaped_text_update_breaks(RID p_shaped) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -2819,7 +2950,7 @@ bool TextServerFallback::shaped_text_update_breaks(RID p_shaped) { } bool TextServerFallback::shaped_text_update_justification_ops(RID p_shaped) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -2835,7 +2966,7 @@ bool TextServerFallback::shaped_text_update_justification_ops(RID p_shaped) { } void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, float p_width, uint16_t p_trim_flags) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped_line); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped_line); ERR_FAIL_COND_MSG(!sd, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); @@ -2863,9 +2994,9 @@ void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl return; } - Vector<ShapedTextData::Span> &spans = sd->spans; + Vector<ShapedTextDataFallback::Span> &spans = sd->spans; if (sd->parent != RID()) { - ShapedTextData *parent_sd = shaped_owner.get_or_null(sd->parent); + ShapedTextDataFallback *parent_sd = shaped_owner.get_or_null(sd->parent); ERR_FAIL_COND(!parent_sd->valid); spans = parent_sd->spans; } @@ -2987,39 +3118,39 @@ void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl } int TextServerFallback::shaped_text_get_trim_pos(RID p_shaped) const { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextData invalid."); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.trim_pos; } int TextServerFallback::shaped_text_get_ellipsis_pos(RID p_shaped) const { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextData invalid."); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.ellipsis_pos; } const Glyph *TextServerFallback::shaped_text_get_ellipsis_glyphs(RID p_shaped) const { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, nullptr, "ShapedTextData invalid."); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V_MSG(!sd, nullptr, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.ellipsis_glyph_buf.ptr(); } int TextServerFallback::shaped_text_get_ellipsis_glyph_count(RID p_shaped) const { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, 0, "ShapedTextData invalid."); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V_MSG(!sd, 0, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.ellipsis_glyph_buf.size(); } bool TextServerFallback::shaped_text_shape(RID p_shaped) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -3046,7 +3177,7 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) { // "Shape" string. for (int i = 0; i < sd->spans.size(); i++) { - const ShapedTextData::Span &span = sd->spans[i]; + const ShapedTextDataFallback::Span &span = sd->spans[i]; if (span.embedded_key != Variant()) { // Embedded object. if (sd->orientation == ORIENTATION_HORIZONTAL) { @@ -3092,6 +3223,7 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) { } if (gl.font_rid.is_valid()) { + bool subpos = (font_get_subpixel_positioning(gl.font_rid) == SUBPIXEL_POSITIONING_ONE_HALF) || (font_get_subpixel_positioning(gl.font_rid) == SUBPIXEL_POSITIONING_ONE_QUARTER) || (font_get_subpixel_positioning(gl.font_rid) == SUBPIXEL_POSITIONING_AUTO && gl.font_size <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE); if (sd->text[j - sd->start] != 0 && !is_linebreak(sd->text[j - sd->start])) { if (sd->orientation == ORIENTATION_HORIZONTAL) { gl.advance = Math::round(font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x); @@ -3126,6 +3258,9 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) { } } } + if (sd->orientation == ORIENTATION_HORIZONTAL && !subpos) { + gl.advance = Math::round(gl.advance); + } } else if (sd->preserve_invalid || (sd->preserve_control && is_control(gl.index))) { // Glyph not found, replace with hex code box. if (sd->orientation == ORIENTATION_HORIZONTAL) { @@ -3146,7 +3281,7 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) { // Align embedded objects to baseline. float full_ascent = sd->ascent; float full_descent = sd->descent; - for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { + for (KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) { if (sd->orientation == ORIENTATION_HORIZONTAL) { switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { case INLINE_ALIGNMENT_TO_TOP: { @@ -3212,7 +3347,7 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) { } bool TextServerFallback::shaped_text_is_ready(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -3220,7 +3355,7 @@ bool TextServerFallback::shaped_text_is_ready(RID p_shaped) const { } const Glyph *TextServerFallback::shaped_text_get_glyphs(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, nullptr); MutexLock lock(sd->mutex); @@ -3231,7 +3366,7 @@ const Glyph *TextServerFallback::shaped_text_get_glyphs(RID p_shaped) const { } int TextServerFallback::shaped_text_get_glyph_count(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0); MutexLock lock(sd->mutex); @@ -3242,7 +3377,7 @@ int TextServerFallback::shaped_text_get_glyph_count(RID p_shaped) const { } const Glyph *TextServerFallback::shaped_text_sort_logical(RID p_shaped) { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, nullptr); MutexLock lock(sd->mutex); @@ -3254,7 +3389,7 @@ const Glyph *TextServerFallback::shaped_text_sort_logical(RID p_shaped) { } Vector2i TextServerFallback::shaped_text_get_range(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, Vector2i()); MutexLock lock(sd->mutex); @@ -3263,11 +3398,11 @@ Vector2i TextServerFallback::shaped_text_get_range(RID p_shaped) const { Array TextServerFallback::shaped_text_get_objects(RID p_shaped) const { Array ret; - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, ret); MutexLock lock(sd->mutex); - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { + for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) { ret.push_back(E.key); } @@ -3275,7 +3410,7 @@ Array TextServerFallback::shaped_text_get_objects(RID p_shaped) const { } Rect2 TextServerFallback::shaped_text_get_object_rect(RID p_shaped, Variant p_key) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, Rect2()); MutexLock lock(sd->mutex); @@ -3287,7 +3422,7 @@ Rect2 TextServerFallback::shaped_text_get_object_rect(RID p_shaped, Variant p_ke } Size2 TextServerFallback::shaped_text_get_size(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, Size2()); MutexLock lock(sd->mutex); @@ -3295,14 +3430,14 @@ Size2 TextServerFallback::shaped_text_get_size(RID p_shaped) const { const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped); } if (sd->orientation == TextServer::ORIENTATION_HORIZONTAL) { - return Size2(sd->width, sd->ascent + sd->descent); + return Size2(sd->width, sd->ascent + sd->descent).ceil(); } else { - return Size2(sd->ascent + sd->descent, sd->width); + return Size2(sd->ascent + sd->descent, sd->width).ceil(); } } float TextServerFallback::shaped_text_get_ascent(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); MutexLock lock(sd->mutex); @@ -3313,7 +3448,7 @@ float TextServerFallback::shaped_text_get_ascent(RID p_shaped) const { } float TextServerFallback::shaped_text_get_descent(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); MutexLock lock(sd->mutex); @@ -3324,18 +3459,18 @@ float TextServerFallback::shaped_text_get_descent(RID p_shaped) const { } float TextServerFallback::shaped_text_get_width(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); MutexLock lock(sd->mutex); if (!sd->valid) { const_cast<TextServerFallback *>(this)->shaped_text_shape(p_shaped); } - return sd->width; + return Math::ceil(sd->width); } float TextServerFallback::shaped_text_get_underline_position(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); MutexLock lock(sd->mutex); @@ -3347,7 +3482,7 @@ float TextServerFallback::shaped_text_get_underline_position(RID p_shaped) const } float TextServerFallback::shaped_text_get_underline_thickness(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); MutexLock lock(sd->mutex); diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index b356b90d2c..91afd02ae9 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -139,6 +139,7 @@ class TextServerFallback : public TextServer { int fixed_size = 0; bool force_autohinter = false; TextServer::Hinting hinting = TextServer::HINTING_LIGHT; + TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO; Dictionary variation_coordinates; float oversampling = 0.f; @@ -203,17 +204,38 @@ class TextServerFallback : public TextServer { } } + // Shaped text cache data. + + struct ShapedTextDataFallback : public ShapedTextData { + struct Span { + int start = -1; + int end = -1; + + Vector<RID> fonts; + int font_size = 0; + + Variant embedded_key; + + String language; + Dictionary features; + Variant meta; + }; + Vector<Span> spans; + }; + // Common data. float oversampling = 1.f; mutable RID_PtrOwner<FontDataFallback> font_owner; - mutable RID_PtrOwner<ShapedTextData> shaped_owner; + mutable RID_PtrOwner<ShapedTextDataFallback> shaped_owner; + + void _realign(ShapedTextDataFallback *p_sd) const; protected: static void _bind_methods(){}; - void full_copy(ShapedTextData *p_shaped); - void invalidate(ShapedTextData *p_shaped); + void full_copy(ShapedTextDataFallback *p_shaped); + void invalidate(ShapedTextDataFallback *p_shaped); public: virtual bool has_feature(Feature p_feature) const override; @@ -269,6 +291,9 @@ public: virtual void font_set_hinting(RID p_font_rid, TextServer::Hinting p_hinting) override; virtual TextServer::Hinting font_get_hinting(RID p_font_rid) const override; + virtual void font_set_subpixel_positioning(RID p_font_rid, SubpixelPositioning p_subpixel) override; + virtual SubpixelPositioning font_get_subpixel_positioning(RID p_font_rid) const override; + virtual void font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) override; virtual Dictionary font_get_variation_coordinates(RID p_font_rid) const override; @@ -391,10 +416,14 @@ public: virtual void shaped_text_set_preserve_control(RID p_shaped, bool p_enabled) override; virtual bool shaped_text_get_preserve_control(RID p_shaped) const override; - virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "") override; + virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant()) override; virtual bool shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int p_length = 1) override; virtual bool shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER) override; + virtual int shaped_get_span_count(RID p_shaped) const override; + virtual Variant shaped_get_span_meta(RID p_shaped, int p_index) const override; + virtual void shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary()) override; + virtual RID shaped_text_substr(RID p_shaped, int p_start, int p_length) const override; virtual RID shaped_text_get_parent(RID p_shaped) const override; diff --git a/modules/theora/doc_classes/VideoStreamTheora.xml b/modules/theora/doc_classes/VideoStreamTheora.xml index 725f87b046..0f2dece8e7 100644 --- a/modules/theora/doc_classes/VideoStreamTheora.xml +++ b/modules/theora/doc_classes/VideoStreamTheora.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VideoStreamTheora" inherits="VideoStream" version="4.0"> +<class name="VideoStreamTheora" inherits="VideoStream" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> [VideoStream] resource for Ogg Theora videos. </brief_description> diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp index 9d297fcb93..a9652cbba1 100644 --- a/modules/theora/video_stream_theora.cpp +++ b/modules/theora/video_stream_theora.cpp @@ -337,6 +337,7 @@ void VideoStreamPlaybackTheora::set_file(const String &p_file) { Ref<Image> img; img.instantiate(); img->create(w, h, false, Image::FORMAT_RGBA8); + texture->create_from_image(img); } else { /* tear down the partial theora setup */ diff --git a/modules/upnp/doc_classes/UPNP.xml b/modules/upnp/doc_classes/UPNP.xml index 2cd0b8843a..066506922c 100644 --- a/modules/upnp/doc_classes/UPNP.xml +++ b/modules/upnp/doc_classes/UPNP.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="UPNP" inherits="RefCounted" version="4.0"> +<class name="UPNP" inherits="RefCounted" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> UPNP network functions. </brief_description> diff --git a/modules/upnp/doc_classes/UPNPDevice.xml b/modules/upnp/doc_classes/UPNPDevice.xml index b7c2ff7dd7..7749ac18ab 100644 --- a/modules/upnp/doc_classes/UPNPDevice.xml +++ b/modules/upnp/doc_classes/UPNPDevice.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="UPNPDevice" inherits="RefCounted" version="4.0"> +<class name="UPNPDevice" inherits="RefCounted" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> UPNP device. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScript.xml b/modules/visual_script/doc_classes/VisualScript.xml index a452974014..96310538bf 100644 --- a/modules/visual_script/doc_classes/VisualScript.xml +++ b/modules/visual_script/doc_classes/VisualScript.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScript" inherits="Script" version="4.0"> +<class name="VisualScript" inherits="Script" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A script implemented in the Visual Script programming environment. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptBasicTypeConstant.xml b/modules/visual_script/doc_classes/VisualScriptBasicTypeConstant.xml index ed5b814bb7..0ed66f44e1 100644 --- a/modules/visual_script/doc_classes/VisualScriptBasicTypeConstant.xml +++ b/modules/visual_script/doc_classes/VisualScriptBasicTypeConstant.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptBasicTypeConstant" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptBasicTypeConstant" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script node representing a constant from the base types. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptBuiltinFunc.xml b/modules/visual_script/doc_classes/VisualScriptBuiltinFunc.xml index 1ac5768755..647b627d25 100644 --- a/modules/visual_script/doc_classes/VisualScriptBuiltinFunc.xml +++ b/modules/visual_script/doc_classes/VisualScriptBuiltinFunc.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptBuiltinFunc" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptBuiltinFunc" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script node used to call built-in functions. </brief_description> @@ -96,123 +96,125 @@ <constant name="MATH_LERP" value="26" enum="BuiltinFunc"> Returns a number linearly interpolated between the first two inputs, based on the third input. Uses the formula [code]a + (a - b) * t[/code]. </constant> - <constant name="MATH_INVERSE_LERP" value="27" enum="BuiltinFunc"> + <constant name="MATH_CUBIC_INTERPOLATE" value="27" enum="BuiltinFunc"> </constant> - <constant name="MATH_RANGE_LERP" value="28" enum="BuiltinFunc"> + <constant name="MATH_INVERSE_LERP" value="28" enum="BuiltinFunc"> </constant> - <constant name="MATH_MOVE_TOWARD" value="29" enum="BuiltinFunc"> + <constant name="MATH_RANGE_LERP" value="29" enum="BuiltinFunc"> + </constant> + <constant name="MATH_MOVE_TOWARD" value="30" enum="BuiltinFunc"> Moves the number toward a value, based on the third input. </constant> - <constant name="MATH_RANDOMIZE" value="30" enum="BuiltinFunc"> + <constant name="MATH_RANDOMIZE" value="31" enum="BuiltinFunc"> Randomize the seed (or the internal state) of the random number generator. Current implementation reseeds using a number based on time. </constant> - <constant name="MATH_RANDI" value="31" enum="BuiltinFunc"> + <constant name="MATH_RANDI" value="32" enum="BuiltinFunc"> Returns a random 32 bits integer value. To obtain a random value between 0 to N (where N is smaller than 2^32 - 1), you can use it with the remainder function. </constant> - <constant name="MATH_RANDF" value="32" enum="BuiltinFunc"> + <constant name="MATH_RANDF" value="33" enum="BuiltinFunc"> Returns a random floating-point value between 0 and 1. To obtain a random value between 0 to N, you can use it with multiplication. </constant> - <constant name="MATH_RANDI_RANGE" value="33" enum="BuiltinFunc"> + <constant name="MATH_RANDI_RANGE" value="34" enum="BuiltinFunc"> Returns a random 32-bit integer value between the two inputs. </constant> - <constant name="MATH_RANDF_RANGE" value="34" enum="BuiltinFunc"> + <constant name="MATH_RANDF_RANGE" value="35" enum="BuiltinFunc"> Returns a random floating-point value between the two inputs. </constant> - <constant name="MATH_RANDFN" value="35" enum="BuiltinFunc"> + <constant name="MATH_RANDFN" value="36" enum="BuiltinFunc"> Returns a normally-distributed pseudo-random number, using Box-Muller transform with the specified mean and a standard deviation. This is also called Gaussian distribution. </constant> - <constant name="MATH_SEED" value="36" enum="BuiltinFunc"> + <constant name="MATH_SEED" value="37" enum="BuiltinFunc"> Set the seed for the random number generator. </constant> - <constant name="MATH_RANDSEED" value="37" enum="BuiltinFunc"> + <constant name="MATH_RANDSEED" value="38" enum="BuiltinFunc"> Returns a random value from the given seed, along with the new seed. </constant> - <constant name="MATH_DEG2RAD" value="38" enum="BuiltinFunc"> + <constant name="MATH_DEG2RAD" value="39" enum="BuiltinFunc"> Convert the input from degrees to radians. </constant> - <constant name="MATH_RAD2DEG" value="39" enum="BuiltinFunc"> + <constant name="MATH_RAD2DEG" value="40" enum="BuiltinFunc"> Convert the input from radians to degrees. </constant> - <constant name="MATH_LINEAR2DB" value="40" enum="BuiltinFunc"> + <constant name="MATH_LINEAR2DB" value="41" enum="BuiltinFunc"> Convert the input from linear volume to decibel volume. </constant> - <constant name="MATH_DB2LINEAR" value="41" enum="BuiltinFunc"> + <constant name="MATH_DB2LINEAR" value="42" enum="BuiltinFunc"> Convert the input from decibel volume to linear volume. </constant> - <constant name="MATH_WRAP" value="42" enum="BuiltinFunc"> + <constant name="MATH_WRAP" value="43" enum="BuiltinFunc"> </constant> - <constant name="MATH_WRAPF" value="43" enum="BuiltinFunc"> + <constant name="MATH_WRAPF" value="44" enum="BuiltinFunc"> </constant> - <constant name="MATH_PINGPONG" value="44" enum="BuiltinFunc"> + <constant name="MATH_PINGPONG" value="45" enum="BuiltinFunc"> Returns the [code]value[/code] wrapped between [code]0[/code] and the [code]length[/code]. If the limit is reached, the next value the function returned is decreased to the [code]0[/code] side or increased to the [code]length[/code] side (like a triangle wave). If [code]length[/code] is less than zero, it becomes positive. </constant> - <constant name="LOGIC_MAX" value="45" enum="BuiltinFunc"> + <constant name="LOGIC_MAX" value="46" enum="BuiltinFunc"> Returns the greater of the two numbers, also known as their maximum. </constant> - <constant name="LOGIC_MIN" value="46" enum="BuiltinFunc"> + <constant name="LOGIC_MIN" value="47" enum="BuiltinFunc"> Returns the lesser of the two numbers, also known as their minimum. </constant> - <constant name="LOGIC_CLAMP" value="47" enum="BuiltinFunc"> + <constant name="LOGIC_CLAMP" value="48" enum="BuiltinFunc"> Returns the input clamped inside the given range, ensuring the result is never outside it. Equivalent to [code]min(max(input, range_low), range_high)[/code]. </constant> - <constant name="LOGIC_NEAREST_PO2" value="48" enum="BuiltinFunc"> + <constant name="LOGIC_NEAREST_PO2" value="49" enum="BuiltinFunc"> Returns the nearest power of 2 to the input. </constant> - <constant name="OBJ_WEAKREF" value="49" enum="BuiltinFunc"> + <constant name="OBJ_WEAKREF" value="50" enum="BuiltinFunc"> Create a [WeakRef] from the input. </constant> - <constant name="TYPE_CONVERT" value="50" enum="BuiltinFunc"> + <constant name="TYPE_CONVERT" value="51" enum="BuiltinFunc"> Convert between types. </constant> - <constant name="TYPE_OF" value="51" enum="BuiltinFunc"> + <constant name="TYPE_OF" value="52" enum="BuiltinFunc"> Returns the type of the input as an integer. Check [enum Variant.Type] for the integers that might be returned. </constant> - <constant name="TYPE_EXISTS" value="52" enum="BuiltinFunc"> + <constant name="TYPE_EXISTS" value="53" enum="BuiltinFunc"> Checks if a type is registered in the [ClassDB]. </constant> - <constant name="TEXT_CHAR" value="53" enum="BuiltinFunc"> + <constant name="TEXT_CHAR" value="54" enum="BuiltinFunc"> Returns a character with the given ascii value. </constant> - <constant name="TEXT_STR" value="54" enum="BuiltinFunc"> + <constant name="TEXT_STR" value="55" enum="BuiltinFunc"> Convert the input to a string. </constant> - <constant name="TEXT_PRINT" value="55" enum="BuiltinFunc"> + <constant name="TEXT_PRINT" value="56" enum="BuiltinFunc"> Print the given string to the output window. </constant> - <constant name="TEXT_PRINTERR" value="56" enum="BuiltinFunc"> + <constant name="TEXT_PRINTERR" value="57" enum="BuiltinFunc"> Print the given string to the standard error output. </constant> - <constant name="TEXT_PRINTRAW" value="57" enum="BuiltinFunc"> + <constant name="TEXT_PRINTRAW" value="58" enum="BuiltinFunc"> Print the given string to the standard output, without adding a newline. </constant> - <constant name="TEXT_PRINT_VERBOSE" value="58" enum="BuiltinFunc"> + <constant name="TEXT_PRINT_VERBOSE" value="59" enum="BuiltinFunc"> </constant> - <constant name="VAR_TO_STR" value="59" enum="BuiltinFunc"> + <constant name="VAR_TO_STR" value="60" enum="BuiltinFunc"> Serialize a [Variant] to a string. </constant> - <constant name="STR_TO_VAR" value="60" enum="BuiltinFunc"> + <constant name="STR_TO_VAR" value="61" enum="BuiltinFunc"> Deserialize a [Variant] from a string serialized using [constant VAR_TO_STR]. </constant> - <constant name="VAR_TO_BYTES" value="61" enum="BuiltinFunc"> + <constant name="VAR_TO_BYTES" value="62" enum="BuiltinFunc"> Serialize a [Variant] to a [PackedByteArray]. </constant> - <constant name="BYTES_TO_VAR" value="62" enum="BuiltinFunc"> + <constant name="BYTES_TO_VAR" value="63" enum="BuiltinFunc"> Deserialize a [Variant] from a [PackedByteArray] serialized using [constant VAR_TO_BYTES]. </constant> - <constant name="MATH_SMOOTHSTEP" value="63" enum="BuiltinFunc"> + <constant name="MATH_SMOOTHSTEP" value="64" enum="BuiltinFunc"> Returns a number smoothly interpolated between the first two inputs, based on the third input. Similar to [constant MATH_LERP], but interpolates faster at the beginning and slower at the end. Using Hermite interpolation formula: [codeblock] var t = clamp((weight - from) / (to - from), 0.0, 1.0) return t * t * (3.0 - 2.0 * t) [/codeblock] </constant> - <constant name="MATH_POSMOD" value="64" enum="BuiltinFunc"> + <constant name="MATH_POSMOD" value="65" enum="BuiltinFunc"> </constant> - <constant name="MATH_LERP_ANGLE" value="65" enum="BuiltinFunc"> + <constant name="MATH_LERP_ANGLE" value="66" enum="BuiltinFunc"> </constant> - <constant name="TEXT_ORD" value="66" enum="BuiltinFunc"> + <constant name="TEXT_ORD" value="67" enum="BuiltinFunc"> </constant> - <constant name="FUNC_MAX" value="67" enum="BuiltinFunc"> + <constant name="FUNC_MAX" value="68" enum="BuiltinFunc"> Represents the size of the [enum BuiltinFunc] enum. </constant> </constants> diff --git a/modules/visual_script/doc_classes/VisualScriptClassConstant.xml b/modules/visual_script/doc_classes/VisualScriptClassConstant.xml index ae32500d2f..2509084f0e 100644 --- a/modules/visual_script/doc_classes/VisualScriptClassConstant.xml +++ b/modules/visual_script/doc_classes/VisualScriptClassConstant.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptClassConstant" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptClassConstant" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Gets a constant from a given class. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptComment.xml b/modules/visual_script/doc_classes/VisualScriptComment.xml index 5024aae384..cf4b57ca19 100644 --- a/modules/visual_script/doc_classes/VisualScriptComment.xml +++ b/modules/visual_script/doc_classes/VisualScriptComment.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptComment" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptComment" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script node used to annotate the script. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptComposeArray.xml b/modules/visual_script/doc_classes/VisualScriptComposeArray.xml index ed065759c5..ea73867c4b 100644 --- a/modules/visual_script/doc_classes/VisualScriptComposeArray.xml +++ b/modules/visual_script/doc_classes/VisualScriptComposeArray.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptComposeArray" inherits="VisualScriptLists" version="4.0"> +<class name="VisualScriptComposeArray" inherits="VisualScriptLists" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script Node used to create array from a list of items. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptCondition.xml b/modules/visual_script/doc_classes/VisualScriptCondition.xml index a5dd8c7c1b..a29388569f 100644 --- a/modules/visual_script/doc_classes/VisualScriptCondition.xml +++ b/modules/visual_script/doc_classes/VisualScriptCondition.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptCondition" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptCondition" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script node which branches the flow. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptConstant.xml b/modules/visual_script/doc_classes/VisualScriptConstant.xml index 388c2bddde..645ede9001 100644 --- a/modules/visual_script/doc_classes/VisualScriptConstant.xml +++ b/modules/visual_script/doc_classes/VisualScriptConstant.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptConstant" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptConstant" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Gets a contant's value. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptConstructor.xml b/modules/visual_script/doc_classes/VisualScriptConstructor.xml index 4a3d10aa8e..5ec17350bd 100644 --- a/modules/visual_script/doc_classes/VisualScriptConstructor.xml +++ b/modules/visual_script/doc_classes/VisualScriptConstructor.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptConstructor" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptConstructor" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script node which calls a base type constructor. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptCustomNode.xml b/modules/visual_script/doc_classes/VisualScriptCustomNode.xml index 486f60400a..97b89fb987 100644 --- a/modules/visual_script/doc_classes/VisualScriptCustomNode.xml +++ b/modules/visual_script/doc_classes/VisualScriptCustomNode.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptCustomNode" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptCustomNode" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A scripted Visual Script node. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptCustomNodes.xml b/modules/visual_script/doc_classes/VisualScriptCustomNodes.xml index 1681da7653..f04c862174 100644 --- a/modules/visual_script/doc_classes/VisualScriptCustomNodes.xml +++ b/modules/visual_script/doc_classes/VisualScriptCustomNodes.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptCustomNodes" inherits="Object" version="4.0"> +<class name="VisualScriptCustomNodes" inherits="Object" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Manages custom nodes for the Visual Script editor. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptDeconstruct.xml b/modules/visual_script/doc_classes/VisualScriptDeconstruct.xml index fd9a91c2a5..b544fd9d90 100644 --- a/modules/visual_script/doc_classes/VisualScriptDeconstruct.xml +++ b/modules/visual_script/doc_classes/VisualScriptDeconstruct.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptDeconstruct" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptDeconstruct" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script node which deconstructs a base type instance into its parts. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptEmitSignal.xml b/modules/visual_script/doc_classes/VisualScriptEmitSignal.xml index e102e02aa9..c0cefa0ab7 100644 --- a/modules/visual_script/doc_classes/VisualScriptEmitSignal.xml +++ b/modules/visual_script/doc_classes/VisualScriptEmitSignal.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptEmitSignal" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptEmitSignal" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Emits a specified signal. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptEngineSingleton.xml b/modules/visual_script/doc_classes/VisualScriptEngineSingleton.xml index 468cae852f..f60a048845 100644 --- a/modules/visual_script/doc_classes/VisualScriptEngineSingleton.xml +++ b/modules/visual_script/doc_classes/VisualScriptEngineSingleton.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptEngineSingleton" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptEngineSingleton" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script node returning a singleton from [@GlobalScope]. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptExpression.xml b/modules/visual_script/doc_classes/VisualScriptExpression.xml index 15e16a15f0..14750e7c8d 100644 --- a/modules/visual_script/doc_classes/VisualScriptExpression.xml +++ b/modules/visual_script/doc_classes/VisualScriptExpression.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptExpression" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptExpression" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script node that can execute a custom expression. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptFunction.xml b/modules/visual_script/doc_classes/VisualScriptFunction.xml index e0ca9eb280..74d9f194eb 100644 --- a/modules/visual_script/doc_classes/VisualScriptFunction.xml +++ b/modules/visual_script/doc_classes/VisualScriptFunction.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptFunction" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptFunction" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script node representing a function. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptFunctionCall.xml b/modules/visual_script/doc_classes/VisualScriptFunctionCall.xml index a98cb79106..543263ff8e 100644 --- a/modules/visual_script/doc_classes/VisualScriptFunctionCall.xml +++ b/modules/visual_script/doc_classes/VisualScriptFunctionCall.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptFunctionCall" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptFunctionCall" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script node for calling a function. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptFunctionState.xml b/modules/visual_script/doc_classes/VisualScriptFunctionState.xml index 0d7833446d..ef09c9d4a0 100644 --- a/modules/visual_script/doc_classes/VisualScriptFunctionState.xml +++ b/modules/visual_script/doc_classes/VisualScriptFunctionState.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptFunctionState" inherits="RefCounted" version="4.0"> +<class name="VisualScriptFunctionState" inherits="RefCounted" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script node representing a function state. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptGlobalConstant.xml b/modules/visual_script/doc_classes/VisualScriptGlobalConstant.xml index c6b5b22590..42ada99257 100644 --- a/modules/visual_script/doc_classes/VisualScriptGlobalConstant.xml +++ b/modules/visual_script/doc_classes/VisualScriptGlobalConstant.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptGlobalConstant" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptGlobalConstant" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script node returning a constant from [@GlobalScope]. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptIndexGet.xml b/modules/visual_script/doc_classes/VisualScriptIndexGet.xml index 78fd17c5fc..8828bf9039 100644 --- a/modules/visual_script/doc_classes/VisualScriptIndexGet.xml +++ b/modules/visual_script/doc_classes/VisualScriptIndexGet.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptIndexGet" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptIndexGet" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script node for getting a value from an array or a dictionary. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptIndexSet.xml b/modules/visual_script/doc_classes/VisualScriptIndexSet.xml index 0e5e832c65..5c81dcd339 100644 --- a/modules/visual_script/doc_classes/VisualScriptIndexSet.xml +++ b/modules/visual_script/doc_classes/VisualScriptIndexSet.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptIndexSet" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptIndexSet" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script node for setting a value in an array or a dictionary. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptInputAction.xml b/modules/visual_script/doc_classes/VisualScriptInputAction.xml index eb06d52314..51c2eaf353 100644 --- a/modules/visual_script/doc_classes/VisualScriptInputAction.xml +++ b/modules/visual_script/doc_classes/VisualScriptInputAction.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptInputAction" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptInputAction" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script node returning a state of an action. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptIterator.xml b/modules/visual_script/doc_classes/VisualScriptIterator.xml index d8305728c6..ef6846deba 100644 --- a/modules/visual_script/doc_classes/VisualScriptIterator.xml +++ b/modules/visual_script/doc_classes/VisualScriptIterator.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptIterator" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptIterator" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Steps through items in a given input. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptLists.xml b/modules/visual_script/doc_classes/VisualScriptLists.xml index 373e3c7191..27a81fce2f 100644 --- a/modules/visual_script/doc_classes/VisualScriptLists.xml +++ b/modules/visual_script/doc_classes/VisualScriptLists.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptLists" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptLists" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script virtual class for in-graph editable nodes. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptLocalVar.xml b/modules/visual_script/doc_classes/VisualScriptLocalVar.xml index 29dbddcdf4..dbf9049f0a 100644 --- a/modules/visual_script/doc_classes/VisualScriptLocalVar.xml +++ b/modules/visual_script/doc_classes/VisualScriptLocalVar.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptLocalVar" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptLocalVar" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Gets a local variable's value. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptLocalVarSet.xml b/modules/visual_script/doc_classes/VisualScriptLocalVarSet.xml index 96de8ebfdd..1ae4e20f97 100644 --- a/modules/visual_script/doc_classes/VisualScriptLocalVarSet.xml +++ b/modules/visual_script/doc_classes/VisualScriptLocalVarSet.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptLocalVarSet" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptLocalVarSet" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Changes a local variable's value. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptMathConstant.xml b/modules/visual_script/doc_classes/VisualScriptMathConstant.xml index f559083c80..01c36e763b 100644 --- a/modules/visual_script/doc_classes/VisualScriptMathConstant.xml +++ b/modules/visual_script/doc_classes/VisualScriptMathConstant.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptMathConstant" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptMathConstant" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Commonly used mathematical constants. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptNode.xml b/modules/visual_script/doc_classes/VisualScriptNode.xml index d080d9eac1..2eb99dc25f 100644 --- a/modules/visual_script/doc_classes/VisualScriptNode.xml +++ b/modules/visual_script/doc_classes/VisualScriptNode.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptNode" inherits="Resource" version="4.0"> +<class name="VisualScriptNode" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A node which is part of a [VisualScript]. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptOperator.xml b/modules/visual_script/doc_classes/VisualScriptOperator.xml index 73d28899f6..47ca6ddb90 100644 --- a/modules/visual_script/doc_classes/VisualScriptOperator.xml +++ b/modules/visual_script/doc_classes/VisualScriptOperator.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptOperator" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptOperator" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script node that performs an operation on two values. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptPreload.xml b/modules/visual_script/doc_classes/VisualScriptPreload.xml index e3d60c77bb..146d6cd9c3 100644 --- a/modules/visual_script/doc_classes/VisualScriptPreload.xml +++ b/modules/visual_script/doc_classes/VisualScriptPreload.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptPreload" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptPreload" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Creates a new [Resource] or loads one from the filesystem. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptPropertyGet.xml b/modules/visual_script/doc_classes/VisualScriptPropertyGet.xml index e9f30cb605..77cd6393a9 100644 --- a/modules/visual_script/doc_classes/VisualScriptPropertyGet.xml +++ b/modules/visual_script/doc_classes/VisualScriptPropertyGet.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptPropertyGet" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptPropertyGet" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script node returning a value of a property from an [Object]. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptPropertySet.xml b/modules/visual_script/doc_classes/VisualScriptPropertySet.xml index 96261d2c5e..6cffa328c7 100644 --- a/modules/visual_script/doc_classes/VisualScriptPropertySet.xml +++ b/modules/visual_script/doc_classes/VisualScriptPropertySet.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptPropertySet" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptPropertySet" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script node that sets a property of an [Object]. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptResourcePath.xml b/modules/visual_script/doc_classes/VisualScriptResourcePath.xml index 77e97a7219..6ca8260ade 100644 --- a/modules/visual_script/doc_classes/VisualScriptResourcePath.xml +++ b/modules/visual_script/doc_classes/VisualScriptResourcePath.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptResourcePath" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptResourcePath" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/visual_script/doc_classes/VisualScriptReturn.xml b/modules/visual_script/doc_classes/VisualScriptReturn.xml index 2193f45dc8..1d59392782 100644 --- a/modules/visual_script/doc_classes/VisualScriptReturn.xml +++ b/modules/visual_script/doc_classes/VisualScriptReturn.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptReturn" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptReturn" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Exits a function and returns an optional value. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptSceneNode.xml b/modules/visual_script/doc_classes/VisualScriptSceneNode.xml index ac672d9b3f..a769d11d94 100644 --- a/modules/visual_script/doc_classes/VisualScriptSceneNode.xml +++ b/modules/visual_script/doc_classes/VisualScriptSceneNode.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptSceneNode" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptSceneNode" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Node reference. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptSceneTree.xml b/modules/visual_script/doc_classes/VisualScriptSceneTree.xml index fc383593c5..84ab90892f 100644 --- a/modules/visual_script/doc_classes/VisualScriptSceneTree.xml +++ b/modules/visual_script/doc_classes/VisualScriptSceneTree.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptSceneTree" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptSceneTree" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script node for accessing [SceneTree] methods. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptSelect.xml b/modules/visual_script/doc_classes/VisualScriptSelect.xml index d536e623f7..1aa916f779 100644 --- a/modules/visual_script/doc_classes/VisualScriptSelect.xml +++ b/modules/visual_script/doc_classes/VisualScriptSelect.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptSelect" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptSelect" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Chooses between two input values. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptSelf.xml b/modules/visual_script/doc_classes/VisualScriptSelf.xml index 3c2bd16302..8cc59dbccd 100644 --- a/modules/visual_script/doc_classes/VisualScriptSelf.xml +++ b/modules/visual_script/doc_classes/VisualScriptSelf.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptSelf" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptSelf" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Outputs a reference to the current instance. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptSequence.xml b/modules/visual_script/doc_classes/VisualScriptSequence.xml index 32dcbb9837..9adbc30e0d 100644 --- a/modules/visual_script/doc_classes/VisualScriptSequence.xml +++ b/modules/visual_script/doc_classes/VisualScriptSequence.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptSequence" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptSequence" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Executes a series of Sequence ports. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptSubCall.xml b/modules/visual_script/doc_classes/VisualScriptSubCall.xml index fdf0e24d3e..535e89fc82 100644 --- a/modules/visual_script/doc_classes/VisualScriptSubCall.xml +++ b/modules/visual_script/doc_classes/VisualScriptSubCall.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptSubCall" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptSubCall" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Calls a method called [code]_subcall[/code] in this object. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptSwitch.xml b/modules/visual_script/doc_classes/VisualScriptSwitch.xml index 8e176b56f0..7befe89f50 100644 --- a/modules/visual_script/doc_classes/VisualScriptSwitch.xml +++ b/modules/visual_script/doc_classes/VisualScriptSwitch.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptSwitch" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptSwitch" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Branches program flow based on a given input's value. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptTypeCast.xml b/modules/visual_script/doc_classes/VisualScriptTypeCast.xml index ee8e2ad31e..ec84a75601 100644 --- a/modules/visual_script/doc_classes/VisualScriptTypeCast.xml +++ b/modules/visual_script/doc_classes/VisualScriptTypeCast.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptTypeCast" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptTypeCast" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script node that casts the given value to another type. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptVariableGet.xml b/modules/visual_script/doc_classes/VisualScriptVariableGet.xml index e29765d616..8d99b4b9d0 100644 --- a/modules/visual_script/doc_classes/VisualScriptVariableGet.xml +++ b/modules/visual_script/doc_classes/VisualScriptVariableGet.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptVariableGet" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptVariableGet" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Gets a variable's value. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptVariableSet.xml b/modules/visual_script/doc_classes/VisualScriptVariableSet.xml index b2cc70d62e..4f568cc0f6 100644 --- a/modules/visual_script/doc_classes/VisualScriptVariableSet.xml +++ b/modules/visual_script/doc_classes/VisualScriptVariableSet.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptVariableSet" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptVariableSet" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Changes a variable's value. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptWhile.xml b/modules/visual_script/doc_classes/VisualScriptWhile.xml index f090568608..4e7cccef17 100644 --- a/modules/visual_script/doc_classes/VisualScriptWhile.xml +++ b/modules/visual_script/doc_classes/VisualScriptWhile.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptWhile" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptWhile" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Conditional loop. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptYield.xml b/modules/visual_script/doc_classes/VisualScriptYield.xml index bb7fd8bfb5..ec757a3ac4 100644 --- a/modules/visual_script/doc_classes/VisualScriptYield.xml +++ b/modules/visual_script/doc_classes/VisualScriptYield.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptYield" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptYield" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script node used to pause a function execution. </brief_description> diff --git a/modules/visual_script/doc_classes/VisualScriptYieldSignal.xml b/modules/visual_script/doc_classes/VisualScriptYieldSignal.xml index ad6a7fb4e2..c3f4bc49c5 100644 --- a/modules/visual_script/doc_classes/VisualScriptYieldSignal.xml +++ b/modules/visual_script/doc_classes/VisualScriptYieldSignal.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualScriptYieldSignal" inherits="VisualScriptNode" version="4.0"> +<class name="VisualScriptYieldSignal" inherits="VisualScriptNode" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A Visual Script node yielding for a signal. </brief_description> diff --git a/modules/visual_script/editor/visual_script_editor.cpp b/modules/visual_script/editor/visual_script_editor.cpp index b00b5a2ddd..813902b54e 100644 --- a/modules/visual_script/editor/visual_script_editor.cpp +++ b/modules/visual_script/editor/visual_script_editor.cpp @@ -653,7 +653,6 @@ void VisualScriptEditor::_update_graph(int p_only_id) { List<int> ids; script->get_node_list(&ids); - StringName editor_icons = "EditorIcons"; for (int &E : ids) { if (p_only_id >= 0 && p_only_id != E) { @@ -1082,6 +1081,7 @@ void VisualScriptEditor::_update_members() { Control::get_theme_icon(SNAME("Basis"), SNAME("EditorIcons")), Control::get_theme_icon(SNAME("Transform3D"), SNAME("EditorIcons")), Control::get_theme_icon(SNAME("Color"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("StringName"), SNAME("EditorIcons")), Control::get_theme_icon(SNAME("NodePath"), SNAME("EditorIcons")), Control::get_theme_icon(SNAME("RID"), SNAME("EditorIcons")), Control::get_theme_icon(SNAME("MiniObject"), SNAME("EditorIcons")), @@ -1091,7 +1091,9 @@ void VisualScriptEditor::_update_members() { Control::get_theme_icon(SNAME("Array"), SNAME("EditorIcons")), Control::get_theme_icon(SNAME("PackedByteArray"), SNAME("EditorIcons")), Control::get_theme_icon(SNAME("PackedInt32Array"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("PackedInt64Array"), SNAME("EditorIcons")), Control::get_theme_icon(SNAME("PackedFloat32Array"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("PackedFloat64Array"), SNAME("EditorIcons")), Control::get_theme_icon(SNAME("PackedStringArray"), SNAME("EditorIcons")), Control::get_theme_icon(SNAME("PackedVector2Array"), SNAME("EditorIcons")), Control::get_theme_icon(SNAME("PackedVector3Array"), SNAME("EditorIcons")), @@ -1100,6 +1102,7 @@ void VisualScriptEditor::_update_members() { List<StringName> var_names; script->get_variable_list(&var_names); + var_names.sort_custom<StringName::AlphCompare>(); for (const StringName &E : var_names) { TreeItem *ti = members->create_item(variables); @@ -1511,6 +1514,7 @@ void VisualScriptEditor::_member_button(Object *p_item, int p_column, int p_butt function_name_edit->popup(); function_name_box->set_text(selected); function_name_box->select_all(); + function_name_box->grab_focus(); } } @@ -2099,11 +2103,15 @@ void VisualScriptEditor::_fn_name_box_input(const Ref<InputEvent> &p_event) { Ref<InputEventKey> key = p_event; if (key.is_valid() && key->is_pressed() && key->get_keycode() == Key::ENTER) { function_name_edit->hide(); - _rename_function(selected, function_name_box->get_text()); + _on_fn_name_box_confirmed(); function_name_box->clear(); } } +void VisualScriptEditor::_on_fn_name_box_confirmed() { + _rename_function(selected, function_name_box->get_text()); +} + Variant VisualScriptEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { if (p_from == members) { TreeItem *it = members->get_item_at_position(p_point); @@ -2803,7 +2811,7 @@ void VisualScriptEditor::add_callback(const String &p_function, PackedStringArra String name = p_args[i]; Variant::Type type = Variant::NIL; - if (name.find(":") != -1) { + if (name.contains(":")) { String tt = name.get_slice(":", 1); name = name.get_slice(":", 0); for (int j = 0; j < Variant::VARIANT_MAX; j++) { @@ -3528,7 +3536,7 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri print_error("Category not handled: " + p_category.quote()); } - if (Object::cast_to<VisualScriptFunctionCall>(vnode.ptr()) && p_category != "Class") { + if (Object::cast_to<VisualScriptFunctionCall>(vnode.ptr()) && p_category != "Class" && p_category != "VisualScriptNode") { Vector<String> property_path = p_text.split(":"); String class_of_method = property_path[0]; String method_name = property_path[1]; @@ -3974,6 +3982,7 @@ void VisualScriptEditor::_notification(int p_what) { _update_graph(); } } break; + case NOTIFICATION_VISIBILITY_CHANGED: { update_toggle_scripts_button(); members_section->set_visible(is_visible_in_tree()); @@ -4416,6 +4425,7 @@ void VisualScriptEditor::_member_option(int p_option) { function_name_edit->popup(); function_name_box->set_text(selected); function_name_box->select_all(); + function_name_box->grab_focus(); } } break; case MEMBER_VARIABLE: { @@ -4546,9 +4556,11 @@ VisualScriptEditor::VisualScriptEditor() { member_popup->connect("id_pressed", callable_mp(this, &VisualScriptEditor::_member_option)); function_name_edit = memnew(AcceptDialog); + function_name_edit->set_title(TTR("Rename Function")); function_name_box = memnew(LineEdit); function_name_edit->add_child(function_name_box); function_name_box->connect("gui_input", callable_mp(this, &VisualScriptEditor::_fn_name_box_input)); + function_name_edit->get_ok_button()->connect("pressed", callable_mp(this, &VisualScriptEditor::_on_fn_name_box_confirmed)); function_name_box->set_expand_to_text_length_enabled(true); add_child(function_name_edit); diff --git a/modules/visual_script/editor/visual_script_editor.h b/modules/visual_script/editor/visual_script_editor.h index f1b01aa6dc..e178f5cf72 100644 --- a/modules/visual_script/editor/visual_script_editor.h +++ b/modules/visual_script/editor/visual_script_editor.h @@ -173,10 +173,10 @@ class VisualScriptEditor : public ScriptEditorBase { String member_name; PortAction port_action; - int port_action_node; - int port_action_output; + int port_action_node = 0; + int port_action_output = 0; Vector2 port_action_pos; - int port_action_new_node; + int port_action_new_node = 0; bool saved_pos_dirty = false; @@ -196,7 +196,7 @@ class VisualScriptEditor : public ScriptEditorBase { int _create_new_node_from_name(const String &p_text, const Vector2 &p_point); void _selected_new_virtual_method(const String &p_text, const String &p_category, const bool p_connecting); - int error_line; + int error_line = -1; void _node_selected(Node *p_node); void _center_on_node(int p_id); @@ -247,6 +247,7 @@ class VisualScriptEditor : public ScriptEditorBase { void _graph_gui_input(const Ref<InputEvent> &p_event); void _members_gui_input(const Ref<InputEvent> &p_event); void _fn_name_box_input(const Ref<InputEvent> &p_event); + void _on_fn_name_box_confirmed(); void _rename_function(const String &p_name, const String &p_new_name); void _create_function_dialog(); @@ -267,12 +268,12 @@ class VisualScriptEditor : public ScriptEditorBase { bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); - int editing_id; - int editing_input; + int editing_id = 0; + int editing_input = 0; - bool can_swap; - int data_disconnect_node; - int data_disconnect_port; + bool can_swap = false; + int data_disconnect_node = 0; + int data_disconnect_port = 0; void _default_value_changed(); void _default_value_edited(Node *p_button, int p_id, int p_input_port); diff --git a/modules/visual_script/editor/visual_script_property_selector.cpp b/modules/visual_script/editor/visual_script_property_selector.cpp index 1059d126bc..31406a2a6f 100644 --- a/modules/visual_script/editor/visual_script_property_selector.cpp +++ b/modules/visual_script/editor/visual_script_property_selector.cpp @@ -38,7 +38,6 @@ #include "core/os/keyboard.h" #include "editor/doc_tools.h" #include "editor/editor_feature_profile.h" -#include "editor/editor_node.h" #include "editor/editor_scale.h" #include "scene/main/node.h" #include "scene/main/window.h" @@ -119,9 +118,11 @@ void VisualScriptPropertySelector::_notification(int p_what) { case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { _update_icons(); } break; + case NOTIFICATION_ENTER_TREE: { connect("confirmed", callable_mp(this, &VisualScriptPropertySelector::_confirmed)); } break; + case NOTIFICATION_PROCESS: { // Update background search. if (search_runner.is_valid()) { @@ -379,8 +380,6 @@ void VisualScriptPropertySelector::_bind_methods() { } VisualScriptPropertySelector::VisualScriptPropertySelector() { - virtuals_only = false; - vbox = memnew(VBoxContainer); add_child(vbox); @@ -495,7 +494,7 @@ VisualScriptPropertySelector::VisualScriptPropertySelector() { hbox->add_child(scope_combo); search_box = memnew(LineEdit); - search_box->set_tooltip(TTR("Enter \" \" to show all filterd options\nEnter \".\" to show all filterd methods, operators and constructors\nUse CTRL_KEY to drop property setters")); + search_box->set_tooltip(TTR("Enter \" \" to show all filtered options\nEnter \".\" to show all filtered methods, operators and constructors\nUse CTRL_KEY to drop property setters")); search_box->set_custom_minimum_size(Size2(200, 0) * EDSCALE); search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); search_box->connect("text_changed", callable_mp(this, &VisualScriptPropertySelector::_update_results_s)); @@ -696,7 +695,7 @@ bool VisualScriptPropertySelector::SearchRunner::_phase_match_classes_init() { class_doc.name = selector_ui->base_script; class_doc.inherits = script->get_instance_base_type(); - class_doc.brief_description = ".vs files not suported by EditorHelp::get_doc_data()"; + class_doc.brief_description = ".vs files not supported by EditorHelp::get_doc_data()"; class_doc.description = ""; Object *obj = ObjectDB::get_instance(script->get_instance_id()); @@ -713,9 +712,9 @@ bool VisualScriptPropertySelector::SearchRunner::_phase_match_classes_init() { class_doc.signals.push_back(_get_method_doc(S->get())); } - List<PropertyInfo> propertys; - Object::cast_to<Script>(obj)->get_script_property_list(&propertys); - for (List<PropertyInfo>::Element *P = propertys.front(); P; P = P->next()) { + List<PropertyInfo> properties; + Object::cast_to<Script>(obj)->get_script_property_list(&properties); + for (List<PropertyInfo>::Element *P = properties.front(); P; P = P->next()) { DocData::PropertyDoc pd = DocData::PropertyDoc(); pd.name = P->get().name; pd.type = Variant::get_type_name(P->get().type); @@ -1116,14 +1115,14 @@ TreeItem *VisualScriptPropertySelector::SearchRunner::_create_class_item(TreeIte String details = p_doc->name; if (p_doc->category.begins_with("VisualScriptCustomNode/")) { Vector<String> path = p_doc->name.split("/"); - icon = ui_service->get_theme_icon("VisualScript", "EditorIcons"); + icon = ui_service->get_theme_icon(SNAME("VisualScript"), SNAME("EditorIcons")); text_0 = path[path.size() - 1]; text_1 = "VisualScriptCustomNode"; what = "VisualScriptCustomNode"; details = "CustomNode"; } else if (p_doc->category.begins_with("VisualScriptNode/")) { Vector<String> path = p_doc->name.split("/"); - icon = ui_service->get_theme_icon("VisualScript", "EditorIcons"); + icon = ui_service->get_theme_icon(SNAME("VisualScript"), SNAME("EditorIcons")); text_0 = path[path.size() - 1]; if (p_doc->category.begins_with("VisualScriptNode/deconstruct")) { text_0 = "deconstruct " + text_0; diff --git a/modules/visual_script/editor/visual_script_property_selector.h b/modules/visual_script/editor/visual_script_property_selector.h index 3970c4473e..6b5112f1af 100644 --- a/modules/visual_script/editor/visual_script_property_selector.h +++ b/modules/visual_script/editor/visual_script_property_selector.h @@ -96,16 +96,16 @@ class VisualScriptPropertySelector : public ConfirmationDialog { EditorHelpBit *help_bit; - bool properties; - bool visual_script_generic; - bool connecting; + bool properties = false; + bool visual_script_generic = false; + bool connecting = false; String selected; Variant::Type type; String base_type; String base_script; ObjectID script; Object *instance; - bool virtuals_only; + bool virtuals_only = false; VBoxContainer *vbox; protected: diff --git a/modules/visual_script/visual_script.cpp b/modules/visual_script/visual_script.cpp index fbdf3a654b..9549137aef 100644 --- a/modules/visual_script/visual_script.cpp +++ b/modules/visual_script/visual_script.cpp @@ -930,8 +930,6 @@ void VisualScript::get_script_property_list(List<PropertyInfo> *p_list) const { get_variable_list(&vars); for (const StringName &E : vars) { - //if (!variables[E]._export) - // continue; PropertyInfo pi = variables[E].info; pi.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; p_list->push_back(pi); @@ -1164,9 +1162,6 @@ void VisualScript::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_custom_signal", "name"), &VisualScript::remove_custom_signal); ClassDB::bind_method(D_METHOD("rename_custom_signal", "name", "new_name"), &VisualScript::rename_custom_signal); - //ClassDB::bind_method(D_METHOD("set_variable_info","name","info"),&VScript::set_variable_info); - //ClassDB::bind_method(D_METHOD("get_variable_info","name"),&VScript::set_variable_info); - ClassDB::bind_method(D_METHOD("set_instance_base_type", "type"), &VisualScript::set_instance_base_type); ClassDB::bind_method(D_METHOD("_set_data", "data"), &VisualScript::_set_data); diff --git a/modules/visual_script/visual_script_builtin_funcs.cpp b/modules/visual_script/visual_script_builtin_funcs.cpp index fd55796a66..f6bc855a50 100644 --- a/modules/visual_script/visual_script_builtin_funcs.cpp +++ b/modules/visual_script/visual_script_builtin_funcs.cpp @@ -65,6 +65,7 @@ const char *VisualScriptBuiltinFunc::func_name[VisualScriptBuiltinFunc::FUNC_MAX "step_decimals", "snapped", "lerp", + "cubic_interpolate", "inverse_lerp", "range_lerp", "move_toward", @@ -212,6 +213,7 @@ int VisualScriptBuiltinFunc::get_func_argument_count(BuiltinFunc p_func) { case MATH_WRAPF: case LOGIC_CLAMP: return 3; + case MATH_CUBIC_INTERPOLATE: case MATH_RANGE_LERP: return 5; case FUNC_MAX: { @@ -329,6 +331,19 @@ PropertyInfo VisualScriptBuiltinFunc::get_input_value_port_info(int p_idx) const return PropertyInfo(Variant::FLOAT, "weight"); } } break; + case MATH_CUBIC_INTERPOLATE: { + if (p_idx == 0) { + return PropertyInfo(Variant::FLOAT, "from"); + } else if (p_idx == 1) { + return PropertyInfo(Variant::FLOAT, "to"); + } else if (p_idx == 2) { + return PropertyInfo(Variant::FLOAT, "pre"); + } else if (p_idx == 3) { + return PropertyInfo(Variant::FLOAT, "post"); + } else { + return PropertyInfo(Variant::FLOAT, "weight"); + } + } break; case MATH_RANGE_LERP: { if (p_idx == 0) { return PropertyInfo(Variant::FLOAT, "value"); @@ -525,6 +540,7 @@ PropertyInfo VisualScriptBuiltinFunc::get_output_value_port_info(int p_idx) cons } break; case MATH_SNAPPED: case MATH_LERP: + case MATH_CUBIC_INTERPOLATE: case MATH_LERP_ANGLE: case MATH_INVERSE_LERP: case MATH_RANGE_LERP: @@ -795,6 +811,14 @@ void VisualScriptBuiltinFunc::exec_func(BuiltinFunc p_func, const Variant **p_in VALIDATE_ARG_NUM(2); *r_return = Math::lerp((double)*p_inputs[0], (double)*p_inputs[1], (double)*p_inputs[2]); } break; + case VisualScriptBuiltinFunc::MATH_CUBIC_INTERPOLATE: { + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + VALIDATE_ARG_NUM(2); + VALIDATE_ARG_NUM(3); + VALIDATE_ARG_NUM(4); + *r_return = Math::cubic_interpolate((double)*p_inputs[0], (double)*p_inputs[1], (double)*p_inputs[2], (double)*p_inputs[3], (double)*p_inputs[4]); + } break; case VisualScriptBuiltinFunc::MATH_LERP_ANGLE: { VALIDATE_ARG_NUM(0); VALIDATE_ARG_NUM(1); @@ -1220,6 +1244,7 @@ void VisualScriptBuiltinFunc::_bind_methods() { BIND_ENUM_CONSTANT(MATH_STEP_DECIMALS); BIND_ENUM_CONSTANT(MATH_SNAPPED); BIND_ENUM_CONSTANT(MATH_LERP); + BIND_ENUM_CONSTANT(MATH_CUBIC_INTERPOLATE); BIND_ENUM_CONSTANT(MATH_INVERSE_LERP); BIND_ENUM_CONSTANT(MATH_RANGE_LERP); BIND_ENUM_CONSTANT(MATH_MOVE_TOWARD); @@ -1309,6 +1334,7 @@ void register_visual_script_builtin_func_node() { VisualScriptLanguage::singleton->add_register_func("functions/built_in/step_decimals", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_STEP_DECIMALS>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/snapped", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_SNAPPED>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/lerp", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_LERP>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/cubic_interpolate", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_CUBIC_INTERPOLATE>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/lerp_angle", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_LERP_ANGLE>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/inverse_lerp", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_INVERSE_LERP>); VisualScriptLanguage::singleton->add_register_func("functions/built_in/range_lerp", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_RANGE_LERP>); diff --git a/modules/visual_script/visual_script_builtin_funcs.h b/modules/visual_script/visual_script_builtin_funcs.h index d689296233..18935b9995 100644 --- a/modules/visual_script/visual_script_builtin_funcs.h +++ b/modules/visual_script/visual_script_builtin_funcs.h @@ -65,6 +65,7 @@ public: MATH_STEP_DECIMALS, MATH_SNAPPED, MATH_LERP, + MATH_CUBIC_INTERPOLATE, MATH_INVERSE_LERP, MATH_RANGE_LERP, MATH_MOVE_TOWARD, diff --git a/modules/visual_script/visual_script_expression.cpp b/modules/visual_script/visual_script_expression.cpp index 2abbd19e12..e8942b9788 100644 --- a/modules/visual_script/visual_script_expression.cpp +++ b/modules/visual_script/visual_script_expression.cpp @@ -328,6 +328,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) { }; case '"': { String str; + char32_t prev = 0; while (true) { char32_t ch = GET_CHAR(); @@ -364,9 +365,11 @@ Error VisualScriptExpression::_get_token(Token &r_token) { case 'r': res = 13; break; + case 'U': case 'u': { - // hex number - for (int j = 0; j < 4; j++) { + // Hexadecimal sequence. + int hex_len = (next == 'U') ? 6 : 4; + for (int j = 0; j < hex_len; j++) { char32_t c = GET_CHAR(); if (c == 0) { @@ -374,13 +377,13 @@ Error VisualScriptExpression::_get_token(Token &r_token) { r_token.type = TK_ERROR; return ERR_PARSE_ERROR; } - if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { + if (!is_hex_digit(c)) { _set_error("Malformed hex constant in string"); r_token.type = TK_ERROR; return ERR_PARSE_ERROR; } char32_t v; - if (c >= '0' && c <= '9') { + if (is_digit(c)) { v = c - '0'; } else if (c >= 'a' && c <= 'f') { v = c - 'a'; @@ -403,12 +406,46 @@ Error VisualScriptExpression::_get_token(Token &r_token) { } break; } + // Parse UTF-16 pair. + if ((res & 0xfffffc00) == 0xd800) { + if (prev == 0) { + prev = res; + continue; + } else { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } + } else if ((res & 0xfffffc00) == 0xdc00) { + if (prev == 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired trail surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } else { + res = (prev << 10UL) + res - ((0xd800 << 10UL) + 0xdc00 - 0x10000); + prev = 0; + } + } + if (prev != 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } str += res; - } else { + if (prev != 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } str += ch; } } + if (prev != 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } r_token.type = TK_CONSTANT; r_token.value = str; @@ -420,7 +457,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) { break; } - if (cchar >= '0' && cchar <= '9') { + if (is_digit(cchar)) { //a number String num; @@ -439,7 +476,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) { while (true) { switch (reading) { case READING_INT: { - if (c >= '0' && c <= '9') { + if (is_digit(c)) { //pass } else if (c == '.') { reading = READING_DEC; @@ -452,7 +489,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) { } break; case READING_DEC: { - if (c >= '0' && c <= '9') { + if (is_digit(c)) { } else if (c == 'e') { reading = READING_EXP; @@ -462,7 +499,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) { } break; case READING_EXP: { - if (c >= '0' && c <= '9') { + if (is_digit(c)) { exp_beg = true; } else if ((c == '-' || c == '+') && !exp_sign && !exp_beg) { @@ -495,11 +532,11 @@ Error VisualScriptExpression::_get_token(Token &r_token) { } return OK; - } else if ((cchar >= 'A' && cchar <= 'Z') || (cchar >= 'a' && cchar <= 'z') || cchar == '_') { + } else if (is_ascii_char(cchar) || cchar == '_') { String id; bool first = true; - while ((cchar >= 'A' && cchar <= 'Z') || (cchar >= 'a' && cchar <= 'z') || cchar == '_' || (!first && cchar >= '0' && cchar <= '9')) { + while (is_ascii_char(cchar) || cchar == '_' || (!first && is_digit(cchar))) { id += String::chr(cchar); cchar = GET_CHAR(); first = false; diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp index f3594e5164..e672267b00 100644 --- a/modules/visual_script/visual_script_nodes.cpp +++ b/modules/visual_script/visual_script_nodes.cpp @@ -1784,10 +1784,7 @@ public: virtual int step(const Variant **p_inputs, Variant **p_outputs, StartMode p_start_mode, Variant *p_working_mem, Callable::CallError &r_error, String &r_error_str) { bool valid; - // *p_output[0] points to the same place as *p_inputs[2] so we need a temp to store the value before the change in the next line - Variant temp = *p_inputs[2]; - *p_outputs[0] = *p_inputs[0]; - p_outputs[0]->set(*p_inputs[1], temp, &valid); + ((Variant *)p_inputs[0])->set(*p_inputs[1], *p_inputs[2], &valid); if (!valid) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; @@ -2495,7 +2492,7 @@ static Node *_find_script_node(Node *p_edited_scene, Node *p_current_node, const VisualScriptSceneNode::TypeGuess VisualScriptSceneNode::guess_output_type(TypeGuess *p_inputs, int p_output) const { VisualScriptSceneNode::TypeGuess tg; tg.type = Variant::OBJECT; - tg.gdclass = "Node"; + tg.gdclass = SNAME("Node"); #ifdef TOOLS_ENABLED Ref<Script> script = get_visual_script(); @@ -2649,7 +2646,7 @@ VisualScriptNodeInstance *VisualScriptSceneTree::instantiate(VisualScriptInstanc VisualScriptSceneTree::TypeGuess VisualScriptSceneTree::guess_output_type(TypeGuess *p_inputs, int p_output) const { TypeGuess tg; tg.type = Variant::OBJECT; - tg.gdclass = "SceneTree"; + tg.gdclass = SNAME("SceneTree"); return tg; } @@ -2766,11 +2763,11 @@ PropertyInfo VisualScriptSelf::get_input_value_port_info(int p_idx) const { } PropertyInfo VisualScriptSelf::get_output_value_port_info(int p_idx) const { - String type_name; + StringName type_name; if (get_visual_script().is_valid()) { type_name = get_visual_script()->get_instance_base_type(); } else { - type_name = "instance"; + type_name = SNAME("instance"); } return PropertyInfo(Variant::OBJECT, type_name); @@ -2801,7 +2798,7 @@ VisualScriptNodeInstance *VisualScriptSelf::instantiate(VisualScriptInstance *p_ VisualScriptSelf::TypeGuess VisualScriptSelf::guess_output_type(TypeGuess *p_inputs, int p_output) const { VisualScriptSceneNode::TypeGuess tg; tg.type = Variant::OBJECT; - tg.gdclass = "Object"; + tg.gdclass = SNAME("Object"); Ref<Script> script = get_visual_script(); if (!script.is_valid()) { diff --git a/modules/vorbis/doc_classes/AudioStreamOGGVorbis.xml b/modules/vorbis/doc_classes/AudioStreamOGGVorbis.xml index 4cd278fe83..2f210a6cb4 100644 --- a/modules/vorbis/doc_classes/AudioStreamOGGVorbis.xml +++ b/modules/vorbis/doc_classes/AudioStreamOGGVorbis.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="AudioStreamOGGVorbis" inherits="AudioStream" version="4.0"> +<class name="AudioStreamOGGVorbis" inherits="AudioStream" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/vorbis/doc_classes/AudioStreamPlaybackOGGVorbis.xml b/modules/vorbis/doc_classes/AudioStreamPlaybackOGGVorbis.xml index 05c70d88da..68aa46147f 100644 --- a/modules/vorbis/doc_classes/AudioStreamPlaybackOGGVorbis.xml +++ b/modules/vorbis/doc_classes/AudioStreamPlaybackOGGVorbis.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="AudioStreamPlaybackOGGVorbis" inherits="AudioStreamPlaybackResampled" version="4.0"> +<class name="AudioStreamPlaybackOGGVorbis" inherits="AudioStreamPlaybackResampled" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/vorbis/resource_importer_ogg_vorbis.cpp b/modules/vorbis/resource_importer_ogg_vorbis.cpp index ccd463fd52..d12e65a96a 100644 --- a/modules/vorbis/resource_importer_ogg_vorbis.cpp +++ b/modules/vorbis/resource_importer_ogg_vorbis.cpp @@ -136,11 +136,11 @@ Error ResourceImporterOGGVorbis::import(const String &p_source_file, const Strin // Have a page now. if (!initialized_stream) { - ogg_stream_init(&stream_state, ogg_page_serialno(&page)); - ERR_FAIL_COND_V_MSG((err = ogg_stream_check(&stream_state)), Error::ERR_INVALID_DATA, "Ogg stream error " + itos(err)); + if (ogg_stream_init(&stream_state, ogg_page_serialno(&page))) { + ERR_FAIL_V_MSG(Error::ERR_OUT_OF_MEMORY, "Failed allocating memory for OGG Vorbis stream."); + } initialized_stream = true; } - ERR_FAIL_COND_V_MSG((err = ogg_stream_check(&stream_state)), Error::ERR_INVALID_DATA, "Ogg stream error " + itos(err)); ogg_stream_pagein(&stream_state, &page); ERR_FAIL_COND_V_MSG((err = ogg_stream_check(&stream_state)), Error::ERR_INVALID_DATA, "Ogg stream error " + itos(err)); int desync_iters = 0; @@ -160,10 +160,12 @@ Error ResourceImporterOGGVorbis::import(const String &p_source_file, const Strin break; } if (packet_count == 0 && vorbis_synthesis_idheader(&packet) == 0) { - WARN_PRINT("Found a non-vorbis-header packet in a header position"); + print_verbose("Found a non-vorbis-header packet in a header position"); // Clearly this logical stream is not a vorbis stream, so destroy it and try again with the next page. - ogg_stream_destroy(&stream_state); - initialized_stream = false; + if (initialized_stream) { + ogg_stream_clear(&stream_state); + initialized_stream = false; + } break; } granule_pos = packet.granulepos; @@ -178,6 +180,14 @@ Error ResourceImporterOGGVorbis::import(const String &p_source_file, const Strin ogg_packet_sequence->push_page(granule_pos, packet_data); } } + if (initialized_stream) { + ogg_stream_clear(&stream_state); + } + ogg_sync_clear(&sync_state); + + if (ogg_packet_sequence->get_packet_granule_positions().is_empty()) { + ERR_FAIL_V_MSG(Error::ERR_FILE_CORRUPT, "OGG Vorbis decoding failed. Check that your data is a valid OGG Vorbis audio stream."); + } ogg_vorbis_stream->set_packet_sequence(ogg_packet_sequence); ogg_vorbis_stream->set_loop(loop); diff --git a/modules/webrtc/doc_classes/WebRTCDataChannel.xml b/modules/webrtc/doc_classes/WebRTCDataChannel.xml index cf5735bab5..a9ba8a23de 100644 --- a/modules/webrtc/doc_classes/WebRTCDataChannel.xml +++ b/modules/webrtc/doc_classes/WebRTCDataChannel.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="WebRTCDataChannel" inherits="PacketPeer" version="4.0"> +<class name="WebRTCDataChannel" inherits="PacketPeer" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/webrtc/doc_classes/WebRTCDataChannelExtension.xml b/modules/webrtc/doc_classes/WebRTCDataChannelExtension.xml index 746fabd6e5..f937fba9d6 100644 --- a/modules/webrtc/doc_classes/WebRTCDataChannelExtension.xml +++ b/modules/webrtc/doc_classes/WebRTCDataChannelExtension.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="WebRTCDataChannelExtension" inherits="WebRTCDataChannel" version="4.0"> +<class name="WebRTCDataChannelExtension" inherits="WebRTCDataChannel" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml b/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml index 780791c0d9..3996a002ed 100644 --- a/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml +++ b/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="WebRTCMultiplayerPeer" inherits="MultiplayerPeer" version="4.0"> +<class name="WebRTCMultiplayerPeer" inherits="MultiplayerPeer" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A simple interface to create a peer-to-peer mesh network composed of [WebRTCPeerConnection] that is compatible with the [MultiplayerAPI]. </brief_description> diff --git a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml index 2f2c2c379d..b4d97077e3 100644 --- a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml +++ b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="WebRTCPeerConnection" inherits="RefCounted" version="4.0"> +<class name="WebRTCPeerConnection" inherits="RefCounted" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Interface to a WebRTC peer connection. </brief_description> @@ -117,7 +117,7 @@ </methods> <signals> <signal name="data_channel_received"> - <argument index="0" name="channel" type="Object" /> + <argument index="0" name="channel" type="WebRTCDataChannel" /> <description> Emitted when a new in-band channel is received, i.e. when the channel was created with [code]negotiated: false[/code] (default). The object will be an instance of [WebRTCDataChannel]. You must keep a reference of it or it will be closed automatically. See [method create_data_channel]. diff --git a/modules/webrtc/doc_classes/WebRTCPeerConnectionExtension.xml b/modules/webrtc/doc_classes/WebRTCPeerConnectionExtension.xml index d296fcd6e7..e88acdc845 100644 --- a/modules/webrtc/doc_classes/WebRTCPeerConnectionExtension.xml +++ b/modules/webrtc/doc_classes/WebRTCPeerConnectionExtension.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="WebRTCPeerConnectionExtension" inherits="WebRTCPeerConnection" version="4.0"> +<class name="WebRTCPeerConnectionExtension" inherits="WebRTCPeerConnection" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> </brief_description> <description> diff --git a/modules/webrtc/webrtc_data_channel_js.cpp b/modules/webrtc/webrtc_data_channel_js.cpp index 4c41a4c7ee..0fb074b0c2 100644 --- a/modules/webrtc/webrtc_data_channel_js.cpp +++ b/modules/webrtc/webrtc_data_channel_js.cpp @@ -31,6 +31,7 @@ #ifdef JAVASCRIPT_ENABLED #include "webrtc_data_channel_js.h" + #include "emscripten.h" extern "C" { @@ -104,8 +105,9 @@ int WebRTCDataChannelJS::get_available_packet_count() const { Error WebRTCDataChannelJS::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { ERR_FAIL_COND_V(get_ready_state() != STATE_OPEN, ERR_UNCONFIGURED); - if (queue_count == 0) + if (queue_count == 0) { return ERR_UNAVAILABLE; + } uint32_t to_read = 0; uint32_t left = 0; diff --git a/modules/webrtc/webrtc_peer_connection.cpp b/modules/webrtc/webrtc_peer_connection.cpp index 96cf518c06..7fdf26d3cd 100644 --- a/modules/webrtc/webrtc_peer_connection.cpp +++ b/modules/webrtc/webrtc_peer_connection.cpp @@ -69,7 +69,7 @@ void WebRTCPeerConnection::_bind_methods() { ADD_SIGNAL(MethodInfo("session_description_created", PropertyInfo(Variant::STRING, "type"), PropertyInfo(Variant::STRING, "sdp"))); ADD_SIGNAL(MethodInfo("ice_candidate_created", PropertyInfo(Variant::STRING, "media"), PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::STRING, "name"))); - ADD_SIGNAL(MethodInfo("data_channel_received", PropertyInfo(Variant::OBJECT, "channel"))); + ADD_SIGNAL(MethodInfo("data_channel_received", PropertyInfo(Variant::OBJECT, "channel", PROPERTY_HINT_RESOURCE_TYPE, "WebRTCDataChannel"))); BIND_ENUM_CONSTANT(STATE_NEW); BIND_ENUM_CONSTANT(STATE_CONNECTING); diff --git a/modules/websocket/doc_classes/WebSocketClient.xml b/modules/websocket/doc_classes/WebSocketClient.xml index 5fcf51293d..ad2acf8a21 100644 --- a/modules/websocket/doc_classes/WebSocketClient.xml +++ b/modules/websocket/doc_classes/WebSocketClient.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="WebSocketClient" inherits="WebSocketMultiplayerPeer" version="4.0"> +<class name="WebSocketClient" inherits="WebSocketMultiplayerPeer" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A WebSocket client implementation. </brief_description> diff --git a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml index 8d8ab220e2..4a617f4c82 100644 --- a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml +++ b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="WebSocketMultiplayerPeer" inherits="MultiplayerPeer" version="4.0"> +<class name="WebSocketMultiplayerPeer" inherits="MultiplayerPeer" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Base class for WebSocket server and client. </brief_description> diff --git a/modules/websocket/doc_classes/WebSocketPeer.xml b/modules/websocket/doc_classes/WebSocketPeer.xml index 2f455c32fd..6466654517 100644 --- a/modules/websocket/doc_classes/WebSocketPeer.xml +++ b/modules/websocket/doc_classes/WebSocketPeer.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="WebSocketPeer" inherits="PacketPeer" version="4.0"> +<class name="WebSocketPeer" inherits="PacketPeer" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A class representing a specific WebSocket connection. </brief_description> diff --git a/modules/websocket/doc_classes/WebSocketServer.xml b/modules/websocket/doc_classes/WebSocketServer.xml index f901b089ea..ef3279aac4 100644 --- a/modules/websocket/doc_classes/WebSocketServer.xml +++ b/modules/websocket/doc_classes/WebSocketServer.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="WebSocketServer" inherits="WebSocketMultiplayerPeer" version="4.0"> +<class name="WebSocketServer" inherits="WebSocketMultiplayerPeer" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A WebSocket server implementation. </brief_description> diff --git a/modules/websocket/emws_client.cpp b/modules/websocket/emws_client.cpp index 2d029dfbbc..e051a3b564 100644 --- a/modules/websocket/emws_client.cpp +++ b/modules/websocket/emws_client.cpp @@ -31,6 +31,7 @@ #ifdef JAVASCRIPT_ENABLED #include "emws_client.h" + #include "core/config/project_settings.h" #include "core/io/ip.h" #include "emscripten.h" @@ -45,8 +46,9 @@ void EMWSClient::_esws_on_message(void *obj, const uint8_t *p_data, int p_data_s EMWSClient *client = static_cast<EMWSClient *>(obj); Error err = static_cast<EMWSPeer *>(*client->get_peer(1))->read_msg(p_data, p_data_size, p_is_string == 1); - if (err == OK) + if (err == OK) { client->_on_peer_packet(); + } } void EMWSClient::_esws_on_error(void *obj) { @@ -71,8 +73,9 @@ Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, String proto_string; for (int i = 0; i < p_protocols.size(); i++) { - if (i != 0) + if (i != 0) { proto_string += ","; + } proto_string += p_protocols[i]; } @@ -109,8 +112,9 @@ Ref<WebSocketPeer> EMWSClient::get_peer(int p_peer_id) const { MultiplayerPeer::ConnectionStatus EMWSClient::get_connection_status() const { if (_peer->is_connected_to_host()) { - if (_is_connecting) + if (_is_connecting) { return CONNECTION_CONNECTING; + } return CONNECTION_CONNECTED; } diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp index 77a96c8e4f..86169f88e9 100644 --- a/modules/websocket/emws_peer.cpp +++ b/modules/websocket/emws_peer.cpp @@ -31,6 +31,7 @@ #ifdef JAVASCRIPT_ENABLED #include "emws_peer.h" + #include "core/io/ip.h" void EMWSPeer::set_sock(int p_sock, unsigned int p_in_buf_size, unsigned int p_in_pkt_size, unsigned int p_out_buf_size) { @@ -66,8 +67,9 @@ Error EMWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { } Error EMWSPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { - if (_in_buffer.packets_left() == 0) + if (_in_buffer.packets_left() == 0) { return ERR_UNAVAILABLE; + } int read = 0; Error err = _in_buffer.read_packet(_packet_buffer.ptrw(), _packet_buffer.size(), &_is_string, read); @@ -109,7 +111,7 @@ void EMWSPeer::close(int p_code, String p_reason) { IPAddress EMWSPeer::get_connected_host() const { ERR_FAIL_V_MSG(IPAddress(), "Not supported in HTML5 export."); -}; +} uint16_t EMWSPeer::get_connected_port() const { ERR_FAIL_V_MSG(0, "Not supported in HTML5 export."); diff --git a/modules/websocket/wsl_client.cpp b/modules/websocket/wsl_client.cpp index be1c75c354..1ef571b6ee 100644 --- a/modules/websocket/wsl_client.cpp +++ b/modules/websocket/wsl_client.cpp @@ -163,22 +163,24 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, _peer = Ref<WSLPeer>(memnew(WSLPeer)); if (p_host.is_valid_ip_address()) { - ip_candidates.clear(); - ip_candidates.push_back(IPAddress(p_host)); + _ip_candidates.push_back(IPAddress(p_host)); } else { - ip_candidates = IP::get_singleton()->resolve_hostname_addresses(p_host); - } - - ERR_FAIL_COND_V(ip_candidates.is_empty(), ERR_INVALID_PARAMETER); - - String port = ""; - if ((p_port != 80 && !p_ssl) || (p_port != 443 && p_ssl)) { - port = ":" + itos(p_port); + // Queue hostname for resolution. + _resolver_id = IP::get_singleton()->resolve_hostname_queue_item(p_host); + ERR_FAIL_COND_V(_resolver_id == IP::RESOLVER_INVALID_ID, ERR_INVALID_PARAMETER); + // Check if it was found in cache. + IP::ResolverStatus ip_status = IP::get_singleton()->get_resolve_item_status(_resolver_id); + if (ip_status == IP::RESOLVER_STATUS_DONE) { + _ip_candidates = IP::get_singleton()->get_resolve_item_addresses(_resolver_id); + IP::get_singleton()->erase_resolve_item(_resolver_id); + _resolver_id = IP::RESOLVER_INVALID_ID; + } } - Error err = ERR_BUG; // Should be at least one entry. - while (ip_candidates.size() > 0) { - err = _tcp->connect_to_host(ip_candidates.pop_front(), p_port); + // We assume OK while hostname resolution is pending. + Error err = _resolver_id != IP::RESOLVER_INVALID_ID ? OK : FAILED; + while (_ip_candidates.size()) { + err = _tcp->connect_to_host(_ip_candidates.pop_front(), p_port); if (err == OK) { break; } @@ -200,8 +202,11 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, } _key = WSLPeer::generate_key(); - // TODO custom extra headers (allow overriding this too?) String request = "GET " + p_path + " HTTP/1.1\r\n"; + String port = ""; + if ((p_port != 80 && !p_ssl) || (p_port != 443 && p_ssl)) { + port = ":" + itos(p_port); + } request += "Host: " + p_host + port + "\r\n"; request += "Upgrade: websocket\r\n"; request += "Connection: Upgrade\r\n"; @@ -231,6 +236,30 @@ int WSLClient::get_max_packet_size() const { } void WSLClient::poll() { + if (_resolver_id != IP::RESOLVER_INVALID_ID) { + IP::ResolverStatus ip_status = IP::get_singleton()->get_resolve_item_status(_resolver_id); + if (ip_status == IP::RESOLVER_STATUS_WAITING) { + return; + } + // Anything else is either a candidate or a failure. + Error err = FAILED; + if (ip_status == IP::RESOLVER_STATUS_DONE) { + _ip_candidates = IP::get_singleton()->get_resolve_item_addresses(_resolver_id); + while (_ip_candidates.size()) { + err = _tcp->connect_to_host(_ip_candidates.pop_front(), _port); + if (err == OK) { + break; + } + } + } + IP::get_singleton()->erase_resolve_item(_resolver_id); + _resolver_id = IP::RESOLVER_INVALID_ID; + if (err != OK) { + disconnect_from_host(); + _on_error(); + return; + } + } if (_peer->is_connected_to_host()) { _peer->poll(); if (!_peer->is_connected_to_host()) { @@ -251,7 +280,7 @@ void WSLClient::poll() { _on_error(); break; case StreamPeerTCP::STATUS_CONNECTED: { - ip_candidates.clear(); + _ip_candidates.clear(); Ref<StreamPeerSSL> ssl; if (_use_ssl) { if (_connection == _tcp) { @@ -282,9 +311,9 @@ void WSLClient::poll() { _do_handshake(); } break; case StreamPeerTCP::STATUS_ERROR: - while (ip_candidates.size() > 0) { + while (_ip_candidates.size() > 0) { _tcp->disconnect_from_host(); - if (_tcp->connect_to_host(ip_candidates.pop_front(), _port) == OK) { + if (_tcp->connect_to_host(_ip_candidates.pop_front(), _port) == OK) { return; } } @@ -307,7 +336,7 @@ MultiplayerPeer::ConnectionStatus WSLClient::get_connection_status() const { return CONNECTION_CONNECTED; } - if (_tcp->is_connected_to_host()) { + if (_tcp->is_connected_to_host() || _resolver_id != IP::RESOLVER_INVALID_ID) { return CONNECTION_CONNECTING; } @@ -330,7 +359,12 @@ void WSLClient::disconnect_from_host(int p_code, String p_reason) { memset(_resp_buf, 0, sizeof(_resp_buf)); _resp_pos = 0; - ip_candidates.clear(); + if (_resolver_id != IP::RESOLVER_INVALID_ID) { + IP::get_singleton()->erase_resolve_item(_resolver_id); + _resolver_id = IP::RESOLVER_INVALID_ID; + } + + _ip_candidates.clear(); } IPAddress WSLClient::get_connected_host() const { diff --git a/modules/websocket/wsl_client.h b/modules/websocket/wsl_client.h index 4839d7ab9b..d846e6be00 100644 --- a/modules/websocket/wsl_client.h +++ b/modules/websocket/wsl_client.h @@ -63,10 +63,11 @@ private: String _key; String _host; - int _port; - Array ip_candidates; + uint16_t _port; + Array _ip_candidates; Vector<String> _protocols; bool _use_ssl = false; + IP::ResolverID _resolver_id = IP::RESOLVER_INVALID_ID; void _do_handshake(); bool _verify_headers(String &r_protocol); diff --git a/modules/webxr/doc_classes/WebXRInterface.xml b/modules/webxr/doc_classes/WebXRInterface.xml index 6e224a8242..48447eb074 100644 --- a/modules/webxr/doc_classes/WebXRInterface.xml +++ b/modules/webxr/doc_classes/WebXRInterface.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="WebXRInterface" inherits="XRInterface" version="4.0"> +<class name="WebXRInterface" inherits="XRInterface" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> AR/VR interface using WebXR. </brief_description> diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp index 86b857f72c..06b0e31801 100644 --- a/modules/webxr/webxr_interface_js.cpp +++ b/modules/webxr/webxr_interface_js.cpp @@ -31,11 +31,13 @@ #ifdef JAVASCRIPT_ENABLED #include "webxr_interface_js.h" + #include "core/input/input.h" #include "core/os/os.h" #include "emscripten.h" #include "godot_webxr.h" #include "servers/rendering/renderer_compositor.h" + #include <stdlib.h> void _emwebxr_on_session_supported(char *p_session_mode, int p_supported) { @@ -481,7 +483,6 @@ void WebXRInterfaceJS::_update_tracker(int p_controller_id) { sprintf(name, "axis_%i", i); float value = *((float *)axes + (i + 1)); - ; tracker->set_input(name, value); } free(axes); diff --git a/modules/xatlas_unwrap/register_types.cpp b/modules/xatlas_unwrap/register_types.cpp index 6f397fe285..139df9c735 100644 --- a/modules/xatlas_unwrap/register_types.cpp +++ b/modules/xatlas_unwrap/register_types.cpp @@ -120,6 +120,8 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver xatlas::ChartOptions chart_options; chart_options.fixWinding = true; + ERR_FAIL_COND_V_MSG(p_texel_size <= 0.0f, false, "Texel size must be greater than 0."); + xatlas::PackOptions pack_options; pack_options.padding = 1; pack_options.maxChartSize = 4094; // Lightmap atlassing needs 2 for padding between meshes, so 4096-2 |