diff options
217 files changed, 4832 insertions, 1127 deletions
diff --git a/.github/workflows/static_checks.yml b/.github/workflows/static_checks.yml index 0ed7432833..9b8a86b8e7 100644 --- a/.github/workflows/static_checks.yml +++ b/.github/workflows/static_checks.yml @@ -48,6 +48,8 @@ jobs: - name: Style checks via pre-commit uses: pre-commit/action@v3.0.1 + with: + extra_args: --verbose --files ${{ env.CHANGED_FILES }} - name: File formatting checks (file_format.sh) run: | @@ -14,6 +14,7 @@ Ariel Manzur <ariel@godotengine.org> <punto@godotengine.org> Ariel Manzur <ariel@godotengine.org> <ariel@okamstudio.com> Ariel Manzur <ariel@godotengine.org> <punto@Ariels-Mac-mini.local> Ariel Manzur <ariel@godotengine.org> <punto@Ariels-Mac-mini-2.local> +Arman Elgudzhyan <48544263+puchik@users.noreply.github.com> A Thousand Ships <96648715+AThousandShips@users.noreply.github.com> <over999ships@gmail.com> Bastiaan Olij <mux213@gmail.com> Benjamin <mafortion.benjamin@gmail.com> @@ -77,6 +78,7 @@ Jason Knight <00jknight@gmail.com> <jason@winterpixel.com> Jean-Michel Bernard <jmb462@gmail.com> Jérôme Gully <jerome.gully0@gmail.com> JFonS <joan.fonssanchez@gmail.com> +jitspoe <jitspoe@yahoo.com> <jitspoeAyahoooDcom> Juan Linietsky <reduzio@gmail.com> Juan Linietsky <reduzio@gmail.com> <juan@godotengine.org> Juan Linietsky <reduzio@gmail.com> <juan@okamstudio.com> diff --git a/AUTHORS.md b/AUTHORS.md index f2fc58c1be..0f000a1a75 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -48,6 +48,7 @@ name is available. Anutrix Aren Villanueva (kurikaesu) Ariel Manzur (punto-) + Arman Elgudzhyan (puchik) AThousandShips aXu-AP Bartłomiej T. Listwon (Listwon) @@ -141,6 +142,7 @@ name is available. Jean-Michel Bernard (jmb462) Jérôme Gully (Nutriz) Jia Jun Chai (SkyLucilfer) + jitspoe Joan Fons Sanchez (JFonS) Johan Manuel (29jm) Johannes Witt (HaSa1002) @@ -217,6 +219,7 @@ name is available. Omar El Sheikh (The-O-King) Ovnuniarchos Pascal Richter (ShyRed) + passivestar Patrick Dawson (pkdawson) Patrick Exner (FlameLizard) Patrick (firefly2442) @@ -232,6 +235,7 @@ name is available. Poommetee Ketson (Noshyaar) Przemysław Gołąb (n-pigeon) Rafael M. G. (rafallus) + Raffaele Picca (RPicster) Rafał Mikrut (qarmin) Ralf Hölzemer (rollenrolm) Ramesh Ravone (RameshRavone) @@ -298,6 +302,7 @@ name is available. Zae Chao (zaevi) Zak Stam (zaksnet) Zher Huei Lee (leezh) + Zi Ye (MajorMcDoom) ZuBsPaCe 谢天 (jsjtxietian) 风青山 (Rindbee) diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 2e246e6634..cbb0ad179d 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -411,6 +411,11 @@ Comment: PolyPartition / Triangulator Copyright: 2011-2021, Ivan Fratric and contributors License: Expat +Files: ./thirdparty/misc/qoa.h +Comment: Quite OK Audio Format +Copyright: 2023, Dominic Szablewski +License: Expat + Files: ./thirdparty/misc/r128.c ./thirdparty/misc/r128.h Comment: r128 library @@ -32,10 +32,8 @@ generous deed immortalized in the next stable release of Godot Engine. ## Silver sponsors - Affray Interactive <https://scp.games/pandemic/> Broken Rules <https://brokenrul.es/> Chasing Carrots <https://www.chasing-carrots.com/> - Gamblify <https://www.gamblify.com/> Indoor Astronaut <https://indoorastronaut.ch/> Null <https://null.com/> Orbital Knight <https://www.orbitalknight.com/> @@ -56,6 +54,7 @@ generous deed immortalized in the next stable release of Godot Engine. Garry Newman Isaiah Smith <https://www.isaiahsmith.dev/> Kenney <https://kenney.nl/> + Libretrend <https://libretrend.com/> Life Art Studios <https://lifeartstudios.net/> Lucid Silence Games Matthew Campbell @@ -99,19 +98,21 @@ generous deed immortalized in the next stable release of Godot Engine. Scott Pezza ShikadiGum Silver Creek Entertainment + SolarLabyrinth Stephan Kessler Stephan Lanfermann TigerJ Tim Yuen Violin Iliev Vladimír Chvátil - And 16 anonymous donors + And 15 anonymous donors ## Gold members @reilaos alMoo Games Alva Majo + Amadan Interactive (Cillian Clifford) Antti Vesanen Artur Ilkaev Asher Glick @@ -124,21 +125,31 @@ generous deed immortalized in the next stable release of Godot Engine. Brian Levinsen Brut Carlo del Mundo + Chickensoft ClarkThyLord Cosmin Munteanu + cowoder Coy Humphrey + Daniel Eichler David Chen Zhen David Coles David Hubber David Snopek + Deakcor Delton Ding + dfseifert + Don't You Know Who I Am? Inc. + Dono Dustuu + Edelweiss ElektroFox endaye Ends + Eren Öğrül Eric Phy Faisal Al-Kubaisi (QatariGameDev) FeralBytes + Garrus Vakarian GlassBrick Grau Guangzhou Lingchan @@ -153,6 +164,7 @@ generous deed immortalized in the next stable release of Godot Engine. John Gabriel Jon Woodward José Canepa + Justin Sasso KAR Games Karasu Studio korinVR @@ -167,6 +179,7 @@ generous deed immortalized in the next stable release of Godot Engine. Mara Huldra Martin Šenkeřík Megabit Interactive + Michael Gooch Modus Ponens nezticle Niklas Wahrman @@ -183,19 +196,25 @@ generous deed immortalized in the next stable release of Godot Engine. Robin Ward Saltlight Studio Samuel Judd + ScoreSpace Silverclad Studios Sofox Space Kraken Studios Spoony Panda + TANAKA Yu + TaraSophieDev ThatGamer ThePolyglotProgrammer Tim Nedvyga Tom Langwaldt Trevor Slocum tukon + Vagastella Vincent Foulon Weasel Games WuotanStudios.com + Yury K. + Zee Weasel Zhu Li zikes @@ -530,7 +549,7 @@ generous deed immortalized in the next stable release of Godot Engine. ケルベロス 貴宏 小松 - And 208 anonymous donors + And 201 anonymous donors ## Silver and bronze donors diff --git a/core/io/image.cpp b/core/io/image.cpp index 6096211cff..5498b448d7 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -521,7 +521,7 @@ void Image::convert(Format p_new_format) { // Includes the main image. const int mipmap_count = get_mipmap_count() + 1; - if (format > FORMAT_RGBE9995 || p_new_format > FORMAT_RGBE9995) { + if (Image::is_format_compressed(format) || Image::is_format_compressed(p_new_format)) { ERR_FAIL_MSG("Cannot convert to <-> from compressed formats. Use compress() and decompress() instead."); } else if (format > FORMAT_RGBA8 || p_new_format > FORMAT_RGBA8) { @@ -1662,7 +1662,7 @@ int Image::_get_dst_image_size(int p_width, int p_height, Format p_format, int & } bool Image::_can_modify(Format p_format) const { - return p_format <= FORMAT_RGBE9995; + return !Image::is_format_compressed(p_format); } template <typename Component, int CC, bool renormalize, @@ -2616,7 +2616,11 @@ int Image::get_image_mipmap_offset_and_dimensions(int p_width, int p_height, For } bool Image::is_compressed() const { - return format > FORMAT_RGBE9995; + return is_format_compressed(format); +} + +bool Image::is_format_compressed(Format p_format) { + return p_format > FORMAT_RGBE9995; } Error Image::decompress() { diff --git a/core/io/image.h b/core/io/image.h index 2cabbb767a..daddfac59d 100644 --- a/core/io/image.h +++ b/core/io/image.h @@ -376,6 +376,7 @@ public: Error compress_from_channels(CompressMode p_mode, UsedChannels p_channels, ASTCFormat p_astc_format = ASTC_FORMAT_4x4); Error decompress(); bool is_compressed() const; + static bool is_format_compressed(Format p_format); void fix_alpha_edges(); void premultiply_alpha(); diff --git a/core/math/aabb.h b/core/math/aabb.h index 48a883e64c..c2945a3ef1 100644 --- a/core/math/aabb.h +++ b/core/math/aabb.h @@ -101,7 +101,7 @@ struct _NO_DISCARD_ AABB { _FORCE_INLINE_ void expand_to(const Vector3 &p_vector); /** expand to contain a point if necessary */ _FORCE_INLINE_ AABB abs() const { - return AABB(position + size.min(Vector3()), size.abs()); + return AABB(position + size.minf(0), size.abs()); } Variant intersects_segment_bind(const Vector3 &p_from, const Vector3 &p_to) const; diff --git a/core/math/delaunay_3d.h b/core/math/delaunay_3d.h index 25bd4e8d89..4f21a665de 100644 --- a/core/math/delaunay_3d.h +++ b/core/math/delaunay_3d.h @@ -278,7 +278,7 @@ public: } Vector3i grid_pos = Vector3i(points[i] * proportions * ACCEL_GRID_SIZE); - grid_pos = grid_pos.clamp(Vector3i(), Vector3i(ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1)); + grid_pos = grid_pos.clampi(0, ACCEL_GRID_SIZE - 1); for (List<Simplex *>::Element *E = acceleration_grid[grid_pos.x][grid_pos.y][grid_pos.z].front(); E;) { List<Simplex *>::Element *N = E->next(); //may be deleted @@ -335,8 +335,8 @@ public: Vector3 extents = Vector3(radius2, radius2, radius2); Vector3i from = Vector3i((center - extents) * proportions * ACCEL_GRID_SIZE); Vector3i to = Vector3i((center + extents) * proportions * ACCEL_GRID_SIZE); - from = from.clamp(Vector3i(), Vector3i(ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1)); - to = to.clamp(Vector3i(), Vector3i(ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1)); + from = from.clampi(0, ACCEL_GRID_SIZE - 1); + to = to.clampi(0, ACCEL_GRID_SIZE - 1); for (int32_t x = from.x; x <= to.x; x++) { for (int32_t y = from.y; y <= to.y; y++) { diff --git a/core/math/quick_hull.cpp b/core/math/quick_hull.cpp index 4483f61bc4..6a60a5925d 100644 --- a/core/math/quick_hull.cpp +++ b/core/math/quick_hull.cpp @@ -55,7 +55,7 @@ Error QuickHull::build(const Vector<Vector3> &p_points, Geometry3D::MeshData &r_ HashSet<Vector3> valid_cache; for (int i = 0; i < p_points.size(); i++) { - Vector3 sp = p_points[i].snapped(Vector3(0.0001, 0.0001, 0.0001)); + Vector3 sp = p_points[i].snappedf(0.0001); if (valid_cache.has(sp)) { valid_points.write[i] = false; } else { diff --git a/core/math/rect2.h b/core/math/rect2.h index 7f410feb1c..b4069ae86a 100644 --- a/core/math/rect2.h +++ b/core/math/rect2.h @@ -278,7 +278,7 @@ struct _NO_DISCARD_ Rect2 { } _FORCE_INLINE_ Rect2 abs() const { - return Rect2(position + size.min(Point2()), size.abs()); + return Rect2(position + size.minf(0), size.abs()); } _FORCE_INLINE_ Rect2 round() const { diff --git a/core/math/rect2i.h b/core/math/rect2i.h index 64806414c7..a1338da0bb 100644 --- a/core/math/rect2i.h +++ b/core/math/rect2i.h @@ -213,7 +213,7 @@ struct _NO_DISCARD_ Rect2i { } _FORCE_INLINE_ Rect2i abs() const { - return Rect2i(position + size.min(Point2i()), size.abs()); + return Rect2i(position + size.mini(0), size.abs()); } _FORCE_INLINE_ void set_end(const Vector2i &p_end) { diff --git a/core/math/triangle_mesh.cpp b/core/math/triangle_mesh.cpp index 0da1b8c7ad..01b733183d 100644 --- a/core/math/triangle_mesh.cpp +++ b/core/math/triangle_mesh.cpp @@ -133,7 +133,7 @@ void TriangleMesh::create(const Vector<Vector3> &p_faces, const Vector<int32_t> for (int j = 0; j < 3; j++) { int vidx = -1; - Vector3 vs = v[j].snapped(Vector3(0.0001, 0.0001, 0.0001)); + Vector3 vs = v[j].snappedf(0.0001); HashMap<Vector3, int>::Iterator E = db.find(vs); if (E) { vidx = E->value; diff --git a/core/math/vector2.cpp b/core/math/vector2.cpp index 198fd85d20..e86b97d6a8 100644 --- a/core/math/vector2.cpp +++ b/core/math/vector2.cpp @@ -135,12 +135,24 @@ Vector2 Vector2::clamp(const Vector2 &p_min, const Vector2 &p_max) const { CLAMP(y, p_min.y, p_max.y)); } +Vector2 Vector2::clampf(real_t p_min, real_t p_max) const { + return Vector2( + CLAMP(x, p_min, p_max), + CLAMP(y, p_min, p_max)); +} + Vector2 Vector2::snapped(const Vector2 &p_step) const { return Vector2( Math::snapped(x, p_step.x), Math::snapped(y, p_step.y)); } +Vector2 Vector2::snappedf(real_t p_step) const { + return Vector2( + Math::snapped(x, p_step), + Math::snapped(y, p_step)); +} + Vector2 Vector2::limit_length(real_t p_len) const { const real_t l = length(); Vector2 v = *this; diff --git a/core/math/vector2.h b/core/math/vector2.h index 6ad003edd1..8851942cdd 100644 --- a/core/math/vector2.h +++ b/core/math/vector2.h @@ -89,10 +89,18 @@ struct _NO_DISCARD_ Vector2 { return Vector2(MIN(x, p_vector2.x), MIN(y, p_vector2.y)); } + Vector2 minf(real_t p_scalar) const { + return Vector2(MIN(x, p_scalar), MIN(y, p_scalar)); + } + Vector2 max(const Vector2 &p_vector2) const { return Vector2(MAX(x, p_vector2.x), MAX(y, p_vector2.y)); } + Vector2 maxf(real_t p_scalar) const { + return Vector2(MAX(x, p_scalar), MAX(y, p_scalar)); + } + real_t distance_to(const Vector2 &p_vector2) const; real_t distance_squared_to(const Vector2 &p_vector2) const; real_t angle_to(const Vector2 &p_vector2) const; @@ -168,7 +176,9 @@ struct _NO_DISCARD_ Vector2 { Vector2 ceil() const; Vector2 round() const; Vector2 snapped(const Vector2 &p_by) const; + Vector2 snappedf(real_t p_by) const; Vector2 clamp(const Vector2 &p_min, const Vector2 &p_max) const; + Vector2 clampf(real_t p_min, real_t p_max) const; real_t aspect() const { return width / height; } operator String() const; diff --git a/core/math/vector2i.cpp b/core/math/vector2i.cpp index ba79d439dd..790f564734 100644 --- a/core/math/vector2i.cpp +++ b/core/math/vector2i.cpp @@ -39,12 +39,24 @@ Vector2i Vector2i::clamp(const Vector2i &p_min, const Vector2i &p_max) const { CLAMP(y, p_min.y, p_max.y)); } +Vector2i Vector2i::clampi(int32_t p_min, int32_t p_max) const { + return Vector2i( + CLAMP(x, p_min, p_max), + CLAMP(y, p_min, p_max)); +} + Vector2i Vector2i::snapped(const Vector2i &p_step) const { return Vector2i( Math::snapped(x, p_step.x), Math::snapped(y, p_step.y)); } +Vector2i Vector2i::snappedi(int32_t p_step) const { + return Vector2i( + Math::snapped(x, p_step), + Math::snapped(y, p_step)); +} + int64_t Vector2i::length_squared() const { return x * (int64_t)x + y * (int64_t)y; } diff --git a/core/math/vector2i.h b/core/math/vector2i.h index aa29263a65..aca9ae8272 100644 --- a/core/math/vector2i.h +++ b/core/math/vector2i.h @@ -81,10 +81,18 @@ struct _NO_DISCARD_ Vector2i { return Vector2i(MIN(x, p_vector2i.x), MIN(y, p_vector2i.y)); } + Vector2i mini(int32_t p_scalar) const { + return Vector2i(MIN(x, p_scalar), MIN(y, p_scalar)); + } + Vector2i max(const Vector2i &p_vector2i) const { return Vector2i(MAX(x, p_vector2i.x), MAX(y, p_vector2i.y)); } + Vector2i maxi(int32_t p_scalar) const { + return Vector2i(MAX(x, p_scalar), MAX(y, p_scalar)); + } + double distance_to(const Vector2i &p_to) const { return (p_to - *this).length(); } @@ -127,7 +135,9 @@ struct _NO_DISCARD_ Vector2i { Vector2i sign() const { return Vector2i(SIGN(x), SIGN(y)); } Vector2i abs() const { return Vector2i(Math::abs(x), Math::abs(y)); } Vector2i clamp(const Vector2i &p_min, const Vector2i &p_max) const; + Vector2i clampi(int32_t p_min, int32_t p_max) const; Vector2i snapped(const Vector2i &p_step) const; + Vector2i snappedi(int32_t p_step) const; operator String() const; operator Vector2() const; diff --git a/core/math/vector3.cpp b/core/math/vector3.cpp index fad5f2c0fb..1e90002665 100644 --- a/core/math/vector3.cpp +++ b/core/math/vector3.cpp @@ -52,6 +52,13 @@ Vector3 Vector3::clamp(const Vector3 &p_min, const Vector3 &p_max) const { CLAMP(z, p_min.z, p_max.z)); } +Vector3 Vector3::clampf(real_t p_min, real_t p_max) const { + return Vector3( + CLAMP(x, p_min, p_max), + CLAMP(y, p_min, p_max), + CLAMP(z, p_min, p_max)); +} + void Vector3::snap(const Vector3 &p_step) { x = Math::snapped(x, p_step.x); y = Math::snapped(y, p_step.y); @@ -64,6 +71,18 @@ Vector3 Vector3::snapped(const Vector3 &p_step) const { return v; } +void Vector3::snapf(real_t p_step) { + x = Math::snapped(x, p_step); + y = Math::snapped(y, p_step); + z = Math::snapped(z, p_step); +} + +Vector3 Vector3::snappedf(real_t p_step) const { + Vector3 v = *this; + v.snapf(p_step); + return v; +} + Vector3 Vector3::limit_length(real_t p_len) const { const real_t l = length(); Vector3 v = *this; diff --git a/core/math/vector3.h b/core/math/vector3.h index f5d16984d9..2313eb557a 100644 --- a/core/math/vector3.h +++ b/core/math/vector3.h @@ -80,10 +80,18 @@ struct _NO_DISCARD_ Vector3 { return Vector3(MIN(x, p_vector3.x), MIN(y, p_vector3.y), MIN(z, p_vector3.z)); } + Vector3 minf(real_t p_scalar) const { + return Vector3(MIN(x, p_scalar), MIN(y, p_scalar), MIN(z, p_scalar)); + } + Vector3 max(const Vector3 &p_vector3) const { return Vector3(MAX(x, p_vector3.x), MAX(y, p_vector3.y), MAX(z, p_vector3.z)); } + Vector3 maxf(real_t p_scalar) const { + return Vector3(MAX(x, p_scalar), MAX(y, p_scalar), MAX(z, p_scalar)); + } + _FORCE_INLINE_ real_t length() const; _FORCE_INLINE_ real_t length_squared() const; @@ -96,7 +104,9 @@ struct _NO_DISCARD_ Vector3 { _FORCE_INLINE_ void zero(); void snap(const Vector3 &p_step); + void snapf(real_t p_step); Vector3 snapped(const Vector3 &p_step) const; + Vector3 snappedf(real_t p_step) const; void rotate(const Vector3 &p_axis, real_t p_angle); Vector3 rotated(const Vector3 &p_axis, real_t p_angle) const; @@ -127,6 +137,7 @@ struct _NO_DISCARD_ Vector3 { _FORCE_INLINE_ Vector3 ceil() const; _FORCE_INLINE_ Vector3 round() const; Vector3 clamp(const Vector3 &p_min, const Vector3 &p_max) const; + Vector3 clampf(real_t p_min, real_t p_max) const; _FORCE_INLINE_ real_t distance_to(const Vector3 &p_to) const; _FORCE_INLINE_ real_t distance_squared_to(const Vector3 &p_to) const; diff --git a/core/math/vector3i.cpp b/core/math/vector3i.cpp index f41460e623..93f9d15ac1 100644 --- a/core/math/vector3i.cpp +++ b/core/math/vector3i.cpp @@ -48,6 +48,13 @@ Vector3i Vector3i::clamp(const Vector3i &p_min, const Vector3i &p_max) const { CLAMP(z, p_min.z, p_max.z)); } +Vector3i Vector3i::clampi(int32_t p_min, int32_t p_max) const { + return Vector3i( + CLAMP(x, p_min, p_max), + CLAMP(y, p_min, p_max), + CLAMP(z, p_min, p_max)); +} + Vector3i Vector3i::snapped(const Vector3i &p_step) const { return Vector3i( Math::snapped(x, p_step.x), @@ -55,6 +62,13 @@ Vector3i Vector3i::snapped(const Vector3i &p_step) const { Math::snapped(z, p_step.z)); } +Vector3i Vector3i::snappedi(int32_t p_step) const { + return Vector3i( + Math::snapped(x, p_step), + Math::snapped(y, p_step), + Math::snapped(z, p_step)); +} + Vector3i::operator String() const { return "(" + itos(x) + ", " + itos(y) + ", " + itos(z) + ")"; } diff --git a/core/math/vector3i.h b/core/math/vector3i.h index a9f298bff1..035cfcf9e2 100644 --- a/core/math/vector3i.h +++ b/core/math/vector3i.h @@ -73,10 +73,18 @@ struct _NO_DISCARD_ Vector3i { return Vector3i(MIN(x, p_vector3i.x), MIN(y, p_vector3i.y), MIN(z, p_vector3i.z)); } + Vector3i mini(int32_t p_scalar) const { + return Vector3i(MIN(x, p_scalar), MIN(y, p_scalar), MIN(z, p_scalar)); + } + Vector3i max(const Vector3i &p_vector3i) const { return Vector3i(MAX(x, p_vector3i.x), MAX(y, p_vector3i.y), MAX(z, p_vector3i.z)); } + Vector3i maxi(int32_t p_scalar) const { + return Vector3i(MAX(x, p_scalar), MAX(y, p_scalar), MAX(z, p_scalar)); + } + _FORCE_INLINE_ int64_t length_squared() const; _FORCE_INLINE_ double length() const; @@ -85,7 +93,9 @@ struct _NO_DISCARD_ Vector3i { _FORCE_INLINE_ Vector3i abs() const; _FORCE_INLINE_ Vector3i sign() const; Vector3i clamp(const Vector3i &p_min, const Vector3i &p_max) const; + Vector3i clampi(int32_t p_min, int32_t p_max) const; Vector3i snapped(const Vector3i &p_step) const; + Vector3i snappedi(int32_t p_step) const; _FORCE_INLINE_ double distance_to(const Vector3i &p_to) const; _FORCE_INLINE_ int64_t distance_squared_to(const Vector3i &p_to) const; diff --git a/core/math/vector4.cpp b/core/math/vector4.cpp index e6f6dee42c..555ca6c66c 100644 --- a/core/math/vector4.cpp +++ b/core/math/vector4.cpp @@ -171,12 +171,25 @@ void Vector4::snap(const Vector4 &p_step) { w = Math::snapped(w, p_step.w); } +void Vector4::snapf(real_t p_step) { + x = Math::snapped(x, p_step); + y = Math::snapped(y, p_step); + z = Math::snapped(z, p_step); + w = Math::snapped(w, p_step); +} + Vector4 Vector4::snapped(const Vector4 &p_step) const { Vector4 v = *this; v.snap(p_step); return v; } +Vector4 Vector4::snappedf(real_t p_step) const { + Vector4 v = *this; + v.snapf(p_step); + return v; +} + Vector4 Vector4::inverse() const { return Vector4(1.0f / x, 1.0f / y, 1.0f / z, 1.0f / w); } @@ -189,6 +202,14 @@ Vector4 Vector4::clamp(const Vector4 &p_min, const Vector4 &p_max) const { CLAMP(w, p_min.w, p_max.w)); } +Vector4 Vector4::clampf(real_t p_min, real_t p_max) const { + return Vector4( + CLAMP(x, p_min, p_max), + CLAMP(y, p_min, p_max), + CLAMP(z, p_min, p_max), + CLAMP(w, p_min, p_max)); +} + Vector4::operator String() const { return "(" + String::num_real(x, false) + ", " + String::num_real(y, false) + ", " + String::num_real(z, false) + ", " + String::num_real(w, false) + ")"; } diff --git a/core/math/vector4.h b/core/math/vector4.h index 4dba3126cb..52699c6281 100644 --- a/core/math/vector4.h +++ b/core/math/vector4.h @@ -72,10 +72,18 @@ struct _NO_DISCARD_ Vector4 { return Vector4(MIN(x, p_vector4.x), MIN(y, p_vector4.y), MIN(z, p_vector4.z), MIN(w, p_vector4.w)); } + Vector4 minf(real_t p_scalar) const { + return Vector4(MIN(x, p_scalar), MIN(y, p_scalar), MIN(z, p_scalar), MIN(w, p_scalar)); + } + Vector4 max(const Vector4 &p_vector4) const { return Vector4(MAX(x, p_vector4.x), MAX(y, p_vector4.y), MAX(z, p_vector4.z), MAX(w, p_vector4.w)); } + Vector4 maxf(real_t p_scalar) const { + return Vector4(MAX(x, p_scalar), MAX(y, p_scalar), MAX(z, p_scalar), MAX(w, p_scalar)); + } + _FORCE_INLINE_ real_t length_squared() const; bool is_equal_approx(const Vector4 &p_vec4) const; bool is_zero_approx() const; @@ -101,8 +109,11 @@ struct _NO_DISCARD_ Vector4 { Vector4 posmod(real_t p_mod) const; Vector4 posmodv(const Vector4 &p_modv) const; void snap(const Vector4 &p_step); + void snapf(real_t p_step); Vector4 snapped(const Vector4 &p_step) const; + Vector4 snappedf(real_t p_step) const; Vector4 clamp(const Vector4 &p_min, const Vector4 &p_max) const; + Vector4 clampf(real_t p_min, real_t p_max) const; Vector4 inverse() const; _FORCE_INLINE_ real_t dot(const Vector4 &p_vec4) const; diff --git a/core/math/vector4i.cpp b/core/math/vector4i.cpp index 8e36c6b534..afa77a4988 100644 --- a/core/math/vector4i.cpp +++ b/core/math/vector4i.cpp @@ -65,6 +65,14 @@ Vector4i Vector4i::clamp(const Vector4i &p_min, const Vector4i &p_max) const { CLAMP(w, p_min.w, p_max.w)); } +Vector4i Vector4i::clampi(int32_t p_min, int32_t p_max) const { + return Vector4i( + CLAMP(x, p_min, p_max), + CLAMP(y, p_min, p_max), + CLAMP(z, p_min, p_max), + CLAMP(w, p_min, p_max)); +} + Vector4i Vector4i::snapped(const Vector4i &p_step) const { return Vector4i( Math::snapped(x, p_step.x), @@ -73,6 +81,14 @@ Vector4i Vector4i::snapped(const Vector4i &p_step) const { Math::snapped(w, p_step.w)); } +Vector4i Vector4i::snappedi(int32_t p_step) const { + return Vector4i( + Math::snapped(x, p_step), + Math::snapped(y, p_step), + Math::snapped(z, p_step), + Math::snapped(w, p_step)); +} + Vector4i::operator String() const { return "(" + itos(x) + ", " + itos(y) + ", " + itos(z) + ", " + itos(w) + ")"; } diff --git a/core/math/vector4i.h b/core/math/vector4i.h index 5a96d98d18..8a9c580bc1 100644 --- a/core/math/vector4i.h +++ b/core/math/vector4i.h @@ -75,10 +75,18 @@ struct _NO_DISCARD_ Vector4i { return Vector4i(MIN(x, p_vector4i.x), MIN(y, p_vector4i.y), MIN(z, p_vector4i.z), MIN(w, p_vector4i.w)); } + Vector4i mini(int32_t p_scalar) const { + return Vector4i(MIN(x, p_scalar), MIN(y, p_scalar), MIN(z, p_scalar), MIN(w, p_scalar)); + } + Vector4i max(const Vector4i &p_vector4i) const { return Vector4i(MAX(x, p_vector4i.x), MAX(y, p_vector4i.y), MAX(z, p_vector4i.z), MAX(w, p_vector4i.w)); } + Vector4i maxi(int32_t p_scalar) const { + return Vector4i(MAX(x, p_scalar), MAX(y, p_scalar), MAX(z, p_scalar), MAX(w, p_scalar)); + } + _FORCE_INLINE_ int64_t length_squared() const; _FORCE_INLINE_ double length() const; @@ -90,7 +98,9 @@ struct _NO_DISCARD_ Vector4i { _FORCE_INLINE_ Vector4i abs() const; _FORCE_INLINE_ Vector4i sign() const; Vector4i clamp(const Vector4i &p_min, const Vector4i &p_max) const; + Vector4i clampi(int32_t p_min, int32_t p_max) const; Vector4i snapped(const Vector4i &p_step) const; + Vector4i snappedi(int32_t p_step) const; /* Operators */ diff --git a/core/templates/command_queue_mt.cpp b/core/templates/command_queue_mt.cpp index 0c5c6394a1..d9e5e0b217 100644 --- a/core/templates/command_queue_mt.cpp +++ b/core/templates/command_queue_mt.cpp @@ -41,35 +41,6 @@ void CommandQueueMT::unlock() { mutex.unlock(); } -void CommandQueueMT::wait_for_flush() { - // wait one millisecond for a flush to happen - OS::get_singleton()->delay_usec(1000); -} - -CommandQueueMT::SyncSemaphore *CommandQueueMT::_alloc_sync_sem() { - int idx = -1; - - while (true) { - lock(); - for (int i = 0; i < SYNC_SEMAPHORES; i++) { - if (!sync_sems[i].in_use) { - sync_sems[i].in_use = true; - idx = i; - break; - } - } - unlock(); - - if (idx == -1) { - wait_for_flush(); - } else { - break; - } - } - - return &sync_sems[idx]; -} - CommandQueueMT::CommandQueueMT() { } diff --git a/core/templates/command_queue_mt.h b/core/templates/command_queue_mt.h index a4ac338bed..c149861467 100644 --- a/core/templates/command_queue_mt.h +++ b/core/templates/command_queue_mt.h @@ -32,9 +32,9 @@ #define COMMAND_QUEUE_MT_H #include "core/object/worker_thread_pool.h" +#include "core/os/condition_variable.h" #include "core/os/memory.h" #include "core/os/mutex.h" -#include "core/os/semaphore.h" #include "core/string/print_string.h" #include "core/templates/local_vector.h" #include "core/templates/simple_type.h" @@ -251,14 +251,14 @@ #define DECL_PUSH(N) \ template <typename T, typename M COMMA(N) COMMA_SEP_LIST(TYPE_PARAM, N)> \ void push(T *p_instance, M p_method COMMA(N) COMMA_SEP_LIST(PARAM, N)) { \ - CMD_TYPE(N) *cmd = allocate_and_lock<CMD_TYPE(N)>(); \ + MutexLock mlock(mutex); \ + CMD_TYPE(N) *cmd = allocate<CMD_TYPE(N)>(); \ cmd->instance = p_instance; \ cmd->method = p_method; \ SEMIC_SEP_LIST(CMD_ASSIGN_PARAM, N); \ if (pump_task_id != WorkerThreadPool::INVALID_TASK_ID) { \ WorkerThreadPool::get_singleton()->notify_yield_over(pump_task_id); \ } \ - unlock(); \ } #define CMD_RET_TYPE(N) CommandRet##N<T, M, COMMA_SEP_LIST(TYPE_ARG, N) COMMA(N) R> @@ -266,19 +266,17 @@ #define DECL_PUSH_AND_RET(N) \ template <typename T, typename M, COMMA_SEP_LIST(TYPE_PARAM, N) COMMA(N) typename R> \ void push_and_ret(T *p_instance, M p_method, COMMA_SEP_LIST(PARAM, N) COMMA(N) R *r_ret) { \ - SyncSemaphore *ss = _alloc_sync_sem(); \ - CMD_RET_TYPE(N) *cmd = allocate_and_lock<CMD_RET_TYPE(N)>(); \ + MutexLock mlock(mutex); \ + CMD_RET_TYPE(N) *cmd = allocate<CMD_RET_TYPE(N)>(); \ cmd->instance = p_instance; \ cmd->method = p_method; \ SEMIC_SEP_LIST(CMD_ASSIGN_PARAM, N); \ cmd->ret = r_ret; \ - cmd->sync_sem = ss; \ if (pump_task_id != WorkerThreadPool::INVALID_TASK_ID) { \ WorkerThreadPool::get_singleton()->notify_yield_over(pump_task_id); \ } \ - unlock(); \ - ss->sem.wait(); \ - ss->in_use = false; \ + sync_tail++; \ + _wait_for_sync(mlock); \ } #define CMD_SYNC_TYPE(N) CommandSync##N<T, M COMMA(N) COMMA_SEP_LIST(TYPE_ARG, N)> @@ -286,39 +284,31 @@ #define DECL_PUSH_AND_SYNC(N) \ template <typename T, typename M COMMA(N) COMMA_SEP_LIST(TYPE_PARAM, N)> \ void push_and_sync(T *p_instance, M p_method COMMA(N) COMMA_SEP_LIST(PARAM, N)) { \ - SyncSemaphore *ss = _alloc_sync_sem(); \ - CMD_SYNC_TYPE(N) *cmd = allocate_and_lock<CMD_SYNC_TYPE(N)>(); \ + MutexLock mlock(mutex); \ + CMD_SYNC_TYPE(N) *cmd = allocate<CMD_SYNC_TYPE(N)>(); \ cmd->instance = p_instance; \ cmd->method = p_method; \ SEMIC_SEP_LIST(CMD_ASSIGN_PARAM, N); \ - cmd->sync_sem = ss; \ if (pump_task_id != WorkerThreadPool::INVALID_TASK_ID) { \ WorkerThreadPool::get_singleton()->notify_yield_over(pump_task_id); \ } \ - unlock(); \ - ss->sem.wait(); \ - ss->in_use = false; \ + sync_tail++; \ + _wait_for_sync(mlock); \ } #define MAX_CMD_PARAMS 15 class CommandQueueMT { - struct SyncSemaphore { - Semaphore sem; - bool in_use = false; - }; - struct CommandBase { + bool sync = false; virtual void call() = 0; - virtual SyncSemaphore *get_sync_semaphore() { return nullptr; } virtual ~CommandBase() = default; // Won't be called. }; struct SyncCommand : public CommandBase { - SyncSemaphore *sync_sem = nullptr; - - virtual SyncSemaphore *get_sync_semaphore() override { - return sync_sem; + virtual void call() override {} + SyncCommand() { + sync = true; } }; @@ -340,9 +330,11 @@ class CommandQueueMT { SYNC_SEMAPHORES = 8 }; + BinaryMutex mutex; LocalVector<uint8_t> command_mem; - SyncSemaphore sync_sems[SYNC_SEMAPHORES]; - Mutex mutex; + ConditionVariable sync_cond_var; + uint32_t sync_head = 0; + uint32_t sync_tail = 0; WorkerThreadPool::TaskID pump_task_id = WorkerThreadPool::INVALID_TASK_ID; uint64_t flush_read_ptr = 0; @@ -357,32 +349,23 @@ class CommandQueueMT { return cmd; } - template <typename T> - T *allocate_and_lock() { - lock(); - T *ret = allocate<T>(); - return ret; - } - void _flush() { - lock(); - if (unlikely(flush_read_ptr)) { // Re-entrant call. - unlock(); return; } + lock(); + WorkerThreadPool::thread_enter_command_queue_mt_flush(this); while (flush_read_ptr < command_mem.size()) { uint64_t size = *(uint64_t *)&command_mem[flush_read_ptr]; flush_read_ptr += 8; CommandBase *cmd = reinterpret_cast<CommandBase *>(&command_mem[flush_read_ptr]); - - SyncSemaphore *sync_sem = cmd->get_sync_semaphore(); cmd->call(); - if (sync_sem) { - sync_sem->sem.post(); // Release in case it needs sync/ret. + if (unlikely(cmd->sync)) { + sync_head++; + sync_cond_var.notify_all(); } flush_read_ptr += size; @@ -394,8 +377,12 @@ class CommandQueueMT { unlock(); } - void wait_for_flush(); - SyncSemaphore *_alloc_sync_sem(); + _FORCE_INLINE_ void _wait_for_sync(MutexLock<BinaryMutex> &p_lock) { + uint32_t sync_head_goal = sync_tail; + do { + sync_cond_var.wait(p_lock); + } while (sync_head != sync_head_goal); // Can't use lower-than because of wraparound. + } public: void lock(); diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index fcbfdd4741..37eb16f9b2 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -3515,6 +3515,17 @@ bool Variant::is_shared() const { return is_type_shared(type); } +bool Variant::is_read_only() const { + switch (type) { + case ARRAY: + return reinterpret_cast<const Array *>(_data._mem)->is_read_only(); + case DICTIONARY: + return reinterpret_cast<const Dictionary *>(_data._mem)->is_read_only(); + default: + return false; + } +} + void Variant::_variant_call_error(const String &p_method, Callable::CallError &error) { switch (error.error) { case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: { diff --git a/core/variant/variant.h b/core/variant/variant.h index ea6ae02c1e..93953c4e0e 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -349,6 +349,7 @@ public: bool is_zero() const; bool is_one() const; bool is_null() const; + bool is_read_only() const; // Make sure Variant is not implicitly cast when accessing it with bracket notation (GH-49469). Variant &operator[](const Variant &p_key) = delete; diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index d0d940c47d..986b34c662 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1804,7 +1804,13 @@ static void _register_variant_builtin_methods() { bind_method(Vector2, abs, sarray(), varray()); bind_method(Vector2, sign, sarray(), varray()); bind_method(Vector2, clamp, sarray("min", "max"), varray()); + bind_method(Vector2, clampf, sarray("min", "max"), varray()); bind_method(Vector2, snapped, sarray("step"), varray()); + bind_method(Vector2, snappedf, sarray("step"), varray()); + bind_method(Vector2, min, sarray("with"), varray()); + bind_method(Vector2, minf, sarray("with"), varray()); + bind_method(Vector2, max, sarray("with"), varray()); + bind_method(Vector2, maxf, sarray("with"), varray()); bind_static_method(Vector2, from_angle, sarray("angle"), varray()); @@ -1820,7 +1826,13 @@ static void _register_variant_builtin_methods() { bind_method(Vector2i, sign, sarray(), varray()); bind_method(Vector2i, abs, sarray(), varray()); bind_method(Vector2i, clamp, sarray("min", "max"), varray()); + bind_method(Vector2i, clampi, sarray("min", "max"), varray()); bind_method(Vector2i, snapped, sarray("step"), varray()); + bind_method(Vector2i, snappedi, sarray("step"), varray()); + bind_method(Vector2i, min, sarray("with"), varray()); + bind_method(Vector2i, mini, sarray("with"), varray()); + bind_method(Vector2i, max, sarray("with"), varray()); + bind_method(Vector2i, maxi, sarray("with"), varray()); /* Rect2 */ @@ -1875,7 +1887,9 @@ static void _register_variant_builtin_methods() { bind_method(Vector3, is_finite, sarray(), varray()); bind_method(Vector3, inverse, sarray(), varray()); bind_method(Vector3, clamp, sarray("min", "max"), varray()); + bind_method(Vector3, clampf, sarray("min", "max"), varray()); bind_method(Vector3, snapped, sarray("step"), varray()); + bind_method(Vector3, snappedf, sarray("step"), varray()); bind_method(Vector3, rotated, sarray("axis", "angle"), varray()); bind_method(Vector3, lerp, sarray("to", "weight"), varray()); bind_method(Vector3, slerp, sarray("to", "weight"), varray()); @@ -1896,9 +1910,13 @@ static void _register_variant_builtin_methods() { bind_method(Vector3, project, sarray("b"), varray()); bind_method(Vector3, slide, sarray("n"), varray()); bind_method(Vector3, bounce, sarray("n"), varray()); - bind_method(Vector3, reflect, sarray("direction"), varray()); + bind_method(Vector3, reflect, sarray("n"), varray()); bind_method(Vector3, sign, sarray(), varray()); bind_method(Vector3, octahedron_encode, sarray(), varray()); + bind_method(Vector3, min, sarray("with"), varray()); + bind_method(Vector3, minf, sarray("with"), varray()); + bind_method(Vector3, max, sarray("with"), varray()); + bind_method(Vector3, maxf, sarray("with"), varray()); bind_static_method(Vector3, octahedron_decode, sarray("uv"), varray()); /* Vector3i */ @@ -1912,7 +1930,13 @@ static void _register_variant_builtin_methods() { bind_method(Vector3i, sign, sarray(), varray()); bind_method(Vector3i, abs, sarray(), varray()); bind_method(Vector3i, clamp, sarray("min", "max"), varray()); + bind_method(Vector3i, clampi, sarray("min", "max"), varray()); bind_method(Vector3i, snapped, sarray("step"), varray()); + bind_method(Vector3i, snappedi, sarray("step"), varray()); + bind_method(Vector3i, min, sarray("with"), varray()); + bind_method(Vector3i, mini, sarray("with"), varray()); + bind_method(Vector3i, max, sarray("with"), varray()); + bind_method(Vector3i, maxi, sarray("with"), varray()); /* Vector4 */ @@ -1931,7 +1955,9 @@ static void _register_variant_builtin_methods() { bind_method(Vector4, posmod, sarray("mod"), varray()); bind_method(Vector4, posmodv, sarray("modv"), varray()); bind_method(Vector4, snapped, sarray("step"), varray()); + bind_method(Vector4, snappedf, sarray("step"), varray()); bind_method(Vector4, clamp, sarray("min", "max"), varray()); + bind_method(Vector4, clampf, sarray("min", "max"), varray()); bind_method(Vector4, normalized, sarray(), varray()); bind_method(Vector4, is_normalized, sarray(), varray()); bind_method(Vector4, direction_to, sarray("to"), varray()); @@ -1942,6 +1968,10 @@ static void _register_variant_builtin_methods() { bind_method(Vector4, is_equal_approx, sarray("to"), varray()); bind_method(Vector4, is_zero_approx, sarray(), varray()); bind_method(Vector4, is_finite, sarray(), varray()); + bind_method(Vector4, min, sarray("with"), varray()); + bind_method(Vector4, minf, sarray("with"), varray()); + bind_method(Vector4, max, sarray("with"), varray()); + bind_method(Vector4, maxf, sarray("with"), varray()); /* Vector4i */ @@ -1952,7 +1982,13 @@ static void _register_variant_builtin_methods() { bind_method(Vector4i, sign, sarray(), varray()); bind_method(Vector4i, abs, sarray(), varray()); bind_method(Vector4i, clamp, sarray("min", "max"), varray()); + bind_method(Vector4i, clampi, sarray("min", "max"), varray()); bind_method(Vector4i, snapped, sarray("step"), varray()); + bind_method(Vector4i, snappedi, sarray("step"), varray()); + bind_method(Vector4i, min, sarray("with"), varray()); + bind_method(Vector4i, mini, sarray("with"), varray()); + bind_method(Vector4i, max, sarray("with"), varray()); + bind_method(Vector4i, maxi, sarray("with"), varray()); bind_method(Vector4i, distance_to, sarray("to"), varray()); bind_method(Vector4i, distance_squared_to, sarray("to"), varray()); diff --git a/core/variant/variant_setget.cpp b/core/variant/variant_setget.cpp index 9d5ed22b1a..f49e9e54b3 100644 --- a/core/variant/variant_setget.cpp +++ b/core/variant/variant_setget.cpp @@ -251,15 +251,21 @@ void Variant::set_named(const StringName &p_member, const Variant &p_value, bool return; } } else if (type == Variant::DICTIONARY) { - Variant *v = VariantGetInternalPtr<Dictionary>::get_ptr(this)->getptr(p_member); + Dictionary &dict = *VariantGetInternalPtr<Dictionary>::get_ptr(this); + + if (dict.is_read_only()) { + r_valid = false; + return; + } + + Variant *v = dict.getptr(p_member); if (v) { *v = p_value; - r_valid = true; } else { - VariantGetInternalPtr<Dictionary>::get_ptr(this)->operator[](p_member) = p_value; - r_valid = true; + dict[p_member] = p_value; } + r_valid = true; } else { r_valid = false; } diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 0307684588..f2a6517a0c 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -696,6 +696,7 @@ [codeblock] max(1, 7, 3, -6, 5) # Returns 7 [/codeblock] + [b]Note:[/b] When using this on vectors it will [i]not[/i] perform component-wise maximum, and will pick the largest value when compared using [code]x < y[/code]. To perform component-wise maximum, use [method Vector2.max], [method Vector2i.max], [method Vector3.max], [method Vector3i.max], [method Vector4.max], and [method Vector4i.max]. </description> </method> <method name="maxf"> @@ -729,6 +730,7 @@ [codeblock] min(1, 7, 3, -6, 5) # Returns -6 [/codeblock] + [b]Note:[/b] When using this on vectors it will [i]not[/i] perform component-wise minimum, and will pick the smallest value when compared using [code]x < y[/code]. To perform component-wise minimum, use [method Vector2.min], [method Vector2i.min], [method Vector3.min], [method Vector3i.min], [method Vector4.min], and [method Vector4i.min]. </description> </method> <method name="minf"> diff --git a/doc/classes/Animation.xml b/doc/classes/Animation.xml index 4e9b642674..26ed881502 100644 --- a/doc/classes/Animation.xml +++ b/doc/classes/Animation.xml @@ -595,6 +595,9 @@ </method> </methods> <members> + <member name="capture_included" type="bool" setter="_set_capture_included" getter="is_capture_included" default="false"> + Returns [code]true[/code] if the capture track is included. This is a cached readonly value for performance. + </member> <member name="length" type="float" setter="set_length" getter="get_length" default="1.0"> The total length of the animation (in seconds). [b]Note:[/b] Length is not delimited by the last key, as this one may be before or after the end to ensure correct interpolation and looping. @@ -658,7 +661,7 @@ Update at the keyframes. </constant> <constant name="UPDATE_CAPTURE" value="2" enum="UpdateMode"> - Same as [constant UPDATE_CONTINUOUS] but works as a flag to capture the value of the current object and perform interpolation in some methods. See also [method AnimationMixer.capture] and [method AnimationPlayer.play_with_capture]. + Same as [constant UPDATE_CONTINUOUS] but works as a flag to capture the value of the current object and perform interpolation in some methods. See also [method AnimationMixer.capture], [member AnimationPlayer.playback_auto_capture], and [method AnimationPlayer.play_with_capture]. </constant> <constant name="LOOP_NONE" value="0" enum="LoopMode"> At both ends of the animation, the animation will stop playing. diff --git a/doc/classes/AnimationPlayer.xml b/doc/classes/AnimationPlayer.xml index 1b742bea28..ef9c1ecebe 100644 --- a/doc/classes/AnimationPlayer.xml +++ b/doc/classes/AnimationPlayer.xml @@ -112,7 +112,7 @@ </method> <method name="play_with_capture"> <return type="void" /> - <param index="0" name="name" type="StringName" /> + <param index="0" name="name" type="StringName" default="&""" /> <param index="1" name="duration" type="float" default="-1.0" /> <param index="2" name="custom_blend" type="float" default="-1" /> <param index="3" name="custom_speed" type="float" default="1.0" /> @@ -120,12 +120,13 @@ <param index="5" name="trans_type" type="int" enum="Tween.TransitionType" default="0" /> <param index="6" name="ease_type" type="int" enum="Tween.EaseType" default="0" /> <description> - See [method AnimationMixer.capture]. It is almost the same as the following: + See also [method AnimationMixer.capture]. + You can use this method to use more detailed options for capture than those performed by [member playback_auto_capture]. When [member playback_auto_capture] is [code]false[/code], this method is almost the same as the following: [codeblock] capture(name, duration, trans_type, ease_type) play(name, custom_blend, custom_speed, from_end) [/codeblock] - If name is blank, it specifies [member assigned_animation]. + If [param name] is blank, it specifies [member assigned_animation]. If [param duration] is a negative value, the duration is set to the interval between the current position and the first key, when [param from_end] is [code]true[/code], uses the interval between the current position and the last key instead. [b]Note:[/b] The [param duration] takes [member speed_scale] into account, but [param custom_speed] does not, because the capture cache is interpolated with the blend result and the result may contain multiple animations. </description> @@ -210,6 +211,9 @@ If [code]true[/code] and the engine is running in Movie Maker mode (see [MovieWriter]), exits the engine with [method SceneTree.quit] as soon as an animation is done playing in this [AnimationPlayer]. A message is printed when the engine quits for this reason. [b]Note:[/b] This obeys the same logic as the [signal AnimationMixer.animation_finished] signal, so it will not quit the engine if the animation is set to be looping. </member> + <member name="playback_auto_capture" type="bool" setter="set_auto_capture" getter="is_auto_capture" default="true"> + If [code]true[/code], performs [method AnimationMixer.capture] before playback automatically. This means just [method play_with_capture] is executed with default arguments instead of [method play]. + </member> <member name="playback_default_blend_time" type="float" setter="set_default_blend_time" getter="get_default_blend_time" default="0.0"> The default time in which to blend animations. Ranges from 0 to 4096 with 0.01 precision. </member> diff --git a/doc/classes/AudioStreamWAV.xml b/doc/classes/AudioStreamWAV.xml index 206b6361cc..3df814cb7f 100644 --- a/doc/classes/AudioStreamWAV.xml +++ b/doc/classes/AudioStreamWAV.xml @@ -15,7 +15,7 @@ <return type="int" enum="Error" /> <param index="0" name="path" type="String" /> <description> - Saves the AudioStreamWAV as a WAV file to [param path]. Samples with IMA ADPCM format can't be saved. + Saves the AudioStreamWAV as a WAV file to [param path]. Samples with IMA ADPCM or QOA formats can't be saved. [b]Note:[/b] A [code].wav[/code] extension is automatically appended to [param path] if it is missing. </description> </method> @@ -56,6 +56,9 @@ <constant name="FORMAT_IMA_ADPCM" value="2" enum="Format"> Audio is compressed using IMA ADPCM. </constant> + <constant name="FORMAT_QOA" value="3" enum="Format"> + Audio is compressed as QOA ([url=https://qoaformat.org/]Quite OK Audio[/url]). + </constant> <constant name="LOOP_DISABLED" value="0" enum="LoopMode"> Audio does not loop. </constant> diff --git a/doc/classes/BaseMaterial3D.xml b/doc/classes/BaseMaterial3D.xml index 1bd4183b3c..fc8af02869 100644 --- a/doc/classes/BaseMaterial3D.xml +++ b/doc/classes/BaseMaterial3D.xml @@ -609,6 +609,9 @@ <constant name="BLEND_MODE_MUL" value="3" enum="BlendMode"> The color of the object is multiplied by the background. </constant> + <constant name="BLEND_MODE_PREMULT_ALPHA" value="4" enum="BlendMode"> + The color of the object is added to the background and the alpha channel is used to mask out the background. This is effectively a hybrid of the blend mix and add modes, useful for effects like fire where you want the flame to add but the smoke to mix. By default, this works with unshaded materials using premultiplied textures. For shaded materials, use the [code]PREMUL_ALPHA_FACTOR[/code] built-in so that lighting can be modulated as well. + </constant> <constant name="ALPHA_ANTIALIASING_OFF" value="0" enum="AlphaAntiAliasing"> Disables Alpha AntiAliasing for the material. </constant> diff --git a/doc/classes/CanvasItem.xml b/doc/classes/CanvasItem.xml index 18413c4be5..bf53cb2e14 100644 --- a/doc/classes/CanvasItem.xml +++ b/doc/classes/CanvasItem.xml @@ -77,8 +77,14 @@ <param index="0" name="position" type="Vector2" /> <param index="1" name="radius" type="float" /> <param index="2" name="color" type="Color" /> + <param index="3" name="filled" type="bool" default="true" /> + <param index="4" name="width" type="float" default="-1.0" /> + <param index="5" name="antialiased" type="bool" default="false" /> <description> - Draws a colored, filled circle. See also [method draw_arc], [method draw_polyline] and [method draw_polygon]. + Draws a circle. See also [method draw_arc], [method draw_polyline], and [method draw_polygon]. + If [param filled] is [code]true[/code], the circle will be filled with the [param color] specified. If [param filled] is [code]false[/code], the circle will be drawn as a stroke with the [param color] and [param width] specified. + If [param width] is negative, then two-point primitives will be drawn instead of a four-point ones. This means that when the CanvasItem is scaled, the lines will remain thin. If this behavior is not desired, then pass a positive [param width] like [code]1.0[/code]. + [b]Note:[/b] [param width] is only effective if [param filled] is [code]false[/code]. </description> </method> <method name="draw_colored_polygon"> diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index e614a897d5..a054048266 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -58,7 +58,7 @@ </method> <method name="create_status_indicator"> <return type="int" /> - <param index="0" name="icon" type="Image" /> + <param index="0" name="icon" type="Texture2D" /> <param index="1" name="tooltip" type="String" /> <param index="2" name="callback" type="Callable" /> <description> @@ -1167,20 +1167,41 @@ [b]Note:[/b] This method is implemented on Android, iOS, macOS, Windows, and Linux (X11/Wayland). </description> </method> + <method name="status_indicator_get_rect" qualifiers="const"> + <return type="Rect2" /> + <param index="0" name="id" type="int" /> + <description> + Returns the rectangle for the given status indicator [param id] in screen coordinates. If the status indicator is not visible, returns an empty [Rect2]. + [b]Note:[/b] This method is implemented on macOS and Windows. + </description> + </method> <method name="status_indicator_set_callback"> <return type="void" /> <param index="0" name="id" type="int" /> <param index="1" name="callback" type="Callable" /> <description> Sets the application status indicator activation callback. + [b]Note:[/b] This method is implemented on macOS and Windows. </description> </method> <method name="status_indicator_set_icon"> <return type="void" /> <param index="0" name="id" type="int" /> - <param index="1" name="icon" type="Image" /> + <param index="1" name="icon" type="Texture2D" /> <description> Sets the application status indicator icon. + [b]Note:[/b] This method is implemented on macOS and Windows. + </description> + </method> + <method name="status_indicator_set_menu"> + <return type="void" /> + <param index="0" name="id" type="int" /> + <param index="1" name="menu_rid" type="RID" /> + <description> + Sets the application status indicator native popup menu. + [b]Note:[/b] On macOS, the menu is activated by any mouse button. Its activation callback is [i]not[/i] triggered. + [b]Note:[/b] On Windows, the menu is activated by the right mouse button, selecting the status icon and pressing [kbd]Shift + F10[/kbd], or the applications key. The menu's activation callback for the other mouse buttons is still triggered. + [b]Note:[/b] Native popup is only supported if [NativeMenu] supports the [constant NativeMenu.FEATURE_POPUP_MENU] feature. </description> </method> <method name="status_indicator_set_tooltip"> @@ -1189,6 +1210,7 @@ <param index="1" name="tooltip" type="String" /> <description> Sets the application status indicator tooltip. + [b]Note:[/b] This method is implemented on macOS and Windows. </description> </method> <method name="tablet_get_current_driver" qualifiers="const"> diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 93a7b09fce..0c08dd3605 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -881,7 +881,7 @@ All update modes will ignore builds with different major versions (e.g. Godot 4 -> Godot 5). </member> <member name="network/connection/network_mode" type="int" setter="" getter=""> - Determines whether online features are enabled in the editor, such as the Asset Library or update checks. Disabling these online features helps alleviate privacy concerns by preventing the editor from making HTTP requests to the Godot website, GitHub, or third-party platforms hosting assets from the Asset Library. + Determines whether online features are enabled in the editor, such as the Asset Library or update checks. Disabling these online features helps alleviate privacy concerns by preventing the editor from making HTTP requests to the Godot website or third-party platforms hosting assets from the Asset Library. </member> <member name="network/debug/remote_host" type="String" setter="" getter=""> The address to listen to when starting the remote debugger. This can be set to [code]0.0.0.0[/code] to allow external clients to connect to the remote debugger (instead of restricting the remote debugger to connections from [code]localhost[/code]). diff --git a/doc/classes/HingeJoint3D.xml b/doc/classes/HingeJoint3D.xml index d150c79b78..f794853caf 100644 --- a/doc/classes/HingeJoint3D.xml +++ b/doc/classes/HingeJoint3D.xml @@ -53,7 +53,7 @@ <member name="angular_limit/relaxation" type="float" setter="set_param" getter="get_param" default="1.0"> The lower this value, the more the rotation gets slowed down. </member> - <member name="angular_limit/softness" type="float" setter="set_param" getter="get_param" default="0.9"> + <member name="angular_limit/softness" type="float" setter="set_param" getter="get_param" default="0.9" deprecated="This property is never set by the engine and is kept for compatibility purposes."> </member> <member name="angular_limit/upper" type="float" setter="set_param" getter="get_param" default="1.5708"> The maximum rotation. Only active if [member angular_limit/enable] is [code]true[/code]. @@ -84,7 +84,7 @@ <constant name="PARAM_LIMIT_BIAS" value="3" enum="Param"> The speed with which the rotation across the axis perpendicular to the hinge gets corrected. </constant> - <constant name="PARAM_LIMIT_SOFTNESS" value="4" enum="Param"> + <constant name="PARAM_LIMIT_SOFTNESS" value="4" enum="Param" deprecated="This property is never used by the engine and is kept for compatibility purpose."> </constant> <constant name="PARAM_LIMIT_RELAXATION" value="5" enum="Param"> The lower this value, the more the rotation gets slowed down. diff --git a/doc/classes/PackedScene.xml b/doc/classes/PackedScene.xml index 579b4c5b9f..26d8fa8d5f 100644 --- a/doc/classes/PackedScene.xml +++ b/doc/classes/PackedScene.xml @@ -99,14 +99,14 @@ <return type="int" enum="Error" /> <param index="0" name="path" type="Node" /> <description> - Pack will ignore any sub-nodes not owned by given node. See [member Node.owner]. + Packs the [param path] node, and all owned sub-nodes, into this [PackedScene]. Any existing data will be cleared. See [member Node.owner]. </description> </method> </methods> <members> <member name="_bundled" type="Dictionary" setter="_set_bundled_scene" getter="_get_bundled_scene" default="{ "conn_count": 0, "conns": PackedInt32Array(), "editable_instances": [], "names": PackedStringArray(), "node_count": 0, "node_paths": [], "nodes": PackedInt32Array(), "variants": [], "version": 3 }"> A dictionary representation of the scene contents. - Available keys include "rnames" and "variants" for resources, "node_count", "nodes", "node_paths" for nodes, "editable_instances" for paths to overridden nodes, "conn_count" and "conns" for signal connections, and "version" for the format style of the PackedScene. + Available keys include "names" and "variants" for resources, "node_count", "nodes", "node_paths" for nodes, "editable_instances" for paths to overridden nodes, "conn_count" and "conns" for signal connections, and "version" for the format style of the PackedScene. </member> </members> <constants> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 0d4d8bbebb..e8d7dfb913 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -344,7 +344,7 @@ This setting can be overridden using the [code]--frame-delay <ms;>[/code] command line argument. </member> <member name="application/run/low_processor_mode" type="bool" setter="" getter="" default="false"> - If [code]true[/code], enables low-processor usage mode. This setting only works on desktop platforms. The screen is not redrawn if nothing changes visually. This is meant for writing applications and editors, but is pretty useless (and can hurt performance) in most games. + If [code]true[/code], enables low-processor usage mode. The screen is not redrawn if nothing changes visually. This is meant for writing applications and editors, but is pretty useless (and can hurt performance) in most games. </member> <member name="application/run/low_processor_mode_sleep_usec" type="int" setter="" getter="" default="6900"> Amount of sleeping between frames when the low-processor usage mode is enabled (in microseconds). Higher values will result in lower CPU usage. @@ -2900,6 +2900,9 @@ <member name="xr/openxr/extensions/eye_gaze_interaction" type="bool" setter="" getter="" default="false"> Specify whether to enable eye tracking for this project. Depending on the platform, additional export configuration may be needed. </member> + <member name="xr/openxr/extensions/hand_interaction_profile" type="bool" setter="" getter="" default="false"> + If true the hand interaction profile extension will be activated if supported by the platform. + </member> <member name="xr/openxr/extensions/hand_tracking" type="bool" setter="" getter="" default="true"> If true we enable the hand tracking extension if available. </member> diff --git a/doc/classes/Rect2.xml b/doc/classes/Rect2.xml index 0471d73749..ba7dcab2d7 100644 --- a/doc/classes/Rect2.xml +++ b/doc/classes/Rect2.xml @@ -89,13 +89,13 @@ var rect = Rect2(0, 0, 5, 2) rect = rect.expand(Vector2(10, 0)) # rect is Rect2(0, 0, 10, 2) - rect = rect.expand(Vector2(-5, 5)) # rect is Rect2(-5, 0, 10, 5) + rect = rect.expand(Vector2(-5, 5)) # rect is Rect2(-5, 0, 15, 5) [/gdscript] [csharp] var rect = new Rect2(0, 0, 5, 2); rect = rect.Expand(new Vector2(10, 0)); // rect is Rect2(0, 0, 10, 2) - rect = rect.Expand(new Vector2(-5, 5)); // rect is Rect2(-5, 0, 10, 5) + rect = rect.Expand(new Vector2(-5, 5)); // rect is Rect2(-5, 0, 15, 5) [/csharp] [/codeblocks] </description> diff --git a/doc/classes/Rect2i.xml b/doc/classes/Rect2i.xml index c021a1be26..cbd09f8430 100644 --- a/doc/classes/Rect2i.xml +++ b/doc/classes/Rect2i.xml @@ -88,13 +88,13 @@ var rect = Rect2i(0, 0, 5, 2) rect = rect.expand(Vector2i(10, 0)) # rect is Rect2i(0, 0, 10, 2) - rect = rect.expand(Vector2i(-5, 5)) # rect is Rect2i(-5, 0, 10, 5) + rect = rect.expand(Vector2i(-5, 5)) # rect is Rect2i(-5, 0, 15, 5) [/gdscript] [csharp] var rect = new Rect2I(0, 0, 5, 2); rect = rect.Expand(new Vector2I(10, 0)); // rect is Rect2I(0, 0, 10, 2) - rect = rect.Expand(new Vector2I(-5, 5)); // rect is Rect2I(-5, 0, 10, 5) + rect = rect.Expand(new Vector2I(-5, 5)); // rect is Rect2I(-5, 0, 15, 5) [/csharp] [/codeblocks] </description> diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index 5d90cd6b92..519bba4e7c 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -1979,6 +1979,12 @@ [b]Warning:[/b] This function is primarily intended for editor usage. For in-game use cases, prefer physics collision. </description> </method> + <method name="is_on_render_thread"> + <return type="bool" /> + <description> + Returns [code]true[/code] if our code is currently executing on the rendering thread. + </description> + </method> <method name="light_directional_set_blend_splits"> <return type="void" /> <param index="0" name="light" type="RID" /> diff --git a/doc/classes/ResourceImporterWAV.xml b/doc/classes/ResourceImporterWAV.xml index 5336c98d0f..d3dafb03b6 100644 --- a/doc/classes/ResourceImporterWAV.xml +++ b/doc/classes/ResourceImporterWAV.xml @@ -14,6 +14,7 @@ The compression mode to use on import. [b]Disabled:[/b] Imports audio data without any compression. This results in the highest possible quality. [b]RAM (Ima-ADPCM):[/b] Performs fast lossy compression on import. Low CPU cost, but quality is noticeably decreased compared to Ogg Vorbis or even MP3. + [b]QOA ([url=https://qoaformat.org/]Quite OK Audio[/url]):[/b] Performs lossy compression on import. CPU cost is slightly higher than IMA-ADPCM, but quality is much higher. </member> <member name="edit/loop_begin" type="int" setter="" getter="" default="0"> The begin loop point to use when [member edit/loop_mode] is [b]Forward[/b], [b]Ping-Pong[/b] or [b]Backward[/b]. This is set in seconds after the beginning of the audio file. diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index 01c4074e6d..f4e3c1209f 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -249,6 +249,13 @@ [/codeblock] </description> </method> + <method name="invalidate_paragraph"> + <return type="bool" /> + <param index="0" name="paragraph" type="int" /> + <description> + Invalidates [param paragraph] and all subsequent paragraphs cache. + </description> + </method> <method name="is_menu_visible" qualifiers="const"> <return type="bool" /> <description> @@ -497,9 +504,11 @@ <method name="remove_paragraph"> <return type="bool" /> <param index="0" name="paragraph" type="int" /> + <param index="1" name="no_invalidate" type="bool" default="false" /> <description> Removes a paragraph of content from the label. Returns [code]true[/code] if the paragraph exists. The [param paragraph] argument is the index of the paragraph to remove, it can take values in the interval [code][0, get_paragraph_count() - 1][/code]. + If [param no_invalidate] is set to [code]true[/code], cache for the subsequent paragraphs is not invalidated. Use it for faster updates if deleted paragraph is fully self-contained (have no unclosed tags), or this call is part of the complex edit operation and [method invalidate_paragraph] will be called at the end of operation. </description> </method> <method name="scroll_to_line"> diff --git a/doc/classes/StatusIndicator.xml b/doc/classes/StatusIndicator.xml index e1fcc35ad7..688840b17f 100644 --- a/doc/classes/StatusIndicator.xml +++ b/doc/classes/StatusIndicator.xml @@ -8,10 +8,22 @@ </description> <tutorials> </tutorials> + <methods> + <method name="get_rect" qualifiers="const"> + <return type="Rect2" /> + <description> + Returns the status indicator rectangle in screen coordinates. If this status indicator is not visible, returns an empty [Rect2]. + </description> + </method> + </methods> <members> - <member name="icon" type="Image" setter="set_icon" getter="get_icon"> + <member name="icon" type="Texture2D" setter="set_icon" getter="get_icon"> Status indicator icon. </member> + <member name="menu" type="NodePath" setter="set_menu" getter="get_menu" default="NodePath("")"> + Status indicator native popup menu. If this is set, the [signal pressed] signal is not emitted. + [b]Note:[/b] Native popup is only supported if [NativeMenu] supports [constant NativeMenu.FEATURE_POPUP_MENU] feature. + </member> <member name="tooltip" type="String" setter="set_tooltip" getter="get_tooltip" default=""""> Status indicator tooltip. </member> diff --git a/doc/classes/TileSetAtlasSource.xml b/doc/classes/TileSetAtlasSource.xml index 6f212274f8..a34ca0ce91 100644 --- a/doc/classes/TileSetAtlasSource.xml +++ b/doc/classes/TileSetAtlasSource.xml @@ -295,6 +295,15 @@ # If tile is not already flipped, flip it. $TileMap.set_cell(0, Vector2i(2, 2), source_id, atlas_coords, alternate_id | TileSetAtlasSource.TRANSFORM_FLIP_H) [/codeblock] + [b]Note:[/b] These transformations can be combined to do the equivalent of 0, 90, 180, and 270 degree rotations, as shown below: + [codeblock] + enum TileTransform { + ROTATE_0 = 0, + ROTATE_90 = TileSetAtlasSource.TRANSFORM_TRANSPOSE | TileSetAtlasSource.TRANSFORM_FLIP_H, + ROTATE_180 = TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_FLIP_V, + ROTATE_270 = TileSetAtlasSource.TRANSFORM_TRANSPOSE | TileSetAtlasSource.TRANSFORM_FLIP_V, + } + [/codeblock] </constant> <constant name="TRANSFORM_FLIP_V" value="8192"> Represents cell's vertical flip flag. See [constant TRANSFORM_FLIP_H] for usage. diff --git a/doc/classes/Variant.xml b/doc/classes/Variant.xml index eb837a4643..b420933285 100644 --- a/doc/classes/Variant.xml +++ b/doc/classes/Variant.xml @@ -42,8 +42,8 @@ # Note that Objects are their own special category. # To get the name of the underlying Object type, you need the `get_class()` method. print("foo is a(n) %s" % foo.get_class()) # inject the class name into a formatted string. - # Note also that there is not yet any way to get a script's `class_name` string easily. - # To fetch that value, you can use ProjectSettings.get_global_class_list(). + # Note that this does not get the script's `class_name` global identifier. + # If the `class_name` is needed, use `foo.get_script().get_global_name()` instead. [/gdscript] [csharp] Variant foo = 2; diff --git a/doc/classes/Vector2.xml b/doc/classes/Vector2.xml index 7b166a4fb0..5fadfd0d95 100644 --- a/doc/classes/Vector2.xml +++ b/doc/classes/Vector2.xml @@ -128,6 +128,14 @@ Returns a new vector with all components clamped between the components of [param min] and [param max], by running [method @GlobalScope.clamp] on each component. </description> </method> + <method name="clampf" qualifiers="const"> + <return type="Vector2" /> + <param index="0" name="min" type="float" /> + <param index="1" name="max" type="float" /> + <description> + Returns a new vector with all components clamped between [param min] and [param max], by running [method @GlobalScope.clamp] on each component. + </description> + </method> <method name="cross" qualifiers="const"> <return type="float" /> <param index="0" name="with" type="Vector2" /> @@ -265,18 +273,46 @@ Returns the vector with a maximum length by limiting its length to [param length]. </description> </method> + <method name="max" qualifiers="const"> + <return type="Vector2" /> + <param index="0" name="with" type="Vector2" /> + <description> + Returns the component-wise maximum of this and [param with], equivalent to [code]Vector2(maxf(x, with.x), maxf(y, with.y))[/code]. + </description> + </method> <method name="max_axis_index" qualifiers="const"> <return type="int" /> <description> Returns the axis of the vector's highest value. See [code]AXIS_*[/code] constants. If all components are equal, this method returns [constant AXIS_X]. </description> </method> + <method name="maxf" qualifiers="const"> + <return type="Vector2" /> + <param index="0" name="with" type="float" /> + <description> + Returns the component-wise maximum of this and [param with], equivalent to [code]Vector2(maxf(x, with), maxf(y, with))[/code]. + </description> + </method> + <method name="min" qualifiers="const"> + <return type="Vector2" /> + <param index="0" name="with" type="Vector2" /> + <description> + Returns the component-wise minimum of this and [param with], equivalent to [code]Vector2(minf(x, with.x), minf(y, with.y))[/code]. + </description> + </method> <method name="min_axis_index" qualifiers="const"> <return type="int" /> <description> Returns the axis of the vector's lowest value. See [code]AXIS_*[/code] constants. If all components are equal, this method returns [constant AXIS_Y]. </description> </method> + <method name="minf" qualifiers="const"> + <return type="Vector2" /> + <param index="0" name="with" type="float" /> + <description> + Returns the component-wise minimum of this and [param with], equivalent to [code]Vector2(minf(x, with), minf(y, with))[/code]. + </description> + </method> <method name="move_toward" qualifiers="const"> <return type="Vector2" /> <param index="0" name="to" type="Vector2" /> @@ -371,6 +407,13 @@ Returns a new vector with each component snapped to the nearest multiple of the corresponding component in [param step]. This can also be used to round the components to an arbitrary number of decimals. </description> </method> + <method name="snappedf" qualifiers="const"> + <return type="Vector2" /> + <param index="0" name="step" type="float" /> + <description> + Returns a new vector with each component snapped to the nearest multiple of [param step]. This can also be used to round the components to an arbitrary number of decimals. + </description> + </method> </methods> <members> <member name="x" type="float" setter="" getter="" default="0.0"> diff --git a/doc/classes/Vector2i.xml b/doc/classes/Vector2i.xml index 18291e06a9..4afc62e038 100644 --- a/doc/classes/Vector2i.xml +++ b/doc/classes/Vector2i.xml @@ -64,6 +64,14 @@ Returns a new vector with all components clamped between the components of [param min] and [param max], by running [method @GlobalScope.clamp] on each component. </description> </method> + <method name="clampi" qualifiers="const"> + <return type="Vector2i" /> + <param index="0" name="min" type="int" /> + <param index="1" name="max" type="int" /> + <description> + Returns a new vector with all components clamped between [param min] and [param max], by running [method @GlobalScope.clamp] on each component. + </description> + </method> <method name="distance_squared_to" qualifiers="const"> <return type="int" /> <param index="0" name="to" type="Vector2i" /> @@ -92,18 +100,46 @@ This method runs faster than [method length], so prefer it if you need to compare vectors or need the squared distance for some formula. </description> </method> + <method name="max" qualifiers="const"> + <return type="Vector2i" /> + <param index="0" name="with" type="Vector2i" /> + <description> + Returns the component-wise maximum of this and [param with], equivalent to [code]Vector2i(maxi(x, with.x), maxi(y, with.y))[/code]. + </description> + </method> <method name="max_axis_index" qualifiers="const"> <return type="int" /> <description> Returns the axis of the vector's highest value. See [code]AXIS_*[/code] constants. If all components are equal, this method returns [constant AXIS_X]. </description> </method> + <method name="maxi" qualifiers="const"> + <return type="Vector2i" /> + <param index="0" name="with" type="int" /> + <description> + Returns the component-wise maximum of this and [param with], equivalent to [code]Vector2i(maxi(x, with), maxi(y, with))[/code]. + </description> + </method> + <method name="min" qualifiers="const"> + <return type="Vector2i" /> + <param index="0" name="with" type="Vector2i" /> + <description> + Returns the component-wise minimum of this and [param with], equivalent to [code]Vector2i(mini(x, with.x), mini(y, with.y))[/code]. + </description> + </method> <method name="min_axis_index" qualifiers="const"> <return type="int" /> <description> Returns the axis of the vector's lowest value. See [code]AXIS_*[/code] constants. If all components are equal, this method returns [constant AXIS_Y]. </description> </method> + <method name="mini" qualifiers="const"> + <return type="Vector2i" /> + <param index="0" name="with" type="int" /> + <description> + Returns the component-wise minimum of this and [param with], equivalent to [code]Vector2i(mini(x, with), mini(y, with))[/code]. + </description> + </method> <method name="sign" qualifiers="const"> <return type="Vector2i" /> <description> @@ -117,6 +153,13 @@ Returns a new vector with each component snapped to the closest multiple of the corresponding component in [param step]. </description> </method> + <method name="snappedi" qualifiers="const"> + <return type="Vector2i" /> + <param index="0" name="step" type="int" /> + <description> + Returns a new vector with each component snapped to the closest multiple of [param step]. + </description> + </method> </methods> <members> <member name="x" type="int" setter="" getter="" default="0"> diff --git a/doc/classes/Vector3.xml b/doc/classes/Vector3.xml index 031d91af78..387359a97f 100644 --- a/doc/classes/Vector3.xml +++ b/doc/classes/Vector3.xml @@ -104,6 +104,14 @@ Returns a new vector with all components clamped between the components of [param min] and [param max], by running [method @GlobalScope.clamp] on each component. </description> </method> + <method name="clampf" qualifiers="const"> + <return type="Vector3" /> + <param index="0" name="min" type="float" /> + <param index="1" name="max" type="float" /> + <description> + Returns a new vector with all components clamped between [param min] and [param max], by running [method @GlobalScope.clamp] on each component. + </description> + </method> <method name="cross" qualifiers="const"> <return type="Vector3" /> <param index="0" name="with" type="Vector3" /> @@ -234,18 +242,46 @@ Returns the vector with a maximum length by limiting its length to [param length]. </description> </method> + <method name="max" qualifiers="const"> + <return type="Vector3" /> + <param index="0" name="with" type="Vector3" /> + <description> + Returns the component-wise maximum of this and [param with], equivalent to [code]Vector3(maxf(x, with.x), maxf(y, with.y), maxf(z, with.z))[/code]. + </description> + </method> <method name="max_axis_index" qualifiers="const"> <return type="int" /> <description> Returns the axis of the vector's highest value. See [code]AXIS_*[/code] constants. If all components are equal, this method returns [constant AXIS_X]. </description> </method> + <method name="maxf" qualifiers="const"> + <return type="Vector3" /> + <param index="0" name="with" type="float" /> + <description> + Returns the component-wise maximum of this and [param with], equivalent to [code]Vector3(maxf(x, with), maxf(y, with), maxf(z, with))[/code]. + </description> + </method> + <method name="min" qualifiers="const"> + <return type="Vector3" /> + <param index="0" name="with" type="Vector3" /> + <description> + Returns the component-wise minimum of this and [param with], equivalent to [code]Vector3(minf(x, with.x), minf(y, with.y), minf(z, with.z))[/code]. + </description> + </method> <method name="min_axis_index" qualifiers="const"> <return type="int" /> <description> Returns the axis of the vector's lowest value. See [code]AXIS_*[/code] constants. If all components are equal, this method returns [constant AXIS_Z]. </description> </method> + <method name="minf" qualifiers="const"> + <return type="Vector3" /> + <param index="0" name="with" type="float" /> + <description> + Returns the component-wise minimum of this and [param with], equivalent to [code]Vector3(minf(x, with), minf(y, with), minf(z, with))[/code]. + </description> + </method> <method name="move_toward" qualifiers="const"> <return type="Vector3" /> <param index="0" name="to" type="Vector3" /> @@ -307,10 +343,10 @@ </method> <method name="reflect" qualifiers="const"> <return type="Vector3" /> - <param index="0" name="direction" type="Vector3" /> + <param index="0" name="n" type="Vector3" /> <description> - Returns the result of reflecting the vector from a plane defined by the given direction vector [param direction]. - [b]Note:[/b] [method reflect] differs from what other engines and frameworks call [code skip-lint]reflect()[/code]. In other engines, [code skip-lint]reflect()[/code] takes a normal direction which is a direction perpendicular to the plane. In Godot, you specify a direction parallel to the plane. See also [method bounce] which does what most engines call [code skip-lint]reflect()[/code]. + Returns the result of reflecting the vector through a plane defined by the given normal vector [param n]. + [b]Note:[/b] [method reflect] differs from what other engines and frameworks call [code skip-lint]reflect()[/code]. In other engines, [code skip-lint]reflect()[/code] returns the result of the vector reflected by the given plane. The reflection thus passes through the given normal. While in Godot the reflection passes through the plane and can be thought of as bouncing off the normal. See also [method bounce] which does what most engines call [code skip-lint]reflect()[/code]. </description> </method> <method name="rotated" qualifiers="const"> @@ -365,6 +401,13 @@ Returns a new vector with each component snapped to the nearest multiple of the corresponding component in [param step]. This can also be used to round the components to an arbitrary number of decimals. </description> </method> + <method name="snappedf" qualifiers="const"> + <return type="Vector3" /> + <param index="0" name="step" type="float" /> + <description> + Returns a new vector with each component snapped to the nearest multiple of [param step]. This can also be used to round the components to an arbitrary number of decimals. + </description> + </method> </methods> <members> <member name="x" type="float" setter="" getter="" default="0.0"> diff --git a/doc/classes/Vector3i.xml b/doc/classes/Vector3i.xml index ffebd3e1f3..df4624dbb1 100644 --- a/doc/classes/Vector3i.xml +++ b/doc/classes/Vector3i.xml @@ -59,6 +59,14 @@ Returns a new vector with all components clamped between the components of [param min] and [param max], by running [method @GlobalScope.clamp] on each component. </description> </method> + <method name="clampi" qualifiers="const"> + <return type="Vector3i" /> + <param index="0" name="min" type="int" /> + <param index="1" name="max" type="int" /> + <description> + Returns a new vector with all components clamped between [param min] and [param max], by running [method @GlobalScope.clamp] on each component. + </description> + </method> <method name="distance_squared_to" qualifiers="const"> <return type="int" /> <param index="0" name="to" type="Vector3i" /> @@ -87,18 +95,46 @@ This method runs faster than [method length], so prefer it if you need to compare vectors or need the squared distance for some formula. </description> </method> + <method name="max" qualifiers="const"> + <return type="Vector3i" /> + <param index="0" name="with" type="Vector3i" /> + <description> + Returns the component-wise maximum of this and [param with], equivalent to [code]Vector3i(maxi(x, with.x), maxi(y, with.y), maxi(z, with.z))[/code]. + </description> + </method> <method name="max_axis_index" qualifiers="const"> <return type="int" /> <description> Returns the axis of the vector's highest value. See [code]AXIS_*[/code] constants. If all components are equal, this method returns [constant AXIS_X]. </description> </method> + <method name="maxi" qualifiers="const"> + <return type="Vector3i" /> + <param index="0" name="with" type="int" /> + <description> + Returns the component-wise maximum of this and [param with], equivalent to [code]Vector3i(maxi(x, with), maxi(y, with), maxi(z, with))[/code]. + </description> + </method> + <method name="min" qualifiers="const"> + <return type="Vector3i" /> + <param index="0" name="with" type="Vector3i" /> + <description> + Returns the component-wise minimum of this and [param with], equivalent to [code]Vector3i(mini(x, with.x), mini(y, with.y), mini(z, with.z))[/code]. + </description> + </method> <method name="min_axis_index" qualifiers="const"> <return type="int" /> <description> Returns the axis of the vector's lowest value. See [code]AXIS_*[/code] constants. If all components are equal, this method returns [constant AXIS_Z]. </description> </method> + <method name="mini" qualifiers="const"> + <return type="Vector3i" /> + <param index="0" name="with" type="int" /> + <description> + Returns the component-wise minimum of this and [param with], equivalent to [code]Vector3i(mini(x, with), mini(y, with), mini(z, with))[/code]. + </description> + </method> <method name="sign" qualifiers="const"> <return type="Vector3i" /> <description> @@ -112,6 +148,13 @@ Returns a new vector with each component snapped to the closest multiple of the corresponding component in [param step]. </description> </method> + <method name="snappedi" qualifiers="const"> + <return type="Vector3i" /> + <param index="0" name="step" type="int" /> + <description> + Returns a new vector with each component snapped to the closest multiple of [param step]. + </description> + </method> </methods> <members> <member name="x" type="int" setter="" getter="" default="0"> diff --git a/doc/classes/Vector4.xml b/doc/classes/Vector4.xml index b31cdb01c9..87af370462 100644 --- a/doc/classes/Vector4.xml +++ b/doc/classes/Vector4.xml @@ -64,6 +64,14 @@ Returns a new vector with all components clamped between the components of [param min] and [param max], by running [method @GlobalScope.clamp] on each component. </description> </method> + <method name="clampf" qualifiers="const"> + <return type="Vector4" /> + <param index="0" name="min" type="float" /> + <param index="1" name="max" type="float" /> + <description> + Returns a new vector with all components clamped between [param min] and [param max], by running [method @GlobalScope.clamp] on each component. + </description> + </method> <method name="cubic_interpolate" qualifiers="const"> <return type="Vector4" /> <param index="0" name="b" type="Vector4" /> @@ -176,18 +184,46 @@ Returns the result of the linear interpolation between this vector and [param to] by amount [param weight]. [param weight] is on the range of [code]0.0[/code] to [code]1.0[/code], representing the amount of interpolation. </description> </method> + <method name="max" qualifiers="const"> + <return type="Vector4" /> + <param index="0" name="with" type="Vector4" /> + <description> + Returns the component-wise maximum of this and [param with], equivalent to [code]Vector4(maxf(x, with.x), maxf(y, with.y), maxf(z, with.z), maxf(w, with.w))[/code]. + </description> + </method> <method name="max_axis_index" qualifiers="const"> <return type="int" /> <description> Returns the axis of the vector's highest value. See [code]AXIS_*[/code] constants. If all components are equal, this method returns [constant AXIS_X]. </description> </method> + <method name="maxf" qualifiers="const"> + <return type="Vector4" /> + <param index="0" name="with" type="float" /> + <description> + Returns the component-wise maximum of this and [param with], equivalent to [code]Vector4(maxf(x, with), maxf(y, with), maxf(z, with), maxf(w, with))[/code]. + </description> + </method> + <method name="min" qualifiers="const"> + <return type="Vector4" /> + <param index="0" name="with" type="Vector4" /> + <description> + Returns the component-wise minimum of this and [param with], equivalent to [code]Vector4(minf(x, with.x), minf(y, with.y), minf(z, with.z), minf(w, with.w))[/code]. + </description> + </method> <method name="min_axis_index" qualifiers="const"> <return type="int" /> <description> Returns the axis of the vector's lowest value. See [code]AXIS_*[/code] constants. If all components are equal, this method returns [constant AXIS_W]. </description> </method> + <method name="minf" qualifiers="const"> + <return type="Vector4" /> + <param index="0" name="with" type="float" /> + <description> + Returns the component-wise minimum of this and [param with], equivalent to [code]Vector4(minf(x, with), minf(y, with), minf(z, with), minf(w, with))[/code]. + </description> + </method> <method name="normalized" qualifiers="const"> <return type="Vector4" /> <description> @@ -228,6 +264,13 @@ Returns a new vector with each component snapped to the nearest multiple of the corresponding component in [param step]. This can also be used to round the components to an arbitrary number of decimals. </description> </method> + <method name="snappedf" qualifiers="const"> + <return type="Vector4" /> + <param index="0" name="step" type="float" /> + <description> + Returns a new vector with each component snapped to the nearest multiple of [param step]. This can also be used to round the components to an arbitrary number of decimals. + </description> + </method> </methods> <members> <member name="w" type="float" setter="" getter="" default="0.0"> diff --git a/doc/classes/Vector4i.xml b/doc/classes/Vector4i.xml index f8a0026066..8f54b767e0 100644 --- a/doc/classes/Vector4i.xml +++ b/doc/classes/Vector4i.xml @@ -57,6 +57,14 @@ Returns a new vector with all components clamped between the components of [param min] and [param max], by running [method @GlobalScope.clamp] on each component. </description> </method> + <method name="clampi" qualifiers="const"> + <return type="Vector4i" /> + <param index="0" name="min" type="int" /> + <param index="1" name="max" type="int" /> + <description> + Returns a new vector with all components clamped between [param min] and [param max], by running [method @GlobalScope.clamp] on each component. + </description> + </method> <method name="distance_squared_to" qualifiers="const"> <return type="int" /> <param index="0" name="to" type="Vector4i" /> @@ -85,18 +93,46 @@ This method runs faster than [method length], so prefer it if you need to compare vectors or need the squared distance for some formula. </description> </method> + <method name="max" qualifiers="const"> + <return type="Vector4i" /> + <param index="0" name="with" type="Vector4i" /> + <description> + Returns the component-wise maximum of this and [param with], equivalent to [code]Vector4i(maxi(x, with.x), maxi(y, with.y), maxi(z, with.z), maxi(w, with.w))[/code]. + </description> + </method> <method name="max_axis_index" qualifiers="const"> <return type="int" /> <description> Returns the axis of the vector's highest value. See [code]AXIS_*[/code] constants. If all components are equal, this method returns [constant AXIS_X]. </description> </method> + <method name="maxi" qualifiers="const"> + <return type="Vector4i" /> + <param index="0" name="with" type="int" /> + <description> + Returns the component-wise maximum of this and [param with], equivalent to [code]Vector4i(maxi(x, with), maxi(y, with), maxi(z, with), maxi(w, with))[/code]. + </description> + </method> + <method name="min" qualifiers="const"> + <return type="Vector4i" /> + <param index="0" name="with" type="Vector4i" /> + <description> + Returns the component-wise minimum of this and [param with], equivalent to [code]Vector4i(mini(x, with.x), mini(y, with.y), mini(z, with.z), mini(w, with.w))[/code]. + </description> + </method> <method name="min_axis_index" qualifiers="const"> <return type="int" /> <description> Returns the axis of the vector's lowest value. See [code]AXIS_*[/code] constants. If all components are equal, this method returns [constant AXIS_W]. </description> </method> + <method name="mini" qualifiers="const"> + <return type="Vector4i" /> + <param index="0" name="with" type="int" /> + <description> + Returns the component-wise minimum of this and [param with], equivalent to [code]Vector4i(mini(x, with), mini(y, with), mini(z, with), mini(w, with))[/code]. + </description> + </method> <method name="sign" qualifiers="const"> <return type="Vector4i" /> <description> @@ -110,6 +146,13 @@ Returns a new vector with each component snapped to the closest multiple of the corresponding component in [param step]. </description> </method> + <method name="snappedi" qualifiers="const"> + <return type="Vector4i" /> + <param index="0" name="step" type="int" /> + <description> + Returns a new vector with each component snapped to the closest multiple of [param step]. + </description> + </method> </methods> <members> <member name="w" type="int" setter="" getter="" default="0"> diff --git a/doc/classes/XRServer.xml b/doc/classes/XRServer.xml index d5714980c3..4179ba821c 100644 --- a/doc/classes/XRServer.xml +++ b/doc/classes/XRServer.xml @@ -37,8 +37,8 @@ You should call this method after a few seconds have passed. For example, when the user requests a realignment of the display holding a designated button on a controller for a short period of time, or when implementing a teleport mechanism. </description> </method> - <method name="clear_reference_frame" qualifiers="const"> - <return type="Transform3D" /> + <method name="clear_reference_frame"> + <return type="void" /> <description> Clears the reference frame that was set by previous calls to [method center_on_hmd]. </description> diff --git a/drivers/gles3/effects/copy_effects.cpp b/drivers/gles3/effects/copy_effects.cpp index 6e64652982..47ca832bd7 100644 --- a/drivers/gles3/effects/copy_effects.cpp +++ b/drivers/gles3/effects/copy_effects.cpp @@ -198,8 +198,7 @@ void CopyEffects::bilinear_blur(GLuint p_source_texture, int p_mipmap_count, con for (int i = 1; i < p_mipmap_count; i++) { dest_region.position.x >>= 1; dest_region.position.y >>= 1; - dest_region.size.x = MAX(1, dest_region.size.x >> 1); - dest_region.size.y = MAX(1, dest_region.size.y >> 1); + dest_region.size = Size2i(dest_region.size.x >> 1, dest_region.size.y >> 1).maxi(1); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffers[i % 2]); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, p_source_texture, i); glBlitFramebuffer(source_region.position.x, source_region.position.y, source_region.position.x + source_region.size.x, source_region.position.y + source_region.size.y, @@ -238,8 +237,7 @@ void CopyEffects::gaussian_blur(GLuint p_source_texture, int p_mipmap_count, con for (int i = 1; i < p_mipmap_count; i++) { dest_region.position.x >>= 1; dest_region.position.y >>= 1; - dest_region.size.x = MAX(1, dest_region.size.x >> 1); - dest_region.size.y = MAX(1, dest_region.size.y >> 1); + dest_region.size = Size2i(dest_region.size.x >> 1, dest_region.size.y >> 1).maxi(1); base_size.x >>= 1; base_size.y >>= 1; diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 606bcee775..ee770be3da 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -3041,6 +3041,11 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, } } break; + case GLES3::SceneShaderData::BLEND_MODE_PREMULT_ALPHA: { + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + } break; case GLES3::SceneShaderData::BLEND_MODE_ALPHA_TO_COVERAGE: { // Do nothing for now. } break; diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl index 797b9066a9..2b372cb88d 100644 --- a/drivers/gles3/shaders/scene.glsl +++ b/drivers/gles3/shaders/scene.glsl @@ -1442,6 +1442,9 @@ void main() { float clearcoat_roughness = 0.0; float anisotropy = 0.0; vec2 anisotropy_flow = vec2(1.0, 0.0); +#ifdef PREMUL_ALPHA_USED + float premul_alpha = 1.0; +#endif // PREMUL_ALPHA_USED #ifndef FOG_DISABLED vec4 fog = vec4(0.0); #endif // !FOG_DISABLED @@ -2141,9 +2144,12 @@ void main() { frag_color.rgb += additive_light_color; #endif // USE_ADDITIVE_LIGHTING - frag_color.rgb *= scene_data.luminance_multiplier; #endif // !RENDER_MATERIAL #endif // !MODE_RENDER_DEPTH + +#ifdef PREMUL_ALPHA_USED + frag_color.rgb *= premul_alpha; +#endif // PREMUL_ALPHA_USED } diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp index 62d22dac4d..996c205042 100644 --- a/drivers/gles3/storage/material_storage.cpp +++ b/drivers/gles3/storage/material_storage.cpp @@ -1243,6 +1243,7 @@ MaterialStorage::MaterialStorage() { actions.renames["NORMAL_MAP_DEPTH"] = "normal_map_depth"; actions.renames["ALBEDO"] = "albedo"; actions.renames["ALPHA"] = "alpha"; + actions.renames["PREMUL_ALPHA_FACTOR"] = "premul_alpha"; actions.renames["METALLIC"] = "metallic"; actions.renames["SPECULAR"] = "specular"; actions.renames["ROUGHNESS"] = "roughness"; @@ -1327,6 +1328,7 @@ MaterialStorage::MaterialStorage() { actions.usage_defines["ALPHA_HASH_SCALE"] = "#define ALPHA_HASH_USED\n"; actions.usage_defines["ALPHA_ANTIALIASING_EDGE"] = "#define ALPHA_ANTIALIASING_EDGE_USED\n"; actions.usage_defines["ALPHA_TEXTURE_COORDINATE"] = "@ALPHA_ANTIALIASING_EDGE"; + actions.usage_defines["PREMULT_ALPHA_FACTOR"] = "#define PREMULT_ALPHA_USED"; actions.usage_defines["SSS_STRENGTH"] = "#define ENABLE_SSS\n"; actions.usage_defines["SSS_TRANSMITTANCE_DEPTH"] = "#define ENABLE_TRANSMITTANCE\n"; @@ -1964,13 +1966,9 @@ void MaterialStorage::global_shader_parameters_load_settings(bool p_load_texture Variant value = d["value"]; if (gvtype >= RS::GLOBAL_VAR_TYPE_SAMPLER2D) { - //textire - if (!p_load_textures) { - continue; - } - String path = value; - if (path.is_empty()) { + // Don't load the textures, but still add the parameter so shaders compile correctly while loading. + if (!p_load_textures || path.is_empty()) { value = RID(); } else { Ref<Resource> resource = ResourceLoader::load(path); @@ -2908,6 +2906,7 @@ void SceneShaderData::set_code(const String &p_code) { actions.render_mode_values["blend_mix"] = Pair<int *, int>(&blend_modei, BLEND_MODE_MIX); actions.render_mode_values["blend_sub"] = Pair<int *, int>(&blend_modei, BLEND_MODE_SUB); actions.render_mode_values["blend_mul"] = Pair<int *, int>(&blend_modei, BLEND_MODE_MUL); + actions.render_mode_values["blend_premul_alpha"] = Pair<int *, int>(&blend_modei, BLEND_MODE_PREMULT_ALPHA); actions.render_mode_values["alpha_to_coverage"] = Pair<int *, int>(&alpha_antialiasing_modei, ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE); actions.render_mode_values["alpha_to_coverage_and_one"] = Pair<int *, int>(&alpha_antialiasing_modei, ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE_AND_TO_ONE); diff --git a/drivers/gles3/storage/material_storage.h b/drivers/gles3/storage/material_storage.h index 02aecf33d6..392ebcc570 100644 --- a/drivers/gles3/storage/material_storage.h +++ b/drivers/gles3/storage/material_storage.h @@ -248,6 +248,7 @@ struct SceneShaderData : public ShaderData { BLEND_MODE_ADD, BLEND_MODE_SUB, BLEND_MODE_MUL, + BLEND_MODE_PREMULT_ALPHA, BLEND_MODE_ALPHA_TO_COVERAGE }; diff --git a/drivers/gles3/storage/render_scene_buffers_gles3.cpp b/drivers/gles3/storage/render_scene_buffers_gles3.cpp index 6803c92dc9..cb194933ed 100644 --- a/drivers/gles3/storage/render_scene_buffers_gles3.cpp +++ b/drivers/gles3/storage/render_scene_buffers_gles3.cpp @@ -577,8 +577,7 @@ void RenderSceneBuffersGLES3::check_glow_buffers() { GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton(); Size2i level_size = internal_size; for (int i = 0; i < 4; i++) { - level_size.x = MAX(level_size.x >> 1, 4); - level_size.y = MAX(level_size.y >> 1, 4); + level_size = Size2i(level_size.x >> 1, level_size.y >> 1).maxi(4); glow.levels[i].size = level_size; diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp index 6f32e4d49d..373df8d8de 100644 --- a/drivers/gles3/storage/texture_storage.cpp +++ b/drivers/gles3/storage/texture_storage.cpp @@ -2812,7 +2812,7 @@ void TextureStorage::_render_target_allocate_sdf(RenderTarget *rt) { } rt->process_size = size * scale / 100; - rt->process_size = rt->process_size.max(Size2i(1, 1)); + rt->process_size = rt->process_size.maxi(1); glGenTextures(2, rt->sdf_texture_process); glBindTexture(GL_TEXTURE_2D, rt->sdf_texture_process[0]); diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index 14190a43a5..1e91a326b3 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -1578,31 +1578,7 @@ void EditorFileSystem::_update_script_classes() { update_script_mutex.lock(); for (const String &path : update_script_paths) { - ScriptServer::remove_global_class_by_path(path); // First remove, just in case it changed - - int index = -1; - EditorFileSystemDirectory *efd = find_file(path, &index); - - if (!efd || index < 0) { - // The file was removed - continue; - } - - if (!efd->files[index]->script_class_name.is_empty()) { - String lang; - for (int j = 0; j < ScriptServer::get_language_count(); j++) { - if (ScriptServer::get_language(j)->handles_global_class_type(efd->files[index]->type)) { - lang = ScriptServer::get_language(j)->get_name(); - } - } - if (lang.is_empty()) { - continue; // No lang found that can handle this global class - } - - ScriptServer::add_global_class(efd->files[index]->script_class_name, efd->files[index]->script_class_extends, lang, path); - EditorNode::get_editor_data().script_class_set_icon_path(efd->files[index]->script_class_name, efd->files[index]->script_class_icon_path); - EditorNode::get_editor_data().script_class_set_name(path, efd->files[index]->script_class_name); - } + EditorFileSystem::get_singleton()->register_global_class_script(path, path); } // Parse documentation second, as it requires the class names to be correct and registered @@ -1844,6 +1820,34 @@ HashSet<String> EditorFileSystem::get_valid_extensions() const { return valid_extensions; } +void EditorFileSystem::register_global_class_script(const String &p_search_path, const String &p_target_path) { + ScriptServer::remove_global_class_by_path(p_search_path); // First remove, just in case it changed + + int index = -1; + EditorFileSystemDirectory *efd = find_file(p_search_path, &index); + + if (!efd || index < 0) { + // The file was removed + return; + } + + if (!efd->files[index]->script_class_name.is_empty()) { + String lang; + for (int j = 0; j < ScriptServer::get_language_count(); j++) { + if (ScriptServer::get_language(j)->handles_global_class_type(efd->files[index]->type)) { + lang = ScriptServer::get_language(j)->get_name(); + } + } + if (lang.is_empty()) { + return; // No lang found that can handle this global class + } + + ScriptServer::add_global_class(efd->files[index]->script_class_name, efd->files[index]->script_class_extends, lang, p_target_path); + EditorNode::get_editor_data().script_class_set_icon_path(efd->files[index]->script_class_name, efd->files[index]->script_class_icon_path); + EditorNode::get_editor_data().script_class_set_name(p_target_path, efd->files[index]->script_class_name); + } +} + Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector<String> &p_files) { String importer_name; @@ -2324,8 +2328,9 @@ void EditorFileSystem::reimport_file_with_custom_parameters(const String &p_file } void EditorFileSystem::_reimport_thread(uint32_t p_index, ImportThreadData *p_import_data) { - p_import_data->max_index = MAX(p_import_data->reimport_from + int(p_index), p_import_data->max_index); - _reimport_file(p_import_data->reimport_files[p_import_data->reimport_from + p_index].path); + int current_max = p_import_data->reimport_from + int(p_index); + p_import_data->max_index.exchange_if_greater(current_max); + _reimport_file(p_import_data->reimport_files[current_max].path); } void EditorFileSystem::reimport_files(const Vector<String> &p_files) { @@ -2405,15 +2410,15 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) { importer->import_threaded_begin(); ImportThreadData tdata; - tdata.max_index = from; + tdata.max_index.set(from); tdata.reimport_from = from; tdata.reimport_files = reimport_files.ptr(); WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &EditorFileSystem::_reimport_thread, &tdata, i - from + 1, -1, false, vformat(TTR("Import resources of type: %s"), reimport_files[from].importer)); int current_index = from - 1; do { - if (current_index < tdata.max_index) { - current_index = tdata.max_index; + if (current_index < tdata.max_index.get()) { + current_index = tdata.max_index.get(); pr.step(reimport_files[current_index].path.get_file(), current_index); } OS::get_singleton()->delay_usec(1); diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h index 782d3eee38..ad0e3f10ef 100644 --- a/editor/editor_file_system.h +++ b/editor/editor_file_system.h @@ -282,7 +282,7 @@ class EditorFileSystem : public Node { struct ImportThreadData { const ImportFile *reimport_files; int reimport_from; - int max_index = 0; + SafeNumeric<int> max_index; }; void _reimport_thread(uint32_t p_index, ImportThreadData *p_import_data); @@ -310,6 +310,7 @@ public: void scan_changes(); void update_file(const String &p_file); HashSet<String> get_valid_extensions() const; + void register_global_class_script(const String &p_search_path, const String &p_target_path); EditorFileSystemDirectory *get_filesystem_path(const String &p_path); String get_file_type(const String &p_file) const; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index b9ac7b6d42..5bb9aa91d2 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -3486,6 +3486,10 @@ void EditorNode::remove_editor_plugin(EditorPlugin *p_editor, bool p_config_chan } } + if (singleton->editor_plugin_screen == p_editor) { + singleton->editor_plugin_screen = nullptr; + } + singleton->editor_table.erase(p_editor); } p_editor->make_visible(false); @@ -3512,6 +3516,7 @@ void EditorNode::add_extension_editor_plugin(const StringName &p_class_name) { EditorPlugin *plugin = Object::cast_to<EditorPlugin>(ClassDB::instantiate(p_class_name)); singleton->editor_data.add_extension_editor_plugin(p_class_name, plugin); add_editor_plugin(plugin); + plugin->enable_plugin(); } void EditorNode::remove_extension_editor_plugin(const StringName &p_class_name) { @@ -7255,7 +7260,9 @@ EditorNode::EditorNode() { add_editor_plugin(memnew(AudioBusesEditorPlugin(audio_bus_editor))); for (int i = 0; i < EditorPlugins::get_plugin_count(); i++) { - add_editor_plugin(EditorPlugins::create(i)); + EditorPlugin *plugin = EditorPlugins::create(i); + add_editor_plugin(plugin); + plugin->enable_plugin(); } for (const StringName &extension_class_name : GDExtensionEditorPlugins::get_extension_classes()) { diff --git a/editor/engine_update_label.cpp b/editor/engine_update_label.cpp index 1d7df806de..9984d6f02f 100644 --- a/editor/engine_update_label.cpp +++ b/editor/engine_update_label.cpp @@ -30,6 +30,7 @@ #include "engine_update_label.h" +#include "core/io/json.h" #include "core/os/time.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" @@ -46,7 +47,7 @@ bool EngineUpdateLabel::_can_check_updates() const { void EngineUpdateLabel::_check_update() { checked_update = true; _set_status(UpdateStatus::BUSY); - http->request("https://raw.githubusercontent.com/godotengine/godot-website/master/_data/versions.yml"); + http->request("https://godotengine.org/versions.json"); } void EngineUpdateLabel::_http_request_completed(int p_result, int p_response_code, const PackedStringArray &p_headers, const PackedByteArray &p_body) { @@ -62,12 +63,24 @@ void EngineUpdateLabel::_http_request_completed(int p_result, int p_response_cod return; } - PackedStringArray lines; + Array version_data; { String s; const uint8_t *r = p_body.ptr(); s.parse_utf8((const char *)r, p_body.size()); - lines = s.split("\n"); + + Variant result = JSON::parse_string(s); + if (result == Variant()) { + _set_status(UpdateStatus::ERROR); + _set_message(TTR("Failed to parse version JSON."), theme_cache.error_color); + return; + } + if (result.get_type() != Variant::ARRAY) { + _set_status(UpdateStatus::ERROR); + _set_message(TTR("Received JSON data is not a valid version array."), theme_cache.error_color); + return; + } + version_data = result; } UpdateMode update_mode = UpdateMode(int(EDITOR_GET("network/connection/engine_version_update_mode"))); @@ -78,14 +91,11 @@ void EngineUpdateLabel::_http_request_completed(int p_result, int p_response_cod int current_minor = version_info["minor"]; int current_patch = version_info["patch"]; - int current_version_line = -1; - for (int i = 0; i < lines.size(); i++) { - const String &line = lines[i]; - if (!line.begins_with("- name")) { - continue; - } + Dictionary found_version_info; + for (const Variant &data_bit : version_data) { + const Dictionary info = data_bit; - const String version_string = _extract_sub_string(line); + const String version_string = info["name"]; const PackedStringArray version_bits = version_string.split("."); if (version_bits.size() < 2) { @@ -111,7 +121,7 @@ void EngineUpdateLabel::_http_request_completed(int p_result, int p_response_cod } if (minor > current_minor || patch > current_patch) { - String version_type = _extract_sub_string(lines[i + 1]); + String version_type = info["flavor"]; if (stable_only && _get_version_type(version_type, nullptr) != VersionType::STABLE) { continue; } @@ -120,17 +130,17 @@ void EngineUpdateLabel::_http_request_completed(int p_result, int p_response_cod found_version += "-" + version_type; break; } else if (minor == current_minor && patch == current_patch) { - current_version_line = i; + found_version_info = info; found_version = version_string; break; } } - if (current_version_line == -1 && !found_version.is_empty()) { + if (found_version_info.is_empty() && !found_version.is_empty()) { _set_status(UpdateStatus::UPDATE_AVAILABLE); _set_message(vformat(TTR("Update available: %s."), found_version), theme_cache.update_color); return; - } else if (current_version_line == -1 || stable_only) { + } else if (found_version_info.is_empty() || stable_only) { _set_status(UpdateStatus::UP_TO_DATE); return; } @@ -138,17 +148,11 @@ void EngineUpdateLabel::_http_request_completed(int p_result, int p_response_cod int current_version_index; VersionType current_version_type = _get_version_type(version_info["status"], ¤t_version_index); - for (int i = current_version_line + 1; i < lines.size(); i++) { - const String &line = lines[i]; - if (line.begins_with("- name")) { - break; - } - - if (!line.begins_with(" - name") && !line.begins_with(" flavor")) { - continue; - } + const Array releases = found_version_info["releases"]; + for (const Variant &data_bit : version_data) { + const Dictionary info = data_bit; - const String version_string = _extract_sub_string(line); + const String version_string = info["name"]; int version_index; VersionType version_type = _get_version_type(version_string, &version_index); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 3f96d934a8..ac4991755b 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -1568,34 +1568,7 @@ void FileSystemDock::_update_resource_paths_after_move(const HashMap<String, Str if (I) { ResourceUID::get_singleton()->set_id(I->value, new_path); } - - ScriptServer::remove_global_class_by_path(old_path); - - int index = -1; - EditorFileSystemDirectory *efd = EditorFileSystem::get_singleton()->find_file(old_path, &index); - - if (!efd || index < 0) { - // The file was removed. - continue; - } - - // Update paths for global classes. - if (!efd->get_file_script_class_name(index).is_empty()) { - String lang; - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - if (ScriptServer::get_language(i)->handles_global_class_type(efd->get_file_type(index))) { - lang = ScriptServer::get_language(i)->get_name(); - break; - } - } - if (lang.is_empty()) { - continue; // No language found that can handle this global class. - } - - ScriptServer::add_global_class(efd->get_file_script_class_name(index), efd->get_file_script_class_extends(index), lang, new_path); - EditorNode::get_editor_data().script_class_set_icon_path(efd->get_file_script_class_name(index), efd->get_file_script_class_icon_path(index)); - EditorNode::get_editor_data().script_class_set_name(new_path, efd->get_file_script_class_name(index)); - } + EditorFileSystem::get_singleton()->register_global_class_script(old_path, new_path); } // Rename all resources loaded, be it subresources or actual resources. diff --git a/editor/import/resource_importer_wav.cpp b/editor/import/resource_importer_wav.cpp index c8add6ab20..6d3d474cee 100644 --- a/editor/import/resource_importer_wav.cpp +++ b/editor/import/resource_importer_wav.cpp @@ -90,7 +90,7 @@ void ResourceImporterWAV::get_import_options(const String &p_path, List<ImportOp r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "edit/loop_mode", PROPERTY_HINT_ENUM, "Detect From WAV,Disabled,Forward,Ping-Pong,Backward", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "edit/loop_begin"), 0)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "edit/loop_end"), -1)); - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/mode", PROPERTY_HINT_ENUM, "Disabled,RAM (Ima-ADPCM)"), 0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/mode", PROPERTY_HINT_ENUM, "Disabled,RAM (Ima-ADPCM),QOA (Quite OK Audio)"), 0)); } Error ResourceImporterWAV::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) { @@ -454,13 +454,13 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s is16 = false; } - Vector<uint8_t> dst_data; + Vector<uint8_t> pcm_data; AudioStreamWAV::Format dst_format; if (compression == 1) { dst_format = AudioStreamWAV::FORMAT_IMA_ADPCM; if (format_channels == 1) { - _compress_ima_adpcm(data, dst_data); + _compress_ima_adpcm(data, pcm_data); } else { //byte interleave Vector<float> left; @@ -482,9 +482,9 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s _compress_ima_adpcm(right, bright); int dl = bleft.size(); - dst_data.resize(dl * 2); + pcm_data.resize(dl * 2); - uint8_t *w = dst_data.ptrw(); + uint8_t *w = pcm_data.ptrw(); const uint8_t *rl = bleft.ptr(); const uint8_t *rr = bright.ptr(); @@ -496,13 +496,14 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s } else { dst_format = is16 ? AudioStreamWAV::FORMAT_16_BITS : AudioStreamWAV::FORMAT_8_BITS; - dst_data.resize(data.size() * (is16 ? 2 : 1)); + bool enforce16 = is16 || compression == 2; + pcm_data.resize(data.size() * (enforce16 ? 2 : 1)); { - uint8_t *w = dst_data.ptrw(); + uint8_t *w = pcm_data.ptrw(); int ds = data.size(); for (int i = 0; i < ds; i++) { - if (is16) { + if (enforce16) { int16_t v = CLAMP(data[i] * 32768, -32768, 32767); encode_uint16(v, &w[i * 2]); } else { @@ -513,6 +514,23 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s } } + Vector<uint8_t> dst_data; + if (compression == 2) { + dst_format = AudioStreamWAV::FORMAT_QOA; + qoa_desc desc = { 0, 0, 0, { { { 0 }, { 0 } } } }; + uint32_t qoa_len = 0; + + desc.samplerate = rate; + desc.samples = frames; + desc.channels = format_channels; + + void *encoded = qoa_encode((short *)pcm_data.ptrw(), &desc, &qoa_len); + dst_data.resize(qoa_len); + memcpy(dst_data.ptrw(), encoded, qoa_len); + } else { + dst_data = pcm_data; + } + Ref<AudioStreamWAV> sample; sample.instantiate(); sample->set_data(dst_data); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 5ac5dd6ee6..79a4269f01 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -467,7 +467,7 @@ Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, unsig if (((snap_pixel && (p_modes & SNAP_PIXEL)) || (p_forced_modes & SNAP_PIXEL)) && rotation == 0.0) { // Pixel - output = output.snapped(Size2(1, 1)); + output = output.snappedf(1); } snap_transform = Transform2D(rotation, output); @@ -1625,7 +1625,7 @@ bool CanvasItemEditor::_gui_input_anchors(const Ref<InputEvent> &p_event) { previous_anchor = xform.affine_inverse().xform(_anchor_to_position(control, previous_anchor)); Vector2 new_anchor = xform.xform(snap_point(previous_anchor + (drag_to - drag_from), SNAP_GRID | SNAP_OTHER_NODES, SNAP_NODE_PARENT | SNAP_NODE_SIDES | SNAP_NODE_CENTER, control)); - new_anchor = _position_to_anchor(control, new_anchor).snapped(Vector2(0.001, 0.001)); + new_anchor = _position_to_anchor(control, new_anchor).snappedf(0.001); bool use_single_axis = m->is_shift_pressed(); Vector2 drag_vector = xform.xform(drag_to) - xform.xform(drag_from); diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp index 9fb4d86fa2..f2b38536b5 100644 --- a/editor/plugins/editor_preview_plugins.cpp +++ b/editor/plugins/editor_preview_plugins.cpp @@ -130,7 +130,7 @@ Ref<Texture2D> EditorTexturePreviewPlugin::generate(const Ref<Resource> &p_from, if (new_size.y > p_size.y) { new_size = Vector2(new_size.x * p_size.y / new_size.y, p_size.y); } - Vector2i new_size_i = Vector2i(new_size).max(Vector2i(1, 1)); + Vector2i new_size_i = Vector2i(new_size).maxi(1); img->resize(new_size_i.x, new_size_i.y, Image::INTERPOLATE_CUBIC); post_process_preview(img); diff --git a/editor/plugins/gizmos/navigation_link_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/navigation_link_3d_gizmo_plugin.cpp index 3717b4b1a3..51d15a0a70 100644 --- a/editor/plugins/gizmos/navigation_link_3d_gizmo_plugin.cpp +++ b/editor/plugins/gizmos/navigation_link_3d_gizmo_plugin.cpp @@ -165,7 +165,7 @@ void NavigationLink3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, i if (Node3DEditor::get_singleton()->is_snap_enabled()) { double snap = Node3DEditor::get_singleton()->get_translate_snap(); - intersection.snap(Vector3(snap, snap, snap)); + intersection.snapf(snap); } position = gi.xform(intersection); diff --git a/editor/plugins/gizmos/navigation_region_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/navigation_region_3d_gizmo_plugin.cpp index 088b09ea46..14105f0b3b 100644 --- a/editor/plugins/gizmos/navigation_region_3d_gizmo_plugin.cpp +++ b/editor/plugins/gizmos/navigation_region_3d_gizmo_plugin.cpp @@ -108,8 +108,8 @@ void NavigationRegion3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { for (int j = 0; j < 3; j++) { tw[tidx++] = f.vertex[j]; _EdgeKey ek; - ek.from = f.vertex[j].snapped(Vector3(CMP_EPSILON, CMP_EPSILON, CMP_EPSILON)); - ek.to = f.vertex[(j + 1) % 3].snapped(Vector3(CMP_EPSILON, CMP_EPSILON, CMP_EPSILON)); + ek.from = f.vertex[j].snappedf(CMP_EPSILON); + ek.to = f.vertex[(j + 1) % 3].snappedf(CMP_EPSILON); if (ek.from < ek.to) { SWAP(ek.from, ek.to); } diff --git a/editor/plugins/gizmos/occluder_instance_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/occluder_instance_3d_gizmo_plugin.cpp index 37c3a05b50..d6f649ab9c 100644 --- a/editor/plugins/gizmos/occluder_instance_3d_gizmo_plugin.cpp +++ b/editor/plugins/gizmos/occluder_instance_3d_gizmo_plugin.cpp @@ -163,9 +163,9 @@ void OccluderInstance3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, if (p_id == 2) { Vector2 s = Vector2(intersection.x, intersection.y) * 2.0f; if (snap_enabled) { - s = s.snapped(Vector2(snap, snap)); + s = s.snappedf(snap); } - s = s.max(Vector2(0.001, 0.001)); + s = s.maxf(0.001); qo->set_size(s); } else { float d = intersection[p_id]; diff --git a/editor/plugins/gradient_texture_2d_editor_plugin.cpp b/editor/plugins/gradient_texture_2d_editor_plugin.cpp index bb6096ea34..ebc00c49bb 100644 --- a/editor/plugins/gradient_texture_2d_editor_plugin.cpp +++ b/editor/plugins/gradient_texture_2d_editor_plugin.cpp @@ -42,7 +42,7 @@ Point2 GradientTexture2DEdit::_get_handle_pos(const Handle p_handle) { // Get the handle's mouse position in pixels relative to offset. - return (p_handle == HANDLE_FROM ? texture->get_fill_from() : texture->get_fill_to()).clamp(Vector2(), Vector2(1, 1)) * size; + return (p_handle == HANDLE_FROM ? texture->get_fill_from() : texture->get_fill_to()).clampf(0, 1) * size; } GradientTexture2DEdit::Handle GradientTexture2DEdit::get_handle_at(const Vector2 &p_pos) { @@ -112,9 +112,9 @@ void GradientTexture2DEdit::gui_input(const Ref<InputEvent> &p_event) { return; } - Vector2 new_pos = (mpos / size).clamp(Vector2(0, 0), Vector2(1, 1)); + Vector2 new_pos = (mpos / size).clampf(0, 1); if (snap_enabled || mm->is_command_or_control_pressed()) { - new_pos = new_pos.snapped(Vector2(1.0 / snap_count, 1.0 / snap_count)); + new_pos = new_pos.snappedf(1.0 / snap_count); } // Allow to snap to an axis with Shift. diff --git a/editor/plugins/navigation_obstacle_3d_editor_plugin.cpp b/editor/plugins/navigation_obstacle_3d_editor_plugin.cpp index 869f5b3b10..61b43eaaf1 100644 --- a/editor/plugins/navigation_obstacle_3d_editor_plugin.cpp +++ b/editor/plugins/navigation_obstacle_3d_editor_plugin.cpp @@ -330,9 +330,7 @@ EditorPlugin::AfterGUIInput NavigationObstacle3DEditor::forward_3d_gui_input(Cam } if (!snap_ignore && Node3DEditor::get_singleton()->is_snap_enabled()) { - cpoint = cpoint.snapped(Vector2( - Node3DEditor::get_singleton()->get_translate_snap(), - Node3DEditor::get_singleton()->get_translate_snap())); + cpoint = cpoint.snappedf(Node3DEditor::get_singleton()->get_translate_snap()); } edited_point_pos = cpoint; diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index be14132185..4a418e62ca 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -1448,7 +1448,7 @@ Transform3D Node3DEditorViewport::_compute_transform(TransformMode p_mode, const switch (p_mode) { case TRANSFORM_SCALE: { if (_edit.snap || spatial_editor->is_snap_enabled()) { - p_motion.snap(Vector3(p_extra, p_extra, p_extra)); + p_motion.snapf(p_extra); } Transform3D s; if (p_local) { @@ -1469,7 +1469,7 @@ Transform3D Node3DEditorViewport::_compute_transform(TransformMode p_mode, const } case TRANSFORM_TRANSLATE: { if (_edit.snap || spatial_editor->is_snap_enabled()) { - p_motion.snap(Vector3(p_extra, p_extra, p_extra)); + p_motion.snapf(p_extra); } if (p_local) { @@ -4786,7 +4786,7 @@ void Node3DEditorViewport::update_transform(bool p_shift) { snap = spatial_editor->get_scale_snap() / 100; } Vector3 motion_snapped = motion; - motion_snapped.snap(Vector3(snap, snap, snap)); + motion_snapped.snapf(snap); // This might not be necessary anymore after issue #288 is solved (in 4.0?). // TRANSLATORS: Refers to changing the scale of a node in the 3D editor. set_message(TTR("Scaling:") + " (" + String::num(motion_snapped.x, snap_step_decimals) + ", " + @@ -4858,7 +4858,7 @@ void Node3DEditorViewport::update_transform(bool p_shift) { snap = spatial_editor->get_translate_snap(); } Vector3 motion_snapped = motion; - motion_snapped.snap(Vector3(snap, snap, snap)); + motion_snapped.snapf(snap); // TRANSLATORS: Refers to changing the position of a node in the 3D editor. set_message(TTR("Translating:") + " (" + String::num(motion_snapped.x, snap_step_decimals) + ", " + String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")"); @@ -8997,7 +8997,7 @@ void Node3DEditorPlugin::set_state(const Dictionary &p_state) { Vector3 Node3DEditor::snap_point(Vector3 p_target, Vector3 p_start) const { if (is_snap_enabled()) { real_t snap = get_translate_snap(); - p_target.snap(Vector3(snap, snap, snap)); + p_target.snapf(snap); } return p_target; } diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp index 4e317114a3..f09318277a 100644 --- a/editor/plugins/path_3d_editor_plugin.cpp +++ b/editor/plugins/path_3d_editor_plugin.cpp @@ -117,7 +117,7 @@ void Path3DGizmo::set_handle(int p_id, bool p_secondary, Camera3D *p_camera, con if (p.intersects_ray(ray_from, ray_dir, &inters)) { if (Node3DEditor::get_singleton()->is_snap_enabled()) { float snap = Node3DEditor::get_singleton()->get_translate_snap(); - inters.snap(Vector3(snap, snap, snap)); + inters.snapf(snap); } Vector3 local = gi.xform(inters); @@ -146,7 +146,7 @@ void Path3DGizmo::set_handle(int p_id, bool p_secondary, Camera3D *p_camera, con Vector3 local = gi.xform(inters) - base; if (Node3DEditor::get_singleton()->is_snap_enabled()) { float snap = Node3DEditor::get_singleton()->get_translate_snap(); - local.snap(Vector3(snap, snap, snap)); + local.snapf(snap); } if (info.type == HandleType::HANDLE_TYPE_IN) { diff --git a/editor/plugins/polygon_3d_editor_plugin.cpp b/editor/plugins/polygon_3d_editor_plugin.cpp index da84afc4d7..7c41093774 100644 --- a/editor/plugins/polygon_3d_editor_plugin.cpp +++ b/editor/plugins/polygon_3d_editor_plugin.cpp @@ -334,9 +334,7 @@ EditorPlugin::AfterGUIInput Polygon3DEditor::forward_3d_gui_input(Camera3D *p_ca } if (!snap_ignore && Node3DEditor::get_singleton()->is_snap_enabled()) { - cpoint = cpoint.snapped(Vector2( - Node3DEditor::get_singleton()->get_translate_snap(), - Node3DEditor::get_singleton()->get_translate_snap())); + cpoint = cpoint.snappedf(Node3DEditor::get_singleton()->get_translate_snap()); } edited_point_pos = cpoint; diff --git a/editor/plugins/texture_region_editor_plugin.cpp b/editor/plugins/texture_region_editor_plugin.cpp index 9498e980ec..5557eec694 100644 --- a/editor/plugins/texture_region_editor_plugin.cpp +++ b/editor/plugins/texture_region_editor_plugin.cpp @@ -328,7 +328,7 @@ void TextureRegionEditor::_texture_overlay_input(const Ref<InputEvent> &p_input) drag_from = mtx.affine_inverse().xform(mb->get_position()); if (snap_mode == SNAP_PIXEL) { - drag_from = drag_from.snapped(Vector2(1, 1)); + drag_from = drag_from.snappedf(1); } else if (snap_mode == SNAP_GRID) { drag_from = snap_point(drag_from); } @@ -566,7 +566,7 @@ void TextureRegionEditor::_texture_overlay_input(const Ref<InputEvent> &p_input) } else { Vector2 new_pos = mtx.affine_inverse().xform(mm->get_position()); if (snap_mode == SNAP_PIXEL) { - new_pos = new_pos.snapped(Vector2(1, 1)); + new_pos = new_pos.snappedf(1); } else if (snap_mode == SNAP_GRID) { new_pos = snap_point(new_pos); } diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index f047e4ff16..86a291db0e 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -458,7 +458,7 @@ void GenericTilePolygonEditor::_snap_point(Point2 &r_point) { break; case SNAP_HALF_PIXEL: - r_point = r_point.snapped(Vector2(0.5, 0.5)); + r_point = r_point.snappedf(0.5); break; case SNAP_GRID: { diff --git a/editor/plugins/tiles/tile_proxies_manager_dialog.cpp b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp index ca4ffeecc2..a75ab45106 100644 --- a/editor/plugins/tiles/tile_proxies_manager_dialog.cpp +++ b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp @@ -257,13 +257,13 @@ bool TileProxiesManagerDialog::_set(const StringName &p_name, const Variant &p_v if (p_name == "from_source") { from.source_id = MAX(int(p_value), -1); } else if (p_name == "from_coords") { - from.set_atlas_coords(Vector2i(p_value).max(Vector2i(-1, -1))); + from.set_atlas_coords(Vector2i(p_value).maxi(-1)); } else if (p_name == "from_alternative") { from.alternative_tile = MAX(int(p_value), -1); } else if (p_name == "to_source") { to.source_id = MAX(int(p_value), 0); } else if (p_name == "to_coords") { - to.set_atlas_coords(Vector2i(p_value).max(Vector2i(0, 0))); + to.set_atlas_coords(Vector2i(p_value).maxi(0)); } else if (p_name == "to_alternative") { to.alternative_tile = MAX(int(p_value), 0); } else { diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index 1da2f89c1c..1a1b14bb84 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -1088,7 +1088,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven if (drag_type == DRAG_TYPE_CREATE_BIG_TILE) { // Create big tile. - new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); + new_base_tiles_coords = new_base_tiles_coords.maxi(0).min(grid_size - Vector2i(1, 1)); Rect2i new_rect = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs(); new_rect.size += Vector2i(1, 1); @@ -1100,8 +1100,8 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven } } else if (drag_type == DRAG_TYPE_CREATE_TILES) { // Create tiles. - last_base_tiles_coords = last_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); - new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); + last_base_tiles_coords = last_base_tiles_coords.maxi(0).min(grid_size - Vector2i(1, 1)); + new_base_tiles_coords = new_base_tiles_coords.maxi(0).min(grid_size - Vector2i(1, 1)); Vector<Point2i> line = Geometry2D::bresenham_line(last_base_tiles_coords, new_base_tiles_coords); for (int i = 0; i < line.size(); i++) { @@ -1115,8 +1115,8 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven } else if (drag_type == DRAG_TYPE_REMOVE_TILES) { // Remove tiles. - last_base_tiles_coords = last_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); - new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); + last_base_tiles_coords = last_base_tiles_coords.maxi(0).min(grid_size - Vector2i(1, 1)); + new_base_tiles_coords = new_base_tiles_coords.maxi(0).min(grid_size - Vector2i(1, 1)); Vector<Point2i> line = Geometry2D::bresenham_line(last_base_tiles_coords, new_base_tiles_coords); for (int i = 0; i < line.size(); i++) { @@ -1838,7 +1838,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { Vector2i separation = tile_set_atlas_source->get_separation(); Vector2i tile_size = tile_set_atlas_source->get_texture_region_size(); Vector2i origin = margins + (area.position * (tile_size + separation)); - Vector2i size = area.size * tile_size + (area.size - Vector2i(1, 1)).max(Vector2i(0, 0)) * separation; + Vector2i size = area.size * tile_size + (area.size - Vector2i(1, 1)).maxi(0) * separation; TilesEditorUtils::draw_selection_rect(tile_atlas_control, Rect2i(origin, size)); } else { Vector2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); @@ -2157,7 +2157,7 @@ void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo Vector2i TileSetAtlasSourceEditor::_get_drag_offset_tile_coords(const Vector2i &p_offset) const { Vector2i half_tile_size = tile_set->get_tile_size() / 2; Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position() + half_tile_size * p_offset); - return new_base_tiles_coords.max(Vector2i(-1, -1)).min(tile_set_atlas_source->get_atlas_grid_size()); + return new_base_tiles_coords.maxi(-1).min(tile_set_atlas_source->get_atlas_grid_size()); } void TileSetAtlasSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id) { diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 0dd845270a..9e706ce623 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -1002,7 +1002,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool button->hide(); } - if (i == 0 && custom_editor) { + if (j == 0 && custom_editor) { hb->add_child(custom_editor); custom_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); } else { @@ -1019,22 +1019,22 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool type_box->add_item(TTR("Boolean")); type_box->add_item(TTR("Transform")); type_box->add_item(TTR("Sampler")); - type_box->select(group_node->get_input_port_type(i)); + type_box->select(group_node->get_input_port_type(j)); type_box->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); - type_box->connect("item_selected", callable_mp(editor, &VisualShaderEditor::_change_input_port_type).bind(p_id, i), CONNECT_DEFERRED); + type_box->connect("item_selected", callable_mp(editor, &VisualShaderEditor::_change_input_port_type).bind(p_id, j), CONNECT_DEFERRED); LineEdit *name_box = memnew(LineEdit); hb->add_child(name_box); name_box->set_custom_minimum_size(Size2(65 * EDSCALE, 0)); name_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); name_box->set_text(name_left); - name_box->connect("text_submitted", callable_mp(editor, &VisualShaderEditor::_change_input_port_name).bind(name_box, p_id, i), CONNECT_DEFERRED); - name_box->connect("focus_exited", callable_mp(editor, &VisualShaderEditor::_port_name_focus_out).bind(name_box, p_id, i, false), CONNECT_DEFERRED); + name_box->connect("text_submitted", callable_mp(editor, &VisualShaderEditor::_change_input_port_name).bind(name_box, p_id, j), CONNECT_DEFERRED); + name_box->connect("focus_exited", callable_mp(editor, &VisualShaderEditor::_port_name_focus_out).bind(name_box, p_id, j, false), CONNECT_DEFERRED); Button *remove_btn = memnew(Button); remove_btn->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Remove"), EditorStringName(EditorIcons))); remove_btn->set_tooltip_text(TTR("Remove") + " " + name_left); - remove_btn->connect("pressed", callable_mp(editor, &VisualShaderEditor::_remove_input_port).bind(p_id, i), CONNECT_DEFERRED); + remove_btn->connect("pressed", callable_mp(editor, &VisualShaderEditor::_remove_input_port).bind(p_id, j), CONNECT_DEFERRED); hb->add_child(remove_btn); } else { Label *label = memnew(Label); @@ -1043,7 +1043,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool label->add_theme_style_override("normal", editor->get_theme_stylebox(SNAME("label_style"), SNAME("VShaderEditor"))); //more compact hb->add_child(label); - if (vsnode->is_input_port_default(i, mode) && !port_left_used) { + if (vsnode->is_input_port_default(j, mode) && !port_left_used) { Label *hint_label = memnew(Label); hint_label->set_text(TTR("[default]")); hint_label->add_theme_color_override("font_color", editor->get_theme_color(SNAME("font_readonly_color"), SNAME("TextEdit"))); @@ -4673,7 +4673,7 @@ void VisualShaderEditor::_show_members_dialog(bool at_mouse_pos, VisualShaderNod // Keep dialog within window bounds. Rect2 window_rect = Rect2(get_window()->get_position(), get_window()->get_size()); Rect2 dialog_rect = Rect2(members_dialog->get_position(), members_dialog->get_size()); - Vector2 difference = (dialog_rect.get_end() - window_rect.get_end()).max(Vector2()); + Vector2 difference = (dialog_rect.get_end() - window_rect.get_end()).maxf(0); members_dialog->set_position(members_dialog->get_position() - difference); callable_mp((Control *)node_filter, &Control::grab_focus).call_deferred(); // Still not visible. @@ -4702,7 +4702,7 @@ void VisualShaderEditor::_show_add_varying_dialog() { // Keep dialog within window bounds. Rect2 window_rect = Rect2(DisplayServer::get_singleton()->window_get_position(), DisplayServer::get_singleton()->window_get_size()); Rect2 dialog_rect = Rect2(add_varying_dialog->get_position(), add_varying_dialog->get_size()); - Vector2 difference = (dialog_rect.get_end() - window_rect.get_end()).max(Vector2()); + Vector2 difference = (dialog_rect.get_end() - window_rect.get_end()).maxf(0); add_varying_dialog->set_position(add_varying_dialog->get_position() - difference); } @@ -4713,7 +4713,7 @@ void VisualShaderEditor::_show_remove_varying_dialog() { // Keep dialog within window bounds. Rect2 window_rect = Rect2(DisplayServer::get_singleton()->window_get_position(), DisplayServer::get_singleton()->window_get_size()); Rect2 dialog_rect = Rect2(remove_varying_dialog->get_position(), remove_varying_dialog->get_size()); - Vector2 difference = (dialog_rect.get_end() - window_rect.get_end()).max(Vector2()); + Vector2 difference = (dialog_rect.get_end() - window_rect.get_end()).maxf(0); remove_varying_dialog->set_position(remove_varying_dialog->get_position() - difference); } diff --git a/main/main.cpp b/main/main.cpp index eee634086e..90c84114f2 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -2405,6 +2405,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph // OpenXR project extensions settings. GLOBAL_DEF_BASIC("xr/openxr/extensions/hand_tracking", true); + GLOBAL_DEF_RST_BASIC("xr/openxr/extensions/hand_interaction_profile", false); GLOBAL_DEF_BASIC("xr/openxr/extensions/eye_gaze_interaction", false); #ifdef TOOLS_ENABLED @@ -3182,6 +3183,7 @@ int Main::start() { #ifdef TOOLS_ENABLED String doc_tool_path; + bool doc_tool_implicit_cwd = false; BitField<DocTools::GenerateFlags> gen_flags; String _export_preset; bool export_debug = false; @@ -3252,6 +3254,7 @@ int Main::start() { if (doc_tool_path.begins_with("-")) { // Assuming other command line arg, so default to cwd. doc_tool_path = "."; + doc_tool_implicit_cwd = true; parsed_pair = false; } #ifdef MODULE_GDSCRIPT_ENABLED @@ -3282,6 +3285,7 @@ int Main::start() { // Handle case where no path is given to --doctool. else if (args[i] == "--doctool") { doc_tool_path = "."; + doc_tool_implicit_cwd = true; } #endif } @@ -3308,6 +3312,11 @@ int Main::start() { { Ref<DirAccess> da = DirAccess::open(doc_tool_path); ERR_FAIL_COND_V_MSG(da.is_null(), EXIT_FAILURE, "Argument supplied to --doctool must be a valid directory path."); + // Ensure that doctool is running in the root dir, but only if + // user did not manually specify a path as argument. + if (doc_tool_implicit_cwd) { + ERR_FAIL_COND_V_MSG(!da->dir_exists("doc"), EXIT_FAILURE, "--doctool must be run from the Godot repository's root folder, or specify a path that points there."); + } } #ifndef MODULE_MONO_ENABLED @@ -3636,7 +3645,7 @@ int Main::start() { } } - if (doc_tool_path == ".") { + if (doc_tool_implicit_cwd) { doc_tool_path = "./docs"; } diff --git a/misc/extension_api_validation/4.2-stable.expected b/misc/extension_api_validation/4.2-stable.expected index c0731b18fc..5a9976dc71 100644 --- a/misc/extension_api_validation/4.2-stable.expected +++ b/misc/extension_api_validation/4.2-stable.expected @@ -323,3 +323,17 @@ Validate extension JSON: Error: Field 'classes/TextEdit/methods/set_selection_mo Removed optional arguments set_selection_mode, use set_selection_origin_line/column instead. Compatibility methods registered. + + +GH-84472 +-------- +Validate extension JSON: Error: Field 'classes/CanvasItem/methods/draw_circle/arguments': size changed value in new API, from 3 to 6. + +Optional arguments added. Compatibility methods registered. + + +GH-91098 +-------- +Validate extension JSON: Error: Field 'classes/RichTextLabel/methods/remove_paragraph/arguments': size changed value in new API, from 1 to 2. + +Added optional argument. Compatibility method registered. diff --git a/modules/astcenc/image_compress_astcenc.cpp b/modules/astcenc/image_compress_astcenc.cpp index 31df83efae..941c1f44be 100644 --- a/modules/astcenc/image_compress_astcenc.cpp +++ b/modules/astcenc/image_compress_astcenc.cpp @@ -41,7 +41,7 @@ void _compress_astc(Image *r_img, Image::ASTCFormat p_format) { // TODO: See how to handle lossy quality. Image::Format img_format = r_img->get_format(); - if (img_format >= Image::FORMAT_DXT1) { + if (Image::is_format_compressed(img_format)) { return; // Do not compress, already compressed. } diff --git a/modules/basis_universal/image_compress_basisu.cpp b/modules/basis_universal/image_compress_basisu.cpp index 72e7977eef..d8ef1c0414 100644 --- a/modules/basis_universal/image_compress_basisu.cpp +++ b/modules/basis_universal/image_compress_basisu.cpp @@ -96,17 +96,74 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha } break; } + // Copy the source image data with mipmaps into BasisU. { - // Encode the image with mipmaps. + const int orig_width = image->get_width(); + const int orig_height = image->get_height(); + + bool is_res_div_4 = (orig_width % 4 == 0) && (orig_height % 4 == 0); + + // Image's resolution rounded up to the nearest values divisible by 4. + int next_width = orig_width <= 2 ? orig_width : (orig_width + 3) & ~3; + int next_height = orig_height <= 2 ? orig_height : (orig_height + 3) & ~3; + Vector<uint8_t> image_data = image->get_data(); basisu::vector<basisu::image> basisu_mipmaps; + // Buffer for storing padded mipmap data. + Vector<uint32_t> mip_data_padded; + for (int32_t i = 0; i <= image->get_mipmap_count(); i++) { int ofs, size, width, height; image->get_mipmap_offset_size_and_dimensions(i, ofs, size, width, height); + const uint8_t *image_mip_data = image_data.ptr() + ofs; + + // Pad the mipmap's data if its resolution isn't divisible by 4. + if (image->has_mipmaps() && !is_res_div_4 && (width > 2 && height > 2) && (width != next_width || height != next_height)) { + // Source mip's data interpreted as 32-bit RGBA blocks to help with copying pixel data. + const uint32_t *mip_src_data = reinterpret_cast<const uint32_t *>(image_mip_data); + + // Reserve space in the padded buffer. + mip_data_padded.resize(next_width * next_height); + uint32_t *data_padded_ptr = mip_data_padded.ptrw(); + + // Pad mipmap to the nearest block by smearing. + int x = 0, y = 0; + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + data_padded_ptr[next_width * y + x] = mip_src_data[width * y + x]; + } + + // First, smear in x. + for (; x < next_width; x++) { + data_padded_ptr[next_width * y + x] = data_padded_ptr[next_width * y + x - 1]; + } + } + + // Then, smear in y. + for (; y < next_height; y++) { + for (x = 0; x < next_width; x++) { + data_padded_ptr[next_width * y + x] = data_padded_ptr[next_width * y + x - next_width]; + } + } + + // Override the image_mip_data pointer with our temporary Vector. + image_mip_data = reinterpret_cast<const uint8_t *>(mip_data_padded.ptr()); + + // Override the mipmap's properties. + width = next_width; + height = next_height; + size = mip_data_padded.size() * 4; + } + + // Get the next mipmap's resolution. + next_width /= 2; + next_height /= 2; + + // Copy the source mipmap's data to a BasisU image. basisu::image basisu_image(width, height); - memcpy(basisu_image.get_ptr(), image_data.ptr() + ofs, size); + memcpy(basisu_image.get_ptr(), image_mip_data, size); if (i == 0) { params.m_source_images.push_back(basisu_image); @@ -132,10 +189,10 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha // Copy the encoded data to the buffer. { - uint8_t *w = basisu_data.ptrw(); - *(uint32_t *)w = decompress_format; + uint8_t *wb = basisu_data.ptrw(); + *(uint32_t *)wb = decompress_format; - memcpy(w + 4, basisu_out.get_ptr(), basisu_out.size()); + memcpy(wb + 4, basisu_out.get_ptr(), basisu_out.size()); } return basisu_data; @@ -238,12 +295,11 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) { uint8_t *dst = out_data.ptrw(); memset(dst, 0, out_data.size()); - uint32_t mip_count = Image::get_image_required_mipmaps(basisu_info.m_orig_width, basisu_info.m_orig_height, image_format); - for (uint32_t i = 0; i <= mip_count; i++) { + for (uint32_t i = 0; i < basisu_info.m_total_levels; i++) { basist::basisu_image_level_info basisu_level; transcoder.get_image_level_info(src_ptr, src_size, basisu_level, 0, i); - uint32_t mip_block_or_pixel_count = image_format >= Image::FORMAT_DXT1 ? basisu_level.m_total_blocks : basisu_level.m_orig_width * basisu_level.m_orig_height; + uint32_t mip_block_or_pixel_count = Image::is_format_compressed(image_format) ? basisu_level.m_total_blocks : basisu_level.m_orig_width * basisu_level.m_orig_height; int ofs = Image::get_image_mipmap_offset(basisu_info.m_width, basisu_info.m_height, image_format, i); bool result = transcoder.transcode_image_level(src_ptr, src_size, 0, i, dst + ofs, mip_block_or_pixel_count, basisu_format); diff --git a/modules/cvtt/image_compress_cvtt.cpp b/modules/cvtt/image_compress_cvtt.cpp index e9a7009d7c..7335315c51 100644 --- a/modules/cvtt/image_compress_cvtt.cpp +++ b/modules/cvtt/image_compress_cvtt.cpp @@ -142,7 +142,7 @@ static void _digest_job_queue(void *p_job_queue, uint32_t p_index) { } void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels) { - if (p_image->get_format() >= Image::FORMAT_BPTC_RGBA) { + if (p_image->is_compressed()) { return; //do not compress, already compressed } int w = p_image->get_width(); diff --git a/modules/etcpak/image_compress_etcpak.cpp b/modules/etcpak/image_compress_etcpak.cpp index dcd73101c2..4ce0cf50d9 100644 --- a/modules/etcpak/image_compress_etcpak.cpp +++ b/modules/etcpak/image_compress_etcpak.cpp @@ -92,7 +92,7 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) { uint64_t start_time = OS::get_singleton()->get_ticks_msec(); Image::Format img_format = r_img->get_format(); - if (img_format >= Image::FORMAT_DXT1) { + if (Image::is_format_compressed(img_format)) { return; // Do not compress, already compressed. } if (img_format > Image::FORMAT_RGBA8) { diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index f238958f25..73abf71bde 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1958,19 +1958,22 @@ int GDScriptInstance::get_method_argument_count(const StringName &p_method, bool return 0; } +void GDScriptInstance::_call_implicit_ready_recursively(GDScript *p_script) { + // Call base class first. + if (p_script->_base) { + _call_implicit_ready_recursively(p_script->_base); + } + if (p_script->implicit_ready) { + Callable::CallError err; + p_script->implicit_ready->call(this, nullptr, 0, err); + } +} + Variant GDScriptInstance::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { GDScript *sptr = script.ptr(); if (unlikely(p_method == SNAME("_ready"))) { - // Call implicit ready first, including for the super classes. - while (sptr) { - if (sptr->implicit_ready) { - sptr->implicit_ready->call(this, nullptr, 0, r_error); - } - sptr = sptr->_base; - } - - // Reset this back for the regular call. - sptr = script.ptr(); + // Call implicit ready first, including for the super classes recursively. + _call_implicit_ready_recursively(sptr); } while (sptr) { HashMap<StringName, GDScriptFunction *>::Iterator E = sptr->member_functions.find(p_method); diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 7bd68ac0b1..51267ecb84 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -365,6 +365,8 @@ class GDScriptInstance : public ScriptInstance { SelfList<GDScriptFunctionState>::List pending_func_states; + void _call_implicit_ready_recursively(GDScript *p_script); + public: virtual Object *get_owner() { return owner; } diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index bfe090edb0..5a50bd8648 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -1196,23 +1196,49 @@ void GDScriptByteCodeGenerator::write_call_builtin_type_static(const Address &p_ } void GDScriptByteCodeGenerator::write_call_native_static(const Address &p_target, const StringName &p_class, const StringName &p_method, const Vector<Address> &p_arguments) { - bool is_validated = false; - MethodBind *method = ClassDB::get_method(p_class, p_method); - if (!is_validated) { - // Perform regular call. - append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_NATIVE_STATIC, p_arguments.size() + 1); - for (int i = 0; i < p_arguments.size(); i++) { - append(p_arguments[i]); + // Perform regular call. + append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_NATIVE_STATIC, p_arguments.size() + 1); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + CallTarget ct = get_call_target(p_target); + append(ct.target); + append(method); + append(p_arguments.size()); + ct.cleanup(); + return; +} + +void GDScriptByteCodeGenerator::write_call_native_static_validated(const GDScriptCodeGenerator::Address &p_target, MethodBind *p_method, const Vector<GDScriptCodeGenerator::Address> &p_arguments) { + Variant::Type return_type = Variant::NIL; + bool has_return = p_method->has_return(); + + if (has_return) { + PropertyInfo return_info = p_method->get_return_info(); + return_type = return_info.type; + } + + CallTarget ct = get_call_target(p_target, return_type); + + if (has_return) { + Variant::Type temp_type = temporaries[ct.target.address].type; + if (temp_type != return_type) { + write_type_adjust(ct.target, return_type); } - CallTarget ct = get_call_target(p_target); - append(ct.target); - append(method); - append(p_arguments.size()); - ct.cleanup(); - return; } + + GDScriptFunction::Opcode code = p_method->has_return() ? GDScriptFunction::OPCODE_CALL_NATIVE_STATIC_VALIDATED_RETURN : GDScriptFunction::OPCODE_CALL_NATIVE_STATIC_VALIDATED_NO_RETURN; + append_opcode_and_argcount(code, 1 + p_arguments.size()); + + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(ct.target); + append(p_arguments.size()); + append(p_method); + ct.cleanup(); } void GDScriptByteCodeGenerator::write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) { diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 5a736b2554..34f56a2f5c 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -518,6 +518,7 @@ public: virtual void write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) override; virtual void write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) override; virtual void write_call_native_static(const Address &p_target, const StringName &p_class, const StringName &p_method, const Vector<Address> &p_arguments) override; + virtual void write_call_native_static_validated(const Address &p_target, MethodBind *p_method, const Vector<Address> &p_arguments) override; virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override; virtual void write_call_method_bind_validated(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override; virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index 4c33ed499a..c1c0b61395 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -131,6 +131,7 @@ public: virtual void write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) = 0; virtual void write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) = 0; virtual void write_call_native_static(const Address &p_target, const StringName &p_class, const StringName &p_method, const Vector<Address> &p_arguments) = 0; + virtual void write_call_native_static_validated(const Address &p_target, MethodBind *p_method, const Vector<Address> &p_arguments) = 0; virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0; virtual void write_call_method_bind_validated(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0; virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 734e37bc09..a8a7f3d9f7 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -673,7 +673,15 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } else if (!call->is_super && subscript->base->type == GDScriptParser::Node::IDENTIFIER && call->function_name != SNAME("new") && ClassDB::class_exists(static_cast<GDScriptParser::IdentifierNode *>(subscript->base)->name) && !Engine::get_singleton()->has_singleton(static_cast<GDScriptParser::IdentifierNode *>(subscript->base)->name)) { // It's a static native method call. - gen->write_call_native_static(result, static_cast<GDScriptParser::IdentifierNode *>(subscript->base)->name, subscript->attribute->name, arguments); + StringName class_name = static_cast<GDScriptParser::IdentifierNode *>(subscript->base)->name; + MethodBind *method = ClassDB::get_method(class_name, subscript->attribute->name); + if (_can_use_validate_call(method, arguments)) { + // Exact arguments, use validated call. + gen->write_call_native_static_validated(result, method, arguments); + } else { + // Not exact arguments, use regular static call + gen->write_call_native_static(result, class_name, subscript->attribute->name, arguments); + } } else { GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, subscript->base); if (r_error) { diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index c7873dcd52..8dd04c76dd 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -678,6 +678,50 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 4 + argc; } break; + case OPCODE_CALL_NATIVE_STATIC_VALIDATED_RETURN: { + int instr_var_args = _code_ptr[++ip]; + text += "call native static method validated (return) "; + MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]]; + int argc = _code_ptr[ip + 1 + instr_var_args]; + text += DADDR(1 + argc) + " = "; + text += method->get_instance_class(); + text += "."; + text += method->get_name(); + text += "("; + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(1 + i); + } + text += ")"; + incr = 4 + argc; + } break; + + case OPCODE_CALL_NATIVE_STATIC_VALIDATED_NO_RETURN: { + int instr_var_args = _code_ptr[++ip]; + + text += "call native static method validated (no return) "; + + MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]]; + + int argc = _code_ptr[ip + 1 + instr_var_args]; + + text += method->get_instance_class(); + text += "."; + text += method->get_name(); + text += "("; + + for (int i = 0; i < argc; i++) { + if (i > 0) { + text += ", "; + } + text += DADDR(1 + i); + } + text += ")"; + + incr = 4 + argc; + } break; + case OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN: { int instr_var_args = _code_ptr[++ip]; text += "call method-bind validated (return) "; diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 184d256bcd..430b96115b 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -264,6 +264,8 @@ public: OPCODE_CALL_METHOD_BIND_RET, OPCODE_CALL_BUILTIN_STATIC, OPCODE_CALL_NATIVE_STATIC, + OPCODE_CALL_NATIVE_STATIC_VALIDATED_RETURN, + OPCODE_CALL_NATIVE_STATIC_VALIDATED_NO_RETURN, OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN, OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN, OPCODE_AWAIT, diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 842975698b..4e76965889 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -211,156 +211,158 @@ void (*type_init_function_table[])(Variant *) = { }; #if defined(__GNUC__) -#define OPCODES_TABLE \ - static const void *switch_table_ops[] = { \ - &&OPCODE_OPERATOR, \ - &&OPCODE_OPERATOR_VALIDATED, \ - &&OPCODE_TYPE_TEST_BUILTIN, \ - &&OPCODE_TYPE_TEST_ARRAY, \ - &&OPCODE_TYPE_TEST_NATIVE, \ - &&OPCODE_TYPE_TEST_SCRIPT, \ - &&OPCODE_SET_KEYED, \ - &&OPCODE_SET_KEYED_VALIDATED, \ - &&OPCODE_SET_INDEXED_VALIDATED, \ - &&OPCODE_GET_KEYED, \ - &&OPCODE_GET_KEYED_VALIDATED, \ - &&OPCODE_GET_INDEXED_VALIDATED, \ - &&OPCODE_SET_NAMED, \ - &&OPCODE_SET_NAMED_VALIDATED, \ - &&OPCODE_GET_NAMED, \ - &&OPCODE_GET_NAMED_VALIDATED, \ - &&OPCODE_SET_MEMBER, \ - &&OPCODE_GET_MEMBER, \ - &&OPCODE_SET_STATIC_VARIABLE, \ - &&OPCODE_GET_STATIC_VARIABLE, \ - &&OPCODE_ASSIGN, \ - &&OPCODE_ASSIGN_NULL, \ - &&OPCODE_ASSIGN_TRUE, \ - &&OPCODE_ASSIGN_FALSE, \ - &&OPCODE_ASSIGN_TYPED_BUILTIN, \ - &&OPCODE_ASSIGN_TYPED_ARRAY, \ - &&OPCODE_ASSIGN_TYPED_NATIVE, \ - &&OPCODE_ASSIGN_TYPED_SCRIPT, \ - &&OPCODE_CAST_TO_BUILTIN, \ - &&OPCODE_CAST_TO_NATIVE, \ - &&OPCODE_CAST_TO_SCRIPT, \ - &&OPCODE_CONSTRUCT, \ - &&OPCODE_CONSTRUCT_VALIDATED, \ - &&OPCODE_CONSTRUCT_ARRAY, \ - &&OPCODE_CONSTRUCT_TYPED_ARRAY, \ - &&OPCODE_CONSTRUCT_DICTIONARY, \ - &&OPCODE_CALL, \ - &&OPCODE_CALL_RETURN, \ - &&OPCODE_CALL_ASYNC, \ - &&OPCODE_CALL_UTILITY, \ - &&OPCODE_CALL_UTILITY_VALIDATED, \ - &&OPCODE_CALL_GDSCRIPT_UTILITY, \ - &&OPCODE_CALL_BUILTIN_TYPE_VALIDATED, \ - &&OPCODE_CALL_SELF_BASE, \ - &&OPCODE_CALL_METHOD_BIND, \ - &&OPCODE_CALL_METHOD_BIND_RET, \ - &&OPCODE_CALL_BUILTIN_STATIC, \ - &&OPCODE_CALL_NATIVE_STATIC, \ - &&OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN, \ - &&OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN, \ - &&OPCODE_AWAIT, \ - &&OPCODE_AWAIT_RESUME, \ - &&OPCODE_CREATE_LAMBDA, \ - &&OPCODE_CREATE_SELF_LAMBDA, \ - &&OPCODE_JUMP, \ - &&OPCODE_JUMP_IF, \ - &&OPCODE_JUMP_IF_NOT, \ - &&OPCODE_JUMP_TO_DEF_ARGUMENT, \ - &&OPCODE_JUMP_IF_SHARED, \ - &&OPCODE_RETURN, \ - &&OPCODE_RETURN_TYPED_BUILTIN, \ - &&OPCODE_RETURN_TYPED_ARRAY, \ - &&OPCODE_RETURN_TYPED_NATIVE, \ - &&OPCODE_RETURN_TYPED_SCRIPT, \ - &&OPCODE_ITERATE_BEGIN, \ - &&OPCODE_ITERATE_BEGIN_INT, \ - &&OPCODE_ITERATE_BEGIN_FLOAT, \ - &&OPCODE_ITERATE_BEGIN_VECTOR2, \ - &&OPCODE_ITERATE_BEGIN_VECTOR2I, \ - &&OPCODE_ITERATE_BEGIN_VECTOR3, \ - &&OPCODE_ITERATE_BEGIN_VECTOR3I, \ - &&OPCODE_ITERATE_BEGIN_STRING, \ - &&OPCODE_ITERATE_BEGIN_DICTIONARY, \ - &&OPCODE_ITERATE_BEGIN_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_BYTE_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_INT32_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_INT64_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_FLOAT32_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_FLOAT64_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_STRING_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_VECTOR2_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_VECTOR3_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_COLOR_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_OBJECT, \ - &&OPCODE_ITERATE, \ - &&OPCODE_ITERATE_INT, \ - &&OPCODE_ITERATE_FLOAT, \ - &&OPCODE_ITERATE_VECTOR2, \ - &&OPCODE_ITERATE_VECTOR2I, \ - &&OPCODE_ITERATE_VECTOR3, \ - &&OPCODE_ITERATE_VECTOR3I, \ - &&OPCODE_ITERATE_STRING, \ - &&OPCODE_ITERATE_DICTIONARY, \ - &&OPCODE_ITERATE_ARRAY, \ - &&OPCODE_ITERATE_PACKED_BYTE_ARRAY, \ - &&OPCODE_ITERATE_PACKED_INT32_ARRAY, \ - &&OPCODE_ITERATE_PACKED_INT64_ARRAY, \ - &&OPCODE_ITERATE_PACKED_FLOAT32_ARRAY, \ - &&OPCODE_ITERATE_PACKED_FLOAT64_ARRAY, \ - &&OPCODE_ITERATE_PACKED_STRING_ARRAY, \ - &&OPCODE_ITERATE_PACKED_VECTOR2_ARRAY, \ - &&OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, \ - &&OPCODE_ITERATE_PACKED_COLOR_ARRAY, \ - &&OPCODE_ITERATE_OBJECT, \ - &&OPCODE_STORE_GLOBAL, \ - &&OPCODE_STORE_NAMED_GLOBAL, \ - &&OPCODE_TYPE_ADJUST_BOOL, \ - &&OPCODE_TYPE_ADJUST_INT, \ - &&OPCODE_TYPE_ADJUST_FLOAT, \ - &&OPCODE_TYPE_ADJUST_STRING, \ - &&OPCODE_TYPE_ADJUST_VECTOR2, \ - &&OPCODE_TYPE_ADJUST_VECTOR2I, \ - &&OPCODE_TYPE_ADJUST_RECT2, \ - &&OPCODE_TYPE_ADJUST_RECT2I, \ - &&OPCODE_TYPE_ADJUST_VECTOR3, \ - &&OPCODE_TYPE_ADJUST_VECTOR3I, \ - &&OPCODE_TYPE_ADJUST_TRANSFORM2D, \ - &&OPCODE_TYPE_ADJUST_VECTOR4, \ - &&OPCODE_TYPE_ADJUST_VECTOR4I, \ - &&OPCODE_TYPE_ADJUST_PLANE, \ - &&OPCODE_TYPE_ADJUST_QUATERNION, \ - &&OPCODE_TYPE_ADJUST_AABB, \ - &&OPCODE_TYPE_ADJUST_BASIS, \ - &&OPCODE_TYPE_ADJUST_TRANSFORM3D, \ - &&OPCODE_TYPE_ADJUST_PROJECTION, \ - &&OPCODE_TYPE_ADJUST_COLOR, \ - &&OPCODE_TYPE_ADJUST_STRING_NAME, \ - &&OPCODE_TYPE_ADJUST_NODE_PATH, \ - &&OPCODE_TYPE_ADJUST_RID, \ - &&OPCODE_TYPE_ADJUST_OBJECT, \ - &&OPCODE_TYPE_ADJUST_CALLABLE, \ - &&OPCODE_TYPE_ADJUST_SIGNAL, \ - &&OPCODE_TYPE_ADJUST_DICTIONARY, \ - &&OPCODE_TYPE_ADJUST_ARRAY, \ - &&OPCODE_TYPE_ADJUST_PACKED_BYTE_ARRAY, \ - &&OPCODE_TYPE_ADJUST_PACKED_INT32_ARRAY, \ - &&OPCODE_TYPE_ADJUST_PACKED_INT64_ARRAY, \ - &&OPCODE_TYPE_ADJUST_PACKED_FLOAT32_ARRAY, \ - &&OPCODE_TYPE_ADJUST_PACKED_FLOAT64_ARRAY, \ - &&OPCODE_TYPE_ADJUST_PACKED_STRING_ARRAY, \ - &&OPCODE_TYPE_ADJUST_PACKED_VECTOR2_ARRAY, \ - &&OPCODE_TYPE_ADJUST_PACKED_VECTOR3_ARRAY, \ - &&OPCODE_TYPE_ADJUST_PACKED_COLOR_ARRAY, \ - &&OPCODE_ASSERT, \ - &&OPCODE_BREAKPOINT, \ - &&OPCODE_LINE, \ - &&OPCODE_END \ - }; \ +#define OPCODES_TABLE \ + static const void *switch_table_ops[] = { \ + &&OPCODE_OPERATOR, \ + &&OPCODE_OPERATOR_VALIDATED, \ + &&OPCODE_TYPE_TEST_BUILTIN, \ + &&OPCODE_TYPE_TEST_ARRAY, \ + &&OPCODE_TYPE_TEST_NATIVE, \ + &&OPCODE_TYPE_TEST_SCRIPT, \ + &&OPCODE_SET_KEYED, \ + &&OPCODE_SET_KEYED_VALIDATED, \ + &&OPCODE_SET_INDEXED_VALIDATED, \ + &&OPCODE_GET_KEYED, \ + &&OPCODE_GET_KEYED_VALIDATED, \ + &&OPCODE_GET_INDEXED_VALIDATED, \ + &&OPCODE_SET_NAMED, \ + &&OPCODE_SET_NAMED_VALIDATED, \ + &&OPCODE_GET_NAMED, \ + &&OPCODE_GET_NAMED_VALIDATED, \ + &&OPCODE_SET_MEMBER, \ + &&OPCODE_GET_MEMBER, \ + &&OPCODE_SET_STATIC_VARIABLE, \ + &&OPCODE_GET_STATIC_VARIABLE, \ + &&OPCODE_ASSIGN, \ + &&OPCODE_ASSIGN_NULL, \ + &&OPCODE_ASSIGN_TRUE, \ + &&OPCODE_ASSIGN_FALSE, \ + &&OPCODE_ASSIGN_TYPED_BUILTIN, \ + &&OPCODE_ASSIGN_TYPED_ARRAY, \ + &&OPCODE_ASSIGN_TYPED_NATIVE, \ + &&OPCODE_ASSIGN_TYPED_SCRIPT, \ + &&OPCODE_CAST_TO_BUILTIN, \ + &&OPCODE_CAST_TO_NATIVE, \ + &&OPCODE_CAST_TO_SCRIPT, \ + &&OPCODE_CONSTRUCT, \ + &&OPCODE_CONSTRUCT_VALIDATED, \ + &&OPCODE_CONSTRUCT_ARRAY, \ + &&OPCODE_CONSTRUCT_TYPED_ARRAY, \ + &&OPCODE_CONSTRUCT_DICTIONARY, \ + &&OPCODE_CALL, \ + &&OPCODE_CALL_RETURN, \ + &&OPCODE_CALL_ASYNC, \ + &&OPCODE_CALL_UTILITY, \ + &&OPCODE_CALL_UTILITY_VALIDATED, \ + &&OPCODE_CALL_GDSCRIPT_UTILITY, \ + &&OPCODE_CALL_BUILTIN_TYPE_VALIDATED, \ + &&OPCODE_CALL_SELF_BASE, \ + &&OPCODE_CALL_METHOD_BIND, \ + &&OPCODE_CALL_METHOD_BIND_RET, \ + &&OPCODE_CALL_BUILTIN_STATIC, \ + &&OPCODE_CALL_NATIVE_STATIC, \ + &&OPCODE_CALL_NATIVE_STATIC_VALIDATED_RETURN, \ + &&OPCODE_CALL_NATIVE_STATIC_VALIDATED_NO_RETURN, \ + &&OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN, \ + &&OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN, \ + &&OPCODE_AWAIT, \ + &&OPCODE_AWAIT_RESUME, \ + &&OPCODE_CREATE_LAMBDA, \ + &&OPCODE_CREATE_SELF_LAMBDA, \ + &&OPCODE_JUMP, \ + &&OPCODE_JUMP_IF, \ + &&OPCODE_JUMP_IF_NOT, \ + &&OPCODE_JUMP_TO_DEF_ARGUMENT, \ + &&OPCODE_JUMP_IF_SHARED, \ + &&OPCODE_RETURN, \ + &&OPCODE_RETURN_TYPED_BUILTIN, \ + &&OPCODE_RETURN_TYPED_ARRAY, \ + &&OPCODE_RETURN_TYPED_NATIVE, \ + &&OPCODE_RETURN_TYPED_SCRIPT, \ + &&OPCODE_ITERATE_BEGIN, \ + &&OPCODE_ITERATE_BEGIN_INT, \ + &&OPCODE_ITERATE_BEGIN_FLOAT, \ + &&OPCODE_ITERATE_BEGIN_VECTOR2, \ + &&OPCODE_ITERATE_BEGIN_VECTOR2I, \ + &&OPCODE_ITERATE_BEGIN_VECTOR3, \ + &&OPCODE_ITERATE_BEGIN_VECTOR3I, \ + &&OPCODE_ITERATE_BEGIN_STRING, \ + &&OPCODE_ITERATE_BEGIN_DICTIONARY, \ + &&OPCODE_ITERATE_BEGIN_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_BYTE_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_INT32_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_INT64_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_FLOAT32_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_FLOAT64_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_STRING_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_VECTOR2_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_VECTOR3_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_COLOR_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_OBJECT, \ + &&OPCODE_ITERATE, \ + &&OPCODE_ITERATE_INT, \ + &&OPCODE_ITERATE_FLOAT, \ + &&OPCODE_ITERATE_VECTOR2, \ + &&OPCODE_ITERATE_VECTOR2I, \ + &&OPCODE_ITERATE_VECTOR3, \ + &&OPCODE_ITERATE_VECTOR3I, \ + &&OPCODE_ITERATE_STRING, \ + &&OPCODE_ITERATE_DICTIONARY, \ + &&OPCODE_ITERATE_ARRAY, \ + &&OPCODE_ITERATE_PACKED_BYTE_ARRAY, \ + &&OPCODE_ITERATE_PACKED_INT32_ARRAY, \ + &&OPCODE_ITERATE_PACKED_INT64_ARRAY, \ + &&OPCODE_ITERATE_PACKED_FLOAT32_ARRAY, \ + &&OPCODE_ITERATE_PACKED_FLOAT64_ARRAY, \ + &&OPCODE_ITERATE_PACKED_STRING_ARRAY, \ + &&OPCODE_ITERATE_PACKED_VECTOR2_ARRAY, \ + &&OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, \ + &&OPCODE_ITERATE_PACKED_COLOR_ARRAY, \ + &&OPCODE_ITERATE_OBJECT, \ + &&OPCODE_STORE_GLOBAL, \ + &&OPCODE_STORE_NAMED_GLOBAL, \ + &&OPCODE_TYPE_ADJUST_BOOL, \ + &&OPCODE_TYPE_ADJUST_INT, \ + &&OPCODE_TYPE_ADJUST_FLOAT, \ + &&OPCODE_TYPE_ADJUST_STRING, \ + &&OPCODE_TYPE_ADJUST_VECTOR2, \ + &&OPCODE_TYPE_ADJUST_VECTOR2I, \ + &&OPCODE_TYPE_ADJUST_RECT2, \ + &&OPCODE_TYPE_ADJUST_RECT2I, \ + &&OPCODE_TYPE_ADJUST_VECTOR3, \ + &&OPCODE_TYPE_ADJUST_VECTOR3I, \ + &&OPCODE_TYPE_ADJUST_TRANSFORM2D, \ + &&OPCODE_TYPE_ADJUST_VECTOR4, \ + &&OPCODE_TYPE_ADJUST_VECTOR4I, \ + &&OPCODE_TYPE_ADJUST_PLANE, \ + &&OPCODE_TYPE_ADJUST_QUATERNION, \ + &&OPCODE_TYPE_ADJUST_AABB, \ + &&OPCODE_TYPE_ADJUST_BASIS, \ + &&OPCODE_TYPE_ADJUST_TRANSFORM3D, \ + &&OPCODE_TYPE_ADJUST_PROJECTION, \ + &&OPCODE_TYPE_ADJUST_COLOR, \ + &&OPCODE_TYPE_ADJUST_STRING_NAME, \ + &&OPCODE_TYPE_ADJUST_NODE_PATH, \ + &&OPCODE_TYPE_ADJUST_RID, \ + &&OPCODE_TYPE_ADJUST_OBJECT, \ + &&OPCODE_TYPE_ADJUST_CALLABLE, \ + &&OPCODE_TYPE_ADJUST_SIGNAL, \ + &&OPCODE_TYPE_ADJUST_DICTIONARY, \ + &&OPCODE_TYPE_ADJUST_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_BYTE_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_INT32_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_INT64_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_FLOAT32_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_FLOAT64_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_STRING_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_VECTOR2_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_VECTOR3_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_COLOR_ARRAY, \ + &&OPCODE_ASSERT, \ + &&OPCODE_BREAKPOINT, \ + &&OPCODE_LINE, \ + &&OPCODE_END \ + }; \ static_assert((sizeof(switch_table_ops) / sizeof(switch_table_ops[0]) == (OPCODE_END + 1)), "Opcodes in jump table aren't the same as opcodes in enum."); #define OPCODE(m_op) \ @@ -882,23 +884,27 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #endif #ifdef DEBUG_ENABLED if (!valid) { - Object *obj = dst->get_validated_object(); - String v = index->operator String(); - bool read_only_property = false; - if (obj) { - read_only_property = ClassDB::has_property(obj->get_class_name(), v) && (ClassDB::get_property_setter(obj->get_class_name(), v) == StringName()); - } - if (read_only_property) { - err_text = vformat(R"(Cannot set value into property "%s" (on base "%s") because it is read-only.)", v, _get_var_type(dst)); + if (dst->is_read_only()) { + err_text = "Invalid assignment on read-only value (on base: '" + _get_var_type(dst) + "')."; } else { - if (!v.is_empty()) { - v = "'" + v + "'"; - } else { - v = "of type '" + _get_var_type(index) + "'"; + Object *obj = dst->get_validated_object(); + String v = index->operator String(); + bool read_only_property = false; + if (obj) { + read_only_property = ClassDB::has_property(obj->get_class_name(), v) && (ClassDB::get_property_setter(obj->get_class_name(), v) == StringName()); } - err_text = "Invalid assignment of property or key " + v + " with value of type '" + _get_var_type(value) + "' on a base object of type '" + _get_var_type(dst) + "'."; - if (err_code == Variant::VariantSetError::SET_INDEXED_ERR) { - err_text = "Invalid assignment of index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'."; + if (read_only_property) { + err_text = vformat(R"(Cannot set value into property "%s" (on base "%s") because it is read-only.)", v, _get_var_type(dst)); + } else { + if (!v.is_empty()) { + v = "'" + v + "'"; + } else { + v = "of type '" + _get_var_type(index) + "'"; + } + err_text = "Invalid assignment of property or key " + v + " with value of type '" + _get_var_type(value) + "' on a base object of type '" + _get_var_type(dst) + "'."; + if (err_code == Variant::VariantSetError::SET_INDEXED_ERR) { + err_text = "Invalid assignment of index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'."; + } } } OPCODE_BREAK; @@ -924,13 +930,17 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED if (!valid) { - String v = index->operator String(); - if (!v.is_empty()) { - v = "'" + v + "'"; + if (dst->is_read_only()) { + err_text = "Invalid assignment on read-only value (on base: '" + _get_var_type(dst) + "')."; } else { - v = "of type '" + _get_var_type(index) + "'"; + String v = index->operator String(); + if (!v.is_empty()) { + v = "'" + v + "'"; + } else { + v = "of type '" + _get_var_type(index) + "'"; + } + err_text = "Invalid assignment of property or key " + v + " with value of type '" + _get_var_type(value) + "' on a base object of type '" + _get_var_type(dst) + "'."; } - err_text = "Invalid assignment of property or key " + v + " with value of type '" + _get_var_type(value) + "' on a base object of type '" + _get_var_type(dst) + "'."; OPCODE_BREAK; } #endif @@ -956,13 +966,17 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED if (oob) { - String v = index->operator String(); - if (!v.is_empty()) { - v = "'" + v + "'"; + if (dst->is_read_only()) { + err_text = "Invalid assignment on read-only value (on base: '" + _get_var_type(dst) + "')."; } else { - v = "of type '" + _get_var_type(index) + "'"; + String v = index->operator String(); + if (!v.is_empty()) { + v = "'" + v + "'"; + } else { + v = "of type '" + _get_var_type(index) + "'"; + } + err_text = "Out of bounds set index " + v + " (on base: '" + _get_var_type(dst) + "')"; } - err_text = "Out of bounds set index " + v + " (on base: '" + _get_var_type(dst) + "')"; OPCODE_BREAK; } #endif @@ -1090,15 +1104,19 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED if (!valid) { - Object *obj = dst->get_validated_object(); - bool read_only_property = false; - if (obj) { - read_only_property = ClassDB::has_property(obj->get_class_name(), *index) && (ClassDB::get_property_setter(obj->get_class_name(), *index) == StringName()); - } - if (read_only_property) { - err_text = vformat(R"(Cannot set value into property "%s" (on base "%s") because it is read-only.)", String(*index), _get_var_type(dst)); + if (dst->is_read_only()) { + err_text = "Invalid assignment on read-only value (on base: '" + _get_var_type(dst) + "')."; } else { - err_text = "Invalid assignment of property or key '" + String(*index) + "' with value of type '" + _get_var_type(value) + "' on a base object of type '" + _get_var_type(dst) + "'."; + Object *obj = dst->get_validated_object(); + bool read_only_property = false; + if (obj) { + read_only_property = ClassDB::has_property(obj->get_class_name(), *index) && (ClassDB::get_property_setter(obj->get_class_name(), *index) == StringName()); + } + if (read_only_property) { + err_text = vformat(R"(Cannot set value into property "%s" (on base "%s") because it is read-only.)", String(*index), _get_var_type(dst)); + } else { + err_text = "Invalid assignment of property or key '" + String(*index) + "' with value of type '" + _get_var_type(value) + "' on a base object of type '" + _get_var_type(dst) + "'."; + } } OPCODE_BREAK; } @@ -1956,6 +1974,78 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_CALL_NATIVE_STATIC_VALIDATED_RETURN) { + LOAD_INSTRUCTION_ARGS + CHECK_SPACE(3 + instr_arg_count); + + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + GD_ERR_BREAK(argc < 0); + + GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _methods_count); + MethodBind *method = _methods_ptr[_code_ptr[ip + 2]]; + + Variant **argptrs = instruction_args; + +#ifdef DEBUG_ENABLED + uint64_t call_time = 0; + if (GDScriptLanguage::get_singleton()->profiling && GDScriptLanguage::get_singleton()->profile_native_calls) { + call_time = OS::get_singleton()->get_ticks_usec(); + } +#endif + + GET_INSTRUCTION_ARG(ret, argc); + method->validated_call(nullptr, (const Variant **)argptrs, ret); + +#ifdef DEBUG_ENABLED + if (GDScriptLanguage::get_singleton()->profiling && GDScriptLanguage::get_singleton()->profile_native_calls) { + uint64_t t_taken = OS::get_singleton()->get_ticks_usec() - call_time; + _profile_native_call(t_taken, method->get_name(), method->get_instance_class()); + function_call_time += t_taken; + } +#endif + + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CALL_NATIVE_STATIC_VALIDATED_NO_RETURN) { + LOAD_INSTRUCTION_ARGS + CHECK_SPACE(3 + instr_arg_count); + + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + GD_ERR_BREAK(argc < 0); + + GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _methods_count); + MethodBind *method = _methods_ptr[_code_ptr[ip + 2]]; + + Variant **argptrs = instruction_args; +#ifdef DEBUG_ENABLED + uint64_t call_time = 0; + if (GDScriptLanguage::get_singleton()->profiling && GDScriptLanguage::get_singleton()->profile_native_calls) { + call_time = OS::get_singleton()->get_ticks_usec(); + } +#endif + + GET_INSTRUCTION_ARG(ret, argc); + VariantInternal::initialize(ret, Variant::NIL); + method->validated_call(nullptr, (const Variant **)argptrs, nullptr); + +#ifdef DEBUG_ENABLED + if (GDScriptLanguage::get_singleton()->profiling && GDScriptLanguage::get_singleton()->profile_native_calls) { + uint64_t t_taken = OS::get_singleton()->get_ticks_usec() - call_time; + _profile_native_call(t_taken, method->get_name(), method->get_instance_class()); + function_call_time += t_taken; + } +#endif + + ip += 3; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN) { LOAD_INSTRUCTION_ARGS CHECK_SPACE(3 + instr_arg_count); diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out b/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out index c524a1ae6b..350d5d1d45 100644 --- a/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out +++ b/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out @@ -3,4 +3,4 @@ GDTEST_RUNTIME_ERROR >> on function: test() >> runtime/errors/constant_array_is_deep.gd >> 6 ->> Invalid assignment of property or key '0' with value of type 'int' on a base object of type 'Dictionary'. +>> Invalid assignment on read-only value (on base: 'Dictionary'). diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out index cf51b0262d..5f1f372b0a 100644 --- a/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out +++ b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out @@ -3,4 +3,4 @@ GDTEST_RUNTIME_ERROR >> on function: test() >> runtime/errors/constant_dictionary_is_deep.gd >> 6 ->> Invalid assignment of index '0' (on base: 'Array') with value of type 'int'. +>> Invalid assignment on read-only value (on base: 'Array'). diff --git a/modules/gdscript/tests/scripts/runtime/errors/read_only_dictionary.gd b/modules/gdscript/tests/scripts/runtime/errors/read_only_dictionary.gd new file mode 100644 index 0000000000..2f31ecc52f --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/read_only_dictionary.gd @@ -0,0 +1,4 @@ +func test(): + var dictionary := { "a": 0 } + dictionary.make_read_only() + dictionary.a = 1 diff --git a/modules/gdscript/tests/scripts/runtime/errors/read_only_dictionary.out b/modules/gdscript/tests/scripts/runtime/errors/read_only_dictionary.out new file mode 100644 index 0000000000..f7d531e119 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/read_only_dictionary.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/read_only_dictionary.gd +>> 4 +>> Invalid assignment on read-only value (on base: 'Dictionary'). diff --git a/modules/gdscript/tests/scripts/runtime/features/call_native_static_method_validated.gd b/modules/gdscript/tests/scripts/runtime/features/call_native_static_method_validated.gd new file mode 100644 index 0000000000..35e4dbd6a0 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/call_native_static_method_validated.gd @@ -0,0 +1,7 @@ +func test(): + # Validated native static call with return value. + print(FileAccess.file_exists("some_file")) + + # Validated native static call without return value. + Node.print_orphan_nodes() + diff --git a/modules/gdscript/tests/scripts/runtime/features/call_native_static_method_validated.out b/modules/gdscript/tests/scripts/runtime/features/call_native_static_method_validated.out new file mode 100644 index 0000000000..44302c8137 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/call_native_static_method_validated.out @@ -0,0 +1,2 @@ +GDTEST_OK +false diff --git a/modules/gdscript/tests/scripts/runtime/features/onready_base_before_subclass.gd b/modules/gdscript/tests/scripts/runtime/features/onready_base_before_subclass.gd new file mode 100644 index 0000000000..99156adb28 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/onready_base_before_subclass.gd @@ -0,0 +1,18 @@ +#GH-63329 +class A extends Node: + @onready var a := get_value("a") + + func get_value(var_name: String) -> String: + print(var_name) + return var_name + +class B extends A: + @onready var b := get_value("b") + + func _ready(): + pass + +func test(): + var node := B.new() + node._ready() + node.free() diff --git a/modules/gdscript/tests/scripts/runtime/features/onready_base_before_subclass.out b/modules/gdscript/tests/scripts/runtime/features/onready_base_before_subclass.out new file mode 100644 index 0000000000..b417ce67ca --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/onready_base_before_subclass.out @@ -0,0 +1,3 @@ +GDTEST_OK +a +b diff --git a/modules/mobile_vr/doc_classes/MobileVRInterface.xml b/modules/mobile_vr/doc_classes/MobileVRInterface.xml index 1be8cc828d..8338054142 100644 --- a/modules/mobile_vr/doc_classes/MobileVRInterface.xml +++ b/modules/mobile_vr/doc_classes/MobileVRInterface.xml @@ -34,6 +34,9 @@ <member name="k2" type="float" setter="set_k2" getter="get_k2" default="0.215"> The k2 lens factor, see k1. </member> + <member name="offset_rect" type="Rect2" setter="set_offset_rect" getter="get_offset_rect" default="Rect2(0, 0, 1, 1)"> + Set the offset rect relative to the area being rendered. A length of 1 represents the whole rendering area on that axis. + </member> <member name="oversample" type="float" setter="set_oversample" getter="get_oversample" default="1.5"> The oversample setting. Because of the lens distortion we have to render our buffers at a higher resolution then the screen can natively handle. A value between 1.5 and 2.0 often provides good results but at the cost of performance. </member> diff --git a/modules/mobile_vr/mobile_vr_interface.cpp b/modules/mobile_vr/mobile_vr_interface.cpp index bba56f6468..d23edcd1d1 100644 --- a/modules/mobile_vr/mobile_vr_interface.cpp +++ b/modules/mobile_vr/mobile_vr_interface.cpp @@ -230,6 +230,9 @@ void MobileVRInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("set_display_to_lens", "display_to_lens"), &MobileVRInterface::set_display_to_lens); ClassDB::bind_method(D_METHOD("get_display_to_lens"), &MobileVRInterface::get_display_to_lens); + ClassDB::bind_method(D_METHOD("set_offset_rect", "offset_rect"), &MobileVRInterface::set_offset_rect); + ClassDB::bind_method(D_METHOD("get_offset_rect"), &MobileVRInterface::get_offset_rect); + ClassDB::bind_method(D_METHOD("set_oversample", "oversample"), &MobileVRInterface::set_oversample); ClassDB::bind_method(D_METHOD("get_oversample"), &MobileVRInterface::get_oversample); @@ -243,6 +246,7 @@ void MobileVRInterface::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "iod", PROPERTY_HINT_RANGE, "4.0,10.0,0.1"), "set_iod", "get_iod"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "display_width", PROPERTY_HINT_RANGE, "5.0,25.0,0.1"), "set_display_width", "get_display_width"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "display_to_lens", PROPERTY_HINT_RANGE, "5.0,25.0,0.1"), "set_display_to_lens", "get_display_to_lens"); + ADD_PROPERTY(PropertyInfo(Variant::RECT2, "offset_rect"), "set_offset_rect", "get_offset_rect"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "oversample", PROPERTY_HINT_RANGE, "1.0,2.0,0.1"), "set_oversample", "get_oversample"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "k1", PROPERTY_HINT_RANGE, "0.1,10.0,0.0001"), "set_k1", "get_k1"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "k2", PROPERTY_HINT_RANGE, "0.1,10.0,0.0001"), "set_k2", "get_k2"); @@ -256,6 +260,14 @@ double MobileVRInterface::get_eye_height() const { return eye_height; } +void MobileVRInterface::set_offset_rect(const Rect2 &p_offset_rect) { + offset_rect = p_offset_rect; +} + +Rect2 MobileVRInterface::get_offset_rect() const { + return offset_rect; +} + void MobileVRInterface::set_iod(const double p_iod) { intraocular_dist = p_iod; }; @@ -483,6 +495,8 @@ Vector<BlitToScreen> MobileVRInterface::post_draw_viewport(RID p_render_target, // Because we are rendering to our device we must use our main viewport! ERR_FAIL_COND_V(p_screen_rect == Rect2(), blit_to_screen); + Rect2 modified_screen_rect = Rect2(p_screen_rect.position + offset_rect.position * p_screen_rect.size, p_screen_rect.size * offset_rect.size); + // and add our blits BlitToScreen blit; blit.render_target = p_render_target; @@ -494,16 +508,16 @@ Vector<BlitToScreen> MobileVRInterface::post_draw_viewport(RID p_render_target, blit.lens_distortion.aspect_ratio = aspect; // left eye - blit.dst_rect = p_screen_rect; + blit.dst_rect = modified_screen_rect; blit.dst_rect.size.width *= 0.5; blit.multi_view.layer = 0; blit.lens_distortion.eye_center.x = ((-intraocular_dist / 2.0) + (display_width / 4.0)) / (display_width / 2.0); blit_to_screen.push_back(blit); // right eye - blit.dst_rect = p_screen_rect; + blit.dst_rect = modified_screen_rect; blit.dst_rect.size.width *= 0.5; - blit.dst_rect.position.x = blit.dst_rect.size.width; + blit.dst_rect.position.x += blit.dst_rect.size.width; blit.multi_view.layer = 1; blit.lens_distortion.eye_center.x = ((intraocular_dist / 2.0) - (display_width / 4.0)) / (display_width / 2.0); blit_to_screen.push_back(blit); diff --git a/modules/mobile_vr/mobile_vr_interface.h b/modules/mobile_vr/mobile_vr_interface.h index f680d8aa11..e1d43fff74 100644 --- a/modules/mobile_vr/mobile_vr_interface.h +++ b/modules/mobile_vr/mobile_vr_interface.h @@ -62,6 +62,8 @@ private: double display_to_lens = 4.0; double oversample = 1.5; + Rect2 offset_rect = Rect2(0, 0, 1, 1); // Full screen rect. + double k1 = 0.215; double k2 = 0.215; double aspect = 1.0; @@ -121,6 +123,9 @@ public: void set_display_width(const double p_display_width); double get_display_width() const; + void set_offset_rect(const Rect2 &p_offset_rect); + Rect2 get_offset_rect() const; + void set_display_to_lens(const double p_display_to_lens); double get_display_to_lens() const; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs index 3cc5841097..724fb164e0 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs @@ -57,4 +57,13 @@ public class ScriptPropertiesGeneratorTests "ScriptBoilerplate_ScriptProperties.generated.cs", "OuterClass.NestedClass_ScriptProperties.generated.cs" ); } + + [Fact] + public async void AbstractGenericNode() + { + await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify( + "AbstractGenericNode.cs", + "AbstractGenericNode(Of T)_ScriptProperties.generated.cs" + ); + } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/AbstractGenericNode(Of T)_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/AbstractGenericNode(Of T)_ScriptProperties.generated.cs new file mode 100644 index 0000000000..a561c5fc0d --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/AbstractGenericNode(Of T)_ScriptProperties.generated.cs @@ -0,0 +1,49 @@ +using Godot; +using Godot.NativeInterop; + +partial class AbstractGenericNode<T> +{ +#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword + /// <summary> + /// Cached StringNames for the properties and fields contained in this class, for fast lookup. + /// </summary> + public new class PropertyName : global::Godot.Node.PropertyName { + /// <summary> + /// Cached name for the 'MyArray' property. + /// </summary> + public new static readonly global::Godot.StringName MyArray = "MyArray"; + } + /// <inheritdoc/> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) + { + if (name == PropertyName.MyArray) { + this.MyArray = global::Godot.NativeInterop.VariantUtils.ConvertToArray<T>(value); + return true; + } + return base.SetGodotClassPropertyValue(name, value); + } + /// <inheritdoc/> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) + { + if (name == PropertyName.MyArray) { + value = global::Godot.NativeInterop.VariantUtils.CreateFromArray(this.MyArray); + return true; + } + return base.GetGodotClassPropertyValue(name, out value); + } + /// <summary> + /// Get the property information for all the properties declared in this class. + /// This method is used by Godot to register the available properties in the editor. + /// Do not call this method. + /// </summary> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList() + { + var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>(); + properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.MyArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + return properties; + } +#pragma warning restore CS0109 +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/AbstractGenericNode.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/AbstractGenericNode.cs new file mode 100644 index 0000000000..cee4f67921 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/AbstractGenericNode.cs @@ -0,0 +1,7 @@ +using Godot; + +public abstract partial class AbstractGenericNode<[MustBeVariant] T> : Node +{ + [Export] // This should be included, but without type hints. + public Godot.Collections.Array<T> MyArray { get; set; } = new(); +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs index 462da31d66..2b5eecab8a 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs @@ -66,6 +66,12 @@ public class MustBeVariantGD0301 Method<Rid[]>(); } + public void MethodCallDynamic() + { + dynamic self = this; + self.Method<object>(); + } + public void Method<[MustBeVariant] T>() { } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs index 95eaca4d3d..e894e7a86c 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs @@ -50,8 +50,18 @@ namespace Godot.SourceGenerators var typeSymbol = sm.GetSymbolInfo(typeSyntax).Symbol as ITypeSymbol; Helper.ThrowIfNull(typeSymbol); - var parentSymbol = sm.GetSymbolInfo(parentSyntax).Symbol; - Helper.ThrowIfNull(parentSymbol); + var parentSymbolInfo = sm.GetSymbolInfo(parentSyntax); + var parentSymbol = parentSymbolInfo.Symbol; + if (parentSymbol == null) + { + if (parentSymbolInfo.CandidateReason == CandidateReason.LateBound) + { + // Invocations on dynamic are late bound so we can't retrieve the symbol. + continue; + } + + Helper.ThrowIfNull(parentSymbol); + } if (!ShouldCheckTypeArgument(context, parentSyntax, parentSymbol, typeSyntax, typeSymbol, i)) { diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs index a0e410e31a..21223654f3 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs @@ -658,7 +658,10 @@ namespace Godot.SourceGenerators var elementType = MarshalUtils.GetArrayElementType(type); if (elementType == null) - return false; // Non-generic Array, so there's no hint to add + return false; // Non-generic Array, so there's no hint to add. + + if (elementType.TypeKind == TypeKind.TypeParameter) + return false; // The generic is not constructed, we can't really hint anything. var elementMarshalType = MarshalUtils.ConvertManagedTypeToMarshalType(elementType, typeCache)!.Value; var elementVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(elementMarshalType)!.Value; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs index feaa1d07da..ab7f8ede44 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs @@ -69,7 +69,7 @@ namespace Godot public readonly Aabb Abs() { Vector3 end = End; - Vector3 topLeft = new Vector3(Mathf.Min(_position.X, end.X), Mathf.Min(_position.Y, end.Y), Mathf.Min(_position.Z, end.Z)); + Vector3 topLeft = end.Min(_position); return new Aabb(topLeft, _size.Abs()); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs index 9d9065911e..19721b6cca 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs @@ -69,7 +69,7 @@ namespace Godot public readonly Rect2 Abs() { Vector2 end = End; - Vector2 topLeft = new Vector2(Mathf.Min(_position.X, end.X), Mathf.Min(_position.Y, end.Y)); + Vector2 topLeft = end.Min(_position); return new Rect2(topLeft, _size.Abs()); } @@ -91,14 +91,12 @@ namespace Godot return new Rect2(); } - newRect._position.X = Mathf.Max(b._position.X, _position.X); - newRect._position.Y = Mathf.Max(b._position.Y, _position.Y); + newRect._position = b._position.Max(_position); Vector2 bEnd = b._position + b._size; Vector2 end = _position + _size; - newRect._size.X = Mathf.Min(bEnd.X, end.X) - newRect._position.X; - newRect._size.Y = Mathf.Min(bEnd.Y, end.Y) - newRect._position.Y; + newRect._size = bEnd.Min(end) - newRect._position; return newRect; } @@ -338,11 +336,9 @@ namespace Godot { Rect2 newRect; - newRect._position.X = Mathf.Min(b._position.X, _position.X); - newRect._position.Y = Mathf.Min(b._position.Y, _position.Y); + newRect._position = b._position.Min(_position); - newRect._size.X = Mathf.Max(b._position.X + b._size.X, _position.X + _size.X); - newRect._size.Y = Mathf.Max(b._position.Y + b._size.Y, _position.Y + _size.Y); + newRect._size = (b._position + b._size).Max(_position + _size); newRect._size -= newRect._position; // Make relative again diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs index 65704b3da7..7ee9ff8552 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs @@ -69,7 +69,7 @@ namespace Godot public readonly Rect2I Abs() { Vector2I end = End; - Vector2I topLeft = new Vector2I(Mathf.Min(_position.X, end.X), Mathf.Min(_position.Y, end.Y)); + Vector2I topLeft = end.Min(_position); return new Rect2I(topLeft, _size.Abs()); } @@ -91,14 +91,12 @@ namespace Godot return new Rect2I(); } - newRect._position.X = Mathf.Max(b._position.X, _position.X); - newRect._position.Y = Mathf.Max(b._position.Y, _position.Y); + newRect._position = b._position.Max(_position); Vector2I bEnd = b._position + b._size; Vector2I end = _position + _size; - newRect._size.X = Mathf.Min(bEnd.X, end.X) - newRect._position.X; - newRect._size.Y = Mathf.Min(bEnd.Y, end.Y) - newRect._position.Y; + newRect._size = bEnd.Min(end) - newRect._position; return newRect; } @@ -295,11 +293,9 @@ namespace Godot { Rect2I newRect; - newRect._position.X = Mathf.Min(b._position.X, _position.X); - newRect._position.Y = Mathf.Min(b._position.Y, _position.Y); + newRect._position = b._position.Min(_position); - newRect._size.X = Mathf.Max(b._position.X + b._size.X, _position.X + _size.X); - newRect._size.Y = Mathf.Max(b._position.Y + b._size.Y, _position.Y + _size.Y); + newRect._size = (b._position + b._size).Max(_position + _size); newRect._size -= newRect._position; // Make relative again diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs index 856fd54352..50bf56d832 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs @@ -192,6 +192,23 @@ namespace Godot } /// <summary> + /// Returns a new vector with all components clamped between the + /// <paramref name="min"/> and <paramref name="max"/> using + /// <see cref="Mathf.Clamp(real_t, real_t, real_t)"/>. + /// </summary> + /// <param name="min">The minimum allowed value.</param> + /// <param name="max">The maximum allowed value.</param> + /// <returns>The vector with all components clamped.</returns> + public readonly Vector2 Clamp(real_t min, real_t max) + { + return new Vector2 + ( + Mathf.Clamp(X, min, max), + Mathf.Clamp(Y, min, max) + ); + } + + /// <summary> /// Returns the cross product of this vector and <paramref name="with"/>. /// </summary> /// <param name="with">The other vector.</param> @@ -413,6 +430,70 @@ namespace Godot } /// <summary> + /// Returns the result of the component-wise maximum between + /// this vector and <paramref name="with"/>. + /// Equivalent to <c>new Vector2(Mathf.Max(X, with.X), Mathf.Max(Y, with.Y))</c>. + /// </summary> + /// <param name="with">The other vector to use.</param> + /// <returns>The resulting maximum vector.</returns> + public readonly Vector2 Max(Vector2 with) + { + return new Vector2 + ( + Mathf.Max(X, with.X), + Mathf.Max(Y, with.Y) + ); + } + + /// <summary> + /// Returns the result of the component-wise maximum between + /// this vector and <paramref name="with"/>. + /// Equivalent to <c>new Vector2(Mathf.Max(X, with), Mathf.Max(Y, with))</c>. + /// </summary> + /// <param name="with">The other value to use.</param> + /// <returns>The resulting maximum vector.</returns> + public readonly Vector2 Max(real_t with) + { + return new Vector2 + ( + Mathf.Max(X, with), + Mathf.Max(Y, with) + ); + } + + /// <summary> + /// Returns the result of the component-wise minimum between + /// this vector and <paramref name="with"/>. + /// Equivalent to <c>new Vector2(Mathf.Min(X, with.X), Mathf.Min(Y, with.Y))</c>. + /// </summary> + /// <param name="with">The other vector to use.</param> + /// <returns>The resulting minimum vector.</returns> + public readonly Vector2 Min(Vector2 with) + { + return new Vector2 + ( + Mathf.Min(X, with.X), + Mathf.Min(Y, with.Y) + ); + } + + /// <summary> + /// Returns the result of the component-wise minimum between + /// this vector and <paramref name="with"/>. + /// Equivalent to <c>new Vector2(Mathf.Min(X, with), Mathf.Min(Y, with))</c>. + /// </summary> + /// <param name="with">The other value to use.</param> + /// <returns>The resulting minimum vector.</returns> + public readonly Vector2 Min(real_t with) + { + return new Vector2 + ( + Mathf.Min(X, with), + Mathf.Min(Y, with) + ); + } + + /// <summary> /// Returns the axis of the vector's highest value. See <see cref="Axis"/>. /// If both components are equal, this method returns <see cref="Axis.X"/>. /// </summary> @@ -600,7 +681,7 @@ namespace Godot } /// <summary> - /// Returns this vector with each component snapped to the nearest multiple of <paramref name="step"/>. + /// Returns a new vector with each component snapped to the nearest multiple of the corresponding component in <paramref name="step"/>. /// This can also be used to round to an arbitrary number of decimals. /// </summary> /// <param name="step">A vector value representing the step size to snap to.</param> @@ -611,6 +692,17 @@ namespace Godot } /// <summary> + /// Returns a new vector with each component snapped to the nearest multiple of <paramref name="step"/>. + /// This can also be used to round to an arbitrary number of decimals. + /// </summary> + /// <param name="step">The step size to snap to.</param> + /// <returns>The snapped vector.</returns> + public readonly Vector2 Snapped(real_t step) + { + return new Vector2(Mathf.Snapped(X, step), Mathf.Snapped(Y, step)); + } + + /// <summary> /// Returns a perpendicular vector rotated 90 degrees counter-clockwise /// compared to the original, with the same length. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs index 511cc7971c..9442db4d86 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs @@ -125,6 +125,23 @@ namespace Godot } /// <summary> + /// Returns a new vector with all components clamped between the + /// <paramref name="min"/> and <paramref name="max"/> using + /// <see cref="Mathf.Clamp(int, int, int)"/>. + /// </summary> + /// <param name="min">The minimum allowed value.</param> + /// <param name="max">The maximum allowed value.</param> + /// <returns>The vector with all components clamped.</returns> + public readonly Vector2I Clamp(int min, int max) + { + return new Vector2I + ( + Mathf.Clamp(X, min, max), + Mathf.Clamp(Y, min, max) + ); + } + + /// <summary> /// Returns the squared distance between this vector and <paramref name="to"/>. /// This method runs faster than <see cref="DistanceTo"/>, so prefer it if /// you need to compare vectors or need the squared distance for some formula. @@ -175,6 +192,70 @@ namespace Godot } /// <summary> + /// Returns the result of the component-wise maximum between + /// this vector and <paramref name="with"/>. + /// Equivalent to <c>new Vector2I(Mathf.Max(X, with.X), Mathf.Max(Y, with.Y))</c>. + /// </summary> + /// <param name="with">The other vector to use.</param> + /// <returns>The resulting maximum vector.</returns> + public readonly Vector2I Max(Vector2I with) + { + return new Vector2I + ( + Mathf.Max(X, with.X), + Mathf.Max(Y, with.Y) + ); + } + + /// <summary> + /// Returns the result of the component-wise maximum between + /// this vector and <paramref name="with"/>. + /// Equivalent to <c>new Vector2I(Mathf.Max(X, with), Mathf.Max(Y, with))</c>. + /// </summary> + /// <param name="with">The other value to use.</param> + /// <returns>The resulting maximum vector.</returns> + public readonly Vector2I Max(int with) + { + return new Vector2I + ( + Mathf.Max(X, with), + Mathf.Max(Y, with) + ); + } + + /// <summary> + /// Returns the result of the component-wise minimum between + /// this vector and <paramref name="with"/>. + /// Equivalent to <c>new Vector2I(Mathf.Min(X, with.X), Mathf.Min(Y, with.Y))</c>. + /// </summary> + /// <param name="with">The other vector to use.</param> + /// <returns>The resulting minimum vector.</returns> + public readonly Vector2I Min(Vector2I with) + { + return new Vector2I + ( + Mathf.Min(X, with.X), + Mathf.Min(Y, with.Y) + ); + } + + /// <summary> + /// Returns the result of the component-wise minimum between + /// this vector and <paramref name="with"/>. + /// Equivalent to <c>new Vector2I(Mathf.Min(X, with), Mathf.Min(Y, with))</c>. + /// </summary> + /// <param name="with">The other value to use.</param> + /// <returns>The resulting minimum vector.</returns> + public readonly Vector2I Min(int with) + { + return new Vector2I + ( + Mathf.Min(X, with), + Mathf.Min(Y, with) + ); + } + + /// <summary> /// Returns the axis of the vector's highest value. See <see cref="Axis"/>. /// If both components are equal, this method returns <see cref="Axis.X"/>. /// </summary> @@ -208,6 +289,34 @@ namespace Godot return v; } + /// <summary> + /// Returns a new vector with each component snapped to the closest multiple of the corresponding component in <paramref name="step"/>. + /// </summary> + /// <param name="step">A vector value representing the step size to snap to.</param> + /// <returns>The snapped vector.</returns> + public readonly Vector2I Snapped(Vector2I step) + { + return new Vector2I + ( + (int)Mathf.Snapped((double)X, (double)step.X), + (int)Mathf.Snapped((double)Y, (double)step.Y) + ); + } + + /// <summary> + /// Returns a new vector with each component snapped to the closest multiple of <paramref name="step"/>. + /// </summary> + /// <param name="step">The step size to snap to.</param> + /// <returns>The snapped vector.</returns> + public readonly Vector2I Snapped(int step) + { + return new Vector2I + ( + (int)Mathf.Snapped((double)X, (double)step), + (int)Mathf.Snapped((double)Y, (double)step) + ); + } + // Constants private static readonly Vector2I _minValue = new Vector2I(int.MinValue, int.MinValue); private static readonly Vector2I _maxValue = new Vector2I(int.MaxValue, int.MaxValue); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs index 6300705107..27f2713efa 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs @@ -179,6 +179,24 @@ namespace Godot } /// <summary> + /// Returns a new vector with all components clamped between the + /// <paramref name="min"/> and <paramref name="max"/> using + /// <see cref="Mathf.Clamp(real_t, real_t, real_t)"/>. + /// </summary> + /// <param name="min">The minimum allowed value.</param> + /// <param name="max">The maximum allowed value.</param> + /// <returns>The vector with all components clamped.</returns> + public readonly Vector3 Clamp(real_t min, real_t max) + { + return new Vector3 + ( + Mathf.Clamp(X, min, max), + Mathf.Clamp(Y, min, max), + Mathf.Clamp(Z, min, max) + ); + } + + /// <summary> /// Returns the cross product of this vector and <paramref name="with"/>. /// </summary> /// <param name="with">The other vector.</param> @@ -419,6 +437,57 @@ namespace Godot } /// <summary> + /// Returns the result of the component-wise maximum between + /// this vector and <paramref name="with"/>. + /// Equivalent to <c>new Vector3(Mathf.Max(X, with.X), Mathf.Max(Y, with.Y), Mathf.Max(Z, with.Z))</c>. + /// </summary> + /// <param name="with">The other vector to use.</param> + /// <returns>The resulting maximum vector.</returns> + public readonly Vector3 Max(Vector3 with) + { + return new Vector3 + ( + Mathf.Max(X, with.X), + Mathf.Max(Y, with.Y), + Mathf.Max(Z, with.Z) + ); + } + + /// <summary> + /// Returns the result of the component-wise maximum between + /// this vector and <paramref name="with"/>. + /// Equivalent to <c>new Vector3(Mathf.Max(X, with), Mathf.Max(Y, with), Mathf.Max(Z, with))</c>. + /// </summary> + /// <param name="with">The other value to use.</param> + /// <returns>The resulting maximum vector.</returns> + public readonly Vector3 Max(real_t with) + { + return new Vector3 + ( + Mathf.Max(X, with), + Mathf.Max(Y, with), + Mathf.Max(Z, with) + ); + } + + /// <summary> + /// Returns the result of the component-wise minimum between + /// this vector and <paramref name="with"/>. + /// Equivalent to <c>new Vector3(Mathf.Min(X, with.X), Mathf.Min(Y, with.Y), Mathf.Min(Z, with.Z))</c>. + /// </summary> + /// <param name="with">The other vector to use.</param> + /// <returns>The resulting minimum vector.</returns> + public readonly Vector3 Min(Vector3 with) + { + return new Vector3 + ( + Mathf.Min(X, with.X), + Mathf.Min(Y, with.Y), + Mathf.Min(Z, with.Z) + ); + } + + /// <summary> /// Returns the axis of the vector's highest value. See <see cref="Axis"/>. /// If all components are equal, this method returns <see cref="Axis.X"/>. /// </summary> @@ -643,7 +712,7 @@ namespace Godot } /// <summary> - /// Returns this vector with each component snapped to the nearest multiple of <paramref name="step"/>. + /// Returns a new vector with each component snapped to the nearest multiple of the corresponding component in <paramref name="step"/>. /// This can also be used to round to an arbitrary number of decimals. /// </summary> /// <param name="step">A vector value representing the step size to snap to.</param> @@ -658,6 +727,22 @@ namespace Godot ); } + /// <summary> + /// Returns a new vector with each component snapped to the nearest multiple of <paramref name="step"/>. + /// This can also be used to round to an arbitrary number of decimals. + /// </summary> + /// <param name="step">The step size to snap to.</param> + /// <returns>The snapped vector.</returns> + public readonly Vector3 Snapped(real_t step) + { + return new Vector3 + ( + Mathf.Snapped(X, step), + Mathf.Snapped(Y, step), + Mathf.Snapped(Z, step) + ); + } + // Constants private static readonly Vector3 _zero = new Vector3(0, 0, 0); private static readonly Vector3 _one = new Vector3(1, 1, 1); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs index aea46efc5b..8312e2c231 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs @@ -133,6 +133,24 @@ namespace Godot } /// <summary> + /// Returns a new vector with all components clamped between the + /// <paramref name="min"/> and <paramref name="max"/> using + /// <see cref="Mathf.Clamp(int, int, int)"/>. + /// </summary> + /// <param name="min">The minimum allowed value.</param> + /// <param name="max">The maximum allowed value.</param> + /// <returns>The vector with all components clamped.</returns> + public readonly Vector3I Clamp(int min, int max) + { + return new Vector3I + ( + Mathf.Clamp(X, min, max), + Mathf.Clamp(Y, min, max), + Mathf.Clamp(Z, min, max) + ); + } + + /// <summary> /// Returns the squared distance between this vector and <paramref name="to"/>. /// This method runs faster than <see cref="DistanceTo"/>, so prefer it if /// you need to compare vectors or need the squared distance for some formula. @@ -185,6 +203,74 @@ namespace Godot } /// <summary> + /// Returns the result of the component-wise maximum between + /// this vector and <paramref name="with"/>. + /// Equivalent to <c>new Vector3I(Mathf.Max(X, with.X), Mathf.Max(Y, with.Y), Mathf.Max(Z, with.Z))</c>. + /// </summary> + /// <param name="with">The other vector to use.</param> + /// <returns>The resulting maximum vector.</returns> + public readonly Vector3I Max(Vector3I with) + { + return new Vector3I + ( + Mathf.Max(X, with.X), + Mathf.Max(Y, with.Y), + Mathf.Max(Z, with.Z) + ); + } + + /// <summary> + /// Returns the result of the component-wise maximum between + /// this vector and <paramref name="with"/>. + /// Equivalent to <c>new Vector3I(Mathf.Max(X, with), Mathf.Max(Y, with), Mathf.Max(Z, with))</c>. + /// </summary> + /// <param name="with">The other value to use.</param> + /// <returns>The resulting maximum vector.</returns> + public readonly Vector3I Max(int with) + { + return new Vector3I + ( + Mathf.Max(X, with), + Mathf.Max(Y, with), + Mathf.Max(Z, with) + ); + } + + /// <summary> + /// Returns the result of the component-wise minimum between + /// this vector and <paramref name="with"/>. + /// Equivalent to <c>new Vector3I(Mathf.Min(X, with.X), Mathf.Min(Y, with.Y), Mathf.Min(Z, with.Z))</c>. + /// </summary> + /// <param name="with">The other vector to use.</param> + /// <returns>The resulting minimum vector.</returns> + public readonly Vector3I Min(Vector3I with) + { + return new Vector3I + ( + Mathf.Min(X, with.X), + Mathf.Min(Y, with.Y), + Mathf.Min(Z, with.Z) + ); + } + + /// <summary> + /// Returns the result of the component-wise minimum between + /// this vector and <paramref name="with"/>. + /// Equivalent to <c>new Vector3I(Mathf.Min(X, with), Mathf.Min(Y, with), Mathf.Min(Z, with))</c>. + /// </summary> + /// <param name="with">The other value to use.</param> + /// <returns>The resulting minimum vector.</returns> + public readonly Vector3I Min(int with) + { + return new Vector3I + ( + Mathf.Min(X, with), + Mathf.Min(Y, with), + Mathf.Min(Z, with) + ); + } + + /// <summary> /// Returns the axis of the vector's highest value. See <see cref="Axis"/>. /// If all components are equal, this method returns <see cref="Axis.X"/>. /// </summary> @@ -219,6 +305,36 @@ namespace Godot return v; } + /// <summary> + /// Returns a new vector with each component snapped to the closest multiple of the corresponding component in <paramref name="step"/>. + /// </summary> + /// <param name="step">A vector value representing the step size to snap to.</param> + /// <returns>The snapped vector.</returns> + public readonly Vector3I Snapped(Vector3I step) + { + return new Vector3I + ( + (int)Mathf.Snapped((double)X, (double)step.X), + (int)Mathf.Snapped((double)Y, (double)step.Y), + (int)Mathf.Snapped((double)Z, (double)step.Z) + ); + } + + /// <summary> + /// Returns a new vector with each component snapped to the closest multiple of <paramref name="step"/>. + /// </summary> + /// <param name="step">The step size to snap to.</param> + /// <returns>The snapped vector.</returns> + public readonly Vector3I Snapped(int step) + { + return new Vector3I + ( + (int)Mathf.Snapped((double)X, (double)step), + (int)Mathf.Snapped((double)Y, (double)step), + (int)Mathf.Snapped((double)Z, (double)step) + ); + } + // Constants private static readonly Vector3I _minValue = new Vector3I(int.MinValue, int.MinValue, int.MinValue); private static readonly Vector3I _maxValue = new Vector3I(int.MaxValue, int.MaxValue, int.MaxValue); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs index 7c4832943c..ec59197fa3 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs @@ -177,6 +177,25 @@ namespace Godot } /// <summary> + /// Returns a new vector with all components clamped between the + /// <paramref name="min"/> and <paramref name="max"/> using + /// <see cref="Mathf.Clamp(real_t, real_t, real_t)"/>. + /// </summary> + /// <param name="min">The minimum allowed value.</param> + /// <param name="max">The maximum allowed value.</param> + /// <returns>The vector with all components clamped.</returns> + public readonly Vector4 Clamp(real_t min, real_t max) + { + return new Vector4 + ( + Mathf.Clamp(X, min, max), + Mathf.Clamp(Y, min, max), + Mathf.Clamp(Z, min, max), + Mathf.Clamp(W, min, max) + ); + } + + /// <summary> /// Performs a cubic interpolation between vectors <paramref name="preA"/>, this vector, /// <paramref name="b"/>, and <paramref name="postB"/>, by the given amount <paramref name="weight"/>. /// </summary> @@ -352,6 +371,78 @@ namespace Godot } /// <summary> + /// Returns the result of the component-wise maximum between + /// this vector and <paramref name="with"/>. + /// Equivalent to <c>new Vector4(Mathf.Max(X, with.X), Mathf.Max(Y, with.Y), Mathf.Max(Z, with.Z), Mathf.Max(W, with.W))</c>. + /// </summary> + /// <param name="with">The other vector to use.</param> + /// <returns>The resulting maximum vector.</returns> + public readonly Vector4 Max(Vector4 with) + { + return new Vector4 + ( + Mathf.Max(X, with.X), + Mathf.Max(Y, with.Y), + Mathf.Max(Z, with.Z), + Mathf.Max(W, with.W) + ); + } + + /// <summary> + /// Returns the result of the component-wise maximum between + /// this vector and <paramref name="with"/>. + /// Equivalent to <c>new Vector4(Mathf.Max(X, with), Mathf.Max(Y, with), Mathf.Max(Z, with), Mathf.Max(W, with))</c>. + /// </summary> + /// <param name="with">The other value to use.</param> + /// <returns>The resulting maximum vector.</returns> + public readonly Vector4 Max(real_t with) + { + return new Vector4 + ( + Mathf.Max(X, with), + Mathf.Max(Y, with), + Mathf.Max(Z, with), + Mathf.Max(W, with) + ); + } + + /// <summary> + /// Returns the result of the component-wise minimum between + /// this vector and <paramref name="with"/>. + /// Equivalent to <c>new Vector4(Mathf.Min(X, with.X), Mathf.Min(Y, with.Y), Mathf.Min(Z, with.Z), Mathf.Min(W, with.W))</c>. + /// </summary> + /// <param name="with">The other vector to use.</param> + /// <returns>The resulting minimum vector.</returns> + public readonly Vector4 Min(Vector4 with) + { + return new Vector4 + ( + Mathf.Min(X, with.X), + Mathf.Min(Y, with.Y), + Mathf.Min(Z, with.Z), + Mathf.Min(W, with.W) + ); + } + + /// <summary> + /// Returns the result of the component-wise minimum between + /// this vector and <paramref name="with"/>. + /// Equivalent to <c>new Vector4(Mathf.Min(X, with), Mathf.Min(Y, with), Mathf.Min(Z, with), Mathf.Min(W, with))</c>. + /// </summary> + /// <param name="with">The other value to use.</param> + /// <returns>The resulting minimum vector.</returns> + public readonly Vector4 Min(real_t with) + { + return new Vector4 + ( + Mathf.Min(X, with), + Mathf.Min(Y, with), + Mathf.Min(Z, with), + Mathf.Min(W, with) + ); + } + + /// <summary> /// Returns the axis of the vector's highest value. See <see cref="Axis"/>. /// If all components are equal, this method returns <see cref="Axis.X"/>. /// </summary> @@ -465,7 +556,7 @@ namespace Godot } /// <summary> - /// Returns this vector with each component snapped to the nearest multiple of <paramref name="step"/>. + /// Returns a new vector with each component snapped to the nearest multiple of the corresponding component in <paramref name="step"/>. /// This can also be used to round to an arbitrary number of decimals. /// </summary> /// <param name="step">A vector value representing the step size to snap to.</param> @@ -480,6 +571,22 @@ namespace Godot ); } + /// <summary> + /// Returns a new vector with each component snapped to the nearest multiple of <paramref name="step"/>. + /// This can also be used to round to an arbitrary number of decimals. + /// </summary> + /// <param name="step">The step size to snap to.</param> + /// <returns>The snapped vector.</returns> + public readonly Vector4 Snapped(real_t step) + { + return new Vector4( + Mathf.Snapped(X, step), + Mathf.Snapped(Y, step), + Mathf.Snapped(Z, step), + Mathf.Snapped(W, step) + ); + } + // Constants private static readonly Vector4 _zero = new Vector4(0, 0, 0, 0); private static readonly Vector4 _one = new Vector4(1, 1, 1, 1); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs index 27aa86b7e4..ba8e54b88b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs @@ -150,6 +150,25 @@ namespace Godot } /// <summary> + /// Returns a new vector with all components clamped between + /// <paramref name="min"/> and <paramref name="max"/> using + /// <see cref="Mathf.Clamp(int, int, int)"/>. + /// </summary> + /// <param name="min">The minimum allowed value.</param> + /// <param name="max">The maximum allowed value.</param> + /// <returns>The vector with all components clamped.</returns> + public readonly Vector4I Clamp(int min, int max) + { + return new Vector4I + ( + Mathf.Clamp(X, min, max), + Mathf.Clamp(Y, min, max), + Mathf.Clamp(Z, min, max), + Mathf.Clamp(W, min, max) + ); + } + + /// <summary> /// Returns the squared distance between this vector and <paramref name="to"/>. /// This method runs faster than <see cref="DistanceTo"/>, so prefer it if /// you need to compare vectors or need the squared distance for some formula. @@ -204,6 +223,78 @@ namespace Godot } /// <summary> + /// Returns the result of the component-wise maximum between + /// this vector and <paramref name="with"/>. + /// Equivalent to <c>new Vector4I(Mathf.Max(X, with.X), Mathf.Max(Y, with.Y), Mathf.Max(Z, with.Z), Mathf.Max(W, with.W))</c>. + /// </summary> + /// <param name="with">The other vector to use.</param> + /// <returns>The resulting maximum vector.</returns> + public readonly Vector4I Max(Vector4I with) + { + return new Vector4I + ( + Mathf.Max(X, with.X), + Mathf.Max(Y, with.Y), + Mathf.Max(Z, with.Z), + Mathf.Max(W, with.W) + ); + } + + /// <summary> + /// Returns the result of the component-wise maximum between + /// this vector and <paramref name="with"/>. + /// Equivalent to <c>new Vector4I(Mathf.Max(X, with), Mathf.Max(Y, with), Mathf.Max(Z, with), Mathf.Max(W, with))</c>. + /// </summary> + /// <param name="with">The other value to use.</param> + /// <returns>The resulting maximum vector.</returns> + public readonly Vector4I Max(int with) + { + return new Vector4I + ( + Mathf.Max(X, with), + Mathf.Max(Y, with), + Mathf.Max(Z, with), + Mathf.Max(W, with) + ); + } + + /// <summary> + /// Returns the result of the component-wise minimum between + /// this vector and <paramref name="with"/>. + /// Equivalent to <c>new Vector4I(Mathf.Min(X, with.X), Mathf.Min(Y, with.Y), Mathf.Min(Z, with.Z), Mathf.Min(W, with.W))</c>. + /// </summary> + /// <param name="with">The other vector to use.</param> + /// <returns>The resulting minimum vector.</returns> + public readonly Vector4I Min(Vector4I with) + { + return new Vector4I + ( + Mathf.Min(X, with.X), + Mathf.Min(Y, with.Y), + Mathf.Min(Z, with.Z), + Mathf.Min(W, with.W) + ); + } + + /// <summary> + /// Returns the result of the component-wise minimum between + /// this vector and <paramref name="with"/>. + /// Equivalent to <c>new Vector4I(Mathf.Min(X, with), Mathf.Min(Y, with), Mathf.Min(Z, with), Mathf.Min(W, with))</c>. + /// </summary> + /// <param name="with">The other value to use.</param> + /// <returns>The resulting minimum vector.</returns> + public readonly Vector4I Min(int with) + { + return new Vector4I + ( + Mathf.Min(X, with), + Mathf.Min(Y, with), + Mathf.Min(Z, with), + Mathf.Min(W, with) + ); + } + + /// <summary> /// Returns the axis of the vector's highest value. See <see cref="Axis"/>. /// If all components are equal, this method returns <see cref="Axis.X"/>. /// </summary> @@ -254,6 +345,36 @@ namespace Godot return new Vector4I(Mathf.Sign(X), Mathf.Sign(Y), Mathf.Sign(Z), Mathf.Sign(W)); } + /// <summary> + /// Returns a new vector with each component snapped to the closest multiple of the corresponding component in <paramref name="step"/>. + /// </summary> + /// <param name="step">A vector value representing the step size to snap to.</param> + /// <returns>The snapped vector.</returns> + public readonly Vector4I Snapped(Vector4I step) + { + return new Vector4I( + (int)Mathf.Snapped((double)X, (double)step.X), + (int)Mathf.Snapped((double)Y, (double)step.Y), + (int)Mathf.Snapped((double)Z, (double)step.Z), + (int)Mathf.Snapped((double)W, (double)step.W) + ); + } + + /// <summary> + /// Returns a new vector with each component snapped to the closest multiple of <paramref name="step"/>. + /// </summary> + /// <param name="step">The step size to snap to.</param> + /// <returns>The snapped vector.</returns> + public readonly Vector4I Snapped(int step) + { + return new Vector4I( + (int)Mathf.Snapped((double)X, (double)step), + (int)Mathf.Snapped((double)Y, (double)step), + (int)Mathf.Snapped((double)Z, (double)step), + (int)Mathf.Snapped((double)W, (double)step) + ); + } + // Constants private static readonly Vector4I _minValue = new Vector4I(int.MinValue, int.MinValue, int.MinValue, int.MinValue); private static readonly Vector4I _maxValue = new Vector4I(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue); diff --git a/modules/openxr/action_map/openxr_action_map.cpp b/modules/openxr/action_map/openxr_action_map.cpp index bbcb63a7e6..ba0e4f6cdd 100644 --- a/modules/openxr/action_map/openxr_action_map.cpp +++ b/modules/openxr/action_map/openxr_action_map.cpp @@ -167,11 +167,11 @@ void OpenXRActionMap::create_default_action_sets() { // we still want it to be part of our action map as we may deploy the same game to platforms that do and don't support it. // - the same applies for interaction profiles that are only supported if the relevant extension is supported. - // Create our Godot action set + // 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 + // 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"); @@ -193,7 +193,7 @@ void OpenXRActionMap::create_default_action_sets() { Ref<OpenXRAction> default_pose = action_set->add_new_action("default_pose", "Default pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left," "/user/hand/right," - // "/user/vive_tracker_htcx/role/handheld_object," <-- getting errors on this one + // "/user/vive_tracker_htcx/role/handheld_object," <-- getting errors on this one. "/user/vive_tracker_htcx/role/left_foot," "/user/vive_tracker_htcx/role/right_foot," "/user/vive_tracker_htcx/role/left_shoulder," @@ -213,7 +213,7 @@ void OpenXRActionMap::create_default_action_sets() { Ref<OpenXRAction> haptic = action_set->add_new_action("haptic", "Haptic", OpenXRAction::OPENXR_ACTION_HAPTIC, "/user/hand/left," "/user/hand/right," - // "/user/vive_tracker_htcx/role/handheld_object," <-- getting errors on this one + // "/user/vive_tracker_htcx/role/handheld_object," <-- getting errors on this one. "/user/vive_tracker_htcx/role/left_foot," "/user/vive_tracker_htcx/role/right_foot," "/user/vive_tracker_htcx/role/left_shoulder," @@ -227,7 +227,7 @@ void OpenXRActionMap::create_default_action_sets() { "/user/vive_tracker_htcx/role/camera," "/user/vive_tracker_htcx/role/keyboard"); - // Create our interaction profiles + // 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/aim/pose,/user/hand/right/input/aim/pose"); profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); @@ -235,11 +235,11 @@ void OpenXRActionMap::create_default_action_sets() { profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/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 + // 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 + // Create our Vive controller profile. profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/htc/vive_controller"); profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); @@ -247,64 +247,64 @@ void OpenXRActionMap::create_default_action_sets() { profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/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 + // 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, "/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 + // 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 + // 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 + // Create our WMR controller profile. profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/microsoft/motion_controller"); profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/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(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose"); - // wmr controllers have no select button we can use + // 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 + // 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 convert 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(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); // OpenXR will convert 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 + // 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 + // 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 Meta touch controller 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/aim/pose,/user/hand/right/input/aim/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(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/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 + // 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_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_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, "/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 + // 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 + // 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); @@ -314,73 +314,73 @@ void OpenXRActionMap::create_default_action_sets() { 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(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose"); - profile->add_new_binding(select_button, "/user/hand/left/input/system/click,/user/hand/right/input/system/click"); // system click may not be available + profile->add_new_binding(select_button, "/user/hand/left/input/system/click,/user/hand/right/input/system/click"); // system click may not be available. profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click"); - 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_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_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_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, "/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 pico controller is our thumbstick + // primary on our pico 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"); - // pico controller has no secondary input + // pico 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 + // 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/aim/pose,/user/hand/right/input/aim/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(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose"); - // index controllers have no select button we can use + // 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_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_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 - profile->add_new_binding(grip_force, "/user/hand/left/input/squeeze/force,/user/hand/right/input/squeeze/force"); // grip force seems to be unique to the Valve Index - // primary on our index controller is our thumbstick + 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. + profile->add_new_binding(grip_force, "/user/hand/left/input/squeeze/force,/user/hand/right/input/squeeze/force"); // grip force seems to be unique to the Valve Index. + // 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 + // 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); - // Create our HP MR controller 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/aim/pose,/user/hand/right/input/aim/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(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose"); - // hpmr controllers have no select button we can use + // 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 + // 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 + // 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 + // 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); @@ -391,72 +391,72 @@ void OpenXRActionMap::create_default_action_sets() { 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(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose"); - // Odyssey controllers have no select button we can use + // Odyssey 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"); - // Odyssey controller has no a/b/x/y buttons + // Odyssey 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"); profile->add_new_binding(grip, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); - // primary on our Odyssey controller is our thumbstick, no touch + // primary on our Odyssey 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 Odyssey controller is our trackpad + // secondary on our Odyssey 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 Vive Cosmos controller + // Create our Vive Cosmos controller. profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/htc/vive_cosmos_controller"); profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/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(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose"); profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click"); - profile->add_new_binding(select_button, "/user/hand/right/input/system/click"); // we'll map system to select - 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(select_button, "/user/hand/right/input/system/click"); // we'll map system to select. + 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/click,/user/hand/right/input/trigger/click"); profile->add_new_binding(grip, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); - // primary on our Cosmos controller is our thumbstick + // primary on our Cosmos 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"); - // No secondary on our cosmos controller + // No secondary on our cosmos controller. profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); add_interaction_profile(profile); - // Create our Vive Focus 3 controller + // Create our Vive Focus 3 controller. // Note, Vive Focus 3 currently is not yet supported as a stand alone device - // however HTC currently has a beta OpenXR runtime in testing we may support in the near future + // however HTC currently has a beta OpenXR runtime in testing we may support in the near future. profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/htc/vive_focus3_controller"); profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/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(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose"); profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click"); - profile->add_new_binding(select_button, "/user/hand/right/input/system/click"); // we'll map system to select - 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(select_button, "/user/hand/right/input/system/click"); // we'll map system to select. + 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/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/click,/user/hand/right/input/squeeze/click"); profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); - // primary on our Focus 3 controller is our thumbstick + // primary on our Focus 3 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"); - // We only have a thumb rest + // We only have a thumb rest. profile->add_new_binding(secondary_touch, "/user/hand/left/input/thumbrest/touch,/user/hand/right/input/thumbrest/touch"); profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); add_interaction_profile(profile); - // Create our Huawei controller + // Create our Huawei controller. profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/huawei/controller"); profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); @@ -465,17 +465,17 @@ void OpenXRActionMap::create_default_action_sets() { profile->add_new_binding(menu_button, "/user/hand/left/input/home/click,/user/hand/right/input/home/click"); 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"); - // primary on our Huawei controller is our trackpad + // primary on our Huawei 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"); profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); add_interaction_profile(profile); - // Create our HTC Vive tracker profile + // Create our HTC Vive tracker profile. profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/htc/vive_tracker_htcx"); profile->add_new_binding(default_pose, - // "/user/vive_tracker_htcx/role/handheld_object/input/grip/pose," <-- getting errors on this one + // "/user/vive_tracker_htcx/role/handheld_object/input/grip/pose," <-- getting errors on this one. "/user/vive_tracker_htcx/role/left_foot/input/grip/pose," "/user/vive_tracker_htcx/role/right_foot/input/grip/pose," "/user/vive_tracker_htcx/role/left_shoulder/input/grip/pose," @@ -489,7 +489,7 @@ void OpenXRActionMap::create_default_action_sets() { "/user/vive_tracker_htcx/role/camera/input/grip/pose," "/user/vive_tracker_htcx/role/keyboard/input/grip/pose"); profile->add_new_binding(haptic, - // "/user/vive_tracker_htcx/role/handheld_object/output/haptic," <-- getting errors on this one + // "/user/vive_tracker_htcx/role/handheld_object/output/haptic," <-- getting errors on this one. "/user/vive_tracker_htcx/role/left_foot/output/haptic," "/user/vive_tracker_htcx/role/right_foot/output/haptic," "/user/vive_tracker_htcx/role/left_shoulder/output/haptic," @@ -504,10 +504,30 @@ void OpenXRActionMap::create_default_action_sets() { "/user/vive_tracker_htcx/role/keyboard/output/haptic"); add_interaction_profile(profile); - // Create our eye gaze interaction profile + // Create our eye gaze interaction profile. profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/ext/eye_gaze_interaction"); profile->add_new_binding(default_pose, "/user/eyes_ext/input/gaze_ext/pose"); add_interaction_profile(profile); + + // Create our hand interaction profile. + profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/ext/hand_interaction_ext"); + profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/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(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose"); + + // Use pinch as primary. + profile->add_new_binding(primary, "/user/hand/left/input/pinch_ext/value,/user/hand/right/input/pinch_ext/value"); + profile->add_new_binding(primary_click, "/user/hand/left/input/pinch_ext/ready_ext,/user/hand/right/input/pinch_ext/ready_ext"); + + // Use activation as secondary. + profile->add_new_binding(secondary, "/user/hand/left/input/aim_activate_ext/value,/user/hand/right/input/aim_activate_ext/value"); + profile->add_new_binding(secondary_click, "/user/hand/left/input/aim_activate_ext/ready_ext,/user/hand/right/input/aim_activate_ext/ready_ext"); + + // We link grasp to our grip. + profile->add_new_binding(grip, "/user/hand/left/input/grasp_ext/value,/user/hand/right/input/grasp_ext/value"); + profile->add_new_binding(grip_click, "/user/hand/left/input/grasp_ext/ready_ext,/user/hand/right/input/grasp_ext/ready_ext"); + add_interaction_profile(profile); } void OpenXRActionMap::create_editor_action_sets() { diff --git a/modules/openxr/doc_classes/OpenXRAPIExtension.xml b/modules/openxr/doc_classes/OpenXRAPIExtension.xml index f737f3b642..4419d24dd3 100644 --- a/modules/openxr/doc_classes/OpenXRAPIExtension.xml +++ b/modules/openxr/doc_classes/OpenXRAPIExtension.xml @@ -54,7 +54,7 @@ <method name="get_next_frame_time"> <return type="int" /> <description> - Returns the timing for the next frame. + Returns the predicted display timing for the next frame. </description> </method> <method name="get_play_space"> @@ -63,6 +63,12 @@ Returns the play space, which is an [url=https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrSpace.html]XrSpace[/url] cast to an integer. </description> </method> + <method name="get_predicted_display_time"> + <return type="int" /> + <description> + Returns the predicted display timing for the current frame. + </description> + </method> <method name="get_session"> <return type="int" /> <description> diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml index 05dff7d6ae..86ba1416c8 100644 --- a/modules/openxr/doc_classes/OpenXRInterface.xml +++ b/modules/openxr/doc_classes/OpenXRInterface.xml @@ -106,6 +106,13 @@ [b]Note:[/b] This feature is only available on the compatibility renderer and currently only available on some stand alone headsets. For Vulkan set [member Viewport.vrs_mode] to [code]VRS_XR[/code] on desktop. </description> </method> + <method name="is_hand_interaction_supported" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if OpenXR's hand interaction profile is supported and enabled. + [b]Note:[/b] This only returns a valid value after OpenXR has been initialized. + </description> + </method> <method name="is_hand_tracking_supported"> <return type="bool" /> <description> @@ -147,6 +154,11 @@ </member> </members> <signals> + <signal name="instance_exiting"> + <description> + Informs our OpenXR instance is exiting. + </description> + </signal> <signal name="pose_recentered"> <description> Informs the user queued a recenter of the player position. @@ -169,6 +181,11 @@ Informs our OpenXR session now has focus. </description> </signal> + <signal name="session_loss_pending"> + <description> + Informs our OpenXR session is in the process of being lost. + </description> + </signal> <signal name="session_stopping"> <description> Informs our OpenXR session is stopping. diff --git a/modules/openxr/extensions/openxr_composition_layer_extension.cpp b/modules/openxr/extensions/openxr_composition_layer_extension.cpp index 1fba8e5f8b..51f4a03d52 100644 --- a/modules/openxr/extensions/openxr_composition_layer_extension.cpp +++ b/modules/openxr/extensions/openxr_composition_layer_extension.cpp @@ -274,7 +274,7 @@ bool OpenXRViewportCompositionLayerProvider::update_and_acquire_swapchain(bool p if (swapchain_size == viewport_size && !p_static_image && !static_image) { // We're all good! Just acquire it. // We can ignore should_render here, return will be false. - XrBool32 should_render = true; + bool should_render = true; return swapchain_info.acquire(should_render); } @@ -296,7 +296,7 @@ bool OpenXRViewportCompositionLayerProvider::update_and_acquire_swapchain(bool p // Acquire our image so we can start rendering into it, // we can ignore should_render here, ret will be false. - XrBool32 should_render = true; + bool should_render = true; bool ret = swapchain_info.acquire(should_render); swapchain_size = viewport_size; diff --git a/modules/openxr/extensions/openxr_hand_interaction_extension.cpp b/modules/openxr/extensions/openxr_hand_interaction_extension.cpp new file mode 100644 index 0000000000..65de4b23c4 --- /dev/null +++ b/modules/openxr/extensions/openxr_hand_interaction_extension.cpp @@ -0,0 +1,97 @@ +/**************************************************************************/ +/* openxr_hand_interaction_extension.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "openxr_hand_interaction_extension.h" + +#include "../action_map/openxr_interaction_profile_metadata.h" +#include "core/config/project_settings.h" + +OpenXRHandInteractionExtension *OpenXRHandInteractionExtension::singleton = nullptr; + +OpenXRHandInteractionExtension *OpenXRHandInteractionExtension::get_singleton() { + return singleton; +} + +OpenXRHandInteractionExtension::OpenXRHandInteractionExtension() { + singleton = this; +} + +OpenXRHandInteractionExtension::~OpenXRHandInteractionExtension() { + singleton = nullptr; +} + +HashMap<String, bool *> OpenXRHandInteractionExtension::get_requested_extensions() { + HashMap<String, bool *> request_extensions; + + // Only enable this extension when requested. + // We still register our meta data or the action map editor will fail. + if (GLOBAL_GET("xr/openxr/extensions/hand_interaction_profile")) { + request_extensions[XR_EXT_HAND_INTERACTION_EXTENSION_NAME] = &available; + } + + return request_extensions; +} + +bool OpenXRHandInteractionExtension::is_available() { + return available; +} + +void OpenXRHandInteractionExtension::on_register_metadata() { + OpenXRInteractionProfileMetadata *metadata = OpenXRInteractionProfileMetadata::get_singleton(); + ERR_FAIL_NULL(metadata); + + // Hand interaction profile + metadata->register_interaction_profile("Hand interaction", "/interaction_profiles/ext/hand_interaction_ext", XR_EXT_HAND_INTERACTION_EXTENSION_NAME); + metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Grip pose", "/user/hand/left", "/user/hand/left/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Grip pose", "/user/hand/right", "/user/hand/right/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Aim pose", "/user/hand/left", "/user/hand/left/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Aim pose", "/user/hand/right", "/user/hand/right/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Pinch pose", "/user/hand/left", "/user/hand/left/input/pinch_ext/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Pinch pose", "/user/hand/right", "/user/hand/right/input/pinch_ext/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Poke pose", "/user/hand/left", "/user/hand/left/input/poke_ext/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Poke pose", "/user/hand/right", "/user/hand/right/input/poke_ext/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Palm pose", "/user/hand/left", "/user/hand/left/input/palm_ext/pose", XR_EXT_PALM_POSE_EXTENSION_NAME, OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Palm pose", "/user/hand/right", "/user/hand/right/input/palm_ext/pose", XR_EXT_PALM_POSE_EXTENSION_NAME, OpenXRAction::OPENXR_ACTION_POSE); + + metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Pinch", "/user/hand/left", "/user/hand/left/input/pinch_ext/value", "", OpenXRAction::OPENXR_ACTION_FLOAT); + metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Pinch", "/user/hand/right", "/user/hand/right/input/pinch_ext/value", "", OpenXRAction::OPENXR_ACTION_FLOAT); + metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Pinch ready", "/user/hand/left", "/user/hand/left/input/pinch_ext/ready_ext", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Pinch ready", "/user/hand/right", "/user/hand/right/input/pinch_ext/ready_ext", "", OpenXRAction::OPENXR_ACTION_BOOL); + + metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Aim activate", "/user/hand/left", "/user/hand/left/input/aim_activate_ext/value", "", OpenXRAction::OPENXR_ACTION_FLOAT); + metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Aim activate", "/user/hand/right", "/user/hand/right/input/aim_activate_ext/value", "", OpenXRAction::OPENXR_ACTION_FLOAT); + metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Aim activate ready", "/user/hand/left", "/user/hand/left/input/aim_activate_ext/ready_ext", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Aim activate ready", "/user/hand/right", "/user/hand/right/input/aim_activate_ext/ready_ext", "", OpenXRAction::OPENXR_ACTION_BOOL); + + metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Grasp", "/user/hand/left", "/user/hand/left/input/grasp_ext/value", "", OpenXRAction::OPENXR_ACTION_FLOAT); + metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Grasp", "/user/hand/right", "/user/hand/right/input/grasp_ext/value", "", OpenXRAction::OPENXR_ACTION_FLOAT); + metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Grasp ready", "/user/hand/left", "/user/hand/left/input/grasp_ext/ready_ext", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Grasp ready", "/user/hand/right", "/user/hand/right/input/grasp_ext/ready_ext", "", OpenXRAction::OPENXR_ACTION_BOOL); +} diff --git a/modules/openxr/extensions/openxr_hand_interaction_extension.h b/modules/openxr/extensions/openxr_hand_interaction_extension.h new file mode 100644 index 0000000000..789e300c0b --- /dev/null +++ b/modules/openxr/extensions/openxr_hand_interaction_extension.h @@ -0,0 +1,72 @@ +/**************************************************************************/ +/* openxr_hand_interaction_extension.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef OPENXR_HAND_INTERACTION_EXTENSION_H +#define OPENXR_HAND_INTERACTION_EXTENSION_H + +#include "openxr_extension_wrapper.h" + +// When supported the hand interaction extension introduces an interaction +// profile that becomes active when the user either lets go of their +// controllers or isn't using controllers at all. +// +// The OpenXR specification states that all XR runtimes that support this +// interaction profile should also allow it's controller to use this +// interaction profile. +// This means that if you only supply this interaction profile in your +// action map, it should work both when the player is holding a controller +// or using visual hand tracking. +// +// This allows easier portability between games that use controller +// tracking or hand tracking. +// +// See: https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_EXT_hand_interaction +// for more information. + +class OpenXRHandInteractionExtension : public OpenXRExtensionWrapper { +public: + static OpenXRHandInteractionExtension *get_singleton(); + + OpenXRHandInteractionExtension(); + virtual ~OpenXRHandInteractionExtension() override; + + virtual HashMap<String, bool *> get_requested_extensions() override; + + bool is_available(); + + virtual void on_register_metadata() override; + +private: + static OpenXRHandInteractionExtension *singleton; + + bool available = false; +}; + +#endif // OPENXR_HAND_INTERACTION_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp index 12fa3bed7e..b8a2f58935 100644 --- a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp +++ b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp @@ -128,7 +128,7 @@ void OpenXRHandTrackingExtension::on_process() { } // process our hands - const XrTime time = OpenXRAPI::get_singleton()->get_next_frame_time(); // This data will be used for the next frame we render + const XrTime time = OpenXRAPI::get_singleton()->get_predicted_display_time(); if (time == 0) { // we don't have timing info yet, or we're skipping a frame... return; diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 1fe402341b..40e3ecfefc 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -160,7 +160,7 @@ void OpenXRAPI::OpenXRSwapChainInfo::free() { } } -bool OpenXRAPI::OpenXRSwapChainInfo::acquire(XrBool32 &p_should_render) { +bool OpenXRAPI::OpenXRSwapChainInfo::acquire(bool &p_should_render) { ERR_FAIL_COND_V(image_acquired, true); // This was not released when it should be, error out and reuse... OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); @@ -193,10 +193,18 @@ bool OpenXRAPI::OpenXRSwapChainInfo::acquire(XrBool32 &p_should_render) { XrSwapchainImageWaitInfo swapchain_image_wait_info = { XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO, // type nullptr, // next - 17000000 // timeout in nanoseconds + 1000000000 // 1s timeout in nanoseconds }; - result = openxr_api->xrWaitSwapchainImage(swapchain, &swapchain_image_wait_info); + // Wait for a maximum of 10 seconds before calling it a critical failure... + for (int retry = 0; retry < 10; retry++) { + result = openxr_api->xrWaitSwapchainImage(swapchain, &swapchain_image_wait_info); + if (result != XR_TIMEOUT_EXPIRED) { + break; + } + WARN_PRINT("OpenXR: timed out waiting for swapchain image."); + } + if (!XR_UNQUALIFIED_SUCCESS(result)) { // Make sure end_frame knows we need to submit an empty frame p_should_render = false; @@ -206,6 +214,8 @@ bool OpenXRAPI::OpenXRSwapChainInfo::acquire(XrBool32 &p_should_render) { print_line("OpenXR: failed to wait for swapchain image [", openxr_api->get_error_string(result), "]"); return false; } else { + WARN_PRINT("OpenXR: couldn't to wait for swapchain but not a complete error [" + openxr_api->get_error_string(result) + "]"); + // Make sure to skip trying to acquire the swapchain image in the next frame skip_acquire_swapchain = true; return false; @@ -760,21 +770,6 @@ bool OpenXRAPI::load_supported_view_configuration_views(XrViewConfigurationType print_verbose(String(" - recommended render sample count: ") + itos(view_configuration_views[i].recommendedSwapchainSampleCount)); } - // Allocate buffers we'll be populating with view information. - views = (XrView *)memalloc(sizeof(XrView) * view_count); - ERR_FAIL_NULL_V_MSG(views, false, "OpenXR Couldn't allocate memory for views"); - memset(views, 0, sizeof(XrView) * view_count); - - projection_views = (XrCompositionLayerProjectionView *)memalloc(sizeof(XrCompositionLayerProjectionView) * view_count); - ERR_FAIL_NULL_V_MSG(projection_views, false, "OpenXR Couldn't allocate memory for projection views"); - memset(projection_views, 0, sizeof(XrCompositionLayerProjectionView) * view_count); - - if (submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) { - depth_views = (XrCompositionLayerDepthInfoKHR *)memalloc(sizeof(XrCompositionLayerDepthInfoKHR) * view_count); - ERR_FAIL_NULL_V_MSG(depth_views, false, "OpenXR Couldn't allocate memory for depth views"); - memset(depth_views, 0, sizeof(XrCompositionLayerDepthInfoKHR) * view_count); - } - return true; } @@ -927,6 +922,9 @@ bool OpenXRAPI::setup_play_space() { // If we've previously created a play space, clean it up first. if (play_space != XR_NULL_HANDLE) { + // TODO Investigate if destroying our play space here is safe, + // it may still be used in the rendering thread. + xrDestroySpace(play_space); } play_space = new_play_space; @@ -936,7 +934,11 @@ bool OpenXRAPI::setup_play_space() { if (emulating_local_floor) { // We'll use the STAGE space to get the floor height, but we can't do that until // after xrWaitFrame(), so just set this flag for now. + // Render state will be updated then. should_reset_emulated_floor_height = true; + } else { + // Update render state so this play space is used rendering the upcoming frame. + set_render_play_space(play_space); } return true; @@ -1016,7 +1018,7 @@ bool OpenXRAPI::reset_emulated_floor_height() { identityPose, // pose }; - result = xrLocateSpace(stage_space, local_space, get_next_frame_time(), &stage_location); + result = xrLocateSpace(stage_space, local_space, get_predicted_display_time(), &stage_location); xrDestroySpace(local_space); xrDestroySpace(stage_space); @@ -1042,6 +1044,9 @@ bool OpenXRAPI::reset_emulated_floor_height() { // report that as the reference space to the outside world. reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT; + // Update render state so this play space is used rendering the upcoming frame. + set_render_play_space(play_space); + return true; } @@ -1136,6 +1141,7 @@ bool OpenXRAPI::obtain_swapchain_formats() { } bool OpenXRAPI::create_main_swapchains(Size2i p_size) { + ERR_NOT_ON_RENDER_THREAD_V(false); ERR_FAIL_NULL_V(graphics_extension, false); ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); @@ -1154,12 +1160,12 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) { as we render 3D content into internal buffers that are copied into the swapchain, we do now have (basic) VRS support */ - main_swapchain_size = p_size; + render_state.main_swapchain_size = p_size; uint32_t sample_count = 1; // We start with our color swapchain... if (color_swapchain_format != 0) { - if (!main_swapchains[OPENXR_SWAPCHAIN_COLOR].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, color_swapchain_format, main_swapchain_size.width, main_swapchain_size.height, sample_count, view_count)) { + if (!render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, color_swapchain_format, render_state.main_swapchain_size.width, render_state.main_swapchain_size.height, sample_count, view_count)) { return false; } } @@ -1169,7 +1175,7 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) { // - we support our depth layer extension // - we have our spacewarp extension (not yet implemented) if (depth_swapchain_format != 0 && submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) { - if (!main_swapchains[OPENXR_SWAPCHAIN_DEPTH].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, depth_swapchain_format, main_swapchain_size.width, main_swapchain_size.height, sample_count, view_count)) { + if (!render_state.main_swapchains[OPENXR_SWAPCHAIN_DEPTH].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, depth_swapchain_format, render_state.main_swapchain_size.width, render_state.main_swapchain_size.height, sample_count, view_count)) { return false; } } @@ -1180,36 +1186,36 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) { // TBD } - for (uint32_t i = 0; i < view_count; i++) { - views[i].type = XR_TYPE_VIEW; - views[i].next = nullptr; - - projection_views[i].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW; - projection_views[i].next = nullptr; - projection_views[i].subImage.swapchain = main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_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 = main_swapchain_size.width; - projection_views[i].subImage.imageRect.extent.height = main_swapchain_size.height; - - if (submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available() && depth_views) { - projection_views[i].next = &depth_views[i]; - - depth_views[i].type = XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR; - depth_views[i].next = nullptr; - depth_views[i].subImage.swapchain = main_swapchains[OPENXR_SWAPCHAIN_DEPTH].get_swapchain(); - depth_views[i].subImage.imageArrayIndex = i; - depth_views[i].subImage.imageRect.offset.x = 0; - depth_views[i].subImage.imageRect.offset.y = 0; - depth_views[i].subImage.imageRect.extent.width = main_swapchain_size.width; - depth_views[i].subImage.imageRect.extent.height = main_swapchain_size.height; + for (uint32_t i = 0; i < render_state.view_count; i++) { + render_state.views[i].type = XR_TYPE_VIEW; + render_state.views[i].next = nullptr; + + render_state.projection_views[i].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW; + render_state.projection_views[i].next = nullptr; + render_state.projection_views[i].subImage.swapchain = render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_swapchain(); + render_state.projection_views[i].subImage.imageArrayIndex = i; + render_state.projection_views[i].subImage.imageRect.offset.x = 0; + render_state.projection_views[i].subImage.imageRect.offset.y = 0; + render_state.projection_views[i].subImage.imageRect.extent.width = render_state.main_swapchain_size.width; + render_state.projection_views[i].subImage.imageRect.extent.height = render_state.main_swapchain_size.height; + + if (render_state.submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available() && render_state.depth_views) { + render_state.projection_views[i].next = &render_state.depth_views[i]; + + render_state.depth_views[i].type = XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR; + render_state.depth_views[i].next = nullptr; + render_state.depth_views[i].subImage.swapchain = render_state.main_swapchains[OPENXR_SWAPCHAIN_DEPTH].get_swapchain(); + render_state.depth_views[i].subImage.imageArrayIndex = i; + render_state.depth_views[i].subImage.imageRect.offset.x = 0; + render_state.depth_views[i].subImage.imageRect.offset.y = 0; + render_state.depth_views[i].subImage.imageRect.extent.width = render_state.main_swapchain_size.width; + render_state.depth_views[i].subImage.imageRect.extent.height = render_state.main_swapchain_size.height; // OpenXR spec says that: minDepth < maxDepth. - depth_views[i].minDepth = 0.0; - depth_views[i].maxDepth = 1.0; + render_state.depth_views[i].minDepth = 0.0; + render_state.depth_views[i].maxDepth = 1.0; // But we can reverse near and far for reverse-Z. - depth_views[i].nearZ = 100.0; // Near and far Z will be set to the correct values in fill_projection_matrix - depth_views[i].farZ = 0.01; + render_state.depth_views[i].nearZ = 100.0; // Near and far Z will be set to the correct values in fill_projection_matrix + render_state.depth_views[i].farZ = 0.01; } }; @@ -1217,23 +1223,33 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) { }; void OpenXRAPI::destroy_session() { - if (running && session != XR_NULL_HANDLE) { - xrEndSession(session); + // TODO need to figure out if we're still rendering our current frame + // in a separate rendering thread and if so, + // if we need to wait for completion. + // We could be pulling the rug from underneath rendering... + + if (running) { + if (session != XR_NULL_HANDLE) { + xrEndSession(session); + } + + running = false; + render_state.running = false; } - if (views != nullptr) { - memfree(views); - views = nullptr; + if (render_state.views != nullptr) { + memfree(render_state.views); + render_state.views = nullptr; } - if (projection_views != nullptr) { - memfree(projection_views); - projection_views = nullptr; + if (render_state.projection_views != nullptr) { + memfree(render_state.projection_views); + render_state.projection_views = nullptr; } - if (depth_views != nullptr) { - memfree(depth_views); - depth_views = nullptr; + if (render_state.depth_views != nullptr) { + memfree(render_state.depth_views); + render_state.depth_views = nullptr; } free_main_swapchains(); @@ -1248,6 +1264,7 @@ void OpenXRAPI::destroy_session() { if (play_space != XR_NULL_HANDLE) { xrDestroySpace(play_space); play_space = XR_NULL_HANDLE; + render_state.play_space = XR_NULL_HANDLE; } if (view_space != XR_NULL_HANDLE) { xrDestroySpace(view_space); @@ -1298,6 +1315,7 @@ bool OpenXRAPI::on_state_ready() { // we're running running = true; + set_render_session_running(true); for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { wrapper->on_state_ready(); @@ -1374,34 +1392,37 @@ bool OpenXRAPI::on_state_stopping() { } running = false; + set_render_session_running(false); } - // TODO further cleanup - return true; } bool OpenXRAPI::on_state_loss_pending() { print_verbose("On state loss pending"); + if (xr_interface) { + xr_interface->on_state_loss_pending(); + } + 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() { print_verbose("On state existing"); + if (xr_interface) { + xr_interface->on_state_exiting(); + } + 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; } @@ -1419,10 +1440,7 @@ void OpenXRAPI::set_view_configuration(XrViewConfigurationType p_view_configurat bool OpenXRAPI::set_requested_reference_space(XrReferenceSpaceType p_requested_reference_space) { requested_reference_space = p_requested_reference_space; - - if (is_initialized()) { - return setup_play_space(); - } + play_space_is_dirty = true; return true; } @@ -1625,11 +1643,6 @@ bool OpenXRAPI::initialize_session() { return false; } - if (!setup_play_space()) { - destroy_session(); - return false; - } - if (!setup_view_space()) { destroy_session(); return false; @@ -1645,6 +1658,8 @@ bool OpenXRAPI::initialize_session() { return false; } + allocate_view_buffers(view_count, submit_depth_buffer); + return true; } @@ -1696,12 +1711,18 @@ XrHandTrackerEXT OpenXRAPI::get_hand_tracker(int p_hand_index) { } Size2 OpenXRAPI::get_recommended_target_size() { + RenderingServer *rendering_server = RenderingServer::get_singleton(); ERR_FAIL_NULL_V(view_configuration_views, Size2()); Size2 target_size; - target_size.width = view_configuration_views[0].recommendedImageRectWidth * render_target_size_multiplier; - target_size.height = view_configuration_views[0].recommendedImageRectHeight * render_target_size_multiplier; + if (rendering_server && rendering_server->is_on_render_thread()) { + target_size.width = view_configuration_views[0].recommendedImageRectWidth * render_state.render_target_size_multiplier; + target_size.height = view_configuration_views[0].recommendedImageRectHeight * render_state.render_target_size_multiplier; + } else { + target_size.width = view_configuration_views[0].recommendedImageRectWidth * render_target_size_multiplier; + target_size.height = view_configuration_views[0].recommendedImageRectHeight * render_target_size_multiplier; + } return target_size; } @@ -1713,14 +1734,12 @@ XRPose::TrackingConfidence OpenXRAPI::get_head_center(Transform3D &r_transform, return XRPose::XR_TRACKING_CONFIDENCE_NONE; } - // xrWaitFrame not run yet - if (frame_state.predictedDisplayTime == 0) { + // Get display time + XrTime display_time = get_predicted_display_time(); + if (display_time == 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 @@ -1764,54 +1783,47 @@ XRPose::TrackingConfidence OpenXRAPI::get_head_center(Transform3D &r_transform, } bool OpenXRAPI::get_view_transform(uint32_t p_view, Transform3D &r_transform) { - if (!running) { - return false; - } + ERR_NOT_ON_RENDER_THREAD_V(false); - // xrWaitFrame not run yet - if (frame_state.predictedDisplayTime == 0) { + if (!render_state.running) { return false; } // we don't have valid view info - if (views == nullptr || !view_pose_valid) { + if (render_state.views == nullptr || !render_state.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); + r_transform = transform_from_pose(render_state.views[p_view].pose); return true; } bool OpenXRAPI::get_view_projection(uint32_t p_view, double p_z_near, double p_z_far, Projection &p_camera_matrix) { + ERR_NOT_ON_RENDER_THREAD_V(false); ERR_FAIL_NULL_V(graphics_extension, false); - if (!running) { - return false; - } - - // xrWaitFrame not run yet - if (frame_state.predictedDisplayTime == 0) { + if (!render_state.running) { return false; } // we don't have valid view info - if (views == nullptr || !view_pose_valid) { + if (render_state.views == nullptr || !render_state.view_pose_valid) { return false; } // if we're using depth views, make sure we update our near and far there... - if (depth_views != nullptr) { - for (uint32_t i = 0; i < view_count; i++) { + if (render_state.depth_views != nullptr) { + for (uint32_t i = 0; i < render_state.view_count; i++) { // As we are using reverse-Z these need to be flipped. - depth_views[i].nearZ = p_z_far; - depth_views[i].farZ = p_z_near; + render_state.depth_views[i].nearZ = p_z_far; + render_state.depth_views[i].farZ = p_z_near; } } // now update our projection - return graphics_extension->create_projection_fov(views[p_view].fov, p_z_near, p_z_far, p_camera_matrix); + return graphics_extension->create_projection_fov(render_state.views[p_view].fov, p_z_near, p_z_far, p_camera_matrix); } bool OpenXRAPI::poll_events() { @@ -1934,53 +1946,85 @@ bool OpenXRAPI::poll_events() { } } -bool OpenXRAPI::process() { - ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); +void OpenXRAPI::_allocate_view_buffers(uint32_t p_view_count, bool p_submit_depth_buffer) { + // Must be called from rendering thread! + ERR_NOT_ON_RENDER_THREAD; - if (!poll_events()) { - return false; - } + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL(openxr_api); - if (!running) { - return false; - } + openxr_api->render_state.view_count = p_view_count; + openxr_api->render_state.submit_depth_buffer = p_submit_depth_buffer; - for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { - wrapper->on_process(); + // Allocate buffers we'll be populating with view information. + openxr_api->render_state.views = (XrView *)memalloc(sizeof(XrView) * p_view_count); + ERR_FAIL_NULL_MSG(openxr_api->render_state.views, "OpenXR Couldn't allocate memory for views"); + memset(openxr_api->render_state.views, 0, sizeof(XrView) * p_view_count); + + openxr_api->render_state.projection_views = (XrCompositionLayerProjectionView *)memalloc(sizeof(XrCompositionLayerProjectionView) * p_view_count); + ERR_FAIL_NULL_MSG(openxr_api->render_state.projection_views, "OpenXR Couldn't allocate memory for projection views"); + memset(openxr_api->render_state.projection_views, 0, sizeof(XrCompositionLayerProjectionView) * p_view_count); + + if (p_submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) { + openxr_api->render_state.depth_views = (XrCompositionLayerDepthInfoKHR *)memalloc(sizeof(XrCompositionLayerDepthInfoKHR) * p_view_count); + ERR_FAIL_NULL_MSG(openxr_api->render_state.depth_views, "OpenXR Couldn't allocate memory for depth views"); + memset(openxr_api->render_state.depth_views, 0, sizeof(XrCompositionLayerDepthInfoKHR) * p_view_count); } +} - return true; +void OpenXRAPI::_set_render_session_running(bool p_is_running) { + // Must be called from rendering thread! + ERR_NOT_ON_RENDER_THREAD; + + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL(openxr_api); + openxr_api->render_state.running = p_is_running; } -void OpenXRAPI::free_main_swapchains() { - for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) { - main_swapchains[i].queue_free(); - } +void OpenXRAPI::_set_render_display_info(XrTime p_predicted_display_time, bool p_should_render) { + // Must be called from rendering thread! + ERR_NOT_ON_RENDER_THREAD; + + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL(openxr_api); + openxr_api->render_state.predicted_display_time = p_predicted_display_time; + openxr_api->render_state.should_render = p_should_render; } -void OpenXRAPI::pre_render() { - ERR_FAIL_COND(instance == XR_NULL_HANDLE); +void OpenXRAPI::_set_render_play_space(uint64_t p_play_space) { + // Must be called from rendering thread! + ERR_NOT_ON_RENDER_THREAD; - if (!running) { - return; - } + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL(openxr_api); + openxr_api->render_state.play_space = XrSpace(p_play_space); +} - // Process any swapchains that were queued to be freed - OpenXRSwapChainInfo::free_queued(); +void OpenXRAPI::_set_render_state_multiplier(double p_render_target_size_multiplier) { + // Must be called from rendering thread! + ERR_NOT_ON_RENDER_THREAD; - Size2i swapchain_size = get_recommended_target_size(); - if (swapchain_size != main_swapchain_size) { - // Out with the old. - free_main_swapchains(); + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL(openxr_api); + openxr_api->render_state.render_target_size_multiplier = p_render_target_size_multiplier; +} - // In with the new. - create_main_swapchains(swapchain_size); +bool OpenXRAPI::process() { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); + + if (!poll_events()) { + return false; } - // 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 + if (!running) { + return false; + } + + // We call xrWaitFrame as early as possible, this will allow OpenXR to get + // proper timing info between this point, and when we're ready to start rendering. + // As the name suggests, OpenXR can pause the thread to minimize the time between + // retrieving tracking data and using that tracking data to render. + // OpenXR thus works best if rendering is performed on a separate thread. XrFrameWaitInfo frame_wait_info = { XR_TYPE_FRAME_WAIT_INFO, nullptr }; frame_state.predictedDisplayTime = 0; frame_state.predictedDisplayPeriod = 0; @@ -1995,7 +2039,9 @@ void OpenXRAPI::pre_render() { frame_state.predictedDisplayPeriod = 0; frame_state.shouldRender = false; - return; + set_render_display_info(0, false); + + return false; } if (frame_state.predictedDisplayPeriod > 500000000) { @@ -2004,12 +2050,54 @@ void OpenXRAPI::pre_render() { frame_state.predictedDisplayPeriod = 0; } + set_render_display_info(frame_state.predictedDisplayTime, frame_state.shouldRender); + + if (unlikely(play_space_is_dirty)) { + setup_play_space(); + play_space_is_dirty = false; + } + if (unlikely(should_reset_emulated_floor_height)) { reset_emulated_floor_height(); should_reset_emulated_floor_height = false; } for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_process(); + } + + return true; +} + +void OpenXRAPI::free_main_swapchains() { + for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) { + render_state.main_swapchains[i].queue_free(); + } +} + +void OpenXRAPI::pre_render() { + ERR_FAIL_COND(session == XR_NULL_HANDLE); + + // Must be called from rendering thread! + ERR_NOT_ON_RENDER_THREAD; + + if (!render_state.running) { + return; + } + + // Process any swapchains that were queued to be freed + OpenXRSwapChainInfo::free_queued(); + + Size2i swapchain_size = get_recommended_target_size(); + if (swapchain_size != render_state.main_swapchain_size) { + // Out with the old. + free_main_swapchains(); + + // In with the new. + create_main_swapchains(swapchain_size); + } + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { wrapper->on_pre_render(); } @@ -2028,8 +2116,8 @@ void OpenXRAPI::pre_render() { XR_TYPE_VIEW_LOCATE_INFO, // type nullptr, // next view_configuration, // viewConfigurationType - frame_state.predictedDisplayTime, // displayTime - play_space // space + render_state.predicted_display_time, // displayTime + render_state.play_space // space }; XrViewState view_state = { XR_TYPE_VIEW_STATE, // type @@ -2037,7 +2125,7 @@ void OpenXRAPI::pre_render() { 0 // viewStateFlags }; uint32_t view_count_output; - result = xrLocateViews(session, &view_locate_info, &view_state, view_count, &view_count_output, views); + XrResult result = xrLocateViews(session, &view_locate_info, &view_state, render_state.view_count, &view_count_output, render_state.views); if (XR_FAILED(result)) { print_line("OpenXR: Couldn't locate views [", get_error_string(result), "]"); return; @@ -2050,9 +2138,9 @@ void OpenXRAPI::pre_render() { pose_valid = false; } } - if (view_pose_valid != pose_valid) { - view_pose_valid = pose_valid; - if (!view_pose_valid) { + if (render_state.view_pose_valid != pose_valid) { + render_state.view_pose_valid = pose_valid; + if (!render_state.view_pose_valid) { print_verbose("OpenXR View pose became invalid"); } else { print_verbose("OpenXR View pose became valid"); @@ -2071,23 +2159,24 @@ void OpenXRAPI::pre_render() { } // Reset this, we haven't found a viewport for output yet - has_xr_viewport = false; + render_state.has_xr_viewport = false; } bool OpenXRAPI::pre_draw_viewport(RID p_render_target) { + // Must be called from rendering thread! + ERR_NOT_ON_RENDER_THREAD_V(false); + // We found an XR viewport! - has_xr_viewport = true; + render_state.has_xr_viewport = true; - if (!can_render()) { + if (instance == XR_NULL_HANDLE || session == XR_NULL_HANDLE || !render_state.running || !render_state.view_pose_valid || !render_state.should_render) { return false; } - // TODO: at some point in time we may support multiple viewports in which case we need to handle that... - // Acquire our images for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) { - if (!main_swapchains[i].is_image_acquired() && main_swapchains[i].get_swapchain() != XR_NULL_HANDLE) { - if (!main_swapchains[i].acquire(frame_state.shouldRender)) { + if (!render_state.main_swapchains[i].is_image_acquired() && render_state.main_swapchains[i].get_swapchain() != XR_NULL_HANDLE) { + if (!render_state.main_swapchains[i].acquire(render_state.should_render)) { return false; } } @@ -2101,24 +2190,33 @@ bool OpenXRAPI::pre_draw_viewport(RID p_render_target) { } XrSwapchain OpenXRAPI::get_color_swapchain() { - return main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_swapchain(); + ERR_NOT_ON_RENDER_THREAD_V(XR_NULL_HANDLE); + + return render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_swapchain(); } RID OpenXRAPI::get_color_texture() { - return main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_image(); + ERR_NOT_ON_RENDER_THREAD_V(RID()); + + return render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_image(); } RID OpenXRAPI::get_depth_texture() { + ERR_NOT_ON_RENDER_THREAD_V(RID()); + // Note, image will not be acquired if we didn't have a suitable swap chain format. - if (submit_depth_buffer) { - return main_swapchains[OPENXR_SWAPCHAIN_DEPTH].get_image(); + if (render_state.submit_depth_buffer && render_state.main_swapchains[OPENXR_SWAPCHAIN_DEPTH].is_image_acquired()) { + return render_state.main_swapchains[OPENXR_SWAPCHAIN_DEPTH].get_image(); } else { return RID(); } } void OpenXRAPI::post_draw_viewport(RID p_render_target) { - if (!can_render()) { + // Must be called from rendering thread! + ERR_NOT_ON_RENDER_THREAD; + + if (instance == XR_NULL_HANDLE || session == XR_NULL_HANDLE || !render_state.running || !render_state.view_pose_valid || !render_state.should_render) { return; } @@ -2130,30 +2228,33 @@ void OpenXRAPI::post_draw_viewport(RID p_render_target) { void OpenXRAPI::end_frame() { XrResult result; - ERR_FAIL_COND(instance == XR_NULL_HANDLE); + ERR_FAIL_COND(session == XR_NULL_HANDLE); - if (!running) { + // Must be called from rendering thread! + ERR_NOT_ON_RENDER_THREAD; + + if (!render_state.running) { return; } - if (frame_state.shouldRender && view_pose_valid) { - if (!has_xr_viewport) { + if (render_state.should_render && render_state.view_pose_valid) { + if (!render_state.has_xr_viewport) { print_line("OpenXR: No viewport was marked with use_xr, there is no rendered output!"); - } else if (!main_swapchains[OPENXR_SWAPCHAIN_COLOR].is_image_acquired()) { + } else if (!render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].is_image_acquired()) { print_line("OpenXR: No swapchain could be acquired to render to!"); } } // must have: - // - shouldRender set to true + // - should_render 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 || !main_swapchains[OPENXR_SWAPCHAIN_COLOR].is_image_acquired()) { + if (!render_state.should_render || !render_state.view_pose_valid || !render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].is_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 + render_state.predicted_display_time, // displayTime environment_blend_mode, // environmentBlendMode 0, // layerCount nullptr // layers @@ -2170,14 +2271,14 @@ void OpenXRAPI::end_frame() { // release our swapchain image if we acquired it for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) { - if (main_swapchains[i].is_image_acquired()) { - main_swapchains[i].release(); + if (render_state.main_swapchains[i].is_image_acquired()) { + render_state.main_swapchains[i].release(); } } - for (uint32_t eye = 0; eye < view_count; eye++) { - projection_views[eye].fov = views[eye].fov; - projection_views[eye].pose = views[eye].pose; + for (uint32_t eye = 0; eye < render_state.view_count; eye++) { + render_state.projection_views[eye].fov = render_state.views[eye].fov; + render_state.projection_views[eye].pose = render_state.views[eye].pose; } Vector<OrderedCompositionLayer> ordered_layers_list; @@ -2210,9 +2311,9 @@ void OpenXRAPI::end_frame() { XR_TYPE_COMPOSITION_LAYER_PROJECTION, // type nullptr, // next layer_flags, // layerFlags - play_space, // space - view_count, // viewCount - projection_views, // views + render_state.play_space, // space + render_state.view_count, // viewCount + render_state.projection_views, // views }; ordered_layers_list.push_back({ (const XrCompositionLayerBaseHeader *)&projection_layer, 0 }); @@ -2228,7 +2329,7 @@ void OpenXRAPI::end_frame() { XrFrameEndInfo frame_end_info = { XR_TYPE_FRAME_END_INFO, // type nullptr, // next - frame_state.predictedDisplayTime, // displayTime + render_state.predicted_display_time, // displayTime environment_blend_mode, // environmentBlendMode static_cast<uint32_t>(layers_list.size()), // layerCount layers_list.ptr() // layers @@ -2271,6 +2372,7 @@ double OpenXRAPI::get_render_target_size_multiplier() const { void OpenXRAPI::set_render_target_size_multiplier(double multiplier) { render_target_size_multiplier = multiplier; + set_render_state_multiplier(multiplier); } bool OpenXRAPI::is_foveation_supported() const { @@ -2414,10 +2516,6 @@ OpenXRAPI::OpenXRAPI() { submit_depth_buffer = GLOBAL_GET("xr/openxr/submit_depth_buffer"); } - - // Reset a few things that can't be done in our class definition. - frame_state.predictedDisplayTime = 0; - frame_state.predictedDisplayPeriod = 0; } OpenXRAPI::~OpenXRAPI() { @@ -3132,7 +3230,7 @@ XRPose::TrackingConfidence OpenXRAPI::get_action_pose(RID p_action, RID p_tracke return XRPose::XR_TRACKING_CONFIDENCE_NONE; } - XrTime display_time = get_next_frame_time(); + XrTime display_time = get_predicted_display_time(); if (display_time == 0) { return XRPose::XR_TRACKING_CONFIDENCE_NONE; } diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index e835366200..c95867810c 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -46,13 +46,11 @@ #include "core/templates/rb_map.h" #include "core/templates/rid_owner.h" #include "core/templates/vector.h" +#include "servers/rendering_server.h" #include "servers/xr/xr_pose.h" #include <openxr/openxr.h> -// Note, OpenXR code that we wrote for our plugin makes use of C++20 notation for initializing 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 OpenXRInterface; @@ -77,7 +75,7 @@ public: static void free_queued(); void free(); - bool acquire(XrBool32 &p_should_render); + bool acquire(bool &p_should_render); bool release(); RID get_image(); }; @@ -151,9 +149,6 @@ private: uint32_t view_count = 0; XrViewConfigurationView *view_configuration_views = nullptr; - XrView *views = nullptr; - XrCompositionLayerProjectionView *projection_views = nullptr; - XrCompositionLayerDepthInfoKHR *depth_views = nullptr; // Only used by Composition Layer Depth Extension if available enum OpenXRSwapChainTypes { OPENXR_SWAPCHAIN_COLOR, @@ -164,14 +159,11 @@ private: int64_t color_swapchain_format = 0; int64_t depth_swapchain_format = 0; - Size2i main_swapchain_size = { 0, 0 }; - OpenXRSwapChainInfo main_swapchains[OPENXR_SWAPCHAIN_MAX]; + bool play_space_is_dirty = true; 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 has_xr_viewport = false; bool emulating_local_floor = false; bool should_reset_emulated_floor_height = false; @@ -328,6 +320,72 @@ private: // convenience void copy_string_to_char_buffer(const String p_string, char *p_buffer, int p_buffer_len); + // Render state, Only accessible in rendering thread + struct RenderState { + bool running = false; + bool should_render = false; + bool has_xr_viewport = false; + XrTime predicted_display_time = 0; + XrSpace play_space = XR_NULL_HANDLE; + double render_target_size_multiplier = 1.0; + + uint32_t view_count = 0; + XrView *views = nullptr; + XrCompositionLayerProjectionView *projection_views = nullptr; + XrCompositionLayerDepthInfoKHR *depth_views = nullptr; // Only used by Composition Layer Depth Extension if available + bool submit_depth_buffer = false; // if set to true we submit depth buffers to OpenXR if a suitable extension is enabled. + bool view_pose_valid = false; + + Size2i main_swapchain_size; + OpenXRSwapChainInfo main_swapchains[OPENXR_SWAPCHAIN_MAX]; + } render_state; + + static void _allocate_view_buffers(uint32_t p_view_count, bool p_submit_depth_buffer); + static void _set_render_session_running(bool p_is_running); + static void _set_render_display_info(XrTime p_predicted_display_time, bool p_should_render); + static void _set_render_play_space(uint64_t p_play_space); + static void _set_render_state_multiplier(double p_render_target_size_multiplier); + + _FORCE_INLINE_ void allocate_view_buffers(uint32_t p_view_count, bool p_submit_depth_buffer) { + // If we're rendering on a separate thread, we may still be processing the last frame, don't communicate this till we're ready... + RenderingServer *rendering_server = RenderingServer::get_singleton(); + ERR_FAIL_NULL(rendering_server); + + rendering_server->call_on_render_thread(callable_mp_static(&OpenXRAPI::_allocate_view_buffers).bind(p_view_count, p_submit_depth_buffer)); + } + + _FORCE_INLINE_ void set_render_session_running(bool p_is_running) { + // If we're rendering on a separate thread, we may still be processing the last frame, don't communicate this till we're ready... + RenderingServer *rendering_server = RenderingServer::get_singleton(); + ERR_FAIL_NULL(rendering_server); + + rendering_server->call_on_render_thread(callable_mp_static(&OpenXRAPI::_set_render_session_running).bind(p_is_running)); + } + + _FORCE_INLINE_ void set_render_display_info(XrTime p_predicted_display_time, bool p_should_render) { + // If we're rendering on a separate thread, we may still be processing the last frame, don't communicate this till we're ready... + RenderingServer *rendering_server = RenderingServer::get_singleton(); + ERR_FAIL_NULL(rendering_server); + + rendering_server->call_on_render_thread(callable_mp_static(&OpenXRAPI::_set_render_display_info).bind(p_predicted_display_time, p_should_render)); + } + + _FORCE_INLINE_ void set_render_play_space(XrSpace p_play_space) { + // If we're rendering on a separate thread, we may still be processing the last frame, don't communicate this till we're ready... + RenderingServer *rendering_server = RenderingServer::get_singleton(); + ERR_FAIL_NULL(rendering_server); + + rendering_server->call_on_render_thread(callable_mp_static(&OpenXRAPI::_set_render_play_space).bind(uint64_t(p_play_space))); + } + + _FORCE_INLINE_ void set_render_state_multiplier(double p_render_target_size_multiplier) { + // If we're rendering on a separate thread, we may still be processing the last frame, don't communicate this till we're ready... + RenderingServer *rendering_server = RenderingServer::get_singleton(); + ERR_FAIL_NULL(rendering_server); + + rendering_server->call_on_render_thread(callable_mp_static(&OpenXRAPI::_set_render_state_multiplier).bind(p_render_target_size_multiplier)); + } + public: XrInstance get_instance() const { return instance; }; XrSystemId get_system_id() const { return system_id; }; @@ -384,9 +442,13 @@ public: bool initialize_session(); void finish(); - XrSpace get_play_space() const { return play_space; } - 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; } + _FORCE_INLINE_ XrSpace get_play_space() const { return play_space; } + _FORCE_INLINE_ XrTime get_predicted_display_time() { return frame_state.predictedDisplayTime; } + _FORCE_INLINE_ XrTime get_next_frame_time() { return frame_state.predictedDisplayTime + frame_state.predictedDisplayPeriod; } + _FORCE_INLINE_ bool can_render() { + ERR_ON_RENDER_THREAD_V(false); + return instance != XR_NULL_HANDLE && session != XR_NULL_HANDLE && running && frame_state.shouldRender; + } XrHandTrackerEXT get_hand_tracker(int p_hand_index); diff --git a/modules/openxr/openxr_api_extension.cpp b/modules/openxr/openxr_api_extension.cpp index fae0fc13d3..a1744fa1db 100644 --- a/modules/openxr/openxr_api_extension.cpp +++ b/modules/openxr/openxr_api_extension.cpp @@ -48,6 +48,7 @@ void OpenXRAPIExtension::_bind_methods() { ClassDB::bind_method(D_METHOD("is_running"), &OpenXRAPIExtension::is_running); ClassDB::bind_method(D_METHOD("get_play_space"), &OpenXRAPIExtension::get_play_space); + ClassDB::bind_method(D_METHOD("get_predicted_display_time"), &OpenXRAPIExtension::get_predicted_display_time); ClassDB::bind_method(D_METHOD("get_next_frame_time"), &OpenXRAPIExtension::get_next_frame_time); ClassDB::bind_method(D_METHOD("can_render"), &OpenXRAPIExtension::can_render); @@ -130,8 +131,17 @@ uint64_t OpenXRAPIExtension::get_play_space() { return (uint64_t)OpenXRAPI::get_singleton()->get_play_space(); } +int64_t OpenXRAPIExtension::get_predicted_display_time() { + ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), 0); + return (XrTime)OpenXRAPI::get_singleton()->get_predicted_display_time(); +} + int64_t OpenXRAPIExtension::get_next_frame_time() { ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), 0); + + // In the past we needed to look a frame ahead, may be calling this unintentionally so lets warn the dev. + WARN_PRINT_ONCE("OpenXR: Next frame timing called, verify this is intended."); + return (XrTime)OpenXRAPI::get_singleton()->get_next_frame_time(); } diff --git a/modules/openxr/openxr_api_extension.h b/modules/openxr/openxr_api_extension.h index 576e497798..cff2c4738e 100644 --- a/modules/openxr/openxr_api_extension.h +++ b/modules/openxr/openxr_api_extension.h @@ -69,6 +69,7 @@ public: bool is_running(); uint64_t get_play_space(); + int64_t get_predicted_display_time(); int64_t get_next_frame_time(); bool can_render(); diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index aa68441f03..39a61d1b4d 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -35,6 +35,7 @@ #include "servers/rendering/rendering_server_globals.h" #include "extensions/openxr_eye_gaze_interaction.h" +#include "extensions/openxr_hand_interaction_extension.h" #include "thirdparty/openxr/include/openxr/openxr.h" void OpenXRInterface::_bind_methods() { @@ -43,6 +44,8 @@ void OpenXRInterface::_bind_methods() { ADD_SIGNAL(MethodInfo("session_stopping")); ADD_SIGNAL(MethodInfo("session_focussed")); ADD_SIGNAL(MethodInfo("session_visible")); + ADD_SIGNAL(MethodInfo("session_loss_pending")); + ADD_SIGNAL(MethodInfo("instance_exiting")); ADD_SIGNAL(MethodInfo("pose_recentered")); ADD_SIGNAL(MethodInfo("refresh_rate_changed", PropertyInfo(Variant::FLOAT, "refresh_rate"))); @@ -91,6 +94,7 @@ void OpenXRInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("get_hand_joint_angular_velocity", "hand", "joint"), &OpenXRInterface::get_hand_joint_angular_velocity); ClassDB::bind_method(D_METHOD("is_hand_tracking_supported"), &OpenXRInterface::is_hand_tracking_supported); + ClassDB::bind_method(D_METHOD("is_hand_interaction_supported"), &OpenXRInterface::is_hand_interaction_supported); ClassDB::bind_method(D_METHOD("is_eye_gaze_interaction_supported"), &OpenXRInterface::is_eye_gaze_interaction_supported); BIND_ENUM_CONSTANT(HAND_LEFT); @@ -806,6 +810,21 @@ bool OpenXRInterface::is_hand_tracking_supported() { } } +bool OpenXRInterface::is_hand_interaction_supported() const { + if (openxr_api == nullptr) { + return false; + } else if (!openxr_api->is_initialized()) { + return false; + } else { + OpenXRHandInteractionExtension *hand_interaction_ext = OpenXRHandInteractionExtension::get_singleton(); + if (hand_interaction_ext == nullptr) { + return false; + } else { + return hand_interaction_ext->is_available(); + } + } +} + bool OpenXRInterface::is_eye_gaze_interaction_supported() { if (openxr_api == nullptr) { return false; @@ -1258,6 +1277,14 @@ void OpenXRInterface::on_state_stopping() { emit_signal(SNAME("session_stopping")); } +void OpenXRInterface::on_state_loss_pending() { + emit_signal(SNAME("session_loss_pending")); +} + +void OpenXRInterface::on_state_exiting() { + emit_signal(SNAME("instance_exiting")); +} + void OpenXRInterface::on_pose_recentered() { emit_signal(SNAME("pose_recentered")); } diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h index e916c7dac2..ac33304757 100644 --- a/modules/openxr/openxr_interface.h +++ b/modules/openxr/openxr_interface.h @@ -31,6 +31,29 @@ #ifndef OPENXR_INTERFACE_H #define OPENXR_INTERFACE_H +// A note on multithreading and thread safety in OpenXR. +// +// Most entry points will be called from the main thread in Godot +// however a number of entry points will be called from the +// rendering thread, potentially while we're already processing +// the next frame on the main thread. +// +// OpenXR itself has been designed with threading in mind including +// a high likelihood that the XR runtime runs in separate threads +// as well. +// Hence all the frame timing information, use of swapchains and +// sync functions. +// Do note that repeated calls to tracking APIs will provide +// increasingly more accurate data for the same timestamp as +// tracking data is continuously updated. +// +// For our code we mostly implement this in our OpenXRAPI class. +// We store data accessed from the rendering thread in a separate +// struct, setting values through our renderer command queue. +// +// As some data is setup before we start rendering, and cleaned up +// after we've stopped, that is accessed directly from both threads. + #include "action_map/openxr_action_map.h" #include "extensions/openxr_hand_tracking_extension.h" #include "openxr_api.h" @@ -110,6 +133,7 @@ public: virtual TrackingStatus get_tracking_status() const override; bool is_hand_tracking_supported(); + bool is_hand_interaction_supported() const; bool is_eye_gaze_interaction_supported(); bool initialize_on_startup() const; @@ -173,6 +197,8 @@ public: void on_state_visible(); void on_state_focused(); void on_state_stopping(); + void on_state_loss_pending(); + void on_state_exiting(); void on_pose_recentered(); void on_refresh_rate_changes(float p_new_rate); void tracker_profile_changed(RID p_tracker, RID p_interaction_profile); diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp index eb0527f07c..85514737f2 100644 --- a/modules/openxr/register_types.cpp +++ b/modules/openxr/register_types.cpp @@ -49,6 +49,7 @@ #include "extensions/openxr_composition_layer_extension.h" #include "extensions/openxr_eye_gaze_interaction.h" #include "extensions/openxr_fb_display_refresh_rate_extension.h" +#include "extensions/openxr_hand_interaction_extension.h" #include "extensions/openxr_hand_tracking_extension.h" #include "extensions/openxr_htc_controller_extension.h" #include "extensions/openxr_htc_vive_tracker_extension.h" @@ -124,6 +125,7 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) { OpenXRAPI::register_extension_wrapper(memnew(OpenXRML2ControllerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRMetaControllerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXREyeGazeInteractionExtension)); + OpenXRAPI::register_extension_wrapper(memnew(OpenXRHandInteractionExtension)); // register gated extensions if (GLOBAL_GET("xr/openxr/extensions/hand_tracking")) { diff --git a/platform/android/SCsub b/platform/android/SCsub index 7380511d6d..4d76ffb180 100644 --- a/platform/android/SCsub +++ b/platform/android/SCsub @@ -1,5 +1,6 @@ #!/usr/bin/env python +import sys import subprocess from methods import print_warning @@ -82,10 +83,21 @@ if lib_arch_dir != "": env_android.Command(out_dir + "/libc++_shared.so", stl_lib_path, Copy("$TARGET", "$SOURCE")) def generate_apk(target, source, env): + gradle_process = [] + + if sys.platform.startswith("win"): + gradle_process = [ + "cmd", + "/c", + "gradlew.bat", + ] + else: + gradle_process = ["./gradlew"] + if env["target"] != "editor" and env["dev_build"]: subprocess.run( - [ - "./gradlew", + gradle_process + + [ "generateDevTemplate", "--quiet", ], @@ -94,8 +106,8 @@ if lib_arch_dir != "": else: # Android editor with `dev_build=yes` is handled by the `generateGodotEditor` task. subprocess.run( - [ - "./gradlew", + gradle_process + + [ "generateGodotEditor" if env["target"] == "editor" else "generateGodotTemplates", "--quiet", ], diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp index 3a63b87c7a..1701aa650d 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -2461,7 +2461,7 @@ void WaylandThread::_wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_ // According to the tablet proto spec, tilt is expressed in degrees relative // to the Z axis of the tablet, so it shouldn't go over 90 degrees either way, // I think. We'll clamp it just in case. - td.tilt = td.tilt.clamp(Vector2(-90, -90), Vector2(90, 90)); + td.tilt = td.tilt.clampf(-90, 90); mm->set_tilt(td.tilt / 90); diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index e854fe9274..b76cbc126f 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -2225,7 +2225,7 @@ void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) { ERR_FAIL_COND(!windows.has(p_window)); Size2i size = p_size; - size = size.max(Size2i(1, 1)); + size = size.maxi(1); WindowData &wd = windows[p_window]; diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index 5d38bf55ea..db76b7d78a 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -192,7 +192,7 @@ private: HashMap<WindowID, WindowData> windows; struct IndicatorData { - id view; + id delegate; id item; }; @@ -431,10 +431,12 @@ public: virtual void set_native_icon(const String &p_filename) override; virtual void set_icon(const Ref<Image> &p_icon) override; - virtual IndicatorID create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) override; - virtual void status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) override; + virtual IndicatorID create_status_indicator(const Ref<Texture2D> &p_icon, const String &p_tooltip, const Callable &p_callback) override; + virtual void status_indicator_set_icon(IndicatorID p_id, const Ref<Texture2D> &p_icon) override; virtual void status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) override; + virtual void status_indicator_set_menu(IndicatorID p_id, const RID &p_menu_rid) override; virtual void status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) override; + virtual Rect2 status_indicator_get_rect(IndicatorID p_id) const override; virtual void delete_status_indicator(IndicatorID p_id) override; static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error); diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index 6461f50818..0041848c78 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -2321,7 +2321,7 @@ void DisplayServerMacOS::window_set_window_buttons_offset(const Vector2i &p_offs WindowData &wd = windows[p_window]; float scale = screen_get_max_scale(); wd.wb_offset = p_offset / scale; - wd.wb_offset = wd.wb_offset.max(Vector2i(12, 12)); + wd.wb_offset = wd.wb_offset.maxi(12); if (wd.window_button_view) { [wd.window_button_view setOffset:NSMakePoint(wd.wb_offset.x, wd.wb_offset.y)]; } @@ -3151,10 +3151,11 @@ void DisplayServerMacOS::set_icon(const Ref<Image> &p_icon) { } } -DisplayServer::IndicatorID DisplayServerMacOS::create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) { +DisplayServer::IndicatorID DisplayServerMacOS::create_status_indicator(const Ref<Texture2D> &p_icon, const String &p_tooltip, const Callable &p_callback) { NSImage *nsimg = nullptr; if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) { - Ref<Image> img = p_icon->duplicate(); + Ref<Image> img = p_icon->get_image(); + img = img->duplicate(); img->convert(Image::FORMAT_RGBA8); NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] @@ -3192,13 +3193,18 @@ DisplayServer::IndicatorID DisplayServerMacOS::create_status_indicator(const Ref IndicatorData idat; - idat.item = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength]; - idat.view = [[GodotStatusItemView alloc] init]; + NSStatusItem *item = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength]; + idat.item = item; + idat.delegate = [[GodotStatusItemDelegate alloc] init]; + [idat.delegate setCallback:p_callback]; - [idat.view setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]]; - [idat.view setImage:nsimg]; - [idat.view setCallback:p_callback]; - [idat.item setView:idat.view]; + item.button.image = nsimg; + item.button.imagePosition = NSImageOnly; + item.button.imageScaling = NSImageScaleProportionallyUpOrDown; + item.button.target = idat.delegate; + item.button.action = @selector(click:); + [item.button sendActionOn:(NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown | NSEventMaskOtherMouseDown)]; + item.button.toolTip = [NSString stringWithUTF8String:p_tooltip.utf8().get_data()]; IndicatorID iid = indicator_id_counter++; indicators[iid] = idat; @@ -3206,12 +3212,13 @@ DisplayServer::IndicatorID DisplayServerMacOS::create_status_indicator(const Ref return iid; } -void DisplayServerMacOS::status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) { +void DisplayServerMacOS::status_indicator_set_icon(IndicatorID p_id, const Ref<Texture2D> &p_icon) { ERR_FAIL_COND(!indicators.has(p_id)); NSImage *nsimg = nullptr; if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) { - Ref<Image> img = p_icon->duplicate(); + Ref<Image> img = p_icon->get_image(); + img = img->duplicate(); img->convert(Image::FORMAT_RGBA8); NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] @@ -3247,19 +3254,57 @@ void DisplayServerMacOS::status_indicator_set_icon(IndicatorID p_id, const Ref<I } } - [indicators[p_id].view setImage:nsimg]; + NSStatusItem *item = indicators[p_id].item; + item.button.image = nsimg; } void DisplayServerMacOS::status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) { ERR_FAIL_COND(!indicators.has(p_id)); - [indicators[p_id].view setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]]; + NSStatusItem *item = indicators[p_id].item; + item.button.toolTip = [NSString stringWithUTF8String:p_tooltip.utf8().get_data()]; +} + +void DisplayServerMacOS::status_indicator_set_menu(IndicatorID p_id, const RID &p_menu_rid) { + ERR_FAIL_COND(!indicators.has(p_id)); + + NSStatusItem *item = indicators[p_id].item; + if (p_menu_rid.is_valid() && native_menu->has_menu(p_menu_rid)) { + NSMenu *menu = native_menu->get_native_menu_handle(p_menu_rid); + item.menu = menu; + } else { + item.menu = nullptr; + } } void DisplayServerMacOS::status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) { ERR_FAIL_COND(!indicators.has(p_id)); - [indicators[p_id].view setCallback:p_callback]; + [indicators[p_id].delegate setCallback:p_callback]; +} + +Rect2 DisplayServerMacOS::status_indicator_get_rect(IndicatorID p_id) const { + ERR_FAIL_COND_V(!indicators.has(p_id), Rect2()); + + NSStatusItem *item = indicators[p_id].item; + NSView *v = item.button; + const NSRect contentRect = [v frame]; + const NSRect nsrect = [v.window convertRectToScreen:contentRect]; + Rect2 rect; + + // Return the position of the top-left corner, for macOS the y starts at the bottom. + const float scale = screen_get_max_scale(); + rect.size.x = nsrect.size.width; + rect.size.y = nsrect.size.height; + rect.size *= scale; + rect.position.x = nsrect.origin.x; + rect.position.y = (nsrect.origin.y + nsrect.size.height); + rect.position *= scale; + rect.position -= _get_screens_origin(); + // macOS native y-coordinate relative to _get_screens_origin() is negative, + // Godot expects a positive value. + rect.position.y *= -1; + return rect; } void DisplayServerMacOS::delete_status_indicator(IndicatorID p_id) { diff --git a/platform/macos/godot_status_item.h b/platform/macos/godot_status_item.h index 1827baa9bd..5bc790956e 100644 --- a/platform/macos/godot_status_item.h +++ b/platform/macos/godot_status_item.h @@ -37,13 +37,12 @@ #import <AppKit/AppKit.h> #import <Foundation/Foundation.h> -@interface GodotStatusItemView : NSView { - NSImage *image; +@interface GodotStatusItemDelegate : NSObject { Callable cb; } -- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index; -- (void)setImage:(NSImage *)image; +- (IBAction)click:(id)sender; + - (void)setCallback:(const Callable &)callback; @end diff --git a/platform/macos/godot_status_item.mm b/platform/macos/godot_status_item.mm index 71ed0a0f71..0990a16b2b 100644 --- a/platform/macos/godot_status_item.mm +++ b/platform/macos/godot_status_item.mm @@ -32,30 +32,32 @@ #include "display_server_macos.h" -@implementation GodotStatusItemView +@implementation GodotStatusItemDelegate - (id)init { self = [super init]; - image = nullptr; return self; } -- (void)setImage:(NSImage *)newImage { - image = newImage; - [self setNeedsDisplayInRect:self.frame]; -} - -- (void)setCallback:(const Callable &)callback { - cb = callback; -} - -- (void)drawRect:(NSRect)rect { - if (image) { - [image drawInRect:rect]; +- (IBAction)click:(id)sender { + NSEvent *current_event = [NSApp currentEvent]; + MouseButton index = MouseButton::LEFT; + if (current_event) { + if (current_event.type == NSEventTypeLeftMouseDown) { + index = MouseButton::LEFT; + } else if (current_event.type == NSEventTypeRightMouseDown) { + index = MouseButton::RIGHT; + } else if (current_event.type == NSEventTypeOtherMouseDown) { + if ((int)[current_event buttonNumber] == 2) { + index = MouseButton::MIDDLE; + } else if ((int)[current_event buttonNumber] == 3) { + index = MouseButton::MB_XBUTTON1; + } else if ((int)[current_event buttonNumber] == 4) { + index = MouseButton::MB_XBUTTON2; + } + } } -} -- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index { DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); if (!ds) { return; @@ -71,31 +73,8 @@ } } -- (void)mouseDown:(NSEvent *)event { - [super mouseDown:event]; - if (([event modifierFlags] & NSEventModifierFlagControl)) { - [self processMouseEvent:event index:MouseButton::RIGHT]; - } else { - [self processMouseEvent:event index:MouseButton::LEFT]; - } -} - -- (void)rightMouseDown:(NSEvent *)event { - [super rightMouseDown:event]; - - [self processMouseEvent:event index:MouseButton::RIGHT]; -} - -- (void)otherMouseDown:(NSEvent *)event { - [super otherMouseDown:event]; - - if ((int)[event buttonNumber] == 2) { - [self processMouseEvent:event index:MouseButton::MIDDLE]; - } else if ((int)[event buttonNumber] == 3) { - [self processMouseEvent:event index:MouseButton::MB_XBUTTON1]; - } else if ((int)[event buttonNumber] == 4) { - [self processMouseEvent:event index:MouseButton::MB_XBUTTON2]; - } +- (void)setCallback:(const Callable &)callback { + cb = callback; } @end diff --git a/platform/macos/native_menu_macos.h b/platform/macos/native_menu_macos.h index 1d9feb64a7..b5dbb8b9b0 100644 --- a/platform/macos/native_menu_macos.h +++ b/platform/macos/native_menu_macos.h @@ -85,6 +85,8 @@ public: virtual bool has_menu(const RID &p_rid) const override; virtual void free_menu(const RID &p_rid) override; + NSMenu *get_native_menu_handle(const RID &p_rid); + virtual Size2 get_size(const RID &p_rid) const override; virtual void popup(const RID &p_rid, const Vector2i &p_position) override; diff --git a/platform/macos/native_menu_macos.mm b/platform/macos/native_menu_macos.mm index 8c2dd98862..1cf13a2d69 100644 --- a/platform/macos/native_menu_macos.mm +++ b/platform/macos/native_menu_macos.mm @@ -248,6 +248,13 @@ void NativeMenuMacOS::free_menu(const RID &p_rid) { } } +NSMenu *NativeMenuMacOS::get_native_menu_handle(const RID &p_rid) { + MenuData *md = menus.get_or_null(p_rid); + ERR_FAIL_NULL_V(md, nullptr); + + return md->menu; +} + Size2 NativeMenuMacOS::get_size(const RID &p_rid) const { const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL_V(md, Size2()); diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp index 92da7799e7..d42303ad25 100644 --- a/platform/web/export/export_plugin.cpp +++ b/platform/web/export/export_plugin.cpp @@ -585,32 +585,176 @@ bool EditorExportPlatformWeb::poll_export() { } } - int prev = menu_options; - menu_options = preset.is_valid(); + HTTPServerState prev_server_state = server_state; + server_state = HTTP_SERVER_STATE_OFF; if (server->is_listening()) { - if (menu_options == 0) { + if (preset.is_null()) { server->stop(); } else { - menu_options += 1; + server_state = HTTP_SERVER_STATE_ON; } } - return menu_options != prev; + + return server_state != prev_server_state; } Ref<ImageTexture> EditorExportPlatformWeb::get_option_icon(int p_index) const { - return p_index == 1 ? stop_icon : EditorExportPlatform::get_option_icon(p_index); + Ref<ImageTexture> play_icon = EditorExportPlatform::get_option_icon(p_index); + + switch (server_state) { + case HTTP_SERVER_STATE_OFF: { + switch (p_index) { + case 0: + case 1: + return play_icon; + } + } break; + + case HTTP_SERVER_STATE_ON: { + switch (p_index) { + case 0: + return play_icon; + case 1: + return restart_icon; + case 2: + return stop_icon; + } + } break; + } + + ERR_FAIL_V_MSG(nullptr, vformat(R"(EditorExportPlatformWeb option icon index "%s" is invalid.)", p_index)); } int EditorExportPlatformWeb::get_options_count() const { - return menu_options; + if (server_state == HTTP_SERVER_STATE_ON) { + return 3; + } + return 2; +} + +String EditorExportPlatformWeb::get_option_label(int p_index) const { + String run_in_browser = TTR("Run in Browser"); + String start_http_server = TTR("Start HTTP Server"); + String reexport_project = TTR("Re-export Project"); + String stop_http_server = TTR("Stop HTTP Server"); + + switch (server_state) { + case HTTP_SERVER_STATE_OFF: { + switch (p_index) { + case 0: + return run_in_browser; + case 1: + return start_http_server; + } + } break; + + case HTTP_SERVER_STATE_ON: { + switch (p_index) { + case 0: + return run_in_browser; + case 1: + return reexport_project; + case 2: + return stop_http_server; + } + } break; + } + + ERR_FAIL_V_MSG("", vformat(R"(EditorExportPlatformWeb option label index "%s" is invalid.)", p_index)); +} + +String EditorExportPlatformWeb::get_option_tooltip(int p_index) const { + String run_in_browser = TTR("Run exported HTML in the system's default browser."); + String start_http_server = TTR("Start the HTTP server."); + String reexport_project = TTR("Export project again to account for updates."); + String stop_http_server = TTR("Stop the HTTP server."); + + switch (server_state) { + case HTTP_SERVER_STATE_OFF: { + switch (p_index) { + case 0: + return run_in_browser; + case 1: + return start_http_server; + } + } break; + + case HTTP_SERVER_STATE_ON: { + switch (p_index) { + case 0: + return run_in_browser; + case 1: + return reexport_project; + case 2: + return stop_http_server; + } + } break; + } + + ERR_FAIL_V_MSG("", vformat(R"(EditorExportPlatformWeb option tooltip index "%s" is invalid.)", p_index)); } Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) { - if (p_option == 1) { - server->stop(); - return OK; + const uint16_t bind_port = EDITOR_GET("export/web/http_port"); + // Resolve host if needed. + const String bind_host = EDITOR_GET("export/web/http_host"); + const bool use_tls = EDITOR_GET("export/web/use_tls"); + + switch (server_state) { + case HTTP_SERVER_STATE_OFF: { + switch (p_option) { + // Run in Browser. + case 0: { + Error err = _export_project(p_preset, p_debug_flags); + if (err != OK) { + return err; + } + err = _start_server(bind_host, bind_port, use_tls); + if (err != OK) { + return err; + } + return _launch_browser(bind_host, bind_port, use_tls); + } break; + + // Start HTTP Server. + case 1: { + Error err = _export_project(p_preset, p_debug_flags); + if (err != OK) { + return err; + } + return _start_server(bind_host, bind_port, use_tls); + } break; + } + } break; + + case HTTP_SERVER_STATE_ON: { + switch (p_option) { + // Run in Browser. + case 0: { + Error err = _export_project(p_preset, p_debug_flags); + if (err != OK) { + return err; + } + return _launch_browser(bind_host, bind_port, use_tls); + } break; + + // Re-export Project. + case 1: { + return _export_project(p_preset, p_debug_flags); + } break; + + // Stop HTTP Server. + case 2: { + return _stop_server(); + } break; + } + } break; } + ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, vformat(R"(Trying to run EditorExportPlatformWeb, but option "%s" isn't known.)", p_option)); +} + +Error EditorExportPlatformWeb::_export_project(const Ref<EditorExportPreset> &p_preset, int p_debug_flags) { const String dest = EditorPaths::get_singleton()->get_cache_dir().path_join("web"); Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); if (!da->dir_exists(dest)) { @@ -637,35 +781,40 @@ Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int DirAccess::remove_file_or_error(basepath + ".wasm"); DirAccess::remove_file_or_error(basepath + ".icon.png"); DirAccess::remove_file_or_error(basepath + ".apple-touch-icon.png"); - return err; } + return err; +} - const uint16_t bind_port = EDITOR_GET("export/web/http_port"); - // Resolve host if needed. - const String bind_host = EDITOR_GET("export/web/http_host"); +Error EditorExportPlatformWeb::_launch_browser(const String &p_bind_host, const uint16_t p_bind_port, const bool p_use_tls) { + OS::get_singleton()->shell_open(String((p_use_tls ? "https://" : "http://") + p_bind_host + ":" + itos(p_bind_port) + "/tmp_js_export.html")); + // FIXME: Find out how to clean up export files after running the successfully + // exported game. Might not be trivial. + return OK; +} + +Error EditorExportPlatformWeb::_start_server(const String &p_bind_host, const uint16_t p_bind_port, const bool p_use_tls) { IPAddress bind_ip; - if (bind_host.is_valid_ip_address()) { - bind_ip = bind_host; + if (p_bind_host.is_valid_ip_address()) { + bind_ip = p_bind_host; } else { - bind_ip = IP::get_singleton()->resolve_hostname(bind_host); + bind_ip = IP::get_singleton()->resolve_hostname(p_bind_host); } - ERR_FAIL_COND_V_MSG(!bind_ip.is_valid(), ERR_INVALID_PARAMETER, "Invalid editor setting 'export/web/http_host': '" + bind_host + "'. Try using '127.0.0.1'."); + ERR_FAIL_COND_V_MSG(!bind_ip.is_valid(), ERR_INVALID_PARAMETER, "Invalid editor setting 'export/web/http_host': '" + p_bind_host + "'. Try using '127.0.0.1'."); - const bool use_tls = EDITOR_GET("export/web/use_tls"); const String tls_key = EDITOR_GET("export/web/tls_key"); const String tls_cert = EDITOR_GET("export/web/tls_certificate"); // Restart server. server->stop(); - err = server->listen(bind_port, bind_ip, use_tls, tls_key, tls_cert); + Error err = server->listen(p_bind_port, bind_ip, p_use_tls, tls_key, tls_cert); if (err != OK) { add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), vformat(TTR("Error starting HTTP server: %d."), err)); - return err; } + return err; +} - OS::get_singleton()->shell_open(String((use_tls ? "https://" : "http://") + bind_host + ":" + itos(bind_port) + "/tmp_js_export.html")); - // FIXME: Find out how to clean up export files after running the successfully - // exported game. Might not be trivial. +Error EditorExportPlatformWeb::_stop_server() { + server->stop(); return OK; } @@ -691,8 +840,10 @@ EditorExportPlatformWeb::EditorExportPlatformWeb() { Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme(); if (theme.is_valid()) { stop_icon = theme->get_icon(SNAME("Stop"), EditorStringName(EditorIcons)); + restart_icon = theme->get_icon(SNAME("Reload"), EditorStringName(EditorIcons)); } else { stop_icon.instantiate(); + restart_icon.instantiate(); } } } diff --git a/platform/web/export/export_plugin.h b/platform/web/export/export_plugin.h index 952d03cdb4..9d3a1a7861 100644 --- a/platform/web/export/export_plugin.h +++ b/platform/web/export/export_plugin.h @@ -46,10 +46,16 @@ class EditorExportPlatformWeb : public EditorExportPlatform { GDCLASS(EditorExportPlatformWeb, EditorExportPlatform); + enum HTTPServerState { + HTTP_SERVER_STATE_OFF, + HTTP_SERVER_STATE_ON, + }; + Ref<ImageTexture> logo; Ref<ImageTexture> run_icon; Ref<ImageTexture> stop_icon; - int menu_options = 0; + Ref<ImageTexture> restart_icon; + HTTPServerState server_state = HTTP_SERVER_STATE_OFF; Ref<EditorHTTPServer> server; @@ -96,6 +102,11 @@ class EditorExportPlatformWeb : public EditorExportPlatform { Error _build_pwa(const Ref<EditorExportPreset> &p_preset, const String p_path, const Vector<SharedObject> &p_shared_objects); Error _write_or_error(const uint8_t *p_content, int p_len, String p_path); + Error _export_project(const Ref<EditorExportPreset> &p_preset, int p_debug_flags); + Error _launch_browser(const String &p_bind_host, uint16_t p_bind_port, bool p_use_tls); + Error _start_server(const String &p_bind_host, uint16_t p_bind_port, bool p_use_tls); + Error _stop_server(); + public: virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override; @@ -112,8 +123,8 @@ public: virtual bool poll_export() override; virtual int get_options_count() const override; - virtual String get_option_label(int p_index) const override { return p_index ? TTR("Stop HTTP Server") : TTR("Run in Browser"); } - virtual String get_option_tooltip(int p_index) const override { return p_index ? TTR("Stop HTTP Server") : TTR("Run exported HTML in the system's default browser."); } + virtual String get_option_label(int p_index) const override; + virtual String get_option_tooltip(int p_index) const override; virtual Ref<ImageTexture> get_option_icon(int p_index) const override; virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) override; virtual Ref<Texture2D> get_run_icon() const override; diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index ebae00017b..f101d85d58 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -3171,14 +3171,12 @@ void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) { } } -DisplayServer::IndicatorID DisplayServerWindows::create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) { +DisplayServer::IndicatorID DisplayServerWindows::create_status_indicator(const Ref<Texture2D> &p_icon, const String &p_tooltip, const Callable &p_callback) { HICON hicon = nullptr; if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) { - Ref<Image> img = p_icon; - if (img != icon) { - img = img->duplicate(); - img->convert(Image::FORMAT_RGBA8); - } + Ref<Image> img = p_icon->get_image(); + img = img->duplicate(); + img->convert(Image::FORMAT_RGBA8); int w = img->get_width(); int h = img->get_height(); @@ -3241,16 +3239,14 @@ DisplayServer::IndicatorID DisplayServerWindows::create_status_indicator(const R return iid; } -void DisplayServerWindows::status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) { +void DisplayServerWindows::status_indicator_set_icon(IndicatorID p_id, const Ref<Texture2D> &p_icon) { ERR_FAIL_COND(!indicators.has(p_id)); HICON hicon = nullptr; if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) { - Ref<Image> img = p_icon; - if (img != icon) { - img = img->duplicate(); - img->convert(Image::FORMAT_RGBA8); - } + Ref<Image> img = p_icon->get_image(); + img = img->duplicate(); + img->convert(Image::FORMAT_RGBA8); int w = img->get_width(); int h = img->get_height(); @@ -3317,12 +3313,42 @@ void DisplayServerWindows::status_indicator_set_tooltip(IndicatorID p_id, const Shell_NotifyIconW(NIM_MODIFY, &ndat); } +void DisplayServerWindows::status_indicator_set_menu(IndicatorID p_id, const RID &p_menu_rid) { + ERR_FAIL_COND(!indicators.has(p_id)); + + indicators[p_id].menu_rid = p_menu_rid; +} + void DisplayServerWindows::status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) { ERR_FAIL_COND(!indicators.has(p_id)); indicators[p_id].callback = p_callback; } +Rect2 DisplayServerWindows::status_indicator_get_rect(IndicatorID p_id) const { + ERR_FAIL_COND_V(!indicators.has(p_id), Rect2()); + + NOTIFYICONIDENTIFIER nid; + ZeroMemory(&nid, sizeof(NOTIFYICONIDENTIFIER)); + nid.cbSize = sizeof(NOTIFYICONIDENTIFIER); + nid.hWnd = windows[MAIN_WINDOW_ID].hWnd; + nid.uID = p_id; + nid.guidItem = GUID_NULL; + + RECT rect; + if (Shell_NotifyIconGetRect(&nid, &rect) != S_OK) { + return Rect2(); + } + Rect2 ind_rect = Rect2(Point2(rect.left, rect.top) - _get_screens_origin(), Size2(rect.right - rect.left, rect.bottom - rect.top)); + for (int i = 0; i < get_screen_count(); i++) { + Rect2 screen_rect = Rect2(screen_get_position(i), screen_get_size(i)); + if (screen_rect.encloses(ind_rect)) { + return ind_rect; + } + } + return Rect2(); +} + void DisplayServerWindows::delete_status_indicator(IndicatorID p_id) { ERR_FAIL_COND(!indicators.has(p_id)); @@ -3838,7 +3864,19 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mb = MouseButton::MB_XBUTTON1; } if (indicators.has(iid)) { - if (indicators[iid].callback.is_valid()) { + if (lParam == WM_RBUTTONDOWN && indicators[iid].menu_rid.is_valid() && native_menu->has_menu(indicators[iid].menu_rid)) { + NOTIFYICONIDENTIFIER nid; + ZeroMemory(&nid, sizeof(NOTIFYICONIDENTIFIER)); + nid.cbSize = sizeof(NOTIFYICONIDENTIFIER); + nid.hWnd = windows[MAIN_WINDOW_ID].hWnd; + nid.uID = iid; + nid.guidItem = GUID_NULL; + + RECT rect; + if (Shell_NotifyIconGetRect(&nid, &rect) == S_OK) { + native_menu->popup(indicators[iid].menu_rid, Vector2i((rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2)); + } + } else if (indicators[iid].callback.is_valid()) { Variant v_button = mb; Variant v_pos = mouse_get_position(); Variant *v_args[2] = { &v_button, &v_pos }; diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 2fe1b0733d..80f6061348 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -464,6 +464,7 @@ class DisplayServerWindows : public DisplayServer { WNDPROC user_proc = nullptr; struct IndicatorData { + RID menu_rid; Callable callback; }; @@ -684,10 +685,12 @@ public: virtual void set_native_icon(const String &p_filename) override; virtual void set_icon(const Ref<Image> &p_icon) override; - virtual IndicatorID create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) override; - virtual void status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) override; + virtual IndicatorID create_status_indicator(const Ref<Texture2D> &p_icon, const String &p_tooltip, const Callable &p_callback) override; + virtual void status_indicator_set_icon(IndicatorID p_id, const Ref<Texture2D> &p_icon) override; virtual void status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) override; + virtual void status_indicator_set_menu(IndicatorID p_id, const RID &p_rid) override; virtual void status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) override; + virtual Rect2 status_indicator_get_rect(IndicatorID p_id) const override; virtual void delete_status_indicator(IndicatorID p_id) override; virtual void set_context(Context p_context) override; diff --git a/scene/2d/parallax_2d.cpp b/scene/2d/parallax_2d.cpp index 555f3b031c..aacab3213d 100644 --- a/scene/2d/parallax_2d.cpp +++ b/scene/2d/parallax_2d.cpp @@ -144,7 +144,7 @@ void Parallax2D::set_repeat_size(const Size2 &p_repeat_size) { return; } - repeat_size = p_repeat_size.max(Vector2(0, 0)); + repeat_size = p_repeat_size.maxf(0); _update_process(); _update_repeat(); diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index 0dc9834539..db7b80683c 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -880,7 +880,7 @@ void CPUParticles3D::_particles_process(double p_delta) { } break; case EMISSION_SHAPE_RING: { real_t ring_random_angle = Math::randf() * Math_TAU; - real_t ring_random_radius = Math::randf() * (emission_ring_radius - emission_ring_inner_radius) + emission_ring_inner_radius; + real_t ring_random_radius = Math::sqrt(Math::randf() * (emission_ring_radius - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius); Vector3 axis = emission_ring_axis == Vector3(0.0, 0.0, 0.0) ? Vector3(0.0, 0.0, 1.0) : emission_ring_axis.normalized(); Vector3 ortho_axis; if (axis.abs() == Vector3(1.0, 0.0, 0.0)) { diff --git a/scene/3d/decal.cpp b/scene/3d/decal.cpp index 8415fb38cb..485599d0fb 100644 --- a/scene/3d/decal.cpp +++ b/scene/3d/decal.cpp @@ -31,7 +31,7 @@ #include "decal.h" void Decal::set_size(const Vector3 &p_size) { - size = p_size.max(Vector3(0.001, 0.001, 0.001)); + size = p_size.maxf(0.001); RS::get_singleton()->decal_set_size(decal, size); update_gizmos(); } diff --git a/scene/3d/fog_volume.cpp b/scene/3d/fog_volume.cpp index 8af386f282..54631a8dff 100644 --- a/scene/3d/fog_volume.cpp +++ b/scene/3d/fog_volume.cpp @@ -73,7 +73,7 @@ bool FogVolume::_get(const StringName &p_name, Variant &r_property) const { void FogVolume::set_size(const Vector3 &p_size) { size = p_size; - size = size.max(Vector3()); + size = size.maxf(0); RS::get_singleton()->fog_volume_set_size(_get_volume(), size); update_gizmos(); } diff --git a/scene/3d/gpu_particles_collision_3d.cpp b/scene/3d/gpu_particles_collision_3d.cpp index 8fd5f25749..3a05ec9c9e 100644 --- a/scene/3d/gpu_particles_collision_3d.cpp +++ b/scene/3d/gpu_particles_collision_3d.cpp @@ -382,7 +382,7 @@ Vector3i GPUParticlesCollisionSDF3D::get_estimated_cell_size() const { float cell_size = aabb.get_longest_axis_size() / float(subdiv); Vector3i sdf_size = Vector3i(aabb.size / cell_size); - sdf_size = sdf_size.max(Vector3i(1, 1, 1)); + sdf_size = sdf_size.maxi(1); return sdf_size; } @@ -395,7 +395,7 @@ Ref<Image> GPUParticlesCollisionSDF3D::bake() { float cell_size = aabb.get_longest_axis_size() / float(subdiv); Vector3i sdf_size = Vector3i(aabb.size / cell_size); - sdf_size = sdf_size.max(Vector3i(1, 1, 1)); + sdf_size = sdf_size.maxi(1); if (bake_begin_function) { bake_begin_function(100); diff --git a/scene/3d/occluder_instance_3d.cpp b/scene/3d/occluder_instance_3d.cpp index 2f77185d0d..150771545b 100644 --- a/scene/3d/occluder_instance_3d.cpp +++ b/scene/3d/occluder_instance_3d.cpp @@ -192,7 +192,7 @@ void QuadOccluder3D::set_size(const Size2 &p_size) { return; } - size = p_size.max(Size2()); + size = p_size.maxf(0); _update(); } @@ -236,7 +236,7 @@ void BoxOccluder3D::set_size(const Vector3 &p_size) { return; } - size = p_size.max(Vector3()); + size = p_size.maxf(0); _update(); } diff --git a/scene/3d/voxel_gi.cpp b/scene/3d/voxel_gi.cpp index 938d6e5699..fbdda67526 100644 --- a/scene/3d/voxel_gi.cpp +++ b/scene/3d/voxel_gi.cpp @@ -294,7 +294,7 @@ VoxelGI::Subdiv VoxelGI::get_subdiv() const { void VoxelGI::set_size(const Vector3 &p_size) { // Prevent very small size dimensions as these breaks baking if other size dimensions are set very high. - size = p_size.max(Vector3(1.0, 1.0, 1.0)); + size = p_size.maxf(1.0); update_gizmos(); } diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index 5a3a5f9bc0..d22b58346f 100644 --- a/scene/animation/animation_mixer.cpp +++ b/scene/animation/animation_mixer.cpp @@ -1617,7 +1617,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } if (seeked) { // Seek. - int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT, true); + int idx = a->track_find_key(i, time, Animation::FIND_MODE_NEAREST, true); if (idx < 0) { continue; } @@ -1630,6 +1630,9 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { double at_anim_pos = 0.0; switch (anim->get_loop_mode()) { case Animation::LOOP_NONE: { + if (!is_external_seeking && ((!backward && time >= pos + (double)anim->get_length()) || (backward && time <= pos))) { + continue; // Do nothing if current time is outside of length when started. + } at_anim_pos = MIN((double)anim->get_length(), time - pos); // Seek to end. } break; case Animation::LOOP_LINEAR: { @@ -1641,7 +1644,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { default: break; } - if (player2->is_playing()) { + if (player2->is_playing() || !is_external_seeking) { player2->seek(at_anim_pos, false, p_update_only); player2->play(anim_name); t->playing = true; @@ -2090,7 +2093,7 @@ Ref<AnimatedValuesBackup> AnimationMixer::apply_reset(bool p_user_initiated) { void AnimationMixer::capture(const StringName &p_name, double p_duration, Tween::TransitionType p_trans_type, Tween::EaseType p_ease_type) { ERR_FAIL_COND(!active); ERR_FAIL_COND(!has_animation(p_name)); - ERR_FAIL_COND(Math::is_zero_approx(p_duration)); + ERR_FAIL_COND(p_duration <= 0); Ref<Animation> reference_animation = get_animation(p_name); if (!cache_valid) { diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index 7140161eca..e4808a0ecc 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -370,73 +370,21 @@ void AnimationPlayer::play_backwards(const StringName &p_name, double p_custom_b play(p_name, p_custom_blend, -1, true); } -void AnimationPlayer::play_with_capture(const StringName &p_name, double p_duration, double p_custom_blend, float p_custom_scale, bool p_from_end, Tween::TransitionType p_trans_type, Tween::EaseType p_ease_type) { - StringName name = p_name; - if (name == StringName()) { - name = playback.assigned; - } - - if (signbit(p_duration)) { - double max_dur = 0; - Ref<Animation> anim = get_animation(name); - if (anim.is_valid()) { - double current_pos = playback.current.pos; - if (playback.assigned != name) { - current_pos = p_from_end ? anim->get_length() : 0; - } - for (int i = 0; i < anim->get_track_count(); i++) { - if (anim->track_get_type(i) != Animation::TYPE_VALUE) { - continue; - } - if (anim->value_track_get_update_mode(i) != Animation::UPDATE_CAPTURE) { - continue; - } - if (anim->track_get_key_count(i) == 0) { - continue; - } - max_dur = MAX(max_dur, p_from_end ? current_pos - anim->track_get_key_time(i, anim->track_get_key_count(i) - 1) : anim->track_get_key_time(i, 0) - current_pos); - } - } - p_duration = max_dur; +void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) { + if (auto_capture) { + play_with_capture(p_name, -1.0, p_custom_blend, p_custom_scale, p_from_end); + } else { + _play(p_name, p_custom_blend, p_custom_scale, p_from_end); } - - capture(name, p_duration, p_trans_type, p_ease_type); - play(name, p_custom_blend, p_custom_scale, p_from_end); } -void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) { +void AnimationPlayer::_play(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) { StringName name = p_name; if (name == StringName()) { name = playback.assigned; } -#ifdef TOOLS_ENABLED - if (!Engine::get_singleton()->is_editor_hint()) { - bool warn_enabled = false; - if (capture_cache.animation.is_null()) { - Ref<Animation> anim = get_animation(name); - if (anim.is_valid()) { - for (int i = 0; i < anim->get_track_count(); i++) { - if (anim->track_get_type(i) != Animation::TYPE_VALUE) { - continue; - } - if (anim->value_track_get_update_mode(i) != Animation::UPDATE_CAPTURE) { - continue; - } - if (anim->track_get_key_count(i) == 0) { - continue; - } - warn_enabled = true; - } - } - } - if (warn_enabled) { - WARN_PRINT_ONCE_ED("Capture track found. If you want to interpolate animation with captured frame, you can use play_with_capture() instead of play()."); - } - } -#endif - ERR_FAIL_COND_MSG(!animation_set.has(name), vformat("Animation not found: %s.", name)); Playback &c = playback; @@ -525,6 +473,47 @@ void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, floa } } +void AnimationPlayer::_capture(const StringName &p_name, bool p_from_end, double p_duration, Tween::TransitionType p_trans_type, Tween::EaseType p_ease_type) { + StringName name = p_name; + if (name == StringName()) { + name = playback.assigned; + } + + Ref<Animation> anim = get_animation(name); + if (anim.is_null() || !anim->is_capture_included()) { + return; + } + if (signbit(p_duration)) { + double max_dur = 0; + double current_pos = playback.current.pos; + if (playback.assigned != name) { + current_pos = p_from_end ? anim->get_length() : 0; + } + for (int i = 0; i < anim->get_track_count(); i++) { + if (anim->track_get_type(i) != Animation::TYPE_VALUE) { + continue; + } + if (anim->value_track_get_update_mode(i) != Animation::UPDATE_CAPTURE) { + continue; + } + if (anim->track_get_key_count(i) == 0) { + continue; + } + max_dur = MAX(max_dur, p_from_end ? current_pos - anim->track_get_key_time(i, anim->track_get_key_count(i) - 1) : anim->track_get_key_time(i, 0) - current_pos); + } + p_duration = max_dur; + } + if (Math::is_zero_approx(p_duration)) { + return; + } + capture(name, p_duration, p_trans_type, p_ease_type); +} + +void AnimationPlayer::play_with_capture(const StringName &p_name, double p_duration, double p_custom_blend, float p_custom_scale, bool p_from_end, Tween::TransitionType p_trans_type, Tween::EaseType p_ease_type) { + _capture(p_name, p_from_end, p_duration, p_trans_type, p_ease_type); + _play(p_name, p_custom_blend, p_custom_scale, p_from_end); +} + bool AnimationPlayer::is_playing() const { return playing; } @@ -725,6 +714,14 @@ double AnimationPlayer::get_blend_time(const StringName &p_animation1, const Str } } +void AnimationPlayer::set_auto_capture(bool p_auto_capture) { + auto_capture = p_auto_capture; +} + +bool AnimationPlayer::is_auto_capture() const { + return auto_capture; +} + #ifdef TOOLS_ENABLED void AnimationPlayer::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { const String pf = p_function; @@ -815,9 +812,12 @@ void AnimationPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_default_blend_time", "sec"), &AnimationPlayer::set_default_blend_time); ClassDB::bind_method(D_METHOD("get_default_blend_time"), &AnimationPlayer::get_default_blend_time); + ClassDB::bind_method(D_METHOD("set_auto_capture", "auto_capture"), &AnimationPlayer::set_auto_capture); + ClassDB::bind_method(D_METHOD("is_auto_capture"), &AnimationPlayer::is_auto_capture); + ClassDB::bind_method(D_METHOD("play", "name", "custom_blend", "custom_speed", "from_end"), &AnimationPlayer::play, DEFVAL(StringName()), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false)); ClassDB::bind_method(D_METHOD("play_backwards", "name", "custom_blend"), &AnimationPlayer::play_backwards, DEFVAL(StringName()), DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("play_with_capture", "name", "duration", "custom_blend", "custom_speed", "from_end", "trans_type", "ease_type"), &AnimationPlayer::play_with_capture, DEFVAL(-1.0), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false), DEFVAL(Tween::TRANS_LINEAR), DEFVAL(Tween::EASE_IN)); + ClassDB::bind_method(D_METHOD("play_with_capture", "name", "duration", "custom_blend", "custom_speed", "from_end", "trans_type", "ease_type"), &AnimationPlayer::play_with_capture, DEFVAL(StringName()), DEFVAL(-1.0), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false), DEFVAL(Tween::TRANS_LINEAR), DEFVAL(Tween::EASE_IN)); ClassDB::bind_method(D_METHOD("pause"), &AnimationPlayer::pause); ClassDB::bind_method(D_METHOD("stop", "keep_state"), &AnimationPlayer::stop, DEFVAL(false)); ClassDB::bind_method(D_METHOD("is_playing"), &AnimationPlayer::is_playing); @@ -855,6 +855,7 @@ void AnimationPlayer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "current_animation_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_current_animation_position"); ADD_GROUP("Playback Options", "playback_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playback_auto_capture"), "set_auto_capture", "is_auto_capture"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "playback_default_blend_time", PROPERTY_HINT_RANGE, "0,4096,0.01,suffix:s"), "set_default_blend_time", "get_default_blend_time"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "-4,4,0.001,or_less,or_greater"), "set_speed_scale", "get_speed_scale"); diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h index 13e1e37ca9..3b229e7699 100644 --- a/scene/animation/animation_player.h +++ b/scene/animation/animation_player.h @@ -56,6 +56,7 @@ private: float speed_scale = 1.0; double default_blend_time = 0.0; + bool auto_capture = true; bool is_stopping = false; struct PlaybackData { @@ -108,6 +109,8 @@ private: bool reset_on_save = true; bool movie_quit_on_finish = false; + void _play(const StringName &p_name, double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false); + void _capture(const StringName &p_name, bool p_from_end = false, double p_duration = -1.0, Tween::TransitionType p_trans_type = Tween::TRANS_LINEAR, Tween::EaseType p_ease_type = Tween::EASE_IN); void _process_playback_data(PlaybackData &cd, double p_delta, float p_blend, bool p_seeked, bool p_started, bool p_is_current = false); void _blend_playback_data(double p_delta, bool p_started); void _stop_internal(bool p_reset, bool p_keep_state); @@ -158,9 +161,12 @@ public: void set_default_blend_time(double p_default); double get_default_blend_time() const; + void set_auto_capture(bool p_auto_capture); + bool is_auto_capture() const; + void play(const StringName &p_name = StringName(), double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false); void play_backwards(const StringName &p_name = StringName(), double p_custom_blend = -1); - void play_with_capture(const StringName &p_name, double p_duration = -1.0, double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false, Tween::TransitionType p_trans_type = Tween::TRANS_LINEAR, Tween::EaseType p_ease_type = Tween::EASE_IN); + void play_with_capture(const StringName &p_name = StringName(), double p_duration = -1.0, double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false, Tween::TransitionType p_trans_type = Tween::TRANS_LINEAR, Tween::EaseType p_ease_type = Tween::EASE_IN); void queue(const StringName &p_name); Vector<String> get_queue(); void clear_queue(); diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index d430fe9bfc..7ac7ceb6bc 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -142,8 +142,8 @@ Size2 Control::_edit_get_scale() const { void Control::_edit_set_rect(const Rect2 &p_edit_rect) { ERR_FAIL_COND_MSG(!Engine::get_singleton()->is_editor_hint(), "This function can only be used from editor plugins."); - set_position((get_position() + get_transform().basis_xform(p_edit_rect.position)).snapped(Vector2(1, 1)), ControlEditorToolbar::get_singleton()->is_anchors_mode_enabled()); - set_size(p_edit_rect.size.snapped(Vector2(1, 1)), ControlEditorToolbar::get_singleton()->is_anchors_mode_enabled()); + set_position((get_position() + get_transform().basis_xform(p_edit_rect.position)).snappedf(1), ControlEditorToolbar::get_singleton()->is_anchors_mode_enabled()); + set_size(p_edit_rect.size.snappedf(1), ControlEditorToolbar::get_singleton()->is_anchors_mode_enabled()); } Rect2 Control::_edit_get_rect() const { diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index ef9c7e35ed..646e45b27a 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -501,7 +501,7 @@ void GraphEdit::_graph_element_resize_request(const Vector2 &p_new_minsize, Node // Snap the new size to the grid if snapping is enabled. Vector2 new_size = p_new_minsize; if (snapping_enabled ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) { - new_size = new_size.snapped(Vector2(snapping_distance, snapping_distance)); + new_size = new_size.snappedf(snapping_distance); } // Disallow resizing the frame to a size smaller than the minimum size of the attached nodes. @@ -851,7 +851,7 @@ void GraphEdit::_set_position_of_frame_attached_nodes(GraphFrame *p_frame, const Vector2 pos = (attached_node->get_drag_from() * zoom + drag_accum) / zoom; if (snapping_enabled ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) { - pos = pos.snapped(Vector2(snapping_distance, snapping_distance)); + pos = pos.snappedf(snapping_distance); } // Recursively move graph frames. @@ -1678,7 +1678,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { // Snapping can be toggled temporarily by holding down Ctrl. // This is done here as to not toggle the grid when holding down Ctrl. if (snapping_enabled ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) { - pos = pos.snapped(Vector2(snapping_distance, snapping_distance)); + pos = pos.snappedf(snapping_distance); } graph_element->set_position_offset(pos); diff --git a/scene/gui/graph_edit_arranger.cpp b/scene/gui/graph_edit_arranger.cpp index 49998beb42..fa1059c667 100644 --- a/scene/gui/graph_edit_arranger.cpp +++ b/scene/gui/graph_edit_arranger.cpp @@ -180,7 +180,7 @@ void GraphEditArranger::arrange_nodes() { if (graph_edit->is_snapping_enabled()) { float snapping_distance = graph_edit->get_snapping_distance(); - pos = pos.snapped(Vector2(snapping_distance, snapping_distance)); + pos = pos.snappedf(snapping_distance); } graph_node->set_position_offset(pos); graph_node->set_drag(false); diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp index b2617e6fc7..90ce01e383 100644 --- a/scene/gui/progress_bar.cpp +++ b/scene/gui/progress_bar.cpp @@ -41,7 +41,7 @@ Size2 ProgressBar::get_minimum_size() const { TextLine tl = TextLine(txt, theme_cache.font, theme_cache.font_size); minimum_size.height = MAX(minimum_size.height, theme_cache.background_style->get_minimum_size().height + tl.get_size().y); } else { // this is needed, else the progressbar will collapse - minimum_size = minimum_size.max(Size2(1, 1)); + minimum_size = minimum_size.maxf(1); } return minimum_size; } diff --git a/scene/gui/rich_text_label.compat.inc b/scene/gui/rich_text_label.compat.inc index 626278a405..97739c4b79 100644 --- a/scene/gui/rich_text_label.compat.inc +++ b/scene/gui/rich_text_label.compat.inc @@ -38,9 +38,14 @@ void RichTextLabel::_add_image_bind_compat_80410(const Ref<Texture2D> &p_image, add_image(p_image, p_width, p_height, p_color, p_alignment, p_region, Variant(), false, String(), false); } +bool RichTextLabel::_remove_paragraph_bind_compat_91098(int p_paragraph) { + return remove_paragraph(p_paragraph, false); +} + void RichTextLabel::_bind_compatibility_methods() { ClassDB::bind_compatibility_method(D_METHOD("push_meta", "data"), &RichTextLabel::_push_meta_bind_compat_89024); ClassDB::bind_compatibility_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align", "region"), &RichTextLabel::_add_image_bind_compat_80410, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2())); + ClassDB::bind_compatibility_method(D_METHOD("remove_paragraph", "paragraph"), &RichTextLabel::_remove_paragraph_bind_compat_91098); } #endif // DISABLE_DEPRECATED diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 0773181239..19b02f33c6 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -3342,7 +3342,7 @@ void RichTextLabel::_remove_frame(HashSet<Item *> &r_erase_list, ItemFrame *p_fr } } -bool RichTextLabel::remove_paragraph(const int p_paragraph) { +bool RichTextLabel::remove_paragraph(int p_paragraph, bool p_no_invalidate) { _stop_thread(); MutexLock data_lock(data_mutex); @@ -3391,8 +3391,44 @@ bool RichTextLabel::remove_paragraph(const int p_paragraph) { selection.click_frame = nullptr; selection.click_item = nullptr; - deselect(); + selection.active = false; + + if (p_no_invalidate) { + // Do not invalidate cache, only update vertical offsets of the paragraphs after deleted one and scrollbar. + int to_line = main->first_invalid_line.load() - 1; + float total_height = (p_paragraph == 0) ? 0 : _calculate_line_vertical_offset(main->lines[p_paragraph - 1]); + for (int i = p_paragraph; i < to_line; i++) { + MutexLock lock(main->lines[to_line - 1].text_buf->get_mutex()); + main->lines[i].offset.y = total_height; + total_height = _calculate_line_vertical_offset(main->lines[i]); + } + updating_scroll = true; + vscroll->set_max(total_height); + updating_scroll = false; + + main->first_invalid_line.store(MAX(main->first_invalid_line.load() - 1, 0)); + main->first_resized_line.store(MAX(main->first_resized_line.load() - 1, 0)); + main->first_invalid_font_line.store(MAX(main->first_invalid_font_line.load() - 1, 0)); + } else { + // Invalidate cache after the deleted paragraph. + main->first_invalid_line.store(MIN(main->first_invalid_line.load(), p_paragraph)); + main->first_resized_line.store(MIN(main->first_resized_line.load(), p_paragraph)); + main->first_invalid_font_line.store(MIN(main->first_invalid_font_line.load(), p_paragraph)); + } + queue_redraw(); + + return true; +} + +bool RichTextLabel::invalidate_paragraph(int p_paragraph) { + _stop_thread(); + MutexLock data_lock(data_mutex); + + if (p_paragraph >= (int)main->lines.size() || p_paragraph < 0) { + return false; + } + // Invalidate cache. main->first_invalid_line.store(MIN(main->first_invalid_line.load(), p_paragraph)); main->first_resized_line.store(MIN(main->first_resized_line.load(), p_paragraph)); main->first_invalid_font_line.store(MIN(main->first_invalid_font_line.load(), p_paragraph)); @@ -5851,7 +5887,8 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align", "region", "key", "pad", "tooltip", "size_in_percent"), &RichTextLabel::add_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2()), DEFVAL(Variant()), DEFVAL(false), DEFVAL(String()), DEFVAL(false)); ClassDB::bind_method(D_METHOD("update_image", "key", "mask", "image", "width", "height", "color", "inline_align", "region", "pad", "tooltip", "size_in_percent"), &RichTextLabel::update_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2()), DEFVAL(false), DEFVAL(String()), DEFVAL(false)); ClassDB::bind_method(D_METHOD("newline"), &RichTextLabel::add_newline); - ClassDB::bind_method(D_METHOD("remove_paragraph", "paragraph"), &RichTextLabel::remove_paragraph); + ClassDB::bind_method(D_METHOD("remove_paragraph", "paragraph", "no_invalidate"), &RichTextLabel::remove_paragraph, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("invalidate_paragraph", "paragraph"), &RichTextLabel::invalidate_paragraph); ClassDB::bind_method(D_METHOD("push_font", "font", "font_size"), &RichTextLabel::push_font, DEFVAL(0)); ClassDB::bind_method(D_METHOD("push_font_size", "font_size"), &RichTextLabel::push_font_size); ClassDB::bind_method(D_METHOD("push_normal"), &RichTextLabel::push_normal); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 371f6724d7..189ee1da6e 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -134,6 +134,7 @@ protected: #ifndef DISABLE_DEPRECATED void _push_meta_bind_compat_89024(const Variant &p_meta); void _add_image_bind_compat_80410(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region); + bool _remove_paragraph_bind_compat_91098(int p_paragraph); static void _bind_compatibility_methods(); #endif @@ -664,7 +665,8 @@ public: void add_image(const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER, const Rect2 &p_region = Rect2(), const Variant &p_key = Variant(), bool p_pad = false, const String &p_tooltip = String(), bool p_size_in_percent = false); void update_image(const Variant &p_key, BitField<ImageUpdateMask> p_mask, const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER, const Rect2 &p_region = Rect2(), bool p_pad = false, const String &p_tooltip = String(), bool p_size_in_percent = false); void add_newline(); - bool remove_paragraph(const int p_paragraph); + bool remove_paragraph(int p_paragraph, bool p_no_invalidate = false); + bool invalidate_paragraph(int p_paragraph); void push_dropcap(const String &p_string, const Ref<Font> &p_font, int p_size, const Rect2 &p_dropcap_margins = Rect2(), const Color &p_color = Color(1, 1, 1), int p_ol_size = 0, const Color &p_ol_color = Color(0, 0, 0, 0)); void _push_def_font(DefaultFont p_def_font); void _push_def_font_var(DefaultFont p_def_font, const Ref<Font> &p_font, int p_size = -1); diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index e2feb59a8c..dc53cf82e6 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -147,9 +147,7 @@ void TabContainer::_notification(int p_what) { if (get_tab_count() > 0) { _refresh_tab_names(); } - } break; - case NOTIFICATION_POST_ENTER_TREE: { if (setup_current_tab >= -1) { set_current_tab(setup_current_tab); setup_current_tab = -2; @@ -191,6 +189,25 @@ void TabContainer::_notification(int p_what) { } } break; + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible() || setup_current_tab > -2) { + return; + } + + updating_visibility = true; + + // As the visibility change notification will be triggered for all children soon after, + // beat it to the punch and make sure that the correct node is the only one visible first. + // Otherwise, it can prevent a tab change done right before this container was made visible. + Vector<Control *> controls = _get_tab_controls(); + int current = get_current_tab(); + for (int i = 0; i < controls.size(); i++) { + controls[i]->set_visible(i == current); + } + + updating_visibility = false; + } break; + case NOTIFICATION_TRANSLATION_CHANGED: case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_THEME_CHANGED: { @@ -611,6 +628,7 @@ void TabContainer::set_current_tab(int p_current) { setup_current_tab = p_current; return; } + tab_bar->set_current_tab(p_current); } diff --git a/scene/gui/texture_button.cpp b/scene/gui/texture_button.cpp index 0b197c8c02..df90257e03 100644 --- a/scene/gui/texture_button.cpp +++ b/scene/gui/texture_button.cpp @@ -103,7 +103,7 @@ bool TextureButton::has_point(const Point2 &p_point) const { point *= scale; // finally, we need to check if the point is inside a rectangle with a position >= 0,0 and a size <= mask_size - rect.position = Point2().max(_texture_region.position); + rect.position = _texture_region.position.maxf(0); rect.size = mask_size.min(_texture_region.size); } diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp index fc9ac2ab18..bbe5ddf1c3 100644 --- a/scene/gui/texture_progress_bar.cpp +++ b/scene/gui/texture_progress_bar.cpp @@ -249,7 +249,7 @@ Point2 TextureProgressBar::get_relative_center() { p += rad_center_off; p.x /= progress->get_width(); p.y /= progress->get_height(); - p = p.clamp(Point2(), Point2(1, 1)); + p = p.clampf(0, 1); return p; } diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index b17d345f1f..376ace2fe2 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -3429,7 +3429,7 @@ Rect2 Tree::_get_content_rect() const { const real_t v_size = v_scroll->is_visible() ? (v_scroll->get_combined_minimum_size().x + theme_cache.scrollbar_h_separation) : 0; const real_t h_size = h_scroll->is_visible() ? (h_scroll->get_combined_minimum_size().y + theme_cache.scrollbar_v_separation) : 0; const Point2 scroll_begin = _get_scrollbar_layout_rect().get_end() - Vector2(v_size, h_size); - const Size2 offset = (content_rect.get_end() - scroll_begin).max(Vector2(0, 0)); + const Size2 offset = (content_rect.get_end() - scroll_begin).maxf(0); return content_rect.grow_individual(0, 0, -offset.x, -offset.y); } diff --git a/scene/main/canvas_item.compat.inc b/scene/main/canvas_item.compat.inc new file mode 100644 index 0000000000..7136fded15 --- /dev/null +++ b/scene/main/canvas_item.compat.inc @@ -0,0 +1,41 @@ +/**************************************************************************/ +/* canvas_item.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +void CanvasItem::_draw_circle_compat_84472(const Point2 &p_pos, real_t p_radius, const Color &p_color) { + draw_circle(p_pos, p_radius, p_color, true, -1.0, false); +} + +void CanvasItem::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("draw_circle", "position", "radius", "color"), &CanvasItem::_draw_circle_compat_84472); +} + +#endif diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index 56aa453407..cabba0f2ed 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "canvas_item.h" +#include "canvas_item.compat.inc" #include "scene/2d/canvas_group.h" #include "scene/main/canvas_layer.h" @@ -726,11 +727,40 @@ void CanvasItem::draw_rect(const Rect2 &p_rect, const Color &p_color, bool p_fil } } -void CanvasItem::draw_circle(const Point2 &p_pos, real_t p_radius, const Color &p_color) { +void CanvasItem::draw_circle(const Point2 &p_pos, real_t p_radius, const Color &p_color, bool p_filled, real_t p_width, bool p_antialiased) { ERR_THREAD_GUARD; ERR_DRAW_GUARD; - RenderingServer::get_singleton()->canvas_item_add_circle(canvas_item, p_pos, p_radius, p_color); + if (p_filled) { + if (p_width != -1.0) { + WARN_PRINT("The draw_circle() \"width\" argument has no effect when \"filled\" is \"true\"."); + } + + RenderingServer::get_singleton()->canvas_item_add_circle(canvas_item, p_pos, p_radius, p_color); + } else if (p_width >= 2.0 * p_radius) { + RenderingServer::get_singleton()->canvas_item_add_circle(canvas_item, p_pos, p_radius + 0.5 * p_width, p_color); + } else { + // Tessellation count is hardcoded. Keep in sync with the same variable in `RendererCanvasCull::canvas_item_add_circle()`. + const int circle_segments = 64; + + Vector<Vector2> points; + points.resize(circle_segments + 1); + + Vector2 *points_ptr = points.ptrw(); + const real_t circle_point_step = Math_TAU / circle_segments; + + for (int i = 0; i < circle_segments; i++) { + float angle = i * circle_point_step; + points_ptr[i].x = Math::cos(angle) * p_radius; + points_ptr[i].y = Math::sin(angle) * p_radius; + points_ptr[i] += p_pos; + } + points_ptr[circle_segments] = points_ptr[0]; + + Vector<Color> colors = { p_color }; + + RenderingServer::get_singleton()->canvas_item_add_polyline(canvas_item, points, colors, p_width, p_antialiased); + } } void CanvasItem::draw_texture(const Ref<Texture2D> &p_texture, const Point2 &p_pos, const Color &p_modulate) { @@ -1163,7 +1193,7 @@ void CanvasItem::_bind_methods() { ClassDB::bind_method(D_METHOD("draw_multiline", "points", "color", "width"), &CanvasItem::draw_multiline, DEFVAL(-1.0)); ClassDB::bind_method(D_METHOD("draw_multiline_colors", "points", "colors", "width"), &CanvasItem::draw_multiline_colors, DEFVAL(-1.0)); ClassDB::bind_method(D_METHOD("draw_rect", "rect", "color", "filled", "width"), &CanvasItem::draw_rect, DEFVAL(true), DEFVAL(-1.0)); - ClassDB::bind_method(D_METHOD("draw_circle", "position", "radius", "color"), &CanvasItem::draw_circle); + ClassDB::bind_method(D_METHOD("draw_circle", "position", "radius", "color", "filled", "width", "antialiased"), &CanvasItem::draw_circle, DEFVAL(true), DEFVAL(-1.0), DEFVAL(false)); ClassDB::bind_method(D_METHOD("draw_texture", "texture", "position", "modulate"), &CanvasItem::draw_texture, DEFVAL(Color(1, 1, 1, 1))); ClassDB::bind_method(D_METHOD("draw_texture_rect", "texture", "rect", "tile", "modulate", "transpose"), &CanvasItem::draw_texture_rect, DEFVAL(Color(1, 1, 1, 1)), DEFVAL(false)); ClassDB::bind_method(D_METHOD("draw_texture_rect_region", "texture", "rect", "src_rect", "modulate", "transpose", "clip_uv"), &CanvasItem::draw_texture_rect_region, DEFVAL(Color(1, 1, 1, 1)), DEFVAL(false), DEFVAL(true)); diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h index 8cec086ca6..ae7b195ead 100644 --- a/scene/main/canvas_item.h +++ b/scene/main/canvas_item.h @@ -166,6 +166,12 @@ protected: void _notification(int p_what); static void _bind_methods(); + +#ifndef DISABLE_DEPRECATED + void _draw_circle_compat_84472(const Point2 &p_pos, real_t p_radius, const Color &p_color); + static void _bind_compatibility_methods(); +#endif + void _validate_property(PropertyInfo &p_property) const; _FORCE_INLINE_ void set_hide_clip_children(bool p_value) { hide_clip_children = p_value; } @@ -273,7 +279,7 @@ public: void draw_multiline(const Vector<Point2> &p_points, const Color &p_color, real_t p_width = -1.0); void draw_multiline_colors(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width = -1.0); void draw_rect(const Rect2 &p_rect, const Color &p_color, bool p_filled = true, real_t p_width = -1.0); - void draw_circle(const Point2 &p_pos, real_t p_radius, const Color &p_color); + void draw_circle(const Point2 &p_pos, real_t p_radius, const Color &p_color, bool p_filled = true, real_t p_width = -1.0, bool p_antialiased = false); void draw_texture(const Ref<Texture2D> &p_texture, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1, 1)); void draw_texture_rect(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false); void draw_texture_rect_region(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = false); diff --git a/scene/main/status_indicator.cpp b/scene/main/status_indicator.cpp index 54b2ff75ca..891974f68f 100644 --- a/scene/main/status_indicator.cpp +++ b/scene/main/status_indicator.cpp @@ -30,6 +30,8 @@ #include "status_indicator.h" +#include "scene/gui/popup_menu.h" + void StatusIndicator::_notification(int p_what) { ERR_MAIN_THREAD_GUARD; #ifdef TOOLS_ENABLED @@ -43,12 +45,22 @@ void StatusIndicator::_notification(int p_what) { if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_STATUS_INDICATOR)) { if (visible && iid == DisplayServer::INVALID_INDICATOR_ID) { iid = DisplayServer::get_singleton()->create_status_indicator(icon, tooltip, callable_mp(this, &StatusIndicator::_callback)); + PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(menu)); + if (pm) { + RID menu_rid = pm->bind_global_menu(); + DisplayServer::get_singleton()->status_indicator_set_menu(iid, menu_rid); + } } } } break; case NOTIFICATION_EXIT_TREE: { if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_STATUS_INDICATOR)) { if (iid != DisplayServer::INVALID_INDICATOR_ID) { + PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(menu)); + if (pm) { + pm->unbind_global_menu(); + DisplayServer::get_singleton()->status_indicator_set_menu(iid, RID()); + } DisplayServer::get_singleton()->delete_status_indicator(iid); iid = DisplayServer::INVALID_INDICATOR_ID; } @@ -66,11 +78,15 @@ void StatusIndicator::_bind_methods() { ClassDB::bind_method(D_METHOD("get_icon"), &StatusIndicator::get_icon); ClassDB::bind_method(D_METHOD("set_visible", "visible"), &StatusIndicator::set_visible); ClassDB::bind_method(D_METHOD("is_visible"), &StatusIndicator::is_visible); + ClassDB::bind_method(D_METHOD("set_menu", "menu"), &StatusIndicator::set_menu); + ClassDB::bind_method(D_METHOD("get_menu"), &StatusIndicator::get_menu); + ClassDB::bind_method(D_METHOD("get_rect"), &StatusIndicator::get_rect); ADD_SIGNAL(MethodInfo("pressed", PropertyInfo(Variant::INT, "mouse_button"), PropertyInfo(Variant::VECTOR2I, "mouse_position"))); ADD_PROPERTY(PropertyInfo(Variant::STRING, "tooltip", PROPERTY_HINT_MULTILINE_TEXT), "set_tooltip", "get_tooltip"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Image"), "set_icon", "get_icon"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_icon", "get_icon"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "menu", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "PopupMenu"), "set_menu", "get_menu"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible"); } @@ -78,7 +94,7 @@ void StatusIndicator::_callback(MouseButton p_index, const Point2i &p_pos) { emit_signal(SNAME("pressed"), p_index, p_pos); } -void StatusIndicator::set_icon(const Ref<Image> &p_icon) { +void StatusIndicator::set_icon(const Ref<Texture2D> &p_icon) { ERR_MAIN_THREAD_GUARD; icon = p_icon; if (iid != DisplayServer::INVALID_INDICATOR_ID) { @@ -86,7 +102,7 @@ void StatusIndicator::set_icon(const Ref<Image> &p_icon) { } } -Ref<Image> StatusIndicator::get_icon() const { +Ref<Texture2D> StatusIndicator::get_icon() const { return icon; } @@ -102,6 +118,30 @@ String StatusIndicator::get_tooltip() const { return tooltip; } +void StatusIndicator::set_menu(const NodePath &p_menu) { + PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(menu)); + if (pm) { + pm->unbind_global_menu(); + if (iid != DisplayServer::INVALID_INDICATOR_ID) { + DisplayServer::get_singleton()->status_indicator_set_menu(iid, RID()); + } + } + + menu = p_menu; + + pm = Object::cast_to<PopupMenu>(get_node_or_null(menu)); + if (pm) { + if (iid != DisplayServer::INVALID_INDICATOR_ID) { + RID menu_rid = pm->bind_global_menu(); + DisplayServer::get_singleton()->status_indicator_set_menu(iid, menu_rid); + } + } +} + +NodePath StatusIndicator::get_menu() const { + return menu; +} + void StatusIndicator::set_visible(bool p_visible) { ERR_MAIN_THREAD_GUARD; if (visible == p_visible) { @@ -122,8 +162,18 @@ void StatusIndicator::set_visible(bool p_visible) { if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_STATUS_INDICATOR)) { if (visible && iid == DisplayServer::INVALID_INDICATOR_ID) { iid = DisplayServer::get_singleton()->create_status_indicator(icon, tooltip, callable_mp(this, &StatusIndicator::_callback)); + PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(menu)); + if (pm) { + RID menu_rid = pm->bind_global_menu(); + DisplayServer::get_singleton()->status_indicator_set_menu(iid, menu_rid); + } } if (!visible && iid != DisplayServer::INVALID_INDICATOR_ID) { + PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(menu)); + if (pm) { + pm->unbind_global_menu(); + DisplayServer::get_singleton()->status_indicator_set_menu(iid, RID()); + } DisplayServer::get_singleton()->delete_status_indicator(iid); iid = DisplayServer::INVALID_INDICATOR_ID; } @@ -133,3 +183,10 @@ void StatusIndicator::set_visible(bool p_visible) { bool StatusIndicator::is_visible() const { return visible; } + +Rect2 StatusIndicator::get_rect() const { + if (iid == DisplayServer::INVALID_INDICATOR_ID) { + return Rect2(); + } + return DisplayServer::get_singleton()->status_indicator_get_rect(iid); +} diff --git a/scene/main/status_indicator.h b/scene/main/status_indicator.h index aa3aa68d78..cd38da6e6c 100644 --- a/scene/main/status_indicator.h +++ b/scene/main/status_indicator.h @@ -37,10 +37,11 @@ class StatusIndicator : public Node { GDCLASS(StatusIndicator, Node); - Ref<Image> icon; + Ref<Texture2D> icon; String tooltip; bool visible = true; DisplayServer::IndicatorID iid = DisplayServer::INVALID_INDICATOR_ID; + NodePath menu; protected: void _notification(int p_what); @@ -49,14 +50,19 @@ protected: void _callback(MouseButton p_index, const Point2i &p_pos); public: - void set_icon(const Ref<Image> &p_icon); - Ref<Image> get_icon() const; + void set_icon(const Ref<Texture2D> &p_icon); + Ref<Texture2D> get_icon() const; void set_tooltip(const String &p_tooltip); String get_tooltip() const; + void set_menu(const NodePath &p_menu); + NodePath get_menu() const; + void set_visible(bool p_visible); bool is_visible() const; + + Rect2 get_rect() const; }; #endif // STATUS_INDICATOR_H diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 1153d65533..9dbe10f30b 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -688,6 +688,18 @@ void Viewport::_process_picking() { physics_picking_events.clear(); return; } +#ifndef _3D_DISABLED + if (use_xr) { + if (XRServer::get_singleton() != nullptr) { + Ref<XRInterface> xr_interface = XRServer::get_singleton()->get_primary_interface(); + if (xr_interface.is_valid() && xr_interface->is_initialized() && xr_interface->get_view_count() > 1) { + WARN_PRINT_ONCE("Object picking can't be used when stereo rendering, this will be turned off!"); + physics_object_picking = false; // don't try again. + return; + } + } + } +#endif _drop_physics_mouseover(true); @@ -971,7 +983,7 @@ void Viewport::_set_size(const Size2i &p_size, const Size2i &p_size_2d_override, stretch_transform_new.scale(scale); } - Size2i new_size = p_size.max(Size2i(2, 2)); + Size2i new_size = p_size.maxi(2); if (size == new_size && size_allocated == p_allocated && stretch_transform == stretch_transform_new && p_size_2d_override == size_2d_override) { return; } @@ -1722,7 +1734,6 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { gui.mouse_focus_mask.set_flag(button_mask); } else { gui.mouse_focus = gui_find_control(mpos); - gui.last_mouse_focus = gui.mouse_focus; if (!gui.mouse_focus) { return; @@ -2307,6 +2318,7 @@ void Viewport::_gui_force_drag(Control *p_base, const Variant &p_data, Control * gui.dragging = true; gui.drag_data = p_data; gui.mouse_focus = nullptr; + gui.mouse_focus_mask.clear(); if (p_control) { _gui_set_drag_preview(p_base, p_control); @@ -2379,9 +2391,6 @@ void Viewport::_gui_remove_control(Control *p_control) { gui.forced_mouse_focus = false; gui.mouse_focus_mask.clear(); } - if (gui.last_mouse_focus == p_control) { - gui.last_mouse_focus = nullptr; - } if (gui.key_focus == p_control) { gui.key_focus = nullptr; } @@ -2759,7 +2768,7 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { Size2i min_size = gui.currently_dragged_subwindow->get_min_size(); Size2i min_size_clamped = gui.currently_dragged_subwindow->get_clamped_minimum_size(); - min_size_clamped = min_size_clamped.max(Size2i(1, 1)); + min_size_clamped = min_size_clamped.maxi(1); Rect2i r = gui.subwindow_resize_from_rect; @@ -2820,7 +2829,7 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { Size2i max_size = gui.currently_dragged_subwindow->get_max_size(); if ((max_size.x > 0 || max_size.y > 0) && (max_size.x >= min_size.x && max_size.y >= min_size.y)) { - max_size = max_size.max(Size2i(1, 1)); + max_size = max_size.maxi(1); if (r.size.x > max_size.x) { r.size.x = max_size.x; diff --git a/scene/main/viewport.h b/scene/main/viewport.h index c6a757acd0..394d48143c 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -345,7 +345,6 @@ private: bool key_event_accepted = false; HashMap<int, ObjectID> touch_focus; Control *mouse_focus = nullptr; - Control *last_mouse_focus = nullptr; Control *mouse_click_grabber = nullptr; BitField<MouseButtonMask> mouse_focus_mask; Control *key_focus = nullptr; diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 0ccc056a8d..929720fcf4 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -415,7 +415,7 @@ Size2i Window::_clamp_limit_size(const Size2i &p_limit_size) { if (max_window_size != Size2i()) { return p_limit_size.clamp(Vector2i(), max_window_size); } else { - return p_limit_size.max(Vector2i()); + return p_limit_size.maxi(0); } } @@ -1036,7 +1036,7 @@ void Window::_update_window_size() { } if (embedder) { - size = size.max(Size2i(1, 1)); + size = size.maxi(1); embedder->_sub_window_update(this); } else if (window_id != DisplayServer::INVALID_WINDOW_ID) { diff --git a/scene/resources/2d/tile_set.cpp b/scene/resources/2d/tile_set.cpp index 57cc4ad602..6649cb9b82 100644 --- a/scene/resources/2d/tile_set.cpp +++ b/scene/resources/2d/tile_set.cpp @@ -4650,7 +4650,7 @@ Ref<Texture2D> TileSetAtlasSource::get_texture() const { void TileSetAtlasSource::set_margins(Vector2i p_margins) { if (p_margins.x < 0 || p_margins.y < 0) { WARN_PRINT("Atlas source margins should be positive."); - margins = p_margins.max(Vector2i()); + margins = p_margins.maxi(0); } else { margins = p_margins; } @@ -4666,7 +4666,7 @@ Vector2i TileSetAtlasSource::get_margins() const { void TileSetAtlasSource::set_separation(Vector2i p_separation) { if (p_separation.x < 0 || p_separation.y < 0) { WARN_PRINT("Atlas source separation should be positive."); - separation = p_separation.max(Vector2i()); + separation = p_separation.maxi(0); } else { separation = p_separation; } @@ -4682,7 +4682,7 @@ Vector2i TileSetAtlasSource::get_separation() const { void TileSetAtlasSource::set_texture_region_size(Vector2i p_tile_size) { if (p_tile_size.x <= 0 || p_tile_size.y <= 0) { WARN_PRINT("Atlas source tile_size should be strictly positive."); - texture_region_size = p_tile_size.max(Vector2i(1, 1)); + texture_region_size = p_tile_size.maxi(1); } else { texture_region_size = p_tile_size; } diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index cd530f100e..8ffb629ba9 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -247,6 +247,7 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { } vt->update_mode = UpdateMode(um); } + capture_included = capture_included || (vt->update_mode == UPDATE_CAPTURE); Vector<real_t> times = d["times"]; Array values = d["values"]; @@ -966,6 +967,28 @@ void Animation::remove_track(int p_track) { memdelete(t); tracks.remove_at(p_track); emit_changed(); + _check_capture_included(); +} + +void Animation::set_capture_included(bool p_capture_included) { + capture_included = p_capture_included; +} + +bool Animation::is_capture_included() const { + return capture_included; +} + +void Animation::_check_capture_included() { + capture_included = false; + for (int i = 0; i < tracks.size(); i++) { + if (tracks[i]->type == TYPE_VALUE) { + ValueTrack *vt = static_cast<ValueTrack *>(tracks[i]); + if (vt->update_mode == UPDATE_CAPTURE) { + capture_included = true; + break; + } + } + } } int Animation::get_track_count() const { @@ -2681,6 +2704,8 @@ void Animation::value_track_set_update_mode(int p_track, UpdateMode p_mode) { ValueTrack *vt = static_cast<ValueTrack *>(t); vt->update_mode = p_mode; + + capture_included = capture_included || (p_mode == UPDATE_CAPTURE); emit_changed(); } @@ -3870,9 +3895,13 @@ void Animation::_bind_methods() { ClassDB::bind_method(D_METHOD("compress", "page_size", "fps", "split_tolerance"), &Animation::compress, DEFVAL(8192), DEFVAL(120), DEFVAL(4.0)); + ClassDB::bind_method(D_METHOD("_set_capture_included", "capture_included"), &Animation::set_capture_included); + ClassDB::bind_method(D_METHOD("is_capture_included"), &Animation::is_capture_included); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "0.001,99999,0.001,suffix:s"), "set_length", "get_length"); ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "None,Linear,Ping-Pong"), "set_loop_mode", "get_loop_mode"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "step", PROPERTY_HINT_RANGE, "0,4096,0.001,suffix:s"), "set_step", "get_step"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "capture_included", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_READ_ONLY | PROPERTY_USAGE_NO_EDITOR), "_set_capture_included", "is_capture_included"); BIND_ENUM_CONSTANT(TYPE_VALUE); BIND_ENUM_CONSTANT(TYPE_POSITION_3D); diff --git a/scene/resources/animation.h b/scene/resources/animation.h index 6005172c11..cc7bbae8a3 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -268,6 +268,8 @@ private: double length = 1.0; real_t step = 1.0 / 30; LoopMode loop_mode = LOOP_NONE; + bool capture_included = false; + void _check_capture_included(); void _track_update_hash(int p_track); @@ -392,6 +394,9 @@ public: int add_track(TrackType p_type, int p_at_pos = -1); void remove_track(int p_track); + void set_capture_included(bool p_capture_included); + bool is_capture_included() const; + int get_track_count() const; TrackType track_get_type(int p_track) const; diff --git a/scene/resources/audio_stream_wav.cpp b/scene/resources/audio_stream_wav.cpp index 0185c6ef85..ba5dad088f 100644 --- a/scene/resources/audio_stream_wav.cpp +++ b/scene/resources/audio_stream_wav.cpp @@ -86,15 +86,15 @@ void AudioStreamPlaybackWAV::seek(double p_time) { offset = uint64_t(p_time * base->mix_rate) << MIX_FRAC_BITS; } -template <typename Depth, bool is_stereo, bool is_ima_adpcm> -void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, int64_t &p_offset, int32_t &p_increment, uint32_t p_amount, IMA_ADPCM_State *p_ima_adpcm) { +template <typename Depth, bool is_stereo, bool is_ima_adpcm, bool is_qoa> +void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, int64_t &p_offset, int32_t &p_increment, uint32_t p_amount, IMA_ADPCM_State *p_ima_adpcm, QOA_State *p_qoa) { // this function will be compiled branchless by any decent compiler - int32_t final, final_r, next, next_r; + int32_t final = 0, final_r = 0, next = 0, next_r = 0; while (p_amount) { p_amount--; int64_t pos = p_offset >> MIX_FRAC_BITS; - if (is_stereo && !is_ima_adpcm) { + if (is_stereo && !is_ima_adpcm && !is_qoa) { pos <<= 1; } @@ -175,32 +175,77 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, } } else { - final = p_src[pos]; - if (is_stereo) { - final_r = p_src[pos + 1]; - } + if (is_qoa) { + if (pos != p_qoa->cache_pos) { // Prevents triple decoding on lower mix rates. + for (int i = 0; i < 2; i++) { + // Sign operations prevent triple decoding on backward loops, maxing prevents pop. + uint32_t interp_pos = MIN(pos + (i * sign) + (sign < 0), p_qoa->desc->samples - 1); + uint32_t new_data_ofs = 8 + interp_pos / QOA_FRAME_LEN * p_qoa->frame_len; + + if (p_qoa->data_ofs != new_data_ofs) { + p_qoa->data_ofs = new_data_ofs; + const uint8_t *src_ptr = (const uint8_t *)base->data; + src_ptr += p_qoa->data_ofs + AudioStreamWAV::DATA_PAD; + qoa_decode_frame(src_ptr, p_qoa->frame_len, p_qoa->desc, p_qoa->dec, &p_qoa->dec_len); + } - if constexpr (sizeof(Depth) == 1) { /* conditions will not exist anymore when compiled! */ - final <<= 8; + uint32_t dec_idx = (interp_pos % QOA_FRAME_LEN) * p_qoa->desc->channels; + + if ((sign > 0 && i == 0) || (sign < 0 && i == 1)) { + final = p_qoa->dec[dec_idx]; + p_qoa->cache[0] = final; + if (is_stereo) { + final_r = p_qoa->dec[dec_idx + 1]; + p_qoa->cache_r[0] = final_r; + } + } else { + next = p_qoa->dec[dec_idx]; + p_qoa->cache[1] = next; + if (is_stereo) { + next_r = p_qoa->dec[dec_idx + 1]; + p_qoa->cache_r[1] = next_r; + } + } + } + p_qoa->cache_pos = pos; + } else { + final = p_qoa->cache[0]; + if (is_stereo) { + final_r = p_qoa->cache_r[0]; + } + + next = p_qoa->cache[1]; + if (is_stereo) { + next_r = p_qoa->cache_r[1]; + } + } + } else { + final = p_src[pos]; if (is_stereo) { - final_r <<= 8; + final_r = p_src[pos + 1]; } - } - if (is_stereo) { - next = p_src[pos + 2]; - next_r = p_src[pos + 3]; - } else { - next = p_src[pos + 1]; - } + if constexpr (sizeof(Depth) == 1) { /* conditions will not exist anymore when compiled! */ + final <<= 8; + if (is_stereo) { + final_r <<= 8; + } + } - if constexpr (sizeof(Depth) == 1) { - next <<= 8; if (is_stereo) { - next_r <<= 8; + next = p_src[pos + 2]; + next_r = p_src[pos + 3]; + } else { + next = p_src[pos + 1]; } - } + if constexpr (sizeof(Depth) == 1) { + next <<= 8; + if (is_stereo) { + next_r <<= 8; + } + } + } int32_t frac = int64_t(p_offset & MIX_FRAC_MASK); final = final + ((next - final) * frac >> MIX_FRAC_BITS); @@ -240,6 +285,9 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_ case AudioStreamWAV::FORMAT_IMA_ADPCM: len *= 2; break; + case AudioStreamWAV::FORMAT_QOA: + len = qoa.desc->samples * qoa.desc->channels; + break; } if (base->stereo) { @@ -368,27 +416,34 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_ switch (base->format) { case AudioStreamWAV::FORMAT_8_BITS: { if (is_stereo) { - do_resample<int8_t, true, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm); + do_resample<int8_t, true, false, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa); } else { - do_resample<int8_t, false, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm); + do_resample<int8_t, false, false, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa); } } break; case AudioStreamWAV::FORMAT_16_BITS: { if (is_stereo) { - do_resample<int16_t, true, false>((int16_t *)data, dst_buff, offset, increment, target, ima_adpcm); + do_resample<int16_t, true, false, false>((int16_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa); } else { - do_resample<int16_t, false, false>((int16_t *)data, dst_buff, offset, increment, target, ima_adpcm); + do_resample<int16_t, false, false, false>((int16_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa); } } break; case AudioStreamWAV::FORMAT_IMA_ADPCM: { if (is_stereo) { - do_resample<int8_t, true, true>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm); + do_resample<int8_t, true, true, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa); } else { - do_resample<int8_t, false, true>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm); + do_resample<int8_t, false, true, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa); } } break; + case AudioStreamWAV::FORMAT_QOA: { + if (is_stereo) { + do_resample<uint8_t, true, false, true>((uint8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa); + } else { + do_resample<uint8_t, false, false, true>((uint8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa); + } + } break; } dst_buff += target; @@ -412,6 +467,16 @@ void AudioStreamPlaybackWAV::tag_used_streams() { AudioStreamPlaybackWAV::AudioStreamPlaybackWAV() {} +AudioStreamPlaybackWAV::~AudioStreamPlaybackWAV() { + if (qoa.desc) { + memfree(qoa.desc); + } + + if (qoa.dec) { + memfree(qoa.dec); + } +} + ///////////////////// void AudioStreamWAV::set_format(Format p_format) { @@ -475,6 +540,10 @@ double AudioStreamWAV::get_length() const { case AudioStreamWAV::FORMAT_IMA_ADPCM: len *= 2; break; + case AudioStreamWAV::FORMAT_QOA: + qoa_desc desc = { 0, 0, 0, { { { 0 }, { 0 } } } }; + qoa_decode_header((uint8_t *)data + DATA_PAD, QOA_MIN_FILESIZE, &desc); + len = desc.samples * desc.channels; } if (stereo) { @@ -526,8 +595,8 @@ Vector<uint8_t> AudioStreamWAV::get_data() const { } Error AudioStreamWAV::save_to_wav(const String &p_path) { - if (format == AudioStreamWAV::FORMAT_IMA_ADPCM) { - WARN_PRINT("Saving IMA_ADPC samples are not supported yet"); + if (format == AudioStreamWAV::FORMAT_IMA_ADPCM || format == AudioStreamWAV::FORMAT_QOA) { + WARN_PRINT("Saving IMA_ADPCM and QOA samples is not supported yet"); return ERR_UNAVAILABLE; } @@ -548,6 +617,7 @@ Error AudioStreamWAV::save_to_wav(const String &p_path) { byte_pr_sample = 1; break; case AudioStreamWAV::FORMAT_16_BITS: + case AudioStreamWAV::FORMAT_QOA: byte_pr_sample = 2; break; case AudioStreamWAV::FORMAT_IMA_ADPCM: @@ -590,6 +660,7 @@ Error AudioStreamWAV::save_to_wav(const String &p_path) { } break; case AudioStreamWAV::FORMAT_16_BITS: + case AudioStreamWAV::FORMAT_QOA: for (unsigned int i = 0; i < data_bytes / 2; i++) { uint16_t data_point = decode_uint16(&read_data[i * 2]); file->store_16(data_point); @@ -607,6 +678,16 @@ Ref<AudioStreamPlayback> AudioStreamWAV::instantiate_playback() { Ref<AudioStreamPlaybackWAV> sample; sample.instantiate(); sample->base = Ref<AudioStreamWAV>(this); + + if (format == AudioStreamWAV::FORMAT_QOA) { + sample->qoa.desc = (qoa_desc *)memalloc(sizeof(qoa_desc)); + qoa_decode_header((uint8_t *)data + DATA_PAD, QOA_MIN_FILESIZE, sample->qoa.desc); + sample->qoa.frame_len = qoa_max_frame_size(sample->qoa.desc); + int samples_len = (sample->qoa.desc->samples > QOA_FRAME_LEN ? QOA_FRAME_LEN : sample->qoa.desc->samples); + int alloc_len = sample->qoa.desc->channels * samples_len * sizeof(int16_t); + sample->qoa.dec = (int16_t *)memalloc(alloc_len); + } + return sample; } @@ -639,7 +720,7 @@ void AudioStreamWAV::_bind_methods() { ClassDB::bind_method(D_METHOD("save_to_wav", "path"), &AudioStreamWAV::save_to_wav); ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_data", "get_data"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM"), "set_format", "get_format"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM,QOA"), "set_format", "get_format"); ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "Disabled,Forward,Ping-Pong,Backward"), "set_loop_mode", "get_loop_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_begin"), "set_loop_begin", "get_loop_begin"); ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_end"), "set_loop_end", "get_loop_end"); @@ -649,6 +730,7 @@ void AudioStreamWAV::_bind_methods() { BIND_ENUM_CONSTANT(FORMAT_8_BITS); BIND_ENUM_CONSTANT(FORMAT_16_BITS); BIND_ENUM_CONSTANT(FORMAT_IMA_ADPCM); + BIND_ENUM_CONSTANT(FORMAT_QOA); BIND_ENUM_CONSTANT(LOOP_DISABLED); BIND_ENUM_CONSTANT(LOOP_FORWARD); diff --git a/scene/resources/audio_stream_wav.h b/scene/resources/audio_stream_wav.h index 959d1ceca0..146142d8a4 100644 --- a/scene/resources/audio_stream_wav.h +++ b/scene/resources/audio_stream_wav.h @@ -31,7 +31,11 @@ #ifndef AUDIO_STREAM_WAV_H #define AUDIO_STREAM_WAV_H +#define QOA_IMPLEMENTATION +#define QOA_NO_STDIO + #include "servers/audio/audio_stream.h" +#include "thirdparty/misc/qoa.h" class AudioStreamWAV; @@ -54,14 +58,25 @@ class AudioStreamPlaybackWAV : public AudioStreamPlayback { int32_t window_ofs = 0; } ima_adpcm[2]; + struct QOA_State { + qoa_desc *desc = nullptr; + uint32_t data_ofs = 0; + uint32_t frame_len = 0; + int16_t *dec = nullptr; + uint32_t dec_len = 0; + int64_t cache_pos = -1; + int16_t cache[2] = { 0, 0 }; + int16_t cache_r[2] = { 0, 0 }; + } qoa; + int64_t offset = 0; int sign = 1; bool active = false; friend class AudioStreamWAV; Ref<AudioStreamWAV> base; - template <typename Depth, bool is_stereo, bool is_ima_adpcm> - void do_resample(const Depth *p_src, AudioFrame *p_dst, int64_t &p_offset, int32_t &p_increment, uint32_t p_amount, IMA_ADPCM_State *p_ima_adpcm); + template <typename Depth, bool is_stereo, bool is_ima_adpcm, bool is_qoa> + void do_resample(const Depth *p_src, AudioFrame *p_dst, int64_t &p_offset, int32_t &p_increment, uint32_t p_amount, IMA_ADPCM_State *p_ima_adpcm, QOA_State *p_qoa); public: virtual void start(double p_from_pos = 0.0) override; @@ -78,6 +93,7 @@ public: virtual void tag_used_streams() override; AudioStreamPlaybackWAV(); + ~AudioStreamPlaybackWAV(); }; class AudioStreamWAV : public AudioStream { @@ -88,7 +104,8 @@ public: enum Format { FORMAT_8_BITS, FORMAT_16_BITS, - FORMAT_IMA_ADPCM + FORMAT_IMA_ADPCM, + FORMAT_QOA, }; // Keep the ResourceImporterWAV `edit/loop_mode` enum hint in sync with these options. diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index b381096df8..15b40e776c 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -688,6 +688,9 @@ void BaseMaterial3D::_update_shader() { case BLEND_MODE_MUL: code += "blend_mul"; break; + case BLEND_MODE_PREMULT_ALPHA: + code += "blend_premul_alpha"; + break; case BLEND_MODE_MAX: break; // Internal value, skip. } @@ -1819,6 +1822,11 @@ void fragment() {)"; vec3 detail = mix(ALBEDO.rgb, ALBEDO.rgb * detail_tex.rgb, detail_tex.a); )"; } break; + case BLEND_MODE_PREMULT_ALPHA: { + // This is unlikely to ever be used for detail textures, and in order for it to function in the editor, another bit must be used in MaterialKey, + // but there are only 5 bits left, so I'm going to leave this disabled unless it's actually requested. + //code += "\tvec3 detail = (1.0-detail_tex.a)*ALBEDO.rgb+detail_tex.rgb;\n"; + } break; case BLEND_MODE_MAX: break; // Internal value, skip. } @@ -3040,7 +3048,7 @@ void BaseMaterial3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "alpha_hash_scale", PROPERTY_HINT_RANGE, "0,2,0.01"), "set_alpha_hash_scale", "get_alpha_hash_scale"); ADD_PROPERTY(PropertyInfo(Variant::INT, "alpha_antialiasing_mode", PROPERTY_HINT_ENUM, "Disabled,Alpha Edge Blend,Alpha Edge Clip"), "set_alpha_antialiasing", "get_alpha_antialiasing"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "alpha_antialiasing_edge", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_alpha_antialiasing_edge", "get_alpha_antialiasing_edge"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Mix,Add,Subtract,Multiply"), "set_blend_mode", "get_blend_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Mix,Add,Subtract,Multiply,Premultiplied Alpha"), "set_blend_mode", "get_blend_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cull_mode", PROPERTY_HINT_ENUM, "Back,Front,Disabled"), "set_cull_mode", "get_cull_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "depth_draw_mode", PROPERTY_HINT_ENUM, "Opaque Only,Always,Never"), "set_depth_draw_mode", "get_depth_draw_mode"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "no_depth_test"), "set_flag", "get_flag", FLAG_DISABLE_DEPTH_TEST); @@ -3269,6 +3277,7 @@ void BaseMaterial3D::_bind_methods() { BIND_ENUM_CONSTANT(BLEND_MODE_ADD); BIND_ENUM_CONSTANT(BLEND_MODE_SUB); BIND_ENUM_CONSTANT(BLEND_MODE_MUL); + BIND_ENUM_CONSTANT(BLEND_MODE_PREMULT_ALPHA); BIND_ENUM_CONSTANT(ALPHA_ANTIALIASING_OFF); BIND_ENUM_CONSTANT(ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE); diff --git a/scene/resources/material.h b/scene/resources/material.h index 073403f71e..ecf79c581b 100644 --- a/scene/resources/material.h +++ b/scene/resources/material.h @@ -219,6 +219,7 @@ public: BLEND_MODE_ADD, BLEND_MODE_SUB, BLEND_MODE_MUL, + BLEND_MODE_PREMULT_ALPHA, BLEND_MODE_MAX }; diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp index 685625ab72..0b65b33240 100644 --- a/scene/resources/particle_process_material.cpp +++ b/scene/resources/particle_process_material.cpp @@ -634,7 +634,7 @@ void ParticleProcessMaterial::_update_shader() { if (emission_shape == EMISSION_SHAPE_RING) { code += " \n"; code += " float ring_spawn_angle = rand_from_seed(alt_seed) * 2.0 * pi;\n"; - code += " float ring_random_radius = rand_from_seed(alt_seed) * (emission_ring_radius - emission_ring_inner_radius) + emission_ring_inner_radius;\n"; + code += " float ring_random_radius = sqrt(rand_from_seed(alt_seed) * (emission_ring_radius - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius);\n"; code += " vec3 axis = emission_ring_axis == vec3(0.0) ? vec3(0.0, 0.0, 1.0) : normalize(emission_ring_axis);\n"; code += " vec3 ortho_axis = vec3(0.0);\n"; code += " if (abs(axis) == vec3(1.0, 0.0, 0.0)) {\n"; @@ -1136,9 +1136,9 @@ void ParticleProcessMaterial::_update_shader() { code += " if (COLLIDED) emit_count = sub_emitter_amount_at_collision;\n"; } break; case SUB_EMITTER_AT_END: { - code += " float unit_delta = DELTA/LIFETIME;\n"; - code += " float end_time = CUSTOM.w * 0.95;\n"; // if we do at the end we might miss it, as it can just get deactivated by emitter - code += " if (CUSTOM.y < end_time && (CUSTOM.y + unit_delta) >= end_time) emit_count = sub_emitter_amount_at_end;\n"; + code += " if ((CUSTOM.y / CUSTOM.w * LIFETIME) > (LIFETIME - DELTA)) {\n"; + code += " emit_count = sub_emitter_amount_at_end;\n"; + code += " }\n"; } break; default: { } diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 6f1aa5c850..4b51f6c471 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -4928,6 +4928,10 @@ String VisualShaderNodeExpression::generate_code(Shader::Mode p_mode, VisualShad return code; } +bool VisualShaderNodeExpression::is_output_port_expandable(int p_port) const { + return false; +} + void VisualShaderNodeExpression::_bind_methods() { ClassDB::bind_method(D_METHOD("set_expression", "expression"), &VisualShaderNodeExpression::set_expression); ClassDB::bind_method(D_METHOD("get_expression"), &VisualShaderNodeExpression::get_expression); diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index d7270f3ac6..d32e2465b9 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -878,6 +878,7 @@ public: String get_expression() const; virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; + virtual bool is_output_port_expandable(int p_port) const override; VisualShaderNodeExpression(); }; diff --git a/servers/display_server.cpp b/servers/display_server.cpp index 9600caa214..fcbedbc1f8 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -709,12 +709,12 @@ void DisplayServer::set_icon(const Ref<Image> &p_icon) { WARN_PRINT("Icon not supported by this display server."); } -DisplayServer::IndicatorID DisplayServer::create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) { +DisplayServer::IndicatorID DisplayServer::create_status_indicator(const Ref<Texture2D> &p_icon, const String &p_tooltip, const Callable &p_callback) { WARN_PRINT("Status indicator not supported by this display server."); return INVALID_INDICATOR_ID; } -void DisplayServer::status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) { +void DisplayServer::status_indicator_set_icon(IndicatorID p_id, const Ref<Texture2D> &p_icon) { WARN_PRINT("Status indicator not supported by this display server."); } @@ -722,10 +722,19 @@ void DisplayServer::status_indicator_set_tooltip(IndicatorID p_id, const String WARN_PRINT("Status indicator not supported by this display server."); } +void DisplayServer::status_indicator_set_menu(IndicatorID p_id, const RID &p_menu_rid) { + WARN_PRINT("Status indicator not supported by this display server."); +} + void DisplayServer::status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) { WARN_PRINT("Status indicator not supported by this display server."); } +Rect2 DisplayServer::status_indicator_get_rect(IndicatorID p_id) const { + WARN_PRINT("Status indicator not supported by this display server."); + return Rect2(); +} + void DisplayServer::delete_status_indicator(IndicatorID p_id) { WARN_PRINT("Status indicator not supported by this display server."); } @@ -977,7 +986,9 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("create_status_indicator", "icon", "tooltip", "callback"), &DisplayServer::create_status_indicator); ClassDB::bind_method(D_METHOD("status_indicator_set_icon", "id", "icon"), &DisplayServer::status_indicator_set_icon); ClassDB::bind_method(D_METHOD("status_indicator_set_tooltip", "id", "tooltip"), &DisplayServer::status_indicator_set_tooltip); + ClassDB::bind_method(D_METHOD("status_indicator_set_menu", "id", "menu_rid"), &DisplayServer::status_indicator_set_menu); ClassDB::bind_method(D_METHOD("status_indicator_set_callback", "id", "callback"), &DisplayServer::status_indicator_set_callback); + ClassDB::bind_method(D_METHOD("status_indicator_get_rect", "id"), &DisplayServer::status_indicator_get_rect); ClassDB::bind_method(D_METHOD("delete_status_indicator", "id"), &DisplayServer::delete_status_indicator); ClassDB::bind_method(D_METHOD("tablet_get_driver_count"), &DisplayServer::tablet_get_driver_count); diff --git a/servers/display_server.h b/servers/display_server.h index aab51644c0..30f6ee5ccf 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -564,10 +564,12 @@ public: virtual void set_native_icon(const String &p_filename); virtual void set_icon(const Ref<Image> &p_icon); - virtual IndicatorID create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback); - virtual void status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon); + virtual IndicatorID create_status_indicator(const Ref<Texture2D> &p_icon, const String &p_tooltip, const Callable &p_callback); + virtual void status_indicator_set_icon(IndicatorID p_id, const Ref<Texture2D> &p_icon); virtual void status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip); + virtual void status_indicator_set_menu(IndicatorID p_id, const RID &p_menu_rid); virtual void status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback); + virtual Rect2 status_indicator_get_rect(IndicatorID p_id) const; virtual void delete_status_indicator(IndicatorID p_id); enum Context { diff --git a/servers/rendering/renderer_rd/effects/ss_effects.cpp b/servers/rendering/renderer_rd/effects/ss_effects.cpp index 3db82c8fbd..36a2470c7b 100644 --- a/servers/rendering/renderer_rd/effects/ss_effects.cpp +++ b/servers/rendering/renderer_rd/effects/ss_effects.cpp @@ -521,8 +521,7 @@ void SSEffects::downsample_depth(Ref<RenderSceneBuffersRD> p_render_buffers, uin RD::get_singleton()->compute_list_set_push_constant(compute_list, &ss_effects.downsample_push_constant, sizeof(SSEffectsDownsamplePushConstant)); if (use_half_size) { - size.x = MAX(1, size.x >> 1); - size.y = MAX(1, size.y >> 1); + size = Size2i(size.x >> 1, size.y >> 1).maxi(1); } RD::get_singleton()->compute_list_dispatch_threads(compute_list, size.x, size.y, 1); diff --git a/servers/rendering/renderer_rd/environment/fog.cpp b/servers/rendering/renderer_rd/environment/fog.cpp index 48537a97d9..2dfcd67411 100644 --- a/servers/rendering/renderer_rd/environment/fog.cpp +++ b/servers/rendering/renderer_rd/environment/fog.cpp @@ -541,7 +541,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P if (p_cam_projection.is_orthogonal()) { fog_near_size = fog_far_size; } else { - fog_near_size = frustum_near_size.max(Vector2(0.001, 0.001)); + fog_near_size = frustum_near_size.maxf(0.001); } params.fog_frustum_size_begin[0] = fog_near_size.x; @@ -1001,7 +1001,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P if (p_cam_projection.is_orthogonal()) { fog_near_size = fog_far_size; } else { - fog_near_size = frustum_near_size.max(Vector2(0.001, 0.001)); + fog_near_size = frustum_near_size.maxf(0.001); } params.fog_frustum_size_begin[0] = fog_near_size.x; diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index c7ab7ea462..2df0331688 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -1706,7 +1706,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co } if (p_render_data->environment.is_valid()) { - if (environment_get_sdfgi_enabled(p_render_data->environment)) { + if (environment_get_sdfgi_enabled(p_render_data->environment) && get_debug_draw_mode() != RS::VIEWPORT_DEBUG_DRAW_UNSHADED) { using_sdfgi = true; } if (environment_get_ssr_enabled(p_render_data->environment)) { diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp index 8d865ba440..9e0dacc1f2 100644 --- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp @@ -90,6 +90,7 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) { actions.render_mode_values["blend_mix"] = Pair<int *, int>(&blend_mode, BLEND_MODE_MIX); actions.render_mode_values["blend_sub"] = Pair<int *, int>(&blend_mode, BLEND_MODE_SUB); actions.render_mode_values["blend_mul"] = Pair<int *, int>(&blend_mode, BLEND_MODE_MUL); + actions.render_mode_values["blend_premul_alpha"] = Pair<int *, int>(&blend_mode, BLEND_MODE_PREMULT_ALPHA); actions.render_mode_values["alpha_to_coverage"] = Pair<int *, int>(&alpha_antialiasing_mode, ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE); actions.render_mode_values["alpha_to_coverage_and_one"] = Pair<int *, int>(&alpha_antialiasing_mode, ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE_AND_TO_ONE); @@ -244,7 +245,17 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) { blend_attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; blend_attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_ONE; blend_attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ZERO; - } + } break; + case BLEND_MODE_PREMULT_ALPHA: { + blend_attachment.enable_blend = true; + blend_attachment.alpha_blend_op = RD::BLEND_OP_ADD; + blend_attachment.color_blend_op = RD::BLEND_OP_ADD; + blend_attachment.src_color_blend_factor = RD::BLEND_FACTOR_ONE; + blend_attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + blend_attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_ONE; + blend_attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + uses_blend_alpha = true; // Force alpha used because of blend. + } break; } // Color pass -> attachment 0: Color/Diffuse, attachment 1: Separate Specular, attachment 2: Motion Vectors @@ -593,6 +604,7 @@ void SceneShaderForwardClustered::init(const String p_defines) { actions.renames["NORMAL_MAP_DEPTH"] = "normal_map_depth"; actions.renames["ALBEDO"] = "albedo"; actions.renames["ALPHA"] = "alpha"; + actions.renames["PREMUL_ALPHA_FACTOR"] = "premul_alpha"; actions.renames["METALLIC"] = "metallic"; actions.renames["SPECULAR"] = "specular"; actions.renames["ROUGHNESS"] = "roughness"; @@ -672,6 +684,7 @@ void SceneShaderForwardClustered::init(const String p_defines) { actions.usage_defines["INSTANCE_CUSTOM"] = "#define ENABLE_INSTANCE_CUSTOM\n"; actions.usage_defines["POSITION"] = "#define OVERRIDE_POSITION\n"; actions.usage_defines["LIGHT_VERTEX"] = "#define LIGHT_VERTEX_USED\n"; + actions.usage_defines["PREMUL_ALPHA_FACTOR"] = "#define PREMUL_ALPHA_USED\n"; actions.usage_defines["ALPHA_SCISSOR_THRESHOLD"] = "#define ALPHA_SCISSOR_USED\n"; actions.usage_defines["ALPHA_HASH_SCALE"] = "#define ALPHA_HASH_USED\n"; diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h index 3b83b2b582..d5332032f9 100644 --- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h +++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h @@ -106,7 +106,8 @@ public: BLEND_MODE_ADD, BLEND_MODE_SUB, BLEND_MODE_MUL, - BLEND_MODE_ALPHA_TO_COVERAGE + BLEND_MODE_ALPHA_TO_COVERAGE, + BLEND_MODE_PREMULT_ALPHA, }; enum DepthDraw { diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp index 0810f567cb..733cbd1ff2 100644 --- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp @@ -91,6 +91,7 @@ void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) { actions.render_mode_values["blend_mix"] = Pair<int *, int>(&blend_mode, BLEND_MODE_MIX); actions.render_mode_values["blend_sub"] = Pair<int *, int>(&blend_mode, BLEND_MODE_SUB); actions.render_mode_values["blend_mul"] = Pair<int *, int>(&blend_mode, BLEND_MODE_MUL); + actions.render_mode_values["blend_premul_alpha"] = Pair<int *, int>(&blend_mode, BLEND_MODE_PREMULT_ALPHA); actions.render_mode_values["alpha_to_coverage"] = Pair<int *, int>(&alpha_antialiasing_mode, ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE); actions.render_mode_values["alpha_to_coverage_and_one"] = Pair<int *, int>(&alpha_antialiasing_mode, ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE_AND_TO_ONE); @@ -255,7 +256,17 @@ void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) { blend_attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; blend_attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_ONE; blend_attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ZERO; - } + } break; + case BLEND_MODE_PREMULT_ALPHA: { + blend_attachment.enable_blend = true; + blend_attachment.alpha_blend_op = RD::BLEND_OP_ADD; + blend_attachment.color_blend_op = RD::BLEND_OP_ADD; + blend_attachment.src_color_blend_factor = RD::BLEND_FACTOR_ONE; + blend_attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + blend_attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_ONE; + blend_attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + uses_blend_alpha = true; // Force alpha used because of blend. + } break; } RD::PipelineColorBlendState blend_state_blend; @@ -497,6 +508,7 @@ void SceneShaderForwardMobile::init(const String p_defines) { actions.renames["NORMAL_MAP_DEPTH"] = "normal_map_depth"; actions.renames["ALBEDO"] = "albedo"; actions.renames["ALPHA"] = "alpha"; + actions.renames["PREMUL_ALPHA_FACTOR"] = "premul_alpha"; actions.renames["METALLIC"] = "metallic"; actions.renames["SPECULAR"] = "specular"; actions.renames["ROUGHNESS"] = "roughness"; @@ -581,6 +593,7 @@ void SceneShaderForwardMobile::init(const String p_defines) { actions.usage_defines["ALPHA_HASH_SCALE"] = "#define ALPHA_HASH_USED\n"; actions.usage_defines["ALPHA_ANTIALIASING_EDGE"] = "#define ALPHA_ANTIALIASING_EDGE_USED\n"; actions.usage_defines["ALPHA_TEXTURE_COORDINATE"] = "@ALPHA_ANTIALIASING_EDGE"; + actions.usage_defines["PREMUL_ALPHA_FACTOR"] = "#define PREMUL_ALPHA_USED"; actions.usage_defines["SSS_STRENGTH"] = "#define ENABLE_SSS\n"; actions.usage_defines["SSS_TRANSMITTANCE_DEPTH"] = "#define ENABLE_TRANSMITTANCE\n"; diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h index da189c6f67..833b06c1e3 100644 --- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h +++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h @@ -61,6 +61,7 @@ public: BLEND_MODE_ADD, BLEND_MODE_SUB, BLEND_MODE_MUL, + BLEND_MODE_PREMULT_ALPHA, BLEND_MODE_ALPHA_TO_COVERAGE }; diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl index cb07579c4b..20b080da4d 100644 --- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl @@ -914,6 +914,9 @@ vec3 encode24(vec3 v) { void fragment_shader(in SceneData scene_data) { uint instance_index = instance_index_interp; +#ifdef PREMUL_ALPHA_USED + float premul_alpha = 1.0; +#endif // PREMUL_ALPHA_USED //lay out everything, whatever is unused is optimized away anyway vec3 vertex = vertex_interp; #ifdef USE_MULTIVIEW @@ -2458,6 +2461,10 @@ void fragment_shader(in SceneData scene_data) { motion_vector = prev_position_uv - position_uv; #endif + +#if defined(PREMUL_ALPHA_USED) && !defined(MODE_RENDER_DEPTH) + frag_color.rgb *= premul_alpha; +#endif //PREMUL_ALPHA_USED } void main() { diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl index b98ea5a27f..1637326b48 100644 --- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl @@ -749,6 +749,9 @@ void main() { float clearcoat_roughness = 0.0; float anisotropy = 0.0; vec2 anisotropy_flow = vec2(1.0, 0.0); +#ifdef PREMUL_ALPHA_USED + float premul_alpha = 1.0; +#endif #ifndef FOG_DISABLED vec4 fog = vec4(0.0); #endif // !FOG_DISABLED @@ -1846,6 +1849,9 @@ void main() { // On mobile we use a UNORM buffer with 10bpp which results in a range from 0.0 - 1.0 resulting in HDR breaking // We divide by sc_luminance_multiplier to support a range from 0.0 - 2.0 both increasing precision on bright and darker images frag_color.rgb = frag_color.rgb / sc_luminance_multiplier; +#ifdef PREMUL_ALPHA_USED + frag_color.rgb *= premul_alpha; +#endif #endif //MODE_MULTIPLE_RENDER_TARGETS diff --git a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp index 1c3076b128..a10c672379 100644 --- a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp @@ -1656,13 +1656,9 @@ void MaterialStorage::global_shader_parameters_load_settings(bool p_load_texture Variant value = d["value"]; if (gvtype >= RS::GLOBAL_VAR_TYPE_SAMPLER2D) { - //textire - if (!p_load_textures) { - continue; - } - String path = value; - if (path.is_empty()) { + // Don't load the textures, but still add the parameter so shaders compile correctly while loading. + if (!p_load_textures || path.is_empty()) { value = RID(); } else { Ref<Resource> resource = ResourceLoader::load(path); diff --git a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp index b7934cb3de..b5fdf8bebb 100644 --- a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp +++ b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp @@ -83,8 +83,7 @@ void RenderSceneBuffersRD::update_sizes(NamedTexture &p_named_texture) { for (uint32_t mipmap = 0; mipmap < p_named_texture.format.mipmaps; mipmap++) { p_named_texture.sizes.ptrw()[mipmap] = mipmap_size; - mipmap_size.width = MAX(1, mipmap_size.width >> 1); - mipmap_size.height = MAX(1, mipmap_size.height >> 1); + mipmap_size = Size2i(mipmap_size.width >> 1, mipmap_size.height >> 1).maxi(1); } } diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp index af30a32866..76ff566b18 100644 --- a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp @@ -2681,8 +2681,7 @@ void TextureStorage::update_decal_atlas() { mm.size = s; decal_atlas.texture_mipmaps.push_back(mm); - s.width = MAX(1, s.width >> 1); - s.height = MAX(1, s.height >> 1); + s = Vector2i(s.width >> 1, s.height >> 1).maxi(1); } { //create the SRGB variant @@ -3637,7 +3636,7 @@ void TextureStorage::_render_target_allocate_sdf(RenderTarget *rt) { } rt->process_size = size * scale / 100; - rt->process_size = rt->process_size.max(Size2i(1, 1)); + rt->process_size = rt->process_size.maxi(1); tformat.format = RD::DATA_FORMAT_R16G16_SINT; tformat.width = rt->process_size.width; @@ -3838,10 +3837,8 @@ void TextureStorage::render_target_copy_to_back_buffer(RID p_render_target, cons for (int i = 0; i < rt->backbuffer_mipmaps.size(); i++) { region.position.x >>= 1; region.position.y >>= 1; - region.size.x = MAX(1, region.size.x >> 1); - region.size.y = MAX(1, region.size.y >> 1); - texture_size.x = MAX(1, texture_size.x >> 1); - texture_size.y = MAX(1, texture_size.y >> 1); + region.size = Size2i(region.size.x >> 1, region.size.y >> 1).maxi(1); + texture_size = Size2i(texture_size.x >> 1, texture_size.y >> 1).maxi(1); RID mipmap = rt->backbuffer_mipmaps[i]; if (RendererSceneRenderRD::get_singleton()->_render_buffers_can_be_storage()) { @@ -3911,10 +3908,8 @@ void TextureStorage::render_target_gen_back_buffer_mipmaps(RID p_render_target, for (int i = 0; i < rt->backbuffer_mipmaps.size(); i++) { region.position.x >>= 1; region.position.y >>= 1; - region.size.x = MAX(1, region.size.x >> 1); - region.size.y = MAX(1, region.size.y >> 1); - texture_size.x = MAX(1, texture_size.x >> 1); - texture_size.y = MAX(1, texture_size.y >> 1); + region.size = Size2i(region.size.x >> 1, region.size.y >> 1).maxi(1); + texture_size = Size2i(texture_size.x >> 1, texture_size.y >> 1).maxi(1); RID mipmap = rt->backbuffer_mipmaps[i]; diff --git a/servers/rendering/renderer_scene_occlusion_cull.h b/servers/rendering/renderer_scene_occlusion_cull.h index 5adba5dc6a..a848c86bd2 100644 --- a/servers/rendering/renderer_scene_occlusion_cull.h +++ b/servers/rendering/renderer_scene_occlusion_cull.h @@ -98,8 +98,8 @@ public: rect_max = rect_max.max(normalized); } - rect_max = rect_max.min(Vector2(1, 1)); - rect_min = rect_min.max(Vector2(0, 0)); + rect_max = rect_max.minf(1); + rect_min = rect_min.maxf(0); int mip_count = mips.size(); diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index 8de76c7dbc..e0049e3fa4 100644 --- a/servers/rendering/rendering_server_default.h +++ b/servers/rendering/rendering_server_default.h @@ -1048,6 +1048,10 @@ public: virtual void init() override; virtual void finish() override; + virtual bool is_on_render_thread() override { + return Thread::get_caller_id() == server_thread; + } + virtual void call_on_render_thread(const Callable &p_callable) override { if (Thread::get_caller_id() == server_thread) { command_queue.flush_if_pending(); diff --git a/servers/rendering/shader_types.cpp b/servers/rendering/shader_types.cpp index af51083dc3..b2fa0ea9d2 100644 --- a/servers/rendering/shader_types.cpp +++ b/servers/rendering/shader_types.cpp @@ -124,6 +124,7 @@ ShaderTypes::ShaderTypes() { shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["UV2"] = constt(ShaderLanguage::TYPE_VEC2); shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["COLOR"] = constt(ShaderLanguage::TYPE_VEC4); shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["ALBEDO"] = ShaderLanguage::TYPE_VEC3; + shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["PREMUL_ALPHA_FACTOR"] = ShaderLanguage::TYPE_FLOAT; shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["ALPHA"] = ShaderLanguage::TYPE_FLOAT; shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["METALLIC"] = ShaderLanguage::TYPE_FLOAT; shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["SPECULAR"] = ShaderLanguage::TYPE_FLOAT; @@ -208,7 +209,7 @@ ShaderTypes::ShaderTypes() { // spatial render modes { - shader_modes[RS::SHADER_SPATIAL].modes.push_back({ PNAME("blend"), "mix", "add", "sub", "mul" }); + shader_modes[RS::SHADER_SPATIAL].modes.push_back({ PNAME("blend"), "mix", "add", "sub", "mul", "premul_alpha" }); shader_modes[RS::SHADER_SPATIAL].modes.push_back({ PNAME("depth_draw"), "opaque", "always", "never" }); shader_modes[RS::SHADER_SPATIAL].modes.push_back({ PNAME("depth_prepass_alpha") }); shader_modes[RS::SHADER_SPATIAL].modes.push_back({ PNAME("depth_test_disabled") }); diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index bbe6b1ad0d..60e8f18c19 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -1226,6 +1226,10 @@ Error RenderingServer::mesh_create_surface_data_from_arrays(SurfaceData *r_surfa bsformat |= (1 << j); } } + if (bsformat & RS::ARRAY_FORMAT_NORMAL) { + // We must use tangents if using normals. + bsformat |= RS::ARRAY_FORMAT_TANGENT; + } ERR_FAIL_COND_V_MSG(bsformat != (format & RS::ARRAY_FORMAT_BLEND_SHAPE_MASK), ERR_INVALID_PARAMETER, "Blend shape format must match the main array format for Vertex, Normal and Tangent arrays."); } @@ -3426,6 +3430,7 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_rendering_device"), &RenderingServer::get_rendering_device); ClassDB::bind_method(D_METHOD("create_local_rendering_device"), &RenderingServer::create_local_rendering_device); + ClassDB::bind_method(D_METHOD("is_on_render_thread"), &RenderingServer::is_on_render_thread); ClassDB::bind_method(D_METHOD("call_on_render_thread", "callable"), &RenderingServer::call_on_render_thread); #ifndef DISABLE_DEPRECATED diff --git a/servers/rendering_server.h b/servers/rendering_server.h index 8f0150f180..240d82c90b 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -41,6 +41,32 @@ #include "servers/display_server.h" #include "servers/rendering/rendering_device.h" +// Helper macros for code outside of the rendering server, but that is +// called by the rendering server. +#ifdef DEBUG_ENABLED +#define ERR_ON_RENDER_THREAD \ + RenderingServer *rendering_server = RenderingServer::get_singleton(); \ + ERR_FAIL_NULL(rendering_server); \ + ERR_FAIL_COND(rendering_server->is_on_render_thread()); +#define ERR_ON_RENDER_THREAD_V(m_ret) \ + RenderingServer *rendering_server = RenderingServer::get_singleton(); \ + ERR_FAIL_NULL_V(rendering_server, m_ret); \ + ERR_FAIL_COND_V(rendering_server->is_on_render_thread(), m_ret); +#define ERR_NOT_ON_RENDER_THREAD \ + RenderingServer *rendering_server = RenderingServer::get_singleton(); \ + ERR_FAIL_NULL(rendering_server); \ + ERR_FAIL_COND(!rendering_server->is_on_render_thread()); +#define ERR_NOT_ON_RENDER_THREAD_V(m_ret) \ + RenderingServer *rendering_server = RenderingServer::get_singleton(); \ + ERR_FAIL_NULL_V(rendering_server, m_ret); \ + ERR_FAIL_COND_V(!rendering_server->is_on_render_thread(), m_ret); +#else +#define ERR_ON_RENDER_THREAD +#define ERR_ON_RENDER_THREAD_V(m_ret) +#define ERR_NOT_ON_RENDER_THREAD +#define ERR_NOT_ON_RENDER_THREAD_V(m_ret) +#endif + template <typename T> class TypedArray; @@ -1684,7 +1710,7 @@ public: #ifndef DISABLE_DEPRECATED // Never actually used, should be removed when we can break compatibility. - enum Features { + enum Features{ FEATURE_SHADERS, FEATURE_MULTITHREADED, }; @@ -1708,6 +1734,7 @@ public: bool is_render_loop_enabled() const; void set_render_loop_enabled(bool p_enabled); + virtual bool is_on_render_thread() = 0; virtual void call_on_render_thread(const Callable &p_callable) = 0; #ifdef TOOLS_ENABLED diff --git a/servers/xr/xr_interface.h b/servers/xr/xr_interface.h index d7bd212449..809800d8b9 100644 --- a/servers/xr/xr_interface.h +++ b/servers/xr/xr_interface.h @@ -122,17 +122,21 @@ public: /** rendering and internal **/ + // These methods are called from the main thread. + virtual Transform3D get_camera_transform() = 0; /* returns the position of our camera, only used for updating reference frame. For monoscopic this is equal to the views transform, for stereoscopic this should be an average */ + virtual void process() = 0; + + // These methods can be called from both main and render thread. virtual Size2 get_render_target_size() = 0; /* returns the recommended render target size per eye for this device */ virtual uint32_t get_view_count() = 0; /* returns the view count we need (1 is monoscopic, 2 is stereoscopic but can be more) */ - virtual Transform3D get_camera_transform() = 0; /* returns the position of our camera for updating our camera node. For monoscopic this is equal to the views transform, for stereoscopic this should be an average */ + + // These methods are called from the rendering thread. virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) = 0; /* get each views transform */ virtual Projection get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) = 0; /* get each view projection matrix */ virtual RID get_vrs_texture(); /* obtain VRS texture */ virtual RID get_color_texture(); /* obtain color output texture (if applicable) */ virtual RID get_depth_texture(); /* obtain depth output texture (if applicable, used for reprojection) */ virtual RID get_velocity_texture(); /* obtain velocity output texture (if applicable, used for spacewarp) */ - - virtual void process() = 0; virtual void pre_render(){}; virtual bool pre_draw_viewport(RID p_render_target) { return true; }; /* inform XR interface we are about to start our viewport draw process */ virtual Vector<BlitToScreen> post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) = 0; /* inform XR interface we finished our viewport draw process */ diff --git a/servers/xr_server.cpp b/servers/xr_server.cpp index f1105a650d..2cfe98ea1e 100644 --- a/servers/xr_server.cpp +++ b/servers/xr_server.cpp @@ -51,7 +51,7 @@ XRServer *XRServer::singleton = nullptr; XRServer *XRServer::get_singleton() { return singleton; -}; +} void XRServer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_world_scale"), &XRServer::get_world_scale); @@ -59,7 +59,7 @@ void XRServer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_world_origin"), &XRServer::get_world_origin); ClassDB::bind_method(D_METHOD("set_world_origin", "world_origin"), &XRServer::set_world_origin); ClassDB::bind_method(D_METHOD("get_reference_frame"), &XRServer::get_reference_frame); - ClassDB::bind_method(D_METHOD("clear_reference_frame"), &XRServer::get_reference_frame); + ClassDB::bind_method(D_METHOD("clear_reference_frame"), &XRServer::clear_reference_frame); ClassDB::bind_method(D_METHOD("center_on_hmd", "rotation_mode", "keep_height"), &XRServer::center_on_hmd); ClassDB::bind_method(D_METHOD("get_hmd_transform"), &XRServer::get_hmd_transform); @@ -104,11 +104,20 @@ void XRServer::_bind_methods() { ADD_SIGNAL(MethodInfo("tracker_added", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type"))); ADD_SIGNAL(MethodInfo("tracker_updated", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type"))); ADD_SIGNAL(MethodInfo("tracker_removed", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type"))); -}; +} double XRServer::get_world_scale() const { - return world_scale; -}; + RenderingServer *rendering_server = RenderingServer::get_singleton(); + + if (rendering_server && rendering_server->is_on_render_thread()) { + // Return the value with which we're currently rendering, + // if we're on the render thread + return render_state.world_scale; + } else { + // Return our current value + return world_scale; + } +} void XRServer::set_world_scale(double p_world_scale) { if (p_world_scale < 0.01) { @@ -118,19 +127,58 @@ void XRServer::set_world_scale(double p_world_scale) { } world_scale = p_world_scale; -}; + set_render_world_scale(world_scale); +} + +void XRServer::_set_render_world_scale(double p_world_scale) { + // Must be called from rendering thread! + ERR_NOT_ON_RENDER_THREAD; + + XRServer *xr_server = XRServer::get_singleton(); + ERR_FAIL_NULL(xr_server); + xr_server->render_state.world_scale = p_world_scale; +} Transform3D XRServer::get_world_origin() const { - return world_origin; -}; + RenderingServer *rendering_server = RenderingServer::get_singleton(); + + if (rendering_server && rendering_server->is_on_render_thread()) { + // Return the value with which we're currently rendering, + // if we're on the render thread + return render_state.world_origin; + } else { + // Return our current value + return world_origin; + } +} void XRServer::set_world_origin(const Transform3D &p_world_origin) { world_origin = p_world_origin; -}; + set_render_world_origin(world_origin); +} + +void XRServer::_set_render_world_origin(const Transform3D &p_world_origin) { + // Must be called from rendering thread! + ERR_NOT_ON_RENDER_THREAD; + + XRServer *xr_server = XRServer::get_singleton(); + ERR_FAIL_NULL(xr_server); + xr_server->render_state.world_origin = p_world_origin; +} Transform3D XRServer::get_reference_frame() const { - return reference_frame; -}; + RenderingServer *rendering_server = RenderingServer::get_singleton(); + ERR_FAIL_NULL_V(rendering_server, reference_frame); + + if (rendering_server->is_on_render_thread()) { + // Return the value with which we're currently rendering, + // if we're on the render thread + return render_state.reference_frame; + } else { + // Return our current value + return reference_frame; + } +} void XRServer::center_on_hmd(RotationMode p_rotation_mode, bool p_keep_height) { if (primary_interface == nullptr) { @@ -156,27 +204,38 @@ void XRServer::center_on_hmd(RotationMode p_rotation_mode, bool p_keep_height) { } else if (p_rotation_mode == 2) { // remove our rotation, we're only interesting in centering on position new_reference_frame.basis = Basis(); - }; + } // don't negate our height if (p_keep_height) { new_reference_frame.origin.y = 0.0; - }; + } reference_frame = new_reference_frame.inverse(); -}; + set_render_reference_frame(reference_frame); +} void XRServer::clear_reference_frame() { reference_frame = Transform3D(); + set_render_reference_frame(reference_frame); +} + +void XRServer::_set_render_reference_frame(const Transform3D &p_reference_frame) { + // Must be called from rendering thread! + ERR_NOT_ON_RENDER_THREAD; + + XRServer *xr_server = XRServer::get_singleton(); + ERR_FAIL_NULL(xr_server); + xr_server->render_state.reference_frame = p_reference_frame; } Transform3D XRServer::get_hmd_transform() { Transform3D hmd_transform; if (primary_interface != nullptr) { hmd_transform = primary_interface->get_camera_transform(); - }; + } return hmd_transform; -}; +} void XRServer::add_interface(const Ref<XRInterface> &p_interface) { ERR_FAIL_COND(p_interface.is_null()); @@ -185,12 +244,12 @@ void XRServer::add_interface(const Ref<XRInterface> &p_interface) { if (interfaces[i] == p_interface) { ERR_PRINT("Interface was already added"); return; - }; - }; + } + } interfaces.push_back(p_interface); emit_signal(SNAME("interface_added"), p_interface->get_name()); -}; +} void XRServer::remove_interface(const Ref<XRInterface> &p_interface) { ERR_FAIL_COND(p_interface.is_null()); @@ -200,33 +259,33 @@ void XRServer::remove_interface(const Ref<XRInterface> &p_interface) { if (interfaces[i] == p_interface) { idx = i; break; - }; - }; + } + } ERR_FAIL_COND_MSG(idx == -1, "Interface not found."); print_verbose("XR: Removed interface \"" + p_interface->get_name() + "\""); emit_signal(SNAME("interface_removed"), p_interface->get_name()); interfaces.remove_at(idx); -}; +} int XRServer::get_interface_count() const { return interfaces.size(); -}; +} Ref<XRInterface> XRServer::get_interface(int p_index) const { ERR_FAIL_INDEX_V(p_index, interfaces.size(), nullptr); return interfaces[p_index]; -}; +} Ref<XRInterface> XRServer::find_interface(const String &p_name) const { for (int i = 0; i < interfaces.size(); i++) { if (interfaces[i]->get_name() == p_name) { return interfaces[i]; - }; - }; + } + } return Ref<XRInterface>(); -}; +} TypedArray<Dictionary> XRServer::get_interfaces() const { Array ret; @@ -238,14 +297,14 @@ TypedArray<Dictionary> XRServer::get_interfaces() const { iface_info["name"] = interfaces[i]->get_name(); ret.push_back(iface_info); - }; + } return ret; -}; +} Ref<XRInterface> XRServer::get_primary_interface() const { return primary_interface; -}; +} void XRServer::set_primary_interface(const Ref<XRInterface> &p_primary_interface) { if (p_primary_interface.is_null()) { @@ -256,7 +315,7 @@ void XRServer::set_primary_interface(const Ref<XRInterface> &p_primary_interface print_verbose("XR: Primary interface set to: " + primary_interface->get_name()); } -}; +} void XRServer::add_tracker(const Ref<XRTracker> &p_tracker) { ERR_FAIL_COND(p_tracker.is_null()); @@ -272,7 +331,7 @@ void XRServer::add_tracker(const Ref<XRTracker> &p_tracker) { trackers[tracker_name] = p_tracker; emit_signal(SNAME("tracker_added"), tracker_name, p_tracker->get_tracker_type()); } -}; +} void XRServer::remove_tracker(const Ref<XRTracker> &p_tracker) { ERR_FAIL_COND(p_tracker.is_null()); @@ -285,7 +344,7 @@ void XRServer::remove_tracker(const Ref<XRTracker> &p_tracker) { // and remove it trackers.erase(tracker_name); } -}; +} Dictionary XRServer::get_trackers(int p_tracker_types) { Dictionary res; @@ -307,7 +366,7 @@ Ref<XRTracker> XRServer::get_tracker(const StringName &p_name) const { // tracker hasn't been registered yet, which is fine, no need to spam the error log... return Ref<XRTracker>(); } -}; +} PackedStringArray XRServer::get_suggested_tracker_names() const { PackedStringArray arr; @@ -369,9 +428,9 @@ void XRServer::_process() { // ignore, not a valid reference } else if (interfaces[i]->is_initialized()) { interfaces.write[i]->process(); - }; - }; -}; + } + } +} void XRServer::pre_render() { // called from RendererViewport.draw_viewports right before we start drawing our viewports @@ -383,8 +442,8 @@ void XRServer::pre_render() { // ignore, not a valid reference } else if (interfaces[i]->is_initialized()) { interfaces.write[i]->pre_render(); - }; - }; + } + } } void XRServer::end_frame() { @@ -396,14 +455,13 @@ void XRServer::end_frame() { // ignore, not a valid reference } else if (interfaces[i]->is_initialized()) { interfaces.write[i]->end_frame(); - }; - }; + } + } } XRServer::XRServer() { singleton = this; - world_scale = 1.0; -}; +} XRServer::~XRServer() { primary_interface.unref(); @@ -412,4 +470,4 @@ XRServer::~XRServer() { trackers.clear(); singleton = nullptr; -}; +} diff --git a/servers/xr_server.h b/servers/xr_server.h index 717728171a..cd9c241bb0 100644 --- a/servers/xr_server.h +++ b/servers/xr_server.h @@ -36,6 +36,7 @@ #include "core/os/thread_safe.h" #include "core/templates/rid.h" #include "core/variant/variant.h" +#include "rendering_server.h" class XRInterface; class XRTracker; @@ -92,10 +93,46 @@ private: Ref<XRInterface> primary_interface; /* we'll identify one interface as primary, this will be used by our viewports */ - double world_scale; /* scale by which we multiply our tracker positions */ + double world_scale = 1.0; /* scale by which we multiply our tracker positions */ Transform3D world_origin; /* our world origin point, maps a location in our virtual world to the origin point in our real world tracking volume */ Transform3D reference_frame; /* our reference frame */ + // As we may be updating our main state for our next frame while we're still rendering our previous frame, + // we need to keep copies around. + struct RenderState { + double world_scale = 1.0; /* scale by which we multiply our tracker positions */ + Transform3D world_origin; /* our world origin point, maps a location in our virtual world to the origin point in our real world tracking volume */ + Transform3D reference_frame; /* our reference frame */ + } render_state; + + static void _set_render_world_scale(double p_world_scale); + static void _set_render_world_origin(const Transform3D &p_world_origin); + static void _set_render_reference_frame(const Transform3D &p_reference_frame); + + _FORCE_INLINE_ void set_render_world_scale(double p_world_scale) { + // If we're rendering on a separate thread, we may still be processing the last frame, don't communicate this till we're ready... + RenderingServer *rendering_server = RenderingServer::get_singleton(); + ERR_FAIL_NULL(rendering_server); + + rendering_server->call_on_render_thread(callable_mp_static(&XRServer::_set_render_world_scale).bind(p_world_scale)); + } + + _FORCE_INLINE_ void set_render_world_origin(const Transform3D &p_world_origin) { + // If we're rendering on a separate thread, we may still be processing the last frame, don't communicate this till we're ready... + RenderingServer *rendering_server = RenderingServer::get_singleton(); + ERR_FAIL_NULL(rendering_server); + + rendering_server->call_on_render_thread(callable_mp_static(&XRServer::_set_render_world_origin).bind(p_world_origin)); + } + + _FORCE_INLINE_ void set_render_reference_frame(const Transform3D &p_reference_frame) { + // If we're rendering on a separate thread, we may still be processing the last frame, don't communicate this till we're ready... + RenderingServer *rendering_server = RenderingServer::get_singleton(); + ERR_FAIL_NULL(rendering_server); + + rendering_server->call_on_render_thread(callable_mp_static(&XRServer::_set_render_reference_frame).bind(p_reference_frame)); + } + protected: static XRServer *singleton; diff --git a/thirdparty/README.md b/thirdparty/README.md index f2ddd5ebf9..4a7ab7314a 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -679,6 +679,11 @@ Collection of single-file libraries used in Godot components. * Version: git (7bdffb428b2b19ad1c43aa44c714dcc104177e84, 2021) * Modifications: Change from STL to Godot types (see provided patch). * License: MIT +- `qoa.h` + * Upstream: https://github.com/phoboslab/qoa + * Version: git (e4c751d61af2c395ea828c5888e728c1953bf09f, 2024) + * Modifications: Inlined functions and patched compiler warnings. + * License: MIT - `r128.{c,h}` * Upstream: https://github.com/fahickman/r128 * Version: git (6fc177671c47640d5bb69af10cf4ee91050015a1, 2023) diff --git a/thirdparty/misc/patches/qoa-min-fix.patch b/thirdparty/misc/patches/qoa-min-fix.patch new file mode 100644 index 0000000000..1043d8bbe7 --- /dev/null +++ b/thirdparty/misc/patches/qoa-min-fix.patch @@ -0,0 +1,155 @@ +diff --git a/qoa.h b/qoa.h +index aa8fb59434..2dde8df098 100644 +--- a/qoa.h ++++ b/qoa.h +@@ -140,14 +140,14 @@ typedef struct { + #endif + } qoa_desc; + +-unsigned int qoa_encode_header(qoa_desc *qoa, unsigned char *bytes); +-unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned int frame_len, unsigned char *bytes); +-void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len); ++inline unsigned int qoa_encode_header(qoa_desc *qoa, unsigned char *bytes); ++inline unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned int frame_len, unsigned char *bytes); ++inline void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len); + +-unsigned int qoa_max_frame_size(qoa_desc *qoa); +-unsigned int qoa_decode_header(const unsigned char *bytes, int size, qoa_desc *qoa); +-unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa_desc *qoa, short *sample_data, unsigned int *frame_len); +-short *qoa_decode(const unsigned char *bytes, int size, qoa_desc *file); ++inline unsigned int qoa_max_frame_size(qoa_desc *qoa); ++inline unsigned int qoa_decode_header(const unsigned char *bytes, int size, qoa_desc *qoa); ++inline unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa_desc *qoa, short *sample_data, unsigned int *frame_len); ++inline short *qoa_decode(const unsigned char *bytes, int size, qoa_desc *file); + + #ifndef QOA_NO_STDIO + +@@ -366,7 +366,7 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned + ), bytes, &p); + + +- for (int c = 0; c < channels; c++) { ++ for (unsigned int c = 0; c < channels; c++) { + /* Write the current LMS state */ + qoa_uint64_t weights = 0; + qoa_uint64_t history = 0; +@@ -380,9 +380,9 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned + + /* We encode all samples with the channels interleaved on a slice level. + E.g. for stereo: (ch-0, slice 0), (ch 1, slice 0), (ch 0, slice 1), ...*/ +- for (int sample_index = 0; sample_index < frame_len; sample_index += QOA_SLICE_LEN) { ++ for (unsigned int sample_index = 0; sample_index < frame_len; sample_index += QOA_SLICE_LEN) { + +- for (int c = 0; c < channels; c++) { ++ for (unsigned int c = 0; c < channels; c++) { + int slice_len = qoa_clamp(QOA_SLICE_LEN, 0, frame_len - sample_index); + int slice_start = sample_index * channels + c; + int slice_end = (sample_index + slice_len) * channels + c; +@@ -391,10 +391,9 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned + 16 scalefactors, encode all samples for the current slice and + meassure the total squared error. */ + qoa_uint64_t best_rank = -1; +- qoa_uint64_t best_error = -1; +- qoa_uint64_t best_slice; +- qoa_lms_t best_lms; +- int best_scalefactor; ++ qoa_uint64_t best_slice = -1; ++ qoa_lms_t best_lms = {{-1, -1, -1, -1}, {-1, -1, -1, -1}}; ++ int best_scalefactor = -1; + + for (int sfi = 0; sfi < 16; sfi++) { + /* There is a strong correlation between the scalefactors of +@@ -408,7 +407,6 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned + qoa_lms_t lms = qoa->lms[c]; + qoa_uint64_t slice = scalefactor; + qoa_uint64_t current_rank = 0; +- qoa_uint64_t current_error = 0; + + for (int si = slice_start; si < slice_end; si += channels) { + int sample = sample_data[si]; +@@ -438,7 +436,6 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned + qoa_uint64_t error_sq = error * error; + + current_rank += error_sq + weights_penalty * weights_penalty; +- current_error += error_sq; + if (current_rank > best_rank) { + break; + } +@@ -449,7 +446,6 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned + + if (current_rank < best_rank) { + best_rank = current_rank; +- best_error = current_error; + best_slice = slice; + best_lms = lms; + best_scalefactor = scalefactor; +@@ -492,9 +488,9 @@ void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len) + num_frames * QOA_LMS_LEN * 4 * qoa->channels + /* 4 * 4 bytes lms state per channel */ + num_slices * 8 * qoa->channels; /* 8 byte slices */ + +- unsigned char *bytes = QOA_MALLOC(encoded_size); ++ unsigned char *bytes = (unsigned char *)QOA_MALLOC(encoded_size); + +- for (int c = 0; c < qoa->channels; c++) { ++ for (unsigned int c = 0; c < qoa->channels; c++) { + /* Set the initial LMS weights to {0, 0, -1, 2}. This helps with the + prediction of the first few ms of a file. */ + qoa->lms[c].weights[0] = 0; +@@ -517,7 +513,7 @@ void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len) + #endif + + int frame_len = QOA_FRAME_LEN; +- for (int sample_index = 0; sample_index < qoa->samples; sample_index += frame_len) { ++ for (unsigned int sample_index = 0; sample_index < qoa->samples; sample_index += frame_len) { + frame_len = qoa_clamp(QOA_FRAME_LEN, 0, qoa->samples - sample_index); + const short *frame_samples = sample_data + sample_index * qoa->channels; + unsigned int frame_size = qoa_encode_frame(frame_samples, qoa, frame_len, bytes + p); +@@ -580,14 +576,14 @@ unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa + + /* Read and verify the frame header */ + qoa_uint64_t frame_header = qoa_read_u64(bytes, &p); +- int channels = (frame_header >> 56) & 0x0000ff; +- int samplerate = (frame_header >> 32) & 0xffffff; +- int samples = (frame_header >> 16) & 0x00ffff; +- int frame_size = (frame_header ) & 0x00ffff; ++ unsigned int channels = (frame_header >> 56) & 0x0000ff; ++ unsigned int samplerate = (frame_header >> 32) & 0xffffff; ++ unsigned int samples = (frame_header >> 16) & 0x00ffff; ++ unsigned int frame_size = (frame_header ) & 0x00ffff; + + int data_size = frame_size - 8 - QOA_LMS_LEN * 4 * channels; + int num_slices = data_size / 8; +- int max_total_samples = num_slices * QOA_SLICE_LEN; ++ unsigned int max_total_samples = num_slices * QOA_SLICE_LEN; + + if ( + channels != qoa->channels || +@@ -600,7 +596,7 @@ unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa + + + /* Read the LMS state: 4 x 2 bytes history, 4 x 2 bytes weights per channel */ +- for (int c = 0; c < channels; c++) { ++ for (unsigned int c = 0; c < channels; c++) { + qoa_uint64_t history = qoa_read_u64(bytes, &p); + qoa_uint64_t weights = qoa_read_u64(bytes, &p); + for (int i = 0; i < QOA_LMS_LEN; i++) { +@@ -613,8 +609,8 @@ unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa + + + /* Decode all slices for all channels in this frame */ +- for (int sample_index = 0; sample_index < samples; sample_index += QOA_SLICE_LEN) { +- for (int c = 0; c < channels; c++) { ++ for (unsigned int sample_index = 0; sample_index < samples; sample_index += QOA_SLICE_LEN) { ++ for (unsigned int c = 0; c < channels; c++) { + qoa_uint64_t slice = qoa_read_u64(bytes, &p); + + int scalefactor = (slice >> 60) & 0xf; +@@ -647,7 +643,7 @@ short *qoa_decode(const unsigned char *bytes, int size, qoa_desc *qoa) { + + /* Calculate the required size of the sample buffer and allocate */ + int total_samples = qoa->samples * qoa->channels; +- short *sample_data = QOA_MALLOC(total_samples * sizeof(short)); ++ short *sample_data = (short *)QOA_MALLOC(total_samples * sizeof(short)); + + unsigned int sample_index = 0; + unsigned int frame_len; diff --git a/thirdparty/misc/qoa.h b/thirdparty/misc/qoa.h new file mode 100644 index 0000000000..2dde8df098 --- /dev/null +++ b/thirdparty/misc/qoa.h @@ -0,0 +1,728 @@ +/* + +Copyright (c) 2023, Dominic Szablewski - https://phoboslab.org +SPDX-License-Identifier: MIT + +QOA - The "Quite OK Audio" format for fast, lossy audio compression + + +-- Data Format + +QOA encodes pulse-code modulated (PCM) audio data with up to 255 channels, +sample rates from 1 up to 16777215 hertz and a bit depth of 16 bits. + +The compression method employed in QOA is lossy; it discards some information +from the uncompressed PCM data. For many types of audio signals this compression +is "transparent", i.e. the difference from the original file is often not +audible. + +QOA encodes 20 samples of 16 bit PCM data into slices of 64 bits. A single +sample therefore requires 3.2 bits of storage space, resulting in a 5x +compression (16 / 3.2). + +A QOA file consists of an 8 byte file header, followed by a number of frames. +Each frame contains an 8 byte frame header, the current 16 byte en-/decoder +state per channel and 256 slices per channel. Each slice is 8 bytes wide and +encodes 20 samples of audio data. + +All values, including the slices, are big endian. The file layout is as follows: + +struct { + struct { + char magic[4]; // magic bytes "qoaf" + uint32_t samples; // samples per channel in this file + } file_header; + + struct { + struct { + uint8_t num_channels; // no. of channels + uint24_t samplerate; // samplerate in hz + uint16_t fsamples; // samples per channel in this frame + uint16_t fsize; // frame size (includes this header) + } frame_header; + + struct { + int16_t history[4]; // most recent last + int16_t weights[4]; // most recent last + } lms_state[num_channels]; + + qoa_slice_t slices[256][num_channels]; + + } frames[ceil(samples / (256 * 20))]; +} qoa_file_t; + +Each `qoa_slice_t` contains a quantized scalefactor `sf_quant` and 20 quantized +residuals `qrNN`: + +.- QOA_SLICE -- 64 bits, 20 samples --------------------------/ /------------. +| Byte[0] | Byte[1] | Byte[2] \ \ Byte[7] | +| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | 7 6 5 / / 2 1 0 | +|------------+--------+--------+--------+---------+---------+-\ \--+---------| +| sf_quant | qr00 | qr01 | qr02 | qr03 | qr04 | / / | qr19 | +`-------------------------------------------------------------\ \------------` + +Each frame except the last must contain exactly 256 slices per channel. The last +frame may contain between 1 .. 256 (inclusive) slices per channel. The last +slice (for each channel) in the last frame may contain less than 20 samples; the +slice still must be 8 bytes wide, with the unused samples zeroed out. + +Channels are interleaved per slice. E.g. for 2 channel stereo: +slice[0] = L, slice[1] = R, slice[2] = L, slice[3] = R ... + +A valid QOA file or stream must have at least one frame. Each frame must contain +at least one channel and one sample with a samplerate between 1 .. 16777215 +(inclusive). + +If the total number of samples is not known by the encoder, the samples in the +file header may be set to 0x00000000 to indicate that the encoder is +"streaming". In a streaming context, the samplerate and number of channels may +differ from frame to frame. For static files (those with samples set to a +non-zero value), each frame must have the same number of channels and same +samplerate. + +Note that this implementation of QOA only handles files with a known total +number of samples. + +A decoder should support at least 8 channels. The channel layout for channel +counts 1 .. 8 is: + + 1. Mono + 2. L, R + 3. L, R, C + 4. FL, FR, B/SL, B/SR + 5. FL, FR, C, B/SL, B/SR + 6. FL, FR, C, LFE, B/SL, B/SR + 7. FL, FR, C, LFE, B, SL, SR + 8. FL, FR, C, LFE, BL, BR, SL, SR + +QOA predicts each audio sample based on the previously decoded ones using a +"Sign-Sign Least Mean Squares Filter" (LMS). This prediction plus the +dequantized residual forms the final output sample. + +*/ + + + +/* ----------------------------------------------------------------------------- + Header - Public functions */ + +#ifndef QOA_H +#define QOA_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define QOA_MIN_FILESIZE 16 +#define QOA_MAX_CHANNELS 8 + +#define QOA_SLICE_LEN 20 +#define QOA_SLICES_PER_FRAME 256 +#define QOA_FRAME_LEN (QOA_SLICES_PER_FRAME * QOA_SLICE_LEN) +#define QOA_LMS_LEN 4 +#define QOA_MAGIC 0x716f6166 /* 'qoaf' */ + +#define QOA_FRAME_SIZE(channels, slices) \ + (8 + QOA_LMS_LEN * 4 * channels + 8 * slices * channels) + +typedef struct { + int history[QOA_LMS_LEN]; + int weights[QOA_LMS_LEN]; +} qoa_lms_t; + +typedef struct { + unsigned int channels; + unsigned int samplerate; + unsigned int samples; + qoa_lms_t lms[QOA_MAX_CHANNELS]; + #ifdef QOA_RECORD_TOTAL_ERROR + double error; + #endif +} qoa_desc; + +inline unsigned int qoa_encode_header(qoa_desc *qoa, unsigned char *bytes); +inline unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned int frame_len, unsigned char *bytes); +inline void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len); + +inline unsigned int qoa_max_frame_size(qoa_desc *qoa); +inline unsigned int qoa_decode_header(const unsigned char *bytes, int size, qoa_desc *qoa); +inline unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa_desc *qoa, short *sample_data, unsigned int *frame_len); +inline short *qoa_decode(const unsigned char *bytes, int size, qoa_desc *file); + +#ifndef QOA_NO_STDIO + +int qoa_write(const char *filename, const short *sample_data, qoa_desc *qoa); +void *qoa_read(const char *filename, qoa_desc *qoa); + +#endif /* QOA_NO_STDIO */ + + +#ifdef __cplusplus +} +#endif +#endif /* QOA_H */ + + +/* ----------------------------------------------------------------------------- + Implementation */ + +#ifdef QOA_IMPLEMENTATION +#include <stdlib.h> + +#ifndef QOA_MALLOC + #define QOA_MALLOC(sz) malloc(sz) + #define QOA_FREE(p) free(p) +#endif + +typedef unsigned long long qoa_uint64_t; + + +/* The quant_tab provides an index into the dequant_tab for residuals in the +range of -8 .. 8. It maps this range to just 3bits and becomes less accurate at +the higher end. Note that the residual zero is identical to the lowest positive +value. This is mostly fine, since the qoa_div() function always rounds away +from zero. */ + +static const int qoa_quant_tab[17] = { + 7, 7, 7, 5, 5, 3, 3, 1, /* -8..-1 */ + 0, /* 0 */ + 0, 2, 2, 4, 4, 6, 6, 6 /* 1.. 8 */ +}; + + +/* We have 16 different scalefactors. Like the quantized residuals these become +less accurate at the higher end. In theory, the highest scalefactor that we +would need to encode the highest 16bit residual is (2**16)/8 = 8192. However we +rely on the LMS filter to predict samples accurately enough that a maximum +residual of one quarter of the 16 bit range is sufficient. I.e. with the +scalefactor 2048 times the quant range of 8 we can encode residuals up to 2**14. + +The scalefactor values are computed as: +scalefactor_tab[s] <- round(pow(s + 1, 2.75)) */ + +static const int qoa_scalefactor_tab[16] = { + 1, 7, 21, 45, 84, 138, 211, 304, 421, 562, 731, 928, 1157, 1419, 1715, 2048 +}; + + +/* The reciprocal_tab maps each of the 16 scalefactors to their rounded +reciprocals 1/scalefactor. This allows us to calculate the scaled residuals in +the encoder with just one multiplication instead of an expensive division. We +do this in .16 fixed point with integers, instead of floats. + +The reciprocal_tab is computed as: +reciprocal_tab[s] <- ((1<<16) + scalefactor_tab[s] - 1) / scalefactor_tab[s] */ + +static const int qoa_reciprocal_tab[16] = { + 65536, 9363, 3121, 1457, 781, 475, 311, 216, 156, 117, 90, 71, 57, 47, 39, 32 +}; + + +/* The dequant_tab maps each of the scalefactors and quantized residuals to +their unscaled & dequantized version. + +Since qoa_div rounds away from the zero, the smallest entries are mapped to 3/4 +instead of 1. The dequant_tab assumes the following dequantized values for each +of the quant_tab indices and is computed as: +float dqt[8] = {0.75, -0.75, 2.5, -2.5, 4.5, -4.5, 7, -7}; +dequant_tab[s][q] <- round_ties_away_from_zero(scalefactor_tab[s] * dqt[q]) + +The rounding employed here is "to nearest, ties away from zero", i.e. positive +and negative values are treated symmetrically. +*/ + +static const int qoa_dequant_tab[16][8] = { + { 1, -1, 3, -3, 5, -5, 7, -7}, + { 5, -5, 18, -18, 32, -32, 49, -49}, + { 16, -16, 53, -53, 95, -95, 147, -147}, + { 34, -34, 113, -113, 203, -203, 315, -315}, + { 63, -63, 210, -210, 378, -378, 588, -588}, + { 104, -104, 345, -345, 621, -621, 966, -966}, + { 158, -158, 528, -528, 950, -950, 1477, -1477}, + { 228, -228, 760, -760, 1368, -1368, 2128, -2128}, + { 316, -316, 1053, -1053, 1895, -1895, 2947, -2947}, + { 422, -422, 1405, -1405, 2529, -2529, 3934, -3934}, + { 548, -548, 1828, -1828, 3290, -3290, 5117, -5117}, + { 696, -696, 2320, -2320, 4176, -4176, 6496, -6496}, + { 868, -868, 2893, -2893, 5207, -5207, 8099, -8099}, + {1064, -1064, 3548, -3548, 6386, -6386, 9933, -9933}, + {1286, -1286, 4288, -4288, 7718, -7718, 12005, -12005}, + {1536, -1536, 5120, -5120, 9216, -9216, 14336, -14336}, +}; + + +/* The Least Mean Squares Filter is the heart of QOA. It predicts the next +sample based on the previous 4 reconstructed samples. It does so by continuously +adjusting 4 weights based on the residual of the previous prediction. + +The next sample is predicted as the sum of (weight[i] * history[i]). + +The adjustment of the weights is done with a "Sign-Sign-LMS" that adds or +subtracts the residual to each weight, based on the corresponding sample from +the history. This, surprisingly, is sufficient to get worthwhile predictions. + +This is all done with fixed point integers. Hence the right-shifts when updating +the weights and calculating the prediction. */ + +static int qoa_lms_predict(qoa_lms_t *lms) { + int prediction = 0; + for (int i = 0; i < QOA_LMS_LEN; i++) { + prediction += lms->weights[i] * lms->history[i]; + } + return prediction >> 13; +} + +static void qoa_lms_update(qoa_lms_t *lms, int sample, int residual) { + int delta = residual >> 4; + for (int i = 0; i < QOA_LMS_LEN; i++) { + lms->weights[i] += lms->history[i] < 0 ? -delta : delta; + } + + for (int i = 0; i < QOA_LMS_LEN-1; i++) { + lms->history[i] = lms->history[i+1]; + } + lms->history[QOA_LMS_LEN-1] = sample; +} + + +/* qoa_div() implements a rounding division, but avoids rounding to zero for +small numbers. E.g. 0.1 will be rounded to 1. Note that 0 itself still +returns as 0, which is handled in the qoa_quant_tab[]. +qoa_div() takes an index into the .16 fixed point qoa_reciprocal_tab as an +argument, so it can do the division with a cheaper integer multiplication. */ + +static inline int qoa_div(int v, int scalefactor) { + int reciprocal = qoa_reciprocal_tab[scalefactor]; + int n = (v * reciprocal + (1 << 15)) >> 16; + n = n + ((v > 0) - (v < 0)) - ((n > 0) - (n < 0)); /* round away from 0 */ + return n; +} + +static inline int qoa_clamp(int v, int min, int max) { + if (v < min) { return min; } + if (v > max) { return max; } + return v; +} + +/* This specialized clamp function for the signed 16 bit range improves decode +performance quite a bit. The extra if() statement works nicely with the CPUs +branch prediction as this branch is rarely taken. */ + +static inline int qoa_clamp_s16(int v) { + if ((unsigned int)(v + 32768) > 65535) { + if (v < -32768) { return -32768; } + if (v > 32767) { return 32767; } + } + return v; +} + +static inline qoa_uint64_t qoa_read_u64(const unsigned char *bytes, unsigned int *p) { + bytes += *p; + *p += 8; + return + ((qoa_uint64_t)(bytes[0]) << 56) | ((qoa_uint64_t)(bytes[1]) << 48) | + ((qoa_uint64_t)(bytes[2]) << 40) | ((qoa_uint64_t)(bytes[3]) << 32) | + ((qoa_uint64_t)(bytes[4]) << 24) | ((qoa_uint64_t)(bytes[5]) << 16) | + ((qoa_uint64_t)(bytes[6]) << 8) | ((qoa_uint64_t)(bytes[7]) << 0); +} + +static inline void qoa_write_u64(qoa_uint64_t v, unsigned char *bytes, unsigned int *p) { + bytes += *p; + *p += 8; + bytes[0] = (v >> 56) & 0xff; + bytes[1] = (v >> 48) & 0xff; + bytes[2] = (v >> 40) & 0xff; + bytes[3] = (v >> 32) & 0xff; + bytes[4] = (v >> 24) & 0xff; + bytes[5] = (v >> 16) & 0xff; + bytes[6] = (v >> 8) & 0xff; + bytes[7] = (v >> 0) & 0xff; +} + + +/* ----------------------------------------------------------------------------- + Encoder */ + +unsigned int qoa_encode_header(qoa_desc *qoa, unsigned char *bytes) { + unsigned int p = 0; + qoa_write_u64(((qoa_uint64_t)QOA_MAGIC << 32) | qoa->samples, bytes, &p); + return p; +} + +unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned int frame_len, unsigned char *bytes) { + unsigned int channels = qoa->channels; + + unsigned int p = 0; + unsigned int slices = (frame_len + QOA_SLICE_LEN - 1) / QOA_SLICE_LEN; + unsigned int frame_size = QOA_FRAME_SIZE(channels, slices); + int prev_scalefactor[QOA_MAX_CHANNELS] = {0}; + + /* Write the frame header */ + qoa_write_u64(( + (qoa_uint64_t)qoa->channels << 56 | + (qoa_uint64_t)qoa->samplerate << 32 | + (qoa_uint64_t)frame_len << 16 | + (qoa_uint64_t)frame_size + ), bytes, &p); + + + for (unsigned int c = 0; c < channels; c++) { + /* Write the current LMS state */ + qoa_uint64_t weights = 0; + qoa_uint64_t history = 0; + for (int i = 0; i < QOA_LMS_LEN; i++) { + history = (history << 16) | (qoa->lms[c].history[i] & 0xffff); + weights = (weights << 16) | (qoa->lms[c].weights[i] & 0xffff); + } + qoa_write_u64(history, bytes, &p); + qoa_write_u64(weights, bytes, &p); + } + + /* We encode all samples with the channels interleaved on a slice level. + E.g. for stereo: (ch-0, slice 0), (ch 1, slice 0), (ch 0, slice 1), ...*/ + for (unsigned int sample_index = 0; sample_index < frame_len; sample_index += QOA_SLICE_LEN) { + + for (unsigned int c = 0; c < channels; c++) { + int slice_len = qoa_clamp(QOA_SLICE_LEN, 0, frame_len - sample_index); + int slice_start = sample_index * channels + c; + int slice_end = (sample_index + slice_len) * channels + c; + + /* Brute for search for the best scalefactor. Just go through all + 16 scalefactors, encode all samples for the current slice and + meassure the total squared error. */ + qoa_uint64_t best_rank = -1; + qoa_uint64_t best_slice = -1; + qoa_lms_t best_lms = {{-1, -1, -1, -1}, {-1, -1, -1, -1}}; + int best_scalefactor = -1; + + for (int sfi = 0; sfi < 16; sfi++) { + /* There is a strong correlation between the scalefactors of + neighboring slices. As an optimization, start testing + the best scalefactor of the previous slice first. */ + int scalefactor = (sfi + prev_scalefactor[c]) % 16; + + /* We have to reset the LMS state to the last known good one + before trying each scalefactor, as each pass updates the LMS + state when encoding. */ + qoa_lms_t lms = qoa->lms[c]; + qoa_uint64_t slice = scalefactor; + qoa_uint64_t current_rank = 0; + + for (int si = slice_start; si < slice_end; si += channels) { + int sample = sample_data[si]; + int predicted = qoa_lms_predict(&lms); + + int residual = sample - predicted; + int scaled = qoa_div(residual, scalefactor); + int clamped = qoa_clamp(scaled, -8, 8); + int quantized = qoa_quant_tab[clamped + 8]; + int dequantized = qoa_dequant_tab[scalefactor][quantized]; + int reconstructed = qoa_clamp_s16(predicted + dequantized); + + + /* If the weights have grown too large, we introduce a penalty + here. This prevents pops/clicks in certain problem cases */ + int weights_penalty = (( + lms.weights[0] * lms.weights[0] + + lms.weights[1] * lms.weights[1] + + lms.weights[2] * lms.weights[2] + + lms.weights[3] * lms.weights[3] + ) >> 18) - 0x8ff; + if (weights_penalty < 0) { + weights_penalty = 0; + } + + long long error = (sample - reconstructed); + qoa_uint64_t error_sq = error * error; + + current_rank += error_sq + weights_penalty * weights_penalty; + if (current_rank > best_rank) { + break; + } + + qoa_lms_update(&lms, reconstructed, dequantized); + slice = (slice << 3) | quantized; + } + + if (current_rank < best_rank) { + best_rank = current_rank; + best_slice = slice; + best_lms = lms; + best_scalefactor = scalefactor; + } + } + + prev_scalefactor[c] = best_scalefactor; + + qoa->lms[c] = best_lms; + #ifdef QOA_RECORD_TOTAL_ERROR + qoa->error += best_error; + #endif + + /* If this slice was shorter than QOA_SLICE_LEN, we have to left- + shift all encoded data, to ensure the rightmost bits are the empty + ones. This should only happen in the last frame of a file as all + slices are completely filled otherwise. */ + best_slice <<= (QOA_SLICE_LEN - slice_len) * 3; + qoa_write_u64(best_slice, bytes, &p); + } + } + + return p; +} + +void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len) { + if ( + qoa->samples == 0 || + qoa->samplerate == 0 || qoa->samplerate > 0xffffff || + qoa->channels == 0 || qoa->channels > QOA_MAX_CHANNELS + ) { + return NULL; + } + + /* Calculate the encoded size and allocate */ + unsigned int num_frames = (qoa->samples + QOA_FRAME_LEN-1) / QOA_FRAME_LEN; + unsigned int num_slices = (qoa->samples + QOA_SLICE_LEN-1) / QOA_SLICE_LEN; + unsigned int encoded_size = 8 + /* 8 byte file header */ + num_frames * 8 + /* 8 byte frame headers */ + num_frames * QOA_LMS_LEN * 4 * qoa->channels + /* 4 * 4 bytes lms state per channel */ + num_slices * 8 * qoa->channels; /* 8 byte slices */ + + unsigned char *bytes = (unsigned char *)QOA_MALLOC(encoded_size); + + for (unsigned int c = 0; c < qoa->channels; c++) { + /* Set the initial LMS weights to {0, 0, -1, 2}. This helps with the + prediction of the first few ms of a file. */ + qoa->lms[c].weights[0] = 0; + qoa->lms[c].weights[1] = 0; + qoa->lms[c].weights[2] = -(1<<13); + qoa->lms[c].weights[3] = (1<<14); + + /* Explicitly set the history samples to 0, as we might have some + garbage in there. */ + for (int i = 0; i < QOA_LMS_LEN; i++) { + qoa->lms[c].history[i] = 0; + } + } + + + /* Encode the header and go through all frames */ + unsigned int p = qoa_encode_header(qoa, bytes); + #ifdef QOA_RECORD_TOTAL_ERROR + qoa->error = 0; + #endif + + int frame_len = QOA_FRAME_LEN; + for (unsigned int sample_index = 0; sample_index < qoa->samples; sample_index += frame_len) { + frame_len = qoa_clamp(QOA_FRAME_LEN, 0, qoa->samples - sample_index); + const short *frame_samples = sample_data + sample_index * qoa->channels; + unsigned int frame_size = qoa_encode_frame(frame_samples, qoa, frame_len, bytes + p); + p += frame_size; + } + + *out_len = p; + return bytes; +} + + + +/* ----------------------------------------------------------------------------- + Decoder */ + +unsigned int qoa_max_frame_size(qoa_desc *qoa) { + return QOA_FRAME_SIZE(qoa->channels, QOA_SLICES_PER_FRAME); +} + +unsigned int qoa_decode_header(const unsigned char *bytes, int size, qoa_desc *qoa) { + unsigned int p = 0; + if (size < QOA_MIN_FILESIZE) { + return 0; + } + + + /* Read the file header, verify the magic number ('qoaf') and read the + total number of samples. */ + qoa_uint64_t file_header = qoa_read_u64(bytes, &p); + + if ((file_header >> 32) != QOA_MAGIC) { + return 0; + } + + qoa->samples = file_header & 0xffffffff; + if (!qoa->samples) { + return 0; + } + + /* Peek into the first frame header to get the number of channels and + the samplerate. */ + qoa_uint64_t frame_header = qoa_read_u64(bytes, &p); + qoa->channels = (frame_header >> 56) & 0x0000ff; + qoa->samplerate = (frame_header >> 32) & 0xffffff; + + if (qoa->channels == 0 || qoa->samples == 0 || qoa->samplerate == 0) { + return 0; + } + + return 8; +} + +unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa_desc *qoa, short *sample_data, unsigned int *frame_len) { + unsigned int p = 0; + *frame_len = 0; + + if (size < 8 + QOA_LMS_LEN * 4 * qoa->channels) { + return 0; + } + + /* Read and verify the frame header */ + qoa_uint64_t frame_header = qoa_read_u64(bytes, &p); + unsigned int channels = (frame_header >> 56) & 0x0000ff; + unsigned int samplerate = (frame_header >> 32) & 0xffffff; + unsigned int samples = (frame_header >> 16) & 0x00ffff; + unsigned int frame_size = (frame_header ) & 0x00ffff; + + int data_size = frame_size - 8 - QOA_LMS_LEN * 4 * channels; + int num_slices = data_size / 8; + unsigned int max_total_samples = num_slices * QOA_SLICE_LEN; + + if ( + channels != qoa->channels || + samplerate != qoa->samplerate || + frame_size > size || + samples * channels > max_total_samples + ) { + return 0; + } + + + /* Read the LMS state: 4 x 2 bytes history, 4 x 2 bytes weights per channel */ + for (unsigned int c = 0; c < channels; c++) { + qoa_uint64_t history = qoa_read_u64(bytes, &p); + qoa_uint64_t weights = qoa_read_u64(bytes, &p); + for (int i = 0; i < QOA_LMS_LEN; i++) { + qoa->lms[c].history[i] = ((signed short)(history >> 48)); + history <<= 16; + qoa->lms[c].weights[i] = ((signed short)(weights >> 48)); + weights <<= 16; + } + } + + + /* Decode all slices for all channels in this frame */ + for (unsigned int sample_index = 0; sample_index < samples; sample_index += QOA_SLICE_LEN) { + for (unsigned int c = 0; c < channels; c++) { + qoa_uint64_t slice = qoa_read_u64(bytes, &p); + + int scalefactor = (slice >> 60) & 0xf; + int slice_start = sample_index * channels + c; + int slice_end = qoa_clamp(sample_index + QOA_SLICE_LEN, 0, samples) * channels + c; + + for (int si = slice_start; si < slice_end; si += channels) { + int predicted = qoa_lms_predict(&qoa->lms[c]); + int quantized = (slice >> 57) & 0x7; + int dequantized = qoa_dequant_tab[scalefactor][quantized]; + int reconstructed = qoa_clamp_s16(predicted + dequantized); + + sample_data[si] = reconstructed; + slice <<= 3; + + qoa_lms_update(&qoa->lms[c], reconstructed, dequantized); + } + } + } + + *frame_len = samples; + return p; +} + +short *qoa_decode(const unsigned char *bytes, int size, qoa_desc *qoa) { + unsigned int p = qoa_decode_header(bytes, size, qoa); + if (!p) { + return NULL; + } + + /* Calculate the required size of the sample buffer and allocate */ + int total_samples = qoa->samples * qoa->channels; + short *sample_data = (short *)QOA_MALLOC(total_samples * sizeof(short)); + + unsigned int sample_index = 0; + unsigned int frame_len; + unsigned int frame_size; + + /* Decode all frames */ + do { + short *sample_ptr = sample_data + sample_index * qoa->channels; + frame_size = qoa_decode_frame(bytes + p, size - p, qoa, sample_ptr, &frame_len); + + p += frame_size; + sample_index += frame_len; + } while (frame_size && sample_index < qoa->samples); + + qoa->samples = sample_index; + return sample_data; +} + + + +/* ----------------------------------------------------------------------------- + File read/write convenience functions */ + +#ifndef QOA_NO_STDIO +#include <stdio.h> + +int qoa_write(const char *filename, const short *sample_data, qoa_desc *qoa) { + FILE *f = fopen(filename, "wb"); + unsigned int size; + void *encoded; + + if (!f) { + return 0; + } + + encoded = qoa_encode(sample_data, qoa, &size); + if (!encoded) { + fclose(f); + return 0; + } + + fwrite(encoded, 1, size, f); + fclose(f); + + QOA_FREE(encoded); + return size; +} + +void *qoa_read(const char *filename, qoa_desc *qoa) { + FILE *f = fopen(filename, "rb"); + int size, bytes_read; + void *data; + short *sample_data; + + if (!f) { + return NULL; + } + + fseek(f, 0, SEEK_END); + size = ftell(f); + if (size <= 0) { + fclose(f); + return NULL; + } + fseek(f, 0, SEEK_SET); + + data = QOA_MALLOC(size); + if (!data) { + fclose(f); + return NULL; + } + + bytes_read = fread(data, 1, size, f); + fclose(f); + + sample_data = qoa_decode(data, bytes_read, qoa); + QOA_FREE(data); + return sample_data; +} + +#endif /* QOA_NO_STDIO */ +#endif /* QOA_IMPLEMENTATION */ |