diff options
110 files changed, 2905 insertions, 1941 deletions
diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml index 85ae6a5a8f..087b2d15de 100644 --- a/.github/workflows/linux_builds.yml +++ b/.github/workflows/linux_builds.yml @@ -68,6 +68,67 @@ jobs: run: | ./bin/godot.linuxbsd.opt.tools.64.mono --test + linux-editor-sanitizers: + runs-on: "ubuntu-20.04" + name: Editor with sanitizers (target=debug, tools=yes, tests=yes, use_asan=yes, use_ubsan=yes) + + steps: + - uses: actions/checkout@v2 + + # Azure repositories are not reliable, we need to prevent azure giving us packages. + - name: Make apt sources.list use the default Ubuntu repositories + run: | + sudo rm -f /etc/apt/sources.list.d/* + sudo cp -f misc/ci/sources.list /etc/apt/sources.list + sudo apt-get update + + # Install all packages (except scons) + - name: Configure dependencies + run: | + sudo apt-get install build-essential pkg-config libx11-dev libxcursor-dev \ + libxinerama-dev libgl1-mesa-dev libglu-dev libasound2-dev libpulse-dev libudev-dev libxi-dev libxrandr-dev yasm + + # Upload cache on completion and check it out now + - name: Load .scons_cache directory + id: linux-editor-cache + uses: actions/cache@v2 + with: + path: ${{github.workspace}}/.scons_cache/ + key: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} + restore-keys: | + ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} + ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}} + ${{github.job}}-${{env.GODOT_BASE_BRANCH}} + + # Use python 3.x release (works cross platform; best to keep self contained in it's own step) + - name: Set up Python 3.x + uses: actions/setup-python@v2 + with: + # Semantic version range syntax or exact version of a Python version + python-version: '3.x' + # Optional - x64 or x86 architecture, defaults to x64 + architecture: 'x64' + + # Setup scons, print python version and scons version info, so if anything is broken it won't run the build. + - name: Configuring Python packages + run: | + python -c "import sys; print(sys.version)" + python -m pip install scons + python --version + scons --version + + # We should always be explicit with our flags usage here since it's gonna be sure to always set those flags + - name: Compilation + env: + SCONS_CACHE: ${{github.workspace}}/.scons_cache/ + run: | + scons -j2 verbose=yes warnings=all werror=yes platform=linuxbsd tools=yes tests=yes target=debug use_asan=yes use_ubsan=yes + + # Execute unit tests for the editor + - name: Unit Tests + run: | + ./bin/godot.linuxbsd.tools.64s --test + linux-template: runs-on: "ubuntu-20.04" name: Template w/ Mono (target=release, tools=no) @@ -2,10 +2,6 @@ Alexander Holland <alexander.holland@live.de> Alexander Holland <alexander.holland@live.de> <alexander.holland@haw-hamburg.de> Alexander Holland <alexander.holland@live.de> <AlexHolly> Andrea Catania <info@andreacatania.com> -Andreas Haas <liu.gam3@gmail.com> -Andreas Haas <liu.gam3@gmail.com> <hinsbart@gmail.com> -Andreas Haas <liu.gam3@gmail.com> <hinsbart@users.noreply.github.com> -Andreas Haas <liu.gam3@gmail.com> <entenflugstuhl@gmail.com> Anish Bhobe <anishbhobe@hotmail.com> Anutrix <numaanzaheerahmed@yahoo.com> Aren Villanueva <arenvillanueva@yomogi-soft.com> <aren@displaysweet.com> @@ -69,6 +65,11 @@ Kelly Thomas <kelly.thomas@hotmail.com.au> K. S. Ernest (iFire) Lee <ernest.lee@chibifire.com> Leon Krause <lk@leonkrause.com> <eska@eska.me> Leon Krause <lk@leonkrause.com> <eska014@users.noreply.github.com> +Liz Haas <27thLiz@gmail.com> +Liz Haas <27thLiz@gmail.com> <liu.gam3@gmail.com> +Liz Haas <27thLiz@gmail.com> <hinsbart@gmail.com> +Liz Haas <27thLiz@gmail.com> <hinsbart@users.noreply.github.com> +Liz Haas <27thLiz@gmail.com> <entenflugstuhl@gmail.com> Manuel Strey <manuel.strey@gmx.de> Marcelo Fernandez <marcelofg55@gmail.com> Marcin Zawiejski <dragmz@gmail.com> diff --git a/AUTHORS.md b/AUTHORS.md index f1803c672c..40aa50f8b6 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -32,7 +32,6 @@ name is available. Alexey Khoroshavin (allkhor) Alket Rexhepi (alketii) Andrea Catania (AndreaCatania) - Andreas Haas (Hinsbart) Andrii Doroshenko (Xrayez) Andy Moss (MillionOstrich) Anish Bhobe (KidRigger) @@ -97,6 +96,7 @@ name is available. Jakub Grzesik (kubecz3k) James Buck (jbuck3) Jérôme Gully (Nutriz) + Jia Jun Chai (SkyLucilfer) Joan Fons Sanchez (JFonS) Johan Manuel (29jm) Joshua Grams (JoshuaGrams) @@ -107,7 +107,8 @@ name is available. Kostadin Damyanov (Max-Might) K. S. Ernest (iFire) Lee (fire) lawnjelly - Leon Krause (eska014) + Leon Krause (leonkrause) + Liz Haas (27thLiz) Lucien Menassol (Kanabenki) m4nu3lf Maganty Rushyendra (mrushyendra) @@ -134,6 +135,7 @@ name is available. muiroc Nathan Warden (NathanWarden) Nils André-Chang (NilsIrl) + Noah Beard (TwistedTwigleg) Nuno Donato (nunodonato) Ovnuniarchos Pascal Richter (ShyRed) @@ -157,12 +159,15 @@ name is available. Robin Hübner (profan) romulox-x Ruslan Mustakov (endragor) + Ryan Roden-Corrent (rrcore) Saniko (sanikoyes) santouits SaracenOne + Sergey Minakov (naithar) sersoong Shiqing (kawa-yoiko) Simon Wenner (swenner) + Stijn Hinlopen (hinlopen) Swarnim Arun (minraws) Thakee Nathees (ThakeeNathees) Theo Hallenius (TheoXD) @@ -12,26 +12,35 @@ generous deed immortalized in the next stable release of Godot Engine. ## Platinum sponsors + Gamblify <https://www.gamblify.com> Heroic Labs <https://heroiclabs.com> Interblock <http://interblockgaming.com> ## Gold sponsors - Gamblify <https://www.gamblify.com> + None currently, become one! <https://godotengine.org/donate> + +## Silver sponsors + Moonwards <https://www.moonwards.com> +## Bronze sponsors + + Brandon Lamb + ## Mini sponsors AD Ford Alan Beauchamp albinaask Alejandro Saucedo + alex brown Andrew Dunai - Brandon Lamb Christian Baune Christopher Montesano Darkhan Baimyrza Darrin Massena + David Mydlarz Digital Grows Dov Zimring Edward Flick @@ -43,6 +52,7 @@ generous deed immortalized in the next stable release of Godot Engine. Jasper Brooks Javary Co. Jeffery Chiu + Jonah Stich Justin Arnold Kyle Szklenski Marcel Kräml @@ -51,45 +61,39 @@ generous deed immortalized in the next stable release of Godot Engine. Mike King Nathan Warden Neal Gompa (Conan Kudo) + Patrick Schmidt Ronnie Cheng Slobodan Milnovic Stephan Lanfermann Steve + Thomas Krampl Tristan Pemble VilliHaukka + Violin Iliev + 蕭惟允 ## Gold donors - Bjarke - David Gehrig - David Snopek - Ed Morley - Florian Rämisch - Jakub Grzesik - Manuele Finocchiaro - Officine Pixel S.n.c. - Rami - Ronan Zeegers - Sofox - Spicylewd - Taylor Ritenour - Zaven Muradyan - - Andreas Schüle - Andres Hernandez + Andrew Morsillo Asher Glick Austen McRae Bernhard Werner beVR Carlo Cabanilla + Chris Goddard Christopher Case Daniel James + David Gehrig David Giardi - Default Name + David Snopek + Ed Morley eggs + Ellen Poe Florian Breisch + Florian Rämisch Forge Gamejunkey + Jakub Grzesik Javier Roman Jon Woodward Karl Werf @@ -97,95 +101,89 @@ generous deed immortalized in the next stable release of Godot Engine. Lex Steers Luke Maciej Pendolski + Manuele Finocchiaro + Markus Wiesner Matthew Hillier Mohamed Ikbel Boulabiar Monster Vial + Officine Pixel S.n.c. + Rami Rene + Rene Tailleur Retro Village Rob Messick Roland Fredenhagen + Ronan Zeegers Ryan Badour Sandro Jenny Sarksus Scott Wadden Sergey - thechris + Sofox + Spicylewd + Taylor Ritenour Tom Langwaldt Tricky Fat Cat tukon William Wold + xagonist + Zaven Muradyan - Alex Khayrullin - alice gambrell - Andrew Harris - Barugon - Chris Goddard - Chris Serino - Christian Padilla - Conrad Curry - Craig Smith - Darrian Little - dragonage13 - GiulianoB - Hoai Nam Tran - Horváth Péter - Jeff Nyte - Joan Fons - Joshua Flores - Leo Fidel R Liban - Michael Dürwald - Péter Magyar - Petr Malac - Rob - Robert Willes - Ronnie Ashlock - SKison - Thomas Bjarnelöf - Valryia - Vincent Henderson - Vojtěch - Wojciech Chojnacki - Xavier PATRICELLI - Zoran Kukulj - + Aaron Winter Adam Nakonieczny Adam Neumann Alexander J Maynard Alexey Dyadchenko + Alex Khayrullin + alice gambrell Andreas Funke André Frélicot + Andrew Harris aoshiwik - Ben Powell + Barugon + Can Eris Carlos de Sousa Marques Charlie Whitfield Chase Taranto Chelsea Hash Chris Petrich + Chris Serino Christian Alexander Bjørklund Bøhler Christian Leth Jeppesen Cody Parker + Conrad Curry Craig Ostrin - curtis Kramer + Craig Smith D + Darrian Little Dev To be curious Digital Denizen Easypete Edgar Sun Eugenio Hugo Salgüero Jáñez + Felix Brückner flesk F S Gary Hulst gavlig GGGames.org + GiulianoB Guilherme Felipe de C. G. da Silva Heath Hayes + Hoai Nam Tran + Horváth Péter Hu Hund - Isaac Clausman + Jared Jared White - Joe Flood + Jeff Nyte + Joan Fons + Joel Fivat + Joel Höglund John G Gentzel Jose Malheiro Joseph Crane + Joshie Sparks + Joshua Flores Joshua Lesperance Juan Velandia Julian Todd @@ -194,14 +192,18 @@ generous deed immortalized in the next stable release of Godot Engine. Kelteseth kickmaniac kinfox + Lachie Lain Ballard + Leo Fidel R Liban luca duran + MadScientistCarl Marcelo Dornbusch Lopes - Marcelo Henrique Gonçalves + Marisa Clardy Markus Fehr - Markus Wiesner Martin Eigel Matt Eunson + Michael + Michael Dürwald Mikado069 m kaersten MuffinManKen @@ -211,48 +213,64 @@ generous deed immortalized in the next stable release of Godot Engine. Patrick Ting Paul Hocker Paul Von Zimmerman + Pedro Silva Pete Goodwin + Péter Magyar + Petr Malac PhaineOfCatz pl Ranoller + Raymond Harris + Ricardo Alcantara + Rob + Robert Willes Rob McInroy Rocknight Studios + Ronnie Ashlock Ryan + Ryan Wilson Samuel Judd Scott Pilet - Scott Ryan-Taylor Sean Morgan Sean Robertson Sébastien Serban Serafimescu + Shishir Tandale + SKison SleepCircle spilldata + Steven Landow Stoned Xander Tahiti Bos TheLevelOfDetail . + Thomas Bjarnelöf Thomas Kurz + Timothy Pulliam Tobias Bocanegra Trent Fehl - Urho + Valryia + VikFro + Vincent Henderson + Vojtěch William Foster + Wojciech Chojnacki + Xavier PATRICELLI + xzibiting Zhou Tuizhi Zie Weaver - 蕭惟允 + Zoran Kukulj ## Silver donors 1D_Inc Aaron - Aaron Winter - Abel Crunk Abraham Haskins Acheron Adam Adam Brunnmeier - Adam Carr + Adam Carr Adam Long Adam McCurdy - Adam Netzel Adam N Webber Adam Smeltzer Adam Szymański @@ -265,18 +283,22 @@ generous deed immortalized in the next stable release of Godot Engine. AleMax Alessandro Senese Alexander Erlemann + Alexandre Beaudoin alex clavelle + Ali Al-Khalifa Allan Davis Allen Schade Andreas Krampitz André Simões andrew james morris Andrew Mansuetti + Andrew Rosenwinkel Andrew Thomas Ano Nim Anthony Avina AP Condomines Arda Erol + Arisaka Mayuki Armin Preiml Arseniy M Arthur S. Muszynski @@ -285,43 +307,49 @@ generous deed immortalized in the next stable release of Godot Engine. Aubrey Falconer B A Balázs Batári - Balázs Hasprai Bartosz Bielecki Benedikt Ben Vercammen Bernd Jänichen - Bjarne + Bjarne Voigtländer Black Block Blair Allen Bobby CC Wong Bram brian + Brian mc gowan + Brodie Fairhall Burney Waring + Caleb Gartner Cameron Meyer Carl van der Geest Carwyn Edwards Cas Brugman Cassidy James + Chad Steadman Chris Brown Chris Chapin Christian Winter + Christoffer Dahlblom Christoffer Sundbom - Christoph Brodmann Christophe Gagnier Christopher Schmitt Christoph Woinke Clay Heaton Cole Johnson - Cuauhtemoc Moreno Curt King - Daniel Kimblad + CzechBlueBear + Daniel De Macedo Daniel Johnson DanielMaximiano + Daniel Szarfman Daniel Tebbutt + Daren Scot Wilson Dave Walker David May David Woodard - Dimitri Stanojevic + David Zanetti + Dmitry Fisher Dmytro Korchynskyi Dominik Wetzel Donn Eddy @@ -341,6 +369,7 @@ generous deed immortalized in the next stable release of Godot Engine. Eric Ellingson Eric Williams Erkki Seppälä + ET Garcia Evan Rose Fain Faisal Alkubaisi @@ -378,22 +407,20 @@ generous deed immortalized in the next stable release of Godot Engine. Jaiden Gerig Jaime Ruiz-Borau Vizárraga Jako Danar + James James A F Manley Jamiee H Jamie Massey Janders JARKKO PARVIAINEN + Jason Uechi Jean-Baptiste LEPESME Jeff Hungerford Jennifer Graves Jesse Dubay Joe Alden - Joel Fivat - Joel Höglund - Joel Setterberg - Johannes Goslar + Joe Klemmer John Gabriel - John Walker Jomei Jackson Jonas Jonas Bernemann @@ -405,7 +432,6 @@ generous deed immortalized in the next stable release of Godot Engine. Jon Sully Jordy Goodridge Jorge Antunes - Jose Aleman Jose C. Rubio Joseph Catrambone Josh Mitchell @@ -422,19 +448,19 @@ generous deed immortalized in the next stable release of Godot Engine. Karel Němec Kauzig Keedong Park + Keinan Powers Keith Bradner Kent Jofur Kevin McPhillips - Kevin Velasco Kiri Jolly Kjetil Haugland - Klagsam KsyTek Games Kuan Cheang kycho Kyle Appelgate Kyuppin Laurent Tréguier + LEMMiNO Leonardo Dimano Lin Chear Linus Lind Lundgren @@ -447,6 +473,7 @@ generous deed immortalized in the next stable release of Godot Engine. Marco Lardelli Mark Jad Mark Krenz + Markus Martin Markus Michael Egger Martin FIbik Martin Holas @@ -456,16 +483,16 @@ generous deed immortalized in the next stable release of Godot Engine. Marvin Mathieu Matt Edwards - Mauro Pellegrini + Matthew Booe Max Fiedler Maxime Blade Maxwell Megasploot Melissa Mears mewin + Michael Cullen Michael Haney Michał Skwarek - Mikael Olsson Mikayla Mike Birkhead Mike Cunningham @@ -484,12 +511,16 @@ generous deed immortalized in the next stable release of Godot Engine. Nick Allen Nick Macholl Niclas Eriksen + Nicolas Goll-Perrier Nicolás Montaña Nicolas SAN AGUSTIN NZ + '@oddgoo + OKV Oleg Reva Olivier Omar Delarosa + Oscar Domingo Oscar Norlander Pan Ip Parinya Teerakasemsuk @@ -502,16 +533,16 @@ generous deed immortalized in the next stable release of Godot Engine. Penguin Peter Philip Cohoe + Pierre-Nicolas Tollitte Piotr Góral Point08 + Preethi Vaidyanathan pwab Rad Cat Rafa Laguna - Ram Remi Rampin Rémi Verschelde Reneator - Ricardo Alcantara Richard Diss Richard Ivánek Robert Farr (Larington) @@ -522,7 +553,9 @@ generous deed immortalized in the next stable release of Godot Engine. Ronald Ho Hip (CrimsonZA) Ronan Ronny Mühle + Ross Squires Ryan Groom + Sam Caulfield Sam Edson Samuele Zolfanelli Scott D. Yelich @@ -531,15 +564,16 @@ generous deed immortalized in the next stable release of Godot Engine. Sebastian Michailidis Sebastian Vetter Sergio Mello-Grand + Shaher Shane Shane Sicienski Shane Spoor - Shiomi '- Duy Kevin Nguyen + Shiomi - Duy Kevin Nguyen Siim Raidma Simon Jonas Larsen + Simon Schoenenberger Simon Wenner Sintinium - SK smbe19 smo1704 soft circles @@ -547,7 +581,7 @@ generous deed immortalized in the next stable release of Godot Engine. Stefano Caronia Steve Cloete Svenne Krap - Taylor Fahlman + Tannen Helmers Terry tezuvholovdr TheVoiceInMyHead @@ -558,26 +592,30 @@ generous deed immortalized in the next stable release of Godot Engine. Tim Drumheller Tim Erskine Timothy B. MacDonald - Title Plinsut Tobbun Tobias Bradtke - Tom Glenn Toni Duran + Tony Zhao Torgeir Lilleskog Torsten Crass Travis O'Brien Trent Skinner + Triptych + Triumph263 . Troy Bonneau Tryggve Sollid Turgut Temucin Tyler Compton Tyler Stafos UltyX + Uther Valentí Gàmez Vaughan Ling Victor Vigilant Watch + Viktor Ismagilov Vincent Cloutier + Vitor Balbio Vladimir Savin waka nya Wayne Haak @@ -587,9 +625,9 @@ generous deed immortalized in the next stable release of Godot Engine. Wyatt Goodin Yegor Smirnov YiYin Gu - Yuri LaPointe Yuri Sizov Zgegnesh Hemomancer + ΒΑΣΙΛΗΣ ΓΕΩΡΓΑΚΟΠΟΥΛΟΣ 郝晨煜 ## Bronze donors diff --git a/core/SCsub b/core/SCsub index d08f17c60a..40ee78d3ea 100644 --- a/core/SCsub +++ b/core/SCsub @@ -162,13 +162,17 @@ env.CommandNoCache( # Authors env.Depends("#core/authors.gen.h", "../AUTHORS.md") env.CommandNoCache( - "#core/authors.gen.h", "../AUTHORS.md", env.Run(core_builders.make_authors_header, "Generating authors header."), + "#core/authors.gen.h", + "../AUTHORS.md", + env.Run(core_builders.make_authors_header, "Generating authors header."), ) # Donors env.Depends("#core/donors.gen.h", "../DONORS.md") env.CommandNoCache( - "#core/donors.gen.h", "../DONORS.md", env.Run(core_builders.make_donors_header, "Generating donors header."), + "#core/donors.gen.h", + "../DONORS.md", + env.Run(core_builders.make_donors_header, "Generating donors header."), ) # License diff --git a/core/core_builders.py b/core/core_builders.py index d03874608e..004475faa7 100644 --- a/core/core_builders.py +++ b/core/core_builders.py @@ -117,14 +117,18 @@ def make_donors_header(target, source, env): sections = [ "Platinum sponsors", "Gold sponsors", + "Silver sponsors", + "Bronze sponsors", "Mini sponsors", "Gold donors", "Silver donors", "Bronze donors", ] sections_id = [ - "DONORS_SPONSOR_PLAT", + "DONORS_SPONSOR_PLATINUM", "DONORS_SPONSOR_GOLD", + "DONORS_SPONSOR_SILVER", + "DONORS_SPONSOR_BRONZE", "DONORS_SPONSOR_MINI", "DONORS_GOLD", "DONORS_SILVER", diff --git a/core/engine.cpp b/core/engine.cpp index c8bfffd020..d08cf92ecb 100644 --- a/core/engine.cpp +++ b/core/engine.cpp @@ -158,8 +158,10 @@ Array Engine::get_copyright_info() const { Dictionary Engine::get_donor_info() const { Dictionary donors; - donors["platinum_sponsors"] = array_from_info(DONORS_SPONSOR_PLAT); + donors["platinum_sponsors"] = array_from_info(DONORS_SPONSOR_PLATINUM); donors["gold_sponsors"] = array_from_info(DONORS_SPONSOR_GOLD); + donors["silver_sponsors"] = array_from_info(DONORS_SPONSOR_SILVER); + donors["bronze_sponsors"] = array_from_info(DONORS_SPONSOR_BRONZE); donors["mini_sponsors"] = array_from_info(DONORS_SPONSOR_MINI); donors["gold_donors"] = array_from_info(DONORS_GOLD); donors["silver_donors"] = array_from_info(DONORS_SILVER); diff --git a/core/project_settings.h b/core/project_settings.h index 686f6f3873..70b54ec854 100644 --- a/core/project_settings.h +++ b/core/project_settings.h @@ -76,8 +76,8 @@ protected: }; bool registering_order = true; - int last_order = 0; - int last_builtin_order = NO_BUILTIN_ORDER_BASE; + int last_order = NO_BUILTIN_ORDER_BASE; + int last_builtin_order = 0; Map<StringName, VariantContainer> props; String resource_path; Map<StringName, PropertyInfo> custom_prop_info; diff --git a/core/rid_owner.h b/core/rid_owner.h index 2489475c68..30f1e41733 100644 --- a/core/rid_owner.h +++ b/core/rid_owner.h @@ -236,7 +236,7 @@ public: } _FORCE_INLINE_ T *get_ptr_by_index(uint32_t p_index) { - ERR_FAIL_INDEX_V(p_index, alloc_count, nullptr); + ERR_FAIL_UNSIGNED_INDEX_V(p_index, alloc_count, nullptr); if (THREAD_SAFE) { spin_lock.lock(); } diff --git a/doc/classes/AnimatedSprite2D.xml b/doc/classes/AnimatedSprite2D.xml index 8d0534ccd2..7a992d0c03 100644 --- a/doc/classes/AnimatedSprite2D.xml +++ b/doc/classes/AnimatedSprite2D.xml @@ -7,6 +7,7 @@ Animations are created using a [SpriteFrames] resource, which can be configured in the editor via the SpriteFrames panel. </description> <tutorials> + <link title="2D Sprite animation">https://docs.godotengine.org/en/latest/tutorials/2d/2d_sprite_animation.html</link> </tutorials> <methods> <method name="is_playing" qualifiers="const"> diff --git a/doc/classes/AnimatedSprite3D.xml b/doc/classes/AnimatedSprite3D.xml index ad9706a52a..e1fb78e5b5 100644 --- a/doc/classes/AnimatedSprite3D.xml +++ b/doc/classes/AnimatedSprite3D.xml @@ -7,6 +7,7 @@ Animations are created using a [SpriteFrames] resource, which can be configured in the editor via the SpriteFrames panel. </description> <tutorials> + <link title="2D Sprite animation (also applies to 3D)">https://docs.godotengine.org/en/latest/tutorials/2d/2d_sprite_animation.html</link> </tutorials> <methods> <method name="is_playing" qualifiers="const"> diff --git a/doc/classes/AnimatedTexture.xml b/doc/classes/AnimatedTexture.xml index 285e0d5f39..5774842144 100644 --- a/doc/classes/AnimatedTexture.xml +++ b/doc/classes/AnimatedTexture.xml @@ -6,7 +6,8 @@ <description> [AnimatedTexture] is a resource format for frame-based animations, where multiple textures can be chained automatically with a predefined delay for each frame. Unlike [AnimationPlayer] or [AnimatedSprite2D], it isn't a [Node], but has the advantage of being usable anywhere a [Texture2D] resource can be used, e.g. in a [TileSet]. The playback of the animation is controlled by the [member fps] property as well as each frame's optional delay (see [method set_frame_delay]). The animation loops, i.e. it will restart at frame 0 automatically after playing the last frame. - [AnimatedTexture] currently requires all frame textures to have the same size, otherwise the bigger ones will be cropped to match the smallest one. Also, it doesn't support [AtlasTexture]. Each frame needs to be separate image. + [AnimatedTexture] currently requires all frame textures to have the same size, otherwise the bigger ones will be cropped to match the smallest one. + [b]Note:[/b] AnimatedTexture doesn't support using [AtlasTexture]s. Each frame needs to be a separate [Texture2D]. </description> <tutorials> </tutorials> diff --git a/doc/classes/AnimationPlayer.xml b/doc/classes/AnimationPlayer.xml index 1420b1bf64..6267a5a0a9 100644 --- a/doc/classes/AnimationPlayer.xml +++ b/doc/classes/AnimationPlayer.xml @@ -10,6 +10,7 @@ </description> <tutorials> <link>https://docs.godotengine.org/en/latest/getting_started/step_by_step/animations.html</link> + <link title="2D Sprite animation">https://docs.godotengine.org/en/latest/tutorials/2d/2d_sprite_animation.html</link> <link>https://docs.godotengine.org/en/latest/tutorials/animation/index.html</link> </tutorials> <methods> diff --git a/doc/classes/CollisionShape2D.xml b/doc/classes/CollisionShape2D.xml index e32ce9c9f9..d2676e55d0 100644 --- a/doc/classes/CollisionShape2D.xml +++ b/doc/classes/CollisionShape2D.xml @@ -13,7 +13,7 @@ </methods> <members> <member name="disabled" type="bool" setter="set_disabled" getter="is_disabled" default="false"> - A disabled collision shape has no effect in the world. + A disabled collision shape has no effect in the world. This property should be changed with [method Object.set_deferred]. </member> <member name="one_way_collision" type="bool" setter="set_one_way_collision" getter="is_one_way_collision_enabled" default="false"> Sets whether this collision shape should only detect collision on one side (top or bottom). diff --git a/doc/classes/DynamicFont.xml b/doc/classes/DynamicFont.xml index 24df056c6b..5d8ae29175 100644 --- a/doc/classes/DynamicFont.xml +++ b/doc/classes/DynamicFont.xml @@ -12,7 +12,7 @@ dynamic_font.size = 64 $"Label".set("custom_fonts/font", dynamic_font) [/codeblock] - [b]Note:[/b] DynamicFont doesn't support features such as right-to-left typesetting, ligatures, text shaping, variable fonts and optional font features yet. If you wish to "bake" an optional font feature into a TTF font file, you can use [url=https://fontforge.org/]FontForge[/url] to do so. In FontForge, use [b]File > Generate Fonts[/b], click [b]Options[/b], choose the desired features then generate the font. + [b]Note:[/b] DynamicFont doesn't support features such as kerning, right-to-left typesetting, ligatures, text shaping, variable fonts and optional font features yet. If you wish to "bake" an optional font feature into a TTF font file, you can use [url=https://fontforge.org/]FontForge[/url] to do so. In FontForge, use [b]File > Generate Fonts[/b], click [b]Options[/b], choose the desired features then generate the font. </description> <tutorials> </tutorials> diff --git a/doc/classes/EditorExportPlugin.xml b/doc/classes/EditorExportPlugin.xml index 8cfd3b63d6..6bcaabc39e 100644 --- a/doc/classes/EditorExportPlugin.xml +++ b/doc/classes/EditorExportPlugin.xml @@ -76,6 +76,18 @@ <argument index="0" name="path" type="String"> </argument> <description> + Adds a static library (*.a) or dynamic library (*.dylib, *.framework) to Linking Phase in iOS's Xcode project. + </description> + </method> + <method name="add_ios_embedded_framework"> + <return type="void"> + </return> + <argument index="0" name="path" type="String"> + </argument> + <description> + Adds a dynamic library (*.dylib, *.framework) to Linking Phase in iOS's Xcode project and embeds it into resulting binary. + [b]Note:[/b] For static libraries (*.a) works in same way as [code]add_ios_framework[/code]. + This method should not be used for System libraries as they are already present on the device. </description> </method> <method name="add_ios_linker_flags"> diff --git a/doc/classes/Engine.xml b/doc/classes/Engine.xml index 12701d8688..297cccaaac 100644 --- a/doc/classes/Engine.xml +++ b/doc/classes/Engine.xml @@ -34,7 +34,7 @@ </return> <description> Returns a Dictionary of Arrays of donor names. - {[code]platinum_sponsors[/code], [code]gold_sponsors[/code], [code]mini_sponsors[/code], [code]gold_donors[/code], [code]silver_donors[/code], [code]bronze_donors[/code]} + {[code]platinum_sponsors[/code], [code]gold_sponsors[/code], [code]silver_sponsors[/code], [code]bronze_sponsors[/code], [code]mini_sponsors[/code], [code]gold_donors[/code], [code]silver_donors[/code], [code]bronze_donors[/code]} </description> </method> <method name="get_frames_drawn"> diff --git a/doc/classes/HSlider.xml b/doc/classes/HSlider.xml index afe9d10d2e..0cbb4fd455 100644 --- a/doc/classes/HSlider.xml +++ b/doc/classes/HSlider.xml @@ -5,6 +5,7 @@ </brief_description> <description> Horizontal slider. See [Slider]. This one goes from left (min) to right (max). + [b]Note:[/b] The [signal Range.changed] and [signal Range.value_changed] signals are part of the [Range] class which this class inherits from. </description> <tutorials> </tutorials> diff --git a/doc/classes/Line2D.xml b/doc/classes/Line2D.xml index cfd23b28bd..22fe4f6194 100644 --- a/doc/classes/Line2D.xml +++ b/doc/classes/Line2D.xml @@ -85,7 +85,7 @@ The style for the points between the start and the end. </member> <member name="points" type="PackedVector2Array" setter="set_points" getter="get_points" default="PackedVector2Array( )"> - The points that form the lines. The line is drawn between every point set in this array. + The points that form the lines. The line is drawn between every point set in this array. Points are interpreted as local vectors. </member> <member name="round_precision" type="int" setter="set_round_precision" getter="get_round_precision" default="8"> The smoothness of the rounded joints and caps. This is only used if a cap or joint is set as round. diff --git a/doc/classes/PhysicsServer2D.xml b/doc/classes/PhysicsServer2D.xml index b2904c6538..6a1508b0e3 100644 --- a/doc/classes/PhysicsServer2D.xml +++ b/doc/classes/PhysicsServer2D.xml @@ -804,6 +804,7 @@ </argument> <description> Sets a body state using one of the [enum BodyState] constants. + Note that the method doesn't take effect immediately. The state will change on the next physics frame. </description> </method> <method name="body_test_motion"> diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index c44df72878..a1c4fcc53c 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -6,6 +6,7 @@ <description> Rich text can contain custom text, fonts, images and some basic formatting. The label manages these as an internal tag stack. It also adapts itself to given width/heights. [b]Note:[/b] Assignments to [member bbcode_text] clear the tag stack and reconstruct it from the property's contents. Any edits made to [member bbcode_text] will erase previous edits made from other manual sources such as [method append_bbcode] and the [code]push_*[/code] / [method pop] methods. + [b]Note:[/b] Unlike [Label], RichTextLabel doesn't have a [i]property[/i] to horizontally align text to the center. Instead, enable [member bbcode_enabled] and surround the text in a [code][center][/code] tag as follows: [code][center]Example[/center][/code]. There is currently no built-in way to vertically align text either, but this can be emulated by relying on anchors/containers and the [member fit_content_height] property. </description> <tutorials> <link>https://docs.godotengine.org/en/latest/tutorials/gui/bbcode_in_richtextlabel.html</link> diff --git a/doc/classes/Slider.xml b/doc/classes/Slider.xml index 68776df603..f18b2ce39f 100644 --- a/doc/classes/Slider.xml +++ b/doc/classes/Slider.xml @@ -5,6 +5,7 @@ </brief_description> <description> Base class for GUI sliders. + [b]Note:[/b] The [signal Range.changed] and [signal Range.value_changed] signals are part of the [Range] class which this class inherits from. </description> <tutorials> </tutorials> diff --git a/doc/classes/VSlider.xml b/doc/classes/VSlider.xml index 9394d6b430..5830c9eaf3 100644 --- a/doc/classes/VSlider.xml +++ b/doc/classes/VSlider.xml @@ -5,6 +5,7 @@ </brief_description> <description> Vertical slider. See [Slider]. This one goes from bottom (min) to top (max). + [b]Note:[/b] The [signal Range.changed] and [signal Range.value_changed] signals are part of the [Range] class which this class inherits from. </description> <tutorials> </tutorials> diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index facd57418d..d3dff3f375 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -477,11 +477,6 @@ ConnectDialog::ConnectDialog() { advanced->set_text(TTR("Advanced")); advanced->connect("pressed", callable_mp(this, &ConnectDialog::_advanced_pressed)); - // Add spacing so the tree and inspector are the same size. - Control *spacing = memnew(Control); - spacing->set_custom_minimum_size(Size2(0, 4) * EDSCALE); - vbc_right->add_child(spacing); - deferred = memnew(CheckBox); deferred->set_h_size_flags(0); deferred->set_text(TTR("Deferred")); @@ -528,6 +523,10 @@ struct _ConnectionsDockMethodInfoSort { } }; +void ConnectionsDock::_filter_changed(const String &p_text) { + update_tree(); +} + /* * Post-ConnectDialog callback for creating/editing connections. * Creates or edits connections based on state of the ConnectDialog when "Connect" is pressed. @@ -903,6 +902,7 @@ void ConnectionsDock::update_tree() { String name; if (!did_script) { + // Get script signals (including signals from any base scripts). Ref<Script> scr = selectedNode->get_script(); if (scr.is_valid()) { scr->get_script_signal_list(&node_signals2); @@ -928,15 +928,16 @@ void ConnectionsDock::update_tree() { icon = get_theme_icon("Object", "EditorIcons"); } - TreeItem *pitem = nullptr; + TreeItem *section_item = nullptr; + // Create subsections. if (node_signals2.size()) { - pitem = tree->create_item(root); - pitem->set_text(0, name); - pitem->set_icon(0, icon); - pitem->set_selectable(0, false); - pitem->set_editable(0, false); - pitem->set_custom_bg_color(0, get_theme_color("prop_subsection", "Editor")); + section_item = tree->create_item(root); + section_item->set_text(0, name); + section_item->set_icon(0, icon); + section_item->set_selectable(0, false); + section_item->set_editable(0, false); + section_item->set_custom_bg_color(0, get_theme_color("prop_subsection", "Editor")); node_signals2.sort(); } @@ -946,6 +947,12 @@ void ConnectionsDock::update_tree() { StringName signal_name = mi.name; String signaldesc = "("; PackedStringArray argnames; + + String filter_text = search_box->get_text(); + if (!filter_text.is_subsequence_ofi(signal_name)) { + continue; + } + if (mi.arguments.size()) { for (int i = 0; i < mi.arguments.size(); i++) { PropertyInfo &pi = mi.arguments[i]; @@ -965,13 +972,14 @@ void ConnectionsDock::update_tree() { } signaldesc += ")"; - TreeItem *item = tree->create_item(pitem); - item->set_text(0, String(signal_name) + signaldesc); + // Create the children of the subsection - the actual list of signals. + TreeItem *signal_item = tree->create_item(section_item); + signal_item->set_text(0, String(signal_name) + signaldesc); Dictionary sinfo; sinfo["name"] = signal_name; sinfo["args"] = argnames; - item->set_metadata(0, sinfo); - item->set_icon(0, get_theme_icon("Signal", "EditorIcons")); + signal_item->set_metadata(0, sinfo); + signal_item->set_icon(0, get_theme_icon("Signal", "EditorIcons")); // Set tooltip with the signal's documentation. { @@ -1007,7 +1015,7 @@ void ConnectionsDock::update_tree() { } // "::" separators used in make_custom_tooltip for formatting. - item->set_tooltip(0, String(signal_name) + "::" + signaldesc + "::" + descr); + signal_item->set_tooltip(0, String(signal_name) + "::" + signaldesc + "::" + descr); } // List existing connections @@ -1044,11 +1052,11 @@ void ConnectionsDock::update_tree() { path += ")"; } - TreeItem *item2 = tree->create_item(item); - item2->set_text(0, path); + TreeItem *connection_item = tree->create_item(signal_item); + connection_item->set_text(0, path); Connection cd = c; - item2->set_metadata(0, cd); - item2->set_icon(0, get_theme_icon("Slot", "EditorIcons")); + connection_item->set_metadata(0, cd); + connection_item->set_icon(0, get_theme_icon("Slot", "EditorIcons")); } } @@ -1069,6 +1077,14 @@ ConnectionsDock::ConnectionsDock(EditorNode *p_editor) { VBoxContainer *vbc = this; + search_box = memnew(LineEdit); + search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); + search_box->set_placeholder(TTR("Filter signals")); + search_box->set_right_icon(get_theme_icon("Search", "EditorIcons")); + search_box->set_clear_button_enabled(true); + search_box->connect("text_changed", callable_mp(this, &ConnectionsDock::_filter_changed)); + vbc->add_child(search_box); + tree = memnew(ConnectionsDockTree); tree->set_columns(1); tree->set_select_mode(Tree::SELECT_ROW); diff --git a/editor/connections_dialog.h b/editor/connections_dialog.h index 9da9a8fb2c..48fdb91f5a 100644 --- a/editor/connections_dialog.h +++ b/editor/connections_dialog.h @@ -169,9 +169,12 @@ class ConnectionsDock : public VBoxContainer { PopupMenu *signal_menu; PopupMenu *slot_menu; UndoRedo *undo_redo; + LineEdit *search_box; Map<StringName, Map<StringName, String>> descr_cache; + void _filter_changed(const String &p_text); + void _make_or_edit_connection(); void _connect(ConnectDialog::ConnectionData cToMake); void _disconnect(TreeItem &item); diff --git a/editor/editor_about.cpp b/editor/editor_about.cpp index d99726c57c..aae476ccf4 100644 --- a/editor/editor_about.cpp +++ b/editor/editor_about.cpp @@ -155,12 +155,15 @@ EditorAbout::EditorAbout() { List<String> donor_sections; donor_sections.push_back(TTR("Platinum Sponsors")); donor_sections.push_back(TTR("Gold Sponsors")); + donor_sections.push_back(TTR("Silver Sponsors")); + donor_sections.push_back(TTR("Bronze Sponsors")); donor_sections.push_back(TTR("Mini Sponsors")); donor_sections.push_back(TTR("Gold Donors")); donor_sections.push_back(TTR("Silver Donors")); donor_sections.push_back(TTR("Bronze Donors")); - const char *const *donor_src[] = { DONORS_SPONSOR_PLAT, DONORS_SPONSOR_GOLD, - DONORS_SPONSOR_MINI, DONORS_GOLD, DONORS_SILVER, DONORS_BRONZE }; + const char *const *donor_src[] = { DONORS_SPONSOR_PLATINUM, DONORS_SPONSOR_GOLD, + DONORS_SPONSOR_SILVER, DONORS_SPONSOR_BRONZE, DONORS_SPONSOR_MINI, + DONORS_GOLD, DONORS_SILVER, DONORS_BRONZE }; tc->add_child(_populate_list(TTR("Donors"), donor_sections, donor_src, 3)); // License diff --git a/editor/editor_export.cpp b/editor/editor_export.cpp index 951bec2c83..16e69734d3 100644 --- a/editor/editor_export.cpp +++ b/editor/editor_export.cpp @@ -512,10 +512,18 @@ void EditorExportPlugin::add_ios_framework(const String &p_path) { ios_frameworks.push_back(p_path); } +void EditorExportPlugin::add_ios_embedded_framework(const String &p_path) { + ios_embedded_frameworks.push_back(p_path); +} + Vector<String> EditorExportPlugin::get_ios_frameworks() const { return ios_frameworks; } +Vector<String> EditorExportPlugin::get_ios_embedded_frameworks() const { + return ios_embedded_frameworks; +} + void EditorExportPlugin::add_ios_plist_content(const String &p_plist_content) { ios_plist_content += p_plist_content + "\n"; } @@ -592,6 +600,7 @@ void EditorExportPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("add_ios_project_static_lib", "path"), &EditorExportPlugin::add_ios_project_static_lib); ClassDB::bind_method(D_METHOD("add_file", "path", "file", "remap"), &EditorExportPlugin::add_file); ClassDB::bind_method(D_METHOD("add_ios_framework", "path"), &EditorExportPlugin::add_ios_framework); + ClassDB::bind_method(D_METHOD("add_ios_embedded_framework", "path"), &EditorExportPlugin::add_ios_embedded_framework); ClassDB::bind_method(D_METHOD("add_ios_plist_content", "plist_content"), &EditorExportPlugin::add_ios_plist_content); ClassDB::bind_method(D_METHOD("add_ios_linker_flags", "flags"), &EditorExportPlugin::add_ios_linker_flags); ClassDB::bind_method(D_METHOD("add_ios_bundle_file", "path"), &EditorExportPlugin::add_ios_bundle_file); diff --git a/editor/editor_export.h b/editor/editor_export.h index e31b53ad67..bb701b94ec 100644 --- a/editor/editor_export.h +++ b/editor/editor_export.h @@ -290,6 +290,7 @@ class EditorExportPlugin : public Reference { bool skipped; Vector<String> ios_frameworks; + Vector<String> ios_embedded_frameworks; Vector<String> ios_project_static_libs; String ios_plist_content; String ios_linker_flags; @@ -304,6 +305,7 @@ class EditorExportPlugin : public Reference { _FORCE_INLINE_ void _export_end() { ios_frameworks.clear(); + ios_embedded_frameworks.clear(); ios_bundle_files.clear(); ios_plist_content = ""; ios_linker_flags = ""; @@ -322,6 +324,7 @@ protected: void add_shared_object(const String &p_path, const Vector<String> &tags); void add_ios_framework(const String &p_path); + void add_ios_embedded_framework(const String &p_path); void add_ios_project_static_lib(const String &p_path); void add_ios_plist_content(const String &p_plist_content); void add_ios_linker_flags(const String &p_flags); @@ -337,6 +340,7 @@ protected: public: Vector<String> get_ios_frameworks() const; + Vector<String> get_ios_embedded_frameworks() const; Vector<String> get_ios_project_static_libs() const; String get_ios_plist_content() const; String get_ios_linker_flags() const; diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index dea76ac997..34d553b5f9 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -946,14 +946,11 @@ void EditorPropertyEasing::_drag_easing(const Ref<InputEvent> &p_ev) { } float val = get_edited_object()->get(get_edited_property()); - if (val == 0) { - return; - } bool sg = val < 0; val = Math::absf(val); val = Math::log(val) / Math::log((float)2.0); - //logspace + // Logarithmic space. val += rel * 0.05; val = Math::pow(2.0f, val); @@ -961,6 +958,16 @@ void EditorPropertyEasing::_drag_easing(const Ref<InputEvent> &p_ev) { val = -val; } + // 0 is a singularity, but both positive and negative values + // are otherwise allowed. Enforce 0+ as workaround. + if (Math::is_zero_approx(val)) { + val = 0.00001; + } + + // Limit to a reasonable value to prevent the curve going into infinity, + // which can cause crashes and other issues. + val = CLAMP(val, -1'000'000, 1'000'000); + emit_changed(get_edited_property(), val); easing_draw->update(); } @@ -1003,7 +1010,18 @@ void EditorPropertyEasing::_draw_easing() { } easing_draw->draw_multiline(lines, line_color, 1.0); - f->draw(ci, Point2(10, 10 + f->get_ascent()), String::num(exp, 2), font_color); + // Draw more decimals for small numbers since higher precision is usually required for fine adjustments. + int decimals; + if (Math::abs(exp) < 0.1 - CMP_EPSILON) { + decimals = 4; + } else if (Math::abs(exp) < 1 - CMP_EPSILON) { + decimals = 3; + } else if (Math::abs(exp) < 10 - CMP_EPSILON) { + decimals = 2; + } else { + decimals = 1; + } + f->draw(ci, Point2(10, 10 + f->get_ascent()), rtos(exp).pad_decimals(decimals), font_color); } void EditorPropertyEasing::update_property() { @@ -1035,6 +1053,11 @@ void EditorPropertyEasing::_spin_value_changed(double p_value) { if (Math::is_zero_approx(p_value)) { p_value = 0.00001; } + + // Limit to a reasonable value to prevent the curve going into infinity, + // which can cause crashes and other issues. + p_value = CLAMP(p_value, -1'000'000, 1'000'000); + emit_changed(get_edited_property(), p_value); _spin_focus_exited(); } diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index f86b485dd1..7b24e6967a 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -543,6 +543,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { // 3D: Navigation _initial_set("editors/3d/navigation/navigation_scheme", 0); _initial_set("editors/3d/navigation/invert_y_axis", false); + _initial_set("editors/3d/navigation/invert_x_axis", false); hints["editors/3d/navigation/navigation_scheme"] = PropertyInfo(Variant::INT, "editors/3d/navigation/navigation_scheme", PROPERTY_HINT_ENUM, "Godot,Maya,Modo"); _initial_set("editors/3d/navigation/zoom_style", 0); hints["editors/3d/navigation/zoom_style"] = PropertyInfo(Variant::INT, "editors/3d/navigation/zoom_style", PROPERTY_HINT_ENUM, "Vertical, Horizontal"); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 4f37fcf39c..31903c89be 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -2430,11 +2430,31 @@ void FileSystemDock::_file_list_gui_input(Ref<InputEvent> p_event) { } } -void FileSystemDock::_update_import_dock() { - if (!import_dock_needs_update) { +void FileSystemDock::_get_imported_files(const String &p_path, Vector<String> &files) const { + if (!p_path.ends_with("/")) { + if (FileAccess::exists(p_path + ".import")) { + files.push_back(p_path); + } return; } + DirAccess *da = DirAccess::open(p_path); + da->list_dir_begin(); + String n = da->get_next(); + while (n != String()) { + if (n != "." && n != ".." && !n.ends_with(".import")) { + String npath = p_path + n + (da->current_is_dir() ? "/" : ""); + _get_imported_files(npath, files); + } + n = da->get_next(); + } + da->list_dir_end(); +} + +void FileSystemDock::_update_import_dock() { + if (!import_dock_needs_update) + return; + // List selected. Vector<String> selected; if (display_mode == DISPLAY_MODE_TREE_ONLY) { @@ -2444,29 +2464,24 @@ void FileSystemDock::_update_import_dock() { } else { // Use the file list. for (int i = 0; i < files->get_item_count(); i++) { - if (!files->is_selected(i)) { + if (!files->is_selected(i)) continue; - } selected.push_back(files->get_item_metadata(i)); } } + // Expand directory selection + Vector<String> efiles; + for (int i = 0; i < selected.size(); i++) { + _get_imported_files(selected[i], efiles); + } + // Check import. Vector<String> imports; String import_type; - for (int i = 0; i < selected.size(); i++) { - String fpath = selected[i]; - - if (fpath.ends_with("/")) { - imports.clear(); - break; - } - - if (!FileAccess::exists(fpath + ".import")) { - imports.clear(); - break; - } + for (int i = 0; i < efiles.size(); i++) { + String fpath = efiles[i]; Ref<ConfigFile> cf; cf.instance(); Error err = cf->load(fpath + ".import"); diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index b0118f11aa..ec2a075834 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -195,6 +195,7 @@ private: void _file_multi_selected(int p_index, bool p_selected); void _tree_multi_selected(Object *p_item, int p_column, bool p_selected); + void _get_imported_files(const String &p_path, Vector<String> &files) const; void _update_import_dock(); void _get_all_items_in_dir(EditorFileSystemDirectory *efsd, Vector<String> &files, Vector<String> &folders) const; diff --git a/editor/icons/GuiToggleOff.svg b/editor/icons/GuiToggleOff.svg index 928b55b201..9644ef176c 100644 --- a/editor/icons/GuiToggleOff.svg +++ b/editor/icons/GuiToggleOff.svg @@ -1 +1 @@ -<svg height="26" viewBox="0 0 42 25.999998" width="42" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0"><rect fill-opacity=".188235" height="16" rx="9" stroke-width="55.8958" width="38" x="2" y="5"/><circle cx="10" cy="13" r="5" stroke-width="97.3613"/></g></svg> +<svg height="16" viewBox="0 0 38 15.999999" width="38" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0"><rect fill-opacity=".188235" height="14" rx="7" stroke-width="55.8958" width="36" x="1" y="1"/><circle cx="8" cy="8" r="5" stroke-width="97.3613"/></g></svg> diff --git a/editor/icons/GuiToggleOn.svg b/editor/icons/GuiToggleOn.svg index a79a8290b1..8ab0998f71 100644 --- a/editor/icons/GuiToggleOn.svg +++ b/editor/icons/GuiToggleOn.svg @@ -1 +1 @@ -<svg height="26" viewBox="0 0 42 25.999998" width="42" xmlns="http://www.w3.org/2000/svg"><path d="m11 5c-4.986 0-9 3.568-9 8s4.014 8 9 8h20c4.986 0 9-3.568 9-8s-4.014-8-9-8zm21 3a5 5 0 0 1 5 5 5 5 0 0 1 -5 5 5 5 0 0 1 -5-5 5 5 0 0 1 5-5z" fill="#e0e0e0" stroke-width="55.8958"/></svg> +<svg height="16" viewBox="0 0 38 15.999999" width="38" xmlns="http://www.w3.org/2000/svg"><path d="m8 1c-3.878 0-7 3.122-7 7s3.122 7 7 7h22c3.878 0 7-3.122 7-7s-3.122-7-7-7zm22 2a5 5 0 0 1 5 5 5 5 0 0 1 -5 5 5 5 0 0 1 -5-5 5 5 0 0 1 5-5z" fill="#e0e0e0" stroke-width="55.8958"/></svg> diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index b4b81cc7f0..21a75c2f5d 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -2059,7 +2059,12 @@ void Node3DEditorViewport::_nav_pan(Ref<InputEventWithModifiers> p_event, const camera_transform.translate(cursor.pos); camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot); camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot); - Vector3 translation(-p_relative.x * pan_speed, p_relative.y * pan_speed, 0); + const bool invert_x_axis = EditorSettings::get_singleton()->get("editors/3d/navigation/invert_x_axis"); + const bool invert_y_axis = EditorSettings::get_singleton()->get("editors/3d/navigation/invert_y_axis"); + Vector3 translation( + (invert_x_axis ? -1 : 1) * -p_relative.x * pan_speed, + (invert_y_axis ? -1 : 1) * p_relative.y * pan_speed, + 0); translation *= cursor.distance / DISTANCE_DEFAULT; camera_transform.translate(translation); cursor.pos = camera_transform.origin; @@ -2100,17 +2105,24 @@ void Node3DEditorViewport::_nav_orbit(Ref<InputEventWithModifiers> p_event, cons _menu_option(VIEW_PERSPECTIVE); } - real_t degrees_per_pixel = EditorSettings::get_singleton()->get("editors/3d/navigation_feel/orbit_sensitivity"); - real_t radians_per_pixel = Math::deg2rad(degrees_per_pixel); - bool invert_y_axis = EditorSettings::get_singleton()->get("editors/3d/navigation/invert_y_axis"); + const real_t degrees_per_pixel = EditorSettings::get_singleton()->get("editors/3d/navigation_feel/orbit_sensitivity"); + const real_t radians_per_pixel = Math::deg2rad(degrees_per_pixel); + const bool invert_y_axis = EditorSettings::get_singleton()->get("editors/3d/navigation/invert_y_axis"); + const bool invert_x_axis = EditorSettings::get_singleton()->get("editors/3d/navigation/invert_x_axis"); if (invert_y_axis) { cursor.x_rot -= p_relative.y * radians_per_pixel; } else { cursor.x_rot += p_relative.y * radians_per_pixel; } - cursor.y_rot += p_relative.x * radians_per_pixel; + // Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented. cursor.x_rot = CLAMP(cursor.x_rot, -1.57, 1.57); + + if (invert_x_axis) { + cursor.y_rot -= p_relative.x * radians_per_pixel; + } else { + cursor.y_rot += p_relative.x * radians_per_pixel; + } name = ""; _update_name(); } @@ -2125,21 +2137,23 @@ void Node3DEditorViewport::_nav_look(Ref<InputEventWithModifiers> p_event, const _menu_option(VIEW_PERSPECTIVE); } - real_t degrees_per_pixel = EditorSettings::get_singleton()->get("editors/3d/freelook/freelook_sensitivity"); - real_t radians_per_pixel = Math::deg2rad(degrees_per_pixel); - bool invert_y_axis = EditorSettings::get_singleton()->get("editors/3d/navigation/invert_y_axis"); + const real_t degrees_per_pixel = EditorSettings::get_singleton()->get("editors/3d/freelook/freelook_sensitivity"); + const real_t radians_per_pixel = Math::deg2rad(degrees_per_pixel); + const bool invert_y_axis = EditorSettings::get_singleton()->get("editors/3d/navigation/invert_y_axis"); // Note: do NOT assume the camera has the "current" transform, because it is interpolated and may have "lag". - Transform prev_camera_transform = to_camera_transform(cursor); + const Transform prev_camera_transform = to_camera_transform(cursor); if (invert_y_axis) { cursor.x_rot -= p_relative.y * radians_per_pixel; } else { cursor.x_rot += p_relative.y * radians_per_pixel; } - cursor.y_rot += p_relative.x * radians_per_pixel; + // Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented. cursor.x_rot = CLAMP(cursor.x_rot, -1.57, 1.57); + cursor.y_rot += p_relative.x * radians_per_pixel; + // Look is like the opposite of Orbit: the focus point rotates around the camera Transform camera_transform = to_camera_transform(cursor); Vector3 pos = camera_transform.xform(Vector3(0, 0, 0)); diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index 1073da7d8c..18942b371c 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -36,6 +36,8 @@ #include "editor/editor_settings.h" #include "scene/3d/sprite_3d.h" #include "scene/gui/center_container.h" +#include "scene/gui/margin_container.h" +#include "scene/gui/panel_container.h" void SpriteFramesEditor::_gui_input(Ref<InputEvent> p_event) { } @@ -140,8 +142,27 @@ void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) { } } +void SpriteFramesEditor::_sheet_scroll_input(const Ref<InputEvent> &p_event) { + const Ref<InputEventMouseButton> mb = p_event; + + if (mb.is_valid()) { + // Zoom in/out using Ctrl + mouse wheel. This is done on the ScrollContainer + // to allow performing this action anywhere, even if the cursor isn't + // hovering the texture in the workspace. + if (mb->get_button_index() == BUTTON_WHEEL_UP && mb->is_pressed() && mb->get_control()) { + _sheet_zoom_in(); + // Don't scroll up after zooming in. + accept_event(); + } else if (mb->get_button_index() == BUTTON_WHEEL_DOWN && mb->is_pressed() && mb->get_control()) { + _sheet_zoom_out(); + // Don't scroll down after zooming out. + accept_event(); + } + } +} + void SpriteFramesEditor::_sheet_add_frames() { - Size2i size = split_sheet_preview->get_size(); + Size2i size = split_sheet_preview->get_texture()->get_size(); int h = split_sheet_h->get_value(); int v = split_sheet_v->get_value(); @@ -180,6 +201,28 @@ void SpriteFramesEditor::_sheet_add_frames() { undo_redo->commit_action(); } +void SpriteFramesEditor::_sheet_zoom_in() { + if (sheet_zoom < max_sheet_zoom) { + sheet_zoom *= scale_ratio; + Size2 texture_size = split_sheet_preview->get_texture()->get_size(); + split_sheet_preview->set_custom_minimum_size(texture_size * sheet_zoom); + } +} + +void SpriteFramesEditor::_sheet_zoom_out() { + if (sheet_zoom > min_sheet_zoom) { + sheet_zoom /= scale_ratio; + Size2 texture_size = split_sheet_preview->get_texture()->get_size(); + split_sheet_preview->set_custom_minimum_size(texture_size * sheet_zoom); + } +} + +void SpriteFramesEditor::_sheet_zoom_reset() { + sheet_zoom = 1.f; + Size2 texture_size = split_sheet_preview->get_texture()->get_size(); + split_sheet_preview->set_custom_minimum_size(texture_size * sheet_zoom); +} + void SpriteFramesEditor::_sheet_select_clear_all_frames() { bool should_clear = true; for (int i = 0; i < split_sheet_h->get_value() * split_sheet_v->get_value(); i++) { @@ -207,15 +250,18 @@ void SpriteFramesEditor::_prepare_sprite_sheet(const String &p_file) { EditorNode::get_singleton()->show_warning(TTR("Unable to load images")); ERR_FAIL_COND(!texture.is_valid()); } - if (texture != split_sheet_preview->get_texture()) { - //different texture, reset to 4x4 - split_sheet_h->set_value(4); - split_sheet_v->set_value(4); - } + bool new_texture = texture != split_sheet_preview->get_texture(); frames_selected.clear(); last_frame_selected = -1; split_sheet_preview->set_texture(texture); + if (new_texture) { + //different texture, reset to 4x4 + split_sheet_h->set_value(4); + split_sheet_v->set_value(4); + //reset zoom + _sheet_zoom_reset(); + } split_sheet_dialog->popup_centered_ratio(0.65); } @@ -231,8 +277,14 @@ void SpriteFramesEditor::_notification(int p_what) { move_up->set_icon(get_theme_icon("MoveLeft", "EditorIcons")); move_down->set_icon(get_theme_icon("MoveRight", "EditorIcons")); _delete->set_icon(get_theme_icon("Remove", "EditorIcons")); + zoom_out->set_icon(get_theme_icon("ZoomLess", "EditorIcons")); + zoom_1->set_icon(get_theme_icon("ZoomReset", "EditorIcons")); + zoom_in->set_icon(get_theme_icon("ZoomMore", "EditorIcons")); new_anim->set_icon(get_theme_icon("New", "EditorIcons")); remove_anim->set_icon(get_theme_icon("Remove", "EditorIcons")); + split_sheet_zoom_out->set_icon(get_theme_icon("ZoomLess", "EditorIcons")); + split_sheet_zoom_1->set_icon(get_theme_icon("ZoomReset", "EditorIcons")); + split_sheet_zoom_in->set_icon(get_theme_icon("ZoomMore", "EditorIcons")); [[fallthrough]]; } case NOTIFICATION_THEME_CHANGED: { @@ -636,6 +688,54 @@ void SpriteFramesEditor::_animation_fps_changed(double p_value) { undo_redo->commit_action(); } +void SpriteFramesEditor::_tree_input(const Ref<InputEvent> &p_event) { + const Ref<InputEventMouseButton> mb = p_event; + + if (mb.is_valid()) { + if (mb->get_button_index() == BUTTON_WHEEL_UP && mb->is_pressed() && mb->get_control()) { + _zoom_in(); + // Don't scroll up after zooming in. + accept_event(); + } else if (mb->get_button_index() == BUTTON_WHEEL_DOWN && mb->is_pressed() && mb->get_control()) { + _zoom_out(); + // Don't scroll down after zooming out. + accept_event(); + } + } +} + +void SpriteFramesEditor::_zoom_in() { + // Do not zoom in or out with no visible frames + if (frames->get_frame_count(edited_anim) <= 0) { + return; + } + if (thumbnail_zoom < max_thumbnail_zoom) { + thumbnail_zoom *= scale_ratio; + int thumbnail_size = (int)(thumbnail_default_size * thumbnail_zoom); + tree->set_fixed_column_width(thumbnail_size * 3 / 2); + tree->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size)); + } +} + +void SpriteFramesEditor::_zoom_out() { + // Do not zoom in or out with no visible frames + if (frames->get_frame_count(edited_anim) <= 0) { + return; + } + if (thumbnail_zoom > min_thumbnail_zoom) { + thumbnail_zoom /= scale_ratio; + int thumbnail_size = (int)(thumbnail_default_size * thumbnail_zoom); + tree->set_fixed_column_width(thumbnail_size * 3 / 2); + tree->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size)); + } +} + +void SpriteFramesEditor::_zoom_reset() { + thumbnail_zoom = 1.0f; + tree->set_fixed_column_width(thumbnail_default_size * 3 / 2); + tree->set_fixed_icon_size(Size2(thumbnail_default_size, thumbnail_default_size)); +} + void SpriteFramesEditor::_update_library(bool p_skip_selector) { updating = true; @@ -727,6 +827,9 @@ void SpriteFramesEditor::edit(SpriteFrames *p_frames) { } _update_library(); + // Clear zoom and split sheet texture + split_sheet_preview->set_texture(Ref<Texture2D>()); + _zoom_reset(); } else { hide(); } @@ -965,6 +1068,24 @@ SpriteFramesEditor::SpriteFramesEditor() { _delete->set_tooltip(TTR("Delete")); hbc->add_child(_delete); + hbc->add_spacer(); + + zoom_out = memnew(Button); + zoom_out->connect("pressed", callable_mp(this, &SpriteFramesEditor::_zoom_out)); + zoom_out->set_flat(true); + zoom_out->set_tooltip(TTR("Zoom Out")); + hbc->add_child(zoom_out); + zoom_1 = memnew(Button); + zoom_1->connect("pressed", callable_mp(this, &SpriteFramesEditor::_zoom_reset)); + zoom_1->set_flat(true); + zoom_1->set_tooltip(TTR("Zoom Reset")); + hbc->add_child(zoom_1); + zoom_in = memnew(Button); + zoom_in->connect("pressed", callable_mp(this, &SpriteFramesEditor::_zoom_in)); + zoom_in->set_flat(true); + zoom_in->set_tooltip(TTR("Zoom In")); + hbc->add_child(zoom_in); + file = memnew(EditorFileDialog); add_child(file); @@ -972,13 +1093,11 @@ SpriteFramesEditor::SpriteFramesEditor() { tree->set_v_size_flags(SIZE_EXPAND_FILL); tree->set_icon_mode(ItemList::ICON_MODE_TOP); - int thumbnail_size = 96; tree->set_max_columns(0); tree->set_icon_mode(ItemList::ICON_MODE_TOP); - tree->set_fixed_column_width(thumbnail_size * 3 / 2); tree->set_max_text_lines(2); - tree->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size)); tree->set_drag_forwarding(this); + tree->connect("gui_input", callable_mp(this, &SpriteFramesEditor::_tree_input)); sub_vb->add_child(tree); @@ -1042,8 +1161,13 @@ SpriteFramesEditor::SpriteFramesEditor() { split_sheet_vb->add_child(split_sheet_hb); + PanelContainer *split_sheet_panel = memnew(PanelContainer); + split_sheet_panel->set_h_size_flags(SIZE_EXPAND_FILL); + split_sheet_panel->set_v_size_flags(SIZE_EXPAND_FILL); + split_sheet_vb->add_child(split_sheet_panel); + split_sheet_preview = memnew(TextureRect); - split_sheet_preview->set_expand(false); + split_sheet_preview->set_expand(true); split_sheet_preview->set_mouse_filter(MOUSE_FILTER_PASS); split_sheet_preview->connect("draw", callable_mp(this, &SpriteFramesEditor::_sheet_preview_draw)); split_sheet_preview->connect("gui_input", callable_mp(this, &SpriteFramesEditor::_sheet_preview_input)); @@ -1051,20 +1175,58 @@ SpriteFramesEditor::SpriteFramesEditor() { splite_sheet_scroll = memnew(ScrollContainer); splite_sheet_scroll->set_enable_h_scroll(true); splite_sheet_scroll->set_enable_v_scroll(true); - splite_sheet_scroll->set_v_size_flags(SIZE_EXPAND_FILL); + splite_sheet_scroll->connect("gui_input", callable_mp(this, &SpriteFramesEditor::_sheet_scroll_input)); + split_sheet_panel->add_child(splite_sheet_scroll); CenterContainer *cc = memnew(CenterContainer); cc->add_child(split_sheet_preview); cc->set_h_size_flags(SIZE_EXPAND_FILL); cc->set_v_size_flags(SIZE_EXPAND_FILL); splite_sheet_scroll->add_child(cc); - split_sheet_vb->add_child(splite_sheet_scroll); + MarginContainer *split_sheet_zoom_margin = memnew(MarginContainer); + split_sheet_panel->add_child(split_sheet_zoom_margin); + split_sheet_zoom_margin->set_h_size_flags(0); + split_sheet_zoom_margin->set_v_size_flags(0); + split_sheet_zoom_margin->add_theme_constant_override("margin_top", 5); + split_sheet_zoom_margin->add_theme_constant_override("margin_left", 5); + HBoxContainer *split_sheet_zoom_hb = memnew(HBoxContainer); + split_sheet_zoom_margin->add_child(split_sheet_zoom_hb); + + split_sheet_zoom_out = memnew(Button); + split_sheet_zoom_out->set_flat(true); + split_sheet_zoom_out->set_focus_mode(FOCUS_NONE); + split_sheet_zoom_out->set_tooltip(TTR("Zoom Out")); + split_sheet_zoom_out->connect("pressed", callable_mp(this, &SpriteFramesEditor::_sheet_zoom_out)); + split_sheet_zoom_hb->add_child(split_sheet_zoom_out); + split_sheet_zoom_1 = memnew(Button); + split_sheet_zoom_1->set_flat(true); + split_sheet_zoom_1->set_focus_mode(FOCUS_NONE); + split_sheet_zoom_1->set_tooltip(TTR("Zoom Reset")); + split_sheet_zoom_1->connect("pressed", callable_mp(this, &SpriteFramesEditor::_sheet_zoom_reset)); + split_sheet_zoom_hb->add_child(split_sheet_zoom_1); + split_sheet_zoom_in = memnew(Button); + split_sheet_zoom_in->set_flat(true); + split_sheet_zoom_in->set_focus_mode(FOCUS_NONE); + split_sheet_zoom_in->set_tooltip(TTR("Zoom In")); + split_sheet_zoom_in->connect("pressed", callable_mp(this, &SpriteFramesEditor::_sheet_zoom_in)); + split_sheet_zoom_hb->add_child(split_sheet_zoom_in); file_split_sheet = memnew(EditorFileDialog); file_split_sheet->set_title(TTR("Create Frames from Sprite Sheet")); file_split_sheet->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); add_child(file_split_sheet); file_split_sheet->connect("file_selected", callable_mp(this, &SpriteFramesEditor::_prepare_sprite_sheet)); + + // Config scale. + scale_ratio = 1.2f; + thumbnail_default_size = 96; + thumbnail_zoom = 1.0f; + max_thumbnail_zoom = 8.0f; + min_thumbnail_zoom = 0.1f; + sheet_zoom = 1.0f; + max_sheet_zoom = 16.0f; + min_sheet_zoom = 0.01f; + _zoom_reset(); } void SpriteFramesEditorPlugin::edit(Object *p_object) { diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h index ee743fe60d..0dce93f55a 100644 --- a/editor/plugins/sprite_frames_editor_plugin.h +++ b/editor/plugins/sprite_frames_editor_plugin.h @@ -36,6 +36,7 @@ #include "scene/2d/animated_sprite_2d.h" #include "scene/gui/dialogs.h" #include "scene/gui/file_dialog.h" +#include "scene/gui/scroll_container.h" #include "scene/gui/split_container.h" #include "scene/gui/texture_rect.h" #include "scene/gui/tree.h" @@ -52,6 +53,9 @@ class SpriteFramesEditor : public HSplitContainer { Button *empty2; Button *move_up; Button *move_down; + Button *zoom_out; + Button *zoom_1; + Button *zoom_in; ItemList *tree; bool loading_scene; int sel; @@ -79,10 +83,22 @@ class SpriteFramesEditor : public HSplitContainer { TextureRect *split_sheet_preview; SpinBox *split_sheet_h; SpinBox *split_sheet_v; + Button *split_sheet_zoom_out; + Button *split_sheet_zoom_1; + Button *split_sheet_zoom_in; EditorFileDialog *file_split_sheet; Set<int> frames_selected; int last_frame_selected; + float scale_ratio; + int thumbnail_default_size; + float thumbnail_zoom; + float max_thumbnail_zoom; + float min_thumbnail_zoom; + float sheet_zoom; + float max_sheet_zoom; + float min_sheet_zoom; + void _load_pressed(); void _load_scene_pressed(); void _file_load_request(const Vector<String> &p_path, int p_at_pos = -1); @@ -103,6 +119,11 @@ class SpriteFramesEditor : public HSplitContainer { void _animation_loop_changed(); void _animation_fps_changed(double p_value); + void _tree_input(const Ref<InputEvent> &p_event); + void _zoom_in(); + void _zoom_out(); + void _zoom_reset(); + bool updating; UndoRedo *undo_redo; @@ -117,7 +138,11 @@ class SpriteFramesEditor : public HSplitContainer { void _sheet_preview_draw(); void _sheet_spin_changed(double); void _sheet_preview_input(const Ref<InputEvent> &p_event); + void _sheet_scroll_input(const Ref<InputEvent> &p_event); void _sheet_add_frames(); + void _sheet_zoom_in(); + void _sheet_zoom_out(); + void _sheet_zoom_reset(); void _sheet_select_clear_all_frames(); protected: diff --git a/editor/plugins/tile_set_editor_plugin.cpp b/editor/plugins/tile_set_editor_plugin.cpp index a613174ed9..274c64263f 100644 --- a/editor/plugins/tile_set_editor_plugin.cpp +++ b/editor/plugins/tile_set_editor_plugin.cpp @@ -342,11 +342,13 @@ TileSetEditor::TileSetEditor(EditorNode *p_editor) { left_container->add_child(tileset_toolbar_container); tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE] = memnew(Button); + tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE]->set_flat(true); tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tileset_toolbar_button_pressed), varray(TOOL_TILESET_ADD_TEXTURE)); tileset_toolbar_container->add_child(tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE]); tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE]->set_tooltip(TTR("Add Texture(s) to TileSet.")); tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE] = memnew(Button); + tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE]->set_flat(true); tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tileset_toolbar_button_pressed), varray(TOOL_TILESET_REMOVE_TEXTURE)); tileset_toolbar_container->add_child(tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE]); tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE]->set_tooltip(TTR("Remove selected Texture from TileSet.")); @@ -405,12 +407,14 @@ TileSetEditor::TileSetEditor(EditorNode *p_editor) { tools[SELECT_NEXT] = memnew(Button); tool_hb->add_child(tools[SELECT_NEXT]); tool_hb->move_child(tools[SELECT_NEXT], WORKSPACE_CREATE_SINGLE); + tools[SELECT_NEXT]->set_flat(true); tools[SELECT_NEXT]->set_shortcut(ED_SHORTCUT("tileset_editor/next_shape", TTR("Next Coordinate"), KEY_PAGEDOWN)); tools[SELECT_NEXT]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(SELECT_NEXT)); tools[SELECT_NEXT]->set_tooltip(TTR("Select the next shape, subtile, or Tile.")); tools[SELECT_PREVIOUS] = memnew(Button); tool_hb->add_child(tools[SELECT_PREVIOUS]); tool_hb->move_child(tools[SELECT_PREVIOUS], WORKSPACE_CREATE_SINGLE); + tools[SELECT_PREVIOUS]->set_flat(true); tools[SELECT_PREVIOUS]->set_shortcut(ED_SHORTCUT("tileset_editor/previous_shape", TTR("Previous Coordinate"), KEY_PAGEUP)); tools[SELECT_PREVIOUS]->set_tooltip(TTR("Select the previous shape, subtile, or Tile.")); tools[SELECT_PREVIOUS]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(SELECT_PREVIOUS)); @@ -467,6 +471,7 @@ TileSetEditor::TileSetEditor(EditorNode *p_editor) { tools[TOOL_SELECT] = memnew(Button); toolbar->add_child(tools[TOOL_SELECT]); + tools[TOOL_SELECT]->set_flat(true); tools[TOOL_SELECT]->set_toggle_mode(true); tools[TOOL_SELECT]->set_button_group(tg); tools[TOOL_SELECT]->set_pressed(true); @@ -475,20 +480,24 @@ TileSetEditor::TileSetEditor(EditorNode *p_editor) { separator_bitmask = memnew(VSeparator); toolbar->add_child(separator_bitmask); tools[BITMASK_COPY] = memnew(Button); + tools[BITMASK_COPY]->set_flat(true); tools[BITMASK_COPY]->set_tooltip(TTR("Copy bitmask.")); tools[BITMASK_COPY]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(BITMASK_COPY)); toolbar->add_child(tools[BITMASK_COPY]); tools[BITMASK_PASTE] = memnew(Button); + tools[BITMASK_PASTE]->set_flat(true); tools[BITMASK_PASTE]->set_tooltip(TTR("Paste bitmask.")); tools[BITMASK_PASTE]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(BITMASK_PASTE)); toolbar->add_child(tools[BITMASK_PASTE]); tools[BITMASK_CLEAR] = memnew(Button); + tools[BITMASK_CLEAR]->set_flat(true); tools[BITMASK_CLEAR]->set_tooltip(TTR("Erase bitmask.")); tools[BITMASK_CLEAR]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(BITMASK_CLEAR)); toolbar->add_child(tools[BITMASK_CLEAR]); tools[SHAPE_NEW_RECTANGLE] = memnew(Button); toolbar->add_child(tools[SHAPE_NEW_RECTANGLE]); + tools[SHAPE_NEW_RECTANGLE]->set_flat(true); tools[SHAPE_NEW_RECTANGLE]->set_toggle_mode(true); tools[SHAPE_NEW_RECTANGLE]->set_button_group(tg); tools[SHAPE_NEW_RECTANGLE]->set_tooltip(TTR("Create a new rectangle.")); @@ -496,6 +505,7 @@ TileSetEditor::TileSetEditor(EditorNode *p_editor) { tools[SHAPE_NEW_POLYGON] = memnew(Button); toolbar->add_child(tools[SHAPE_NEW_POLYGON]); + tools[SHAPE_NEW_POLYGON]->set_flat(true); tools[SHAPE_NEW_POLYGON]->set_toggle_mode(true); tools[SHAPE_NEW_POLYGON]->set_button_group(tg); tools[SHAPE_NEW_POLYGON]->set_tooltip(TTR("Create a new polygon.")); @@ -504,12 +514,14 @@ TileSetEditor::TileSetEditor(EditorNode *p_editor) { separator_shape_toggle = memnew(VSeparator); toolbar->add_child(separator_shape_toggle); tools[SHAPE_TOGGLE_TYPE] = memnew(Button); + tools[SHAPE_TOGGLE_TYPE]->set_flat(true); tools[SHAPE_TOGGLE_TYPE]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(SHAPE_TOGGLE_TYPE)); toolbar->add_child(tools[SHAPE_TOGGLE_TYPE]); separator_delete = memnew(VSeparator); toolbar->add_child(separator_delete); tools[SHAPE_DELETE] = memnew(Button); + tools[SHAPE_DELETE]->set_flat(true); tools[SHAPE_DELETE]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(SHAPE_DELETE)); toolbar->add_child(tools[SHAPE_DELETE]); @@ -534,11 +546,13 @@ TileSetEditor::TileSetEditor(EditorNode *p_editor) { separator_grid = memnew(VSeparator); toolbar->add_child(separator_grid); tools[SHAPE_KEEP_INSIDE_TILE] = memnew(Button); + tools[SHAPE_KEEP_INSIDE_TILE]->set_flat(true); tools[SHAPE_KEEP_INSIDE_TILE]->set_toggle_mode(true); tools[SHAPE_KEEP_INSIDE_TILE]->set_pressed(true); tools[SHAPE_KEEP_INSIDE_TILE]->set_tooltip(TTR("Keep polygon inside region Rect.")); toolbar->add_child(tools[SHAPE_KEEP_INSIDE_TILE]); tools[TOOL_GRID_SNAP] = memnew(Button); + tools[TOOL_GRID_SNAP]->set_flat(true); tools[TOOL_GRID_SNAP]->set_toggle_mode(true); tools[TOOL_GRID_SNAP]->set_tooltip(TTR("Enable snap and show grid (configurable via the Inspector).")); tools[TOOL_GRID_SNAP]->connect("toggled", callable_mp(this, &TileSetEditor::_on_grid_snap_toggled)); @@ -549,19 +563,23 @@ TileSetEditor::TileSetEditor(EditorNode *p_editor) { toolbar->add_child(separator); tools[ZOOM_OUT] = memnew(Button); + tools[ZOOM_OUT]->set_flat(true); tools[ZOOM_OUT]->connect("pressed", callable_mp(this, &TileSetEditor::_zoom_out)); toolbar->add_child(tools[ZOOM_OUT]); tools[ZOOM_OUT]->set_tooltip(TTR("Zoom Out")); tools[ZOOM_1] = memnew(Button); + tools[ZOOM_1]->set_flat(true); tools[ZOOM_1]->connect("pressed", callable_mp(this, &TileSetEditor::_zoom_reset)); toolbar->add_child(tools[ZOOM_1]); tools[ZOOM_1]->set_tooltip(TTR("Zoom Reset")); tools[ZOOM_IN] = memnew(Button); + tools[ZOOM_IN]->set_flat(true); tools[ZOOM_IN]->connect("pressed", callable_mp(this, &TileSetEditor::_zoom_in)); toolbar->add_child(tools[ZOOM_IN]); tools[ZOOM_IN]->set_tooltip(TTR("Zoom In")); tools[VISIBLE_INFO] = memnew(Button); + tools[VISIBLE_INFO]->set_flat(true); tools[VISIBLE_INFO]->set_toggle_mode(true); tools[VISIBLE_INFO]->set_tooltip(TTR("Display Tile Names (Hold Alt Key)")); toolbar->add_child(tools[VISIBLE_INFO]); diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index 9be1a7c2fe..82ac225ddb 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -113,11 +113,17 @@ void ProjectSettingsEditor::_add_setting() { inspector->set_current_section(setting.get_slice("/", 1)); } -void ProjectSettingsEditor::_delete_setting() { +void ProjectSettingsEditor::_delete_setting(bool p_confirmed) { String setting = _get_setting_name(); Variant value = ps->get(setting); int order = ps->get_order(setting); + if (!p_confirmed) { + del_confirmation->set_text(vformat(TTR("Are you sure you want to delete '%s'?"), setting)); + del_confirmation->popup_centered(); + return; + } + undo_redo->create_action(TTR("Delete Item")); undo_redo->add_do_method(ps, "clear", setting); @@ -171,7 +177,7 @@ void ProjectSettingsEditor::_update_advanced_bar() { } } - disable_add = !bad_category; + disable_add = bad_category; if (!property_text.is_valid_identifier()) { disable_add = true; @@ -327,11 +333,9 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { header->add_child(search_bar); search_box = memnew(LineEdit); - search_box->set_custom_minimum_size(Size2(300, 0)); + search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); search_bar->add_child(search_box); - search_bar->add_spacer(); - advanced = memnew(CheckButton); advanced->set_text(TTR("Advanced")); advanced->connect("pressed", callable_mp(this, &ProjectSettingsEditor::_advanced_pressed)); @@ -345,12 +349,14 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { advanced_bar->hide(); header->add_child(advanced_bar); + advanced_bar->add_child(memnew(HSeparator)); + HBoxContainer *hbc = memnew(HBoxContainer); hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); - advanced_bar->add_margin_child(TTR("Add or remove custom project settings."), hbc, true); + advanced_bar->add_margin_child(TTR("Add or Remove Custom Project Settings:"), hbc, true); category_box = memnew(LineEdit); - category_box->set_custom_minimum_size(Size2(140, 0) * EDSCALE); + category_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); category_box->connect("text_changed", callable_mp(this, &ProjectSettingsEditor::_text_field_changed)); category_box->set_placeholder(TTR("Category")); hbc->add_child(category_box); @@ -370,7 +376,7 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { hbc->add_child(l); type = memnew(OptionButton); - type->set_custom_minimum_size(Size2(70, 0) * EDSCALE); + type->set_custom_minimum_size(Size2(100, 0) * EDSCALE); hbc->add_child(type); // Start at 1 to avoid adding "Nil" as an option @@ -383,26 +389,24 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { hbc->add_child(l); feature_override = memnew(OptionButton); - feature_override->set_custom_minimum_size(Size2(70, 0) * EDSCALE); + feature_override->set_custom_minimum_size(Size2(100, 0) * EDSCALE); feature_override->connect("item_selected", callable_mp(this, &ProjectSettingsEditor::_feature_selected)); hbc->add_child(feature_override); - hbc->add_spacer(); - add_button = memnew(Button); + add_button->set_flat(true); add_button->connect("pressed", callable_mp(this, &ProjectSettingsEditor::_add_setting)); hbc->add_child(add_button); del_button = memnew(Button); - del_button->connect("pressed", callable_mp(this, &ProjectSettingsEditor::_delete_setting)); + del_button->set_flat(true); + del_button->connect("pressed", callable_mp(this, &ProjectSettingsEditor::_delete_setting), varray(false)); hbc->add_child(del_button); error_label = memnew(Label); advanced_bar->add_child(error_label); } - header->add_child(memnew(HSeparator)); - inspector = memnew(SectionedInspector); inspector->get_inspector()->set_undo_redo(EditorNode::get_singleton()->get_undo_redo()); inspector->set_v_size_flags(Control::SIZE_EXPAND_FILL); @@ -468,6 +472,10 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { timer->set_one_shot(true); add_child(timer); + del_confirmation = memnew(ConfirmationDialog); + del_confirmation->connect("confirmed", callable_mp(this, &ProjectSettingsEditor::_delete_setting), varray(true)); + add_child(del_confirmation); + get_ok()->set_text(TTR("Close")); set_hide_on_ok(true); } diff --git a/editor/project_settings_editor.h b/editor/project_settings_editor.h index 0d7e19b242..4ecd28e514 100644 --- a/editor/project_settings_editor.h +++ b/editor/project_settings_editor.h @@ -77,6 +77,8 @@ class ProjectSettingsEditor : public AcceptDialog { OptionButton *feature_override; Label *error_label; + ConfirmationDialog *del_confirmation; + Label *restart_label; TextureRect *restart_icon; PanelContainer *restart_container; @@ -94,7 +96,7 @@ class ProjectSettingsEditor : public AcceptDialog { void _setting_edited(const String &p_name); void _setting_selected(const String &p_path); void _add_setting(); - void _delete_setting(); + void _delete_setting(bool p_confirmed); void _editor_restart_request(); void _editor_restart(); diff --git a/editor/property_selector.cpp b/editor/property_selector.cpp index c6c93fae83..27b11e4fb5 100644 --- a/editor/property_selector.cpp +++ b/editor/property_selector.cpp @@ -84,6 +84,9 @@ void PropertySelector::_update_search() { TreeItem *root = search_options->create_item(); + // Allow using spaces in place of underscores in the search string (makes the search more fault-tolerant). + const String search_text = search_box->get_text().replace(" ", "_"); + if (properties) { List<PropertyInfo> props; @@ -167,7 +170,7 @@ void PropertySelector::_update_search() { continue; } - if (search_box->get_text() != String() && E->get().name.find(search_box->get_text()) == -1) { + if (search_box->get_text() != String() && E->get().name.findn(search_text) == -1) { continue; } @@ -180,7 +183,7 @@ void PropertySelector::_update_search() { item->set_metadata(0, E->get().name); item->set_icon(0, type_icons[E->get().type]); - if (!found && search_box->get_text() != String() && E->get().name.find(search_box->get_text()) != -1) { + if (!found && search_box->get_text() != String() && E->get().name.findn(search_text) != -1) { item->select(0); found = true; } @@ -255,7 +258,7 @@ void PropertySelector::_update_search() { continue; } - if (search_box->get_text() != String() && name.find(search_box->get_text()) == -1) { + if (search_box->get_text() != String() && name.findn(search_text) == -1) { continue; } @@ -270,29 +273,29 @@ void PropertySelector::_update_search() { } else if (mi.return_val.type != Variant::NIL) { desc = Variant::get_type_name(mi.return_val.type); } else { - desc = "void "; + desc = "void"; } - desc += " " + mi.name + " ( "; + desc += vformat(" %s(", mi.name); for (int i = 0; i < mi.arguments.size(); i++) { if (i > 0) { desc += ", "; } + desc += mi.arguments[i].name; + if (mi.arguments[i].type == Variant::NIL) { - desc += "var "; + desc += ": Variant"; } else if (mi.arguments[i].name.find(":") != -1) { - desc += mi.arguments[i].name.get_slice(":", 1) + " "; + desc += vformat(": %s", mi.arguments[i].name.get_slice(":", 1)); mi.arguments[i].name = mi.arguments[i].name.get_slice(":", 0); } else { - desc += Variant::get_type_name(mi.arguments[i].type) + " "; + desc += vformat(": %s", Variant::get_type_name(mi.arguments[i].type)); } - - desc += mi.arguments[i].name; } - desc += " )"; + desc += ")"; if (E->get().flags & METHOD_FLAG_CONST) { desc += " const"; @@ -306,7 +309,7 @@ void PropertySelector::_update_search() { item->set_metadata(0, name); item->set_selectable(0, true); - if (!found && search_box->get_text() != String() && name.find(search_box->get_text()) != -1) { + if (!found && search_box->get_text() != String() && name.findn(search_text) != -1) { item->select(0); found = true; } diff --git a/editor/rename_dialog.cpp b/editor/rename_dialog.cpp index 211e365454..23990bca07 100644 --- a/editor/rename_dialog.cpp +++ b/editor/rename_dialog.cpp @@ -61,18 +61,16 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_und // ---- 1st & 2nd row Label *lbl_search = memnew(Label); - lbl_search->set_text(TTR("Search")); + lbl_search->set_text(TTR("Search:")); lne_search = memnew(LineEdit); - lne_search->set_placeholder(TTR("Search")); lne_search->set_name("lne_search"); lne_search->set_h_size_flags(Control::SIZE_EXPAND_FILL); Label *lbl_replace = memnew(Label); - lbl_replace->set_text(TTR("Replace")); + lbl_replace->set_text(TTR("Replace:")); lne_replace = memnew(LineEdit); - lne_replace->set_placeholder(TTR("Replace")); lne_replace->set_name("lne_replace"); lne_replace->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -84,18 +82,16 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_und // ---- 3rd & 4th row Label *lbl_prefix = memnew(Label); - lbl_prefix->set_text(TTR("Prefix")); + lbl_prefix->set_text(TTR("Prefix:")); lne_prefix = memnew(LineEdit); - lne_prefix->set_placeholder(TTR("Prefix")); lne_prefix->set_name("lne_prefix"); lne_prefix->set_h_size_flags(Control::SIZE_EXPAND_FILL); Label *lbl_suffix = memnew(Label); - lbl_suffix->set_text(TTR("Suffix")); + lbl_suffix->set_text(TTR("Suffix:")); lne_suffix = memnew(LineEdit); - lne_suffix->set_placeholder(TTR("Suffix")); lne_suffix->set_name("lne_suffix"); lne_suffix->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -106,8 +102,6 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_und // -- Feature Tabs - const int feature_min_height = 160 * EDSCALE; - cbut_regex = memnew(CheckButton); cbut_regex->set_text(TTR("Use Regular Expressions")); vbc->add_child(cbut_regex); @@ -118,13 +112,13 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_und tabc_features = memnew(TabContainer); tabc_features->set_tab_align(TabContainer::ALIGN_LEFT); + tabc_features->set_use_hidden_tabs_for_min_size(true); vbc->add_child(tabc_features); // ---- Tab Substitute VBoxContainer *vbc_substitute = memnew(VBoxContainer); vbc_substitute->set_h_size_flags(Control::SIZE_EXPAND_FILL); - vbc_substitute->set_custom_minimum_size(Size2(0, feature_min_height)); vbc_substitute->set_name(TTR("Substitute")); tabc_features->add_child(vbc_substitute); @@ -199,7 +193,7 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_und chk_per_level_counter = memnew(CheckBox); chk_per_level_counter->set_text(TTR("Per-level Counter")); - chk_per_level_counter->set_tooltip(TTR("If set the counter restarts for each group of child nodes.")); + chk_per_level_counter->set_tooltip(TTR("If set, the counter restarts for each group of child nodes.")); vbc_substitute->add_child(chk_per_level_counter); HBoxContainer *hbc_count_options = memnew(HBoxContainer); @@ -241,7 +235,6 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_und VBoxContainer *vbc_process = memnew(VBoxContainer); vbc_process->set_h_size_flags(Control::SIZE_EXPAND_FILL); vbc_process->set_name(TTR("Post-Process")); - vbc_process->set_custom_minimum_size(Size2(0, feature_min_height)); tabc_features->add_child(vbc_process); cbut_process = memnew(CheckBox); @@ -285,18 +278,14 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_und vbc->add_child(sep_preview); lbl_preview_title = memnew(Label); - lbl_preview_title->set_text(TTR("Preview")); vbc->add_child(lbl_preview_title); lbl_preview = memnew(Label); - lbl_preview->set_text(""); - lbl_preview->add_theme_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_theme_color("error_color", "Editor")); vbc->add_child(lbl_preview); // ---- Dialog related set_min_size(Size2(383, 0)); - //set_as_toplevel(true); get_ok()->set_text(TTR("Rename")); Button *but_reset = add_button(TTR("Reset")); @@ -307,7 +296,7 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_und cbut_collapse_features->connect("toggled", callable_mp(this, &RenameDialog::_features_toggled)); - // Substitite Buttons + // Substitute Buttons lne_search->connect("focus_entered", callable_mp(this, &RenameDialog::_update_substitute)); lne_search->connect("focus_exited", callable_mp(this, &RenameDialog::_update_substitute)); @@ -391,7 +380,7 @@ void RenameDialog::_update_preview(String new_text) { String new_name = _apply_rename(preview_node, spn_count_start->get_value()); if (!has_errors) { - lbl_preview_title->set_text(TTR("Preview")); + lbl_preview_title->set_text(TTR("Preview:")); lbl_preview->set_text(new_name); if (new_name == preview_node->get_name()) { @@ -482,7 +471,7 @@ void RenameDialog::_error_handler(void *p_self, const char *p_func, const char * } self->has_errors = true; - self->lbl_preview_title->set_text(TTR("Regular Expression Error")); + self->lbl_preview_title->set_text(TTR("Regular Expression Error:")); self->lbl_preview->add_theme_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_theme_color("error_color", "Editor")); self->lbl_preview->set_text(vformat(TTR("At character %s"), err_str)); } diff --git a/main/SCsub b/main/SCsub index ebadefd450..87d64e48f9 100644 --- a/main/SCsub +++ b/main/SCsub @@ -15,7 +15,9 @@ if env["tests"]: env_main.Depends("#main/splash.gen.h", "#main/splash.png") env_main.CommandNoCache( - "#main/splash.gen.h", "#main/splash.png", env.Run(main_builders.make_splash, "Building splash screen header."), + "#main/splash.gen.h", + "#main/splash.png", + env.Run(main_builders.make_splash, "Building splash screen header."), ) env_main.Depends("#main/splash_editor.gen.h", "#main/splash_editor.png") @@ -27,7 +29,9 @@ env_main.CommandNoCache( env_main.Depends("#main/app_icon.gen.h", "#main/app_icon.png") env_main.CommandNoCache( - "#main/app_icon.gen.h", "#main/app_icon.png", env.Run(main_builders.make_app_icon, "Building application icon."), + "#main/app_icon.gen.h", + "#main/app_icon.png", + env.Run(main_builders.make_app_icon, "Building application icon."), ) lib = env_main.add_library("main", env.main_sources) diff --git a/main/main.cpp b/main/main.cpp index e371bef25f..e45162c0f3 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -369,16 +369,94 @@ void Main::print_help(const char *p_binary) { #endif } +#ifdef TESTS_ENABLED +// The order is the same as in `Main::setup()`, only core and some editor types +// are initialized here. This also combines `Main::setup2()` initialization. +Error Main::test_setup() { + OS::get_singleton()->initialize(); + + engine = memnew(Engine); + + ClassDB::init(); + + register_core_types(); + register_core_driver_types(); + + globals = memnew(ProjectSettings); + + GLOBAL_DEF("debug/settings/crash_handler/message", + String("Please include this when reporting the bug on https://github.com/godotengine/godot/issues")); + + // From `Main::setup2()`. + preregister_module_types(); + preregister_server_types(); + + register_core_singletons(); + + register_server_types(); + register_scene_types(); + +#ifdef TOOLS_ENABLED + ClassDB::set_current_api(ClassDB::API_EDITOR); + EditorNode::register_editor_types(); + + ClassDB::set_current_api(ClassDB::API_CORE); +#endif + register_platform_apis(); + + register_module_types(); + register_driver_types(); + + ClassDB::set_current_api(ClassDB::API_NONE); + + _start_success = true; + + return OK; +} +// The order is the same as in `Main::cleanup()`. +void Main::test_cleanup() { + ERR_FAIL_COND(!_start_success); + + EngineDebugger::deinitialize(); + + ResourceLoader::remove_custom_loaders(); + ResourceSaver::remove_custom_savers(); + +#ifdef TOOLS_ENABLED + EditorNode::unregister_editor_types(); +#endif + unregister_driver_types(); + unregister_module_types(); + unregister_platform_apis(); + unregister_scene_types(); + unregister_server_types(); + + OS::get_singleton()->finalize(); + + if (globals) { + memdelete(globals); + } + if (engine) { + memdelete(engine); + } + + unregister_core_driver_types(); + unregister_core_types(); + + OS::get_singleton()->finalize_core(); +} +#endif + int Main::test_entrypoint(int argc, char *argv[], bool &tests_need_run) { #ifdef TESTS_ENABLED for (int x = 0; x < argc; x++) { if ((strncmp(argv[x], "--test", 6) == 0) && (strlen(argv[x]) == 6)) { tests_need_run = true; - OS::get_singleton()->initialize(); - StringName::setup(); + // TODO: need to come up with different test contexts. + // Not every test requires high-level functionality like `ClassDB`. + test_setup(); int status = test_main(argc, argv); - StringName::cleanup(); - // TODO: fix OS::singleton cleanup + test_cleanup(); return status; } } @@ -1140,7 +1218,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph } if (bool(GLOBAL_GET("display/window/size/always_on_top"))) { - window_flags |= DisplayServer::WINDOW_FLAG_ALWAYS_ON_TOP; + window_flags |= DisplayServer::WINDOW_FLAG_ALWAYS_ON_TOP_BIT; } } diff --git a/main/main.h b/main/main.h index 20c0bebefa..75a1c0d8cd 100644 --- a/main/main.h +++ b/main/main.h @@ -48,6 +48,10 @@ public: static int test_entrypoint(int argc, char *argv[], bool &tests_need_run); static Error setup(const char *execpath, int argc, char *argv[], bool p_second_phase = true); static Error setup2(Thread::ID p_main_tid_override = 0); +#ifdef TESTS_ENABLED + static Error test_setup(); + static void test_cleanup(); +#endif static bool start(); static bool iteration(); @@ -58,7 +62,7 @@ public: static void cleanup(); }; -// Test main override is for the testing behaviour +// Test main override is for the testing behaviour. #define TEST_MAIN_OVERRIDE \ bool run_test = false; \ int return_code = Main::test_entrypoint(argc, argv, run_test); \ diff --git a/methods.py b/methods.py index 65b0e2dd6b..44e381014d 100644 --- a/methods.py +++ b/methods.py @@ -555,18 +555,15 @@ def generate_vs_project(env, num_jobs): # last double quote off, confusing MSBuild env["MSVSBUILDCOM"] = build_commandline( "scons --directory=\"$(ProjectDir.TrimEnd('\\'))\" platform=windows progress=no target=$(Configuration)" - " tools=!tools! -j" - + str(num_jobs) + " tools=!tools! -j" + str(num_jobs) ) env["MSVSREBUILDCOM"] = build_commandline( "scons --directory=\"$(ProjectDir.TrimEnd('\\'))\" platform=windows progress=no target=$(Configuration)" - " tools=!tools! vsproj=yes -j" - + str(num_jobs) + " tools=!tools! vsproj=yes -j" + str(num_jobs) ) env["MSVSCLEANCOM"] = build_commandline( "scons --directory=\"$(ProjectDir.TrimEnd('\\'))\" --clean platform=windows progress=no" - " target=$(Configuration) tools=!tools! -j" - + str(num_jobs) + " target=$(Configuration) tools=!tools! -j" + str(num_jobs) ) # This version information (Win32, x64, Debug, Release, Release_Debug seems to be diff --git a/misc/dist/osx_tools.app/Contents/Info.plist b/misc/dist/osx_tools.app/Contents/Info.plist index fd62bc0ef5..508586904c 100755 --- a/misc/dist/osx_tools.app/Contents/Info.plist +++ b/misc/dist/osx_tools.app/Contents/Info.plist @@ -111,7 +111,7 @@ <string>GDScript.icns</string> <key>UTTypeConformsTo</key> <array> - <string>public.data</string> + <string>public.script</string> </array> <key>UTTypeTagSpecification</key> <dict> diff --git a/modules/bmp/image_loader_bmp.cpp b/modules/bmp/image_loader_bmp.cpp index ac4e534983..757afeb9e3 100644 --- a/modules/bmp/image_loader_bmp.cpp +++ b/modules/bmp/image_loader_bmp.cpp @@ -51,16 +51,20 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image, if (bits_per_pixel == 1) { // Requires bit unpacking... - ERR_FAIL_COND_V(width % 8 != 0, ERR_UNAVAILABLE); - ERR_FAIL_COND_V(height % 8 != 0, ERR_UNAVAILABLE); + ERR_FAIL_COND_V_MSG(width % 8 != 0, ERR_UNAVAILABLE, + vformat("1-bpp BMP images must have a width that is a multiple of 8, but the imported BMP is %d pixels wide.", int(width))); + ERR_FAIL_COND_V_MSG(height % 8 != 0, ERR_UNAVAILABLE, + vformat("1-bpp BMP images must have a height that is a multiple of 8, but the imported BMP is %d pixels tall.", int(height))); } else if (bits_per_pixel == 4) { // Requires bit unpacking... - ERR_FAIL_COND_V(width % 2 != 0, ERR_UNAVAILABLE); - ERR_FAIL_COND_V(height % 2 != 0, ERR_UNAVAILABLE); + ERR_FAIL_COND_V_MSG(width % 2 != 0, ERR_UNAVAILABLE, + vformat("4-bpp BMP images must have a width that is a multiple of 2, but the imported BMP is %d pixels wide.", int(width))); + ERR_FAIL_COND_V_MSG(height % 2 != 0, ERR_UNAVAILABLE, + vformat("4-bpp BMP images must have a height that is a multiple of 2, but the imported BMP is %d pixels tall.", int(height))); } else if (bits_per_pixel == 16) { - ERR_FAIL_V(ERR_UNAVAILABLE); + ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "16-bpp BMP images are not supported."); } // Image data (might be indexed) @@ -72,7 +76,7 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image, } else { // color data_len = width * height * 4; } - ERR_FAIL_COND_V(data_len == 0, ERR_BUG); + ERR_FAIL_COND_V_MSG(data_len == 0, ERR_BUG, "Couldn't parse the BMP image data."); err = data.resize(data_len); uint8_t *data_w = data.ptrw(); @@ -215,13 +219,15 @@ Error ImageLoaderBMP::load_image(Ref<Image> p_image, FileAccess *f, // Info Header bmp_header.bmp_info_header.bmp_header_size = f->get_32(); - ERR_FAIL_COND_V(bmp_header.bmp_info_header.bmp_header_size < BITMAP_INFO_HEADER_MIN_SIZE, ERR_FILE_CORRUPT); + ERR_FAIL_COND_V_MSG(bmp_header.bmp_info_header.bmp_header_size < BITMAP_INFO_HEADER_MIN_SIZE, ERR_FILE_CORRUPT, + vformat("Couldn't parse the BMP info header. The file is likely corrupt: %s", f->get_path())); bmp_header.bmp_info_header.bmp_width = f->get_32(); bmp_header.bmp_info_header.bmp_height = f->get_32(); bmp_header.bmp_info_header.bmp_planes = f->get_16(); - ERR_FAIL_COND_V(bmp_header.bmp_info_header.bmp_planes != 1, ERR_FILE_CORRUPT); + ERR_FAIL_COND_V_MSG(bmp_header.bmp_info_header.bmp_planes != 1, ERR_FILE_CORRUPT, + vformat("Couldn't parse the BMP planes. The file is likely corrupt: %s", f->get_path())); bmp_header.bmp_info_header.bmp_bit_count = f->get_16(); bmp_header.bmp_info_header.bmp_compression = f->get_32(); @@ -236,10 +242,10 @@ Error ImageLoaderBMP::load_image(Ref<Image> p_image, FileAccess *f, case BI_RLE4: case BI_CMYKRLE8: case BI_CMYKRLE4: { - // Stop parsing - String bmp_path = f->get_path(); + // Stop parsing. f->close(); - ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "Compressed BMP files are not supported: " + bmp_path + "."); + ERR_FAIL_V_MSG(ERR_UNAVAILABLE, + vformat("Compressed BMP files are not supported: %s", f->get_path())); } break; } // Don't rely on sizeof(bmp_file_header) as structure padding @@ -255,7 +261,8 @@ Error ImageLoaderBMP::load_image(Ref<Image> p_image, FileAccess *f, if (bmp_header.bmp_info_header.bmp_bit_count <= 8) { // Support 256 colors max color_table_size = 1 << bmp_header.bmp_info_header.bmp_bit_count; - ERR_FAIL_COND_V(color_table_size == 0, ERR_BUG); + ERR_FAIL_COND_V_MSG(color_table_size == 0, ERR_BUG, + vformat("Couldn't parse the BMP color table: %s", f->get_path())); } Vector<uint8_t> bmp_color_table; diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 9170255c02..541d46a2af 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -596,6 +596,21 @@ Error GDScript::reload(bool p_keep_state) { return OK; } + { + String source_path = path; + if (source_path.empty()) { + source_path = get_path(); + } + if (!source_path.empty()) { + MutexLock lock(GDScriptCache::singleton->lock); + Ref<GDScript> self(this); + if (!GDScriptCache::singleton->shallow_gdscript_cache.has(source_path)) { + GDScriptCache::singleton->shallow_gdscript_cache[source_path] = self; + self->unreference(); + } + } + } + valid = false; GDScriptParser parser; Error err = parser.parse(source, path, false); diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 768697bd8b..3253c8e945 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -33,6 +33,7 @@ #include "core/class_db.h" #include "core/hash_map.h" #include "core/io/resource_loader.h" +#include "core/os/file_access.h" #include "core/project_settings.h" #include "core/script_language.h" #include "gdscript.h" @@ -498,7 +499,13 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas if (member.variable->initializer != nullptr) { if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true)) { - push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer); + // Try reverse test since it can be a masked subtype. + if (!is_type_compatible(member.variable->initializer->get_datatype(), datatype, true)) { + push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer); + } else { + // TODO: Add warning. + mark_node_unsafe(member.variable->initializer); + } } else if (datatype.builtin_type == Variant::INT && member.variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { #ifdef DEBUG_ENABLED parser->push_warning(member.variable->initializer, GDScriptWarning::NARROWING_CONVERSION); @@ -597,17 +604,67 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas enum_type.is_meta_type = true; enum_type.is_constant = true; + // Enums can't be nested, so we can safely override this. + current_enum = member.m_enum; + for (int j = 0; j < member.m_enum->values.size(); j++) { - enum_type.enum_values[member.m_enum->values[j].identifier->name] = member.m_enum->values[j].value; + GDScriptParser::EnumNode::Value &element = member.m_enum->values.write[j]; + + if (element.custom_value) { + reduce_expression(element.custom_value); + if (!element.custom_value->is_constant) { + push_error(R"(Enum values must be constant.)", element.custom_value); + } else if (element.custom_value->reduced_value.get_type() != Variant::INT) { + push_error(R"(Enum values must be integers.)", element.custom_value); + } else { + element.value = element.custom_value->reduced_value; + element.resolved = true; + } + } else { + if (element.index > 0) { + element.value = element.parent_enum->values[element.index - 1].value + 1; + } else { + element.value = 0; + } + element.resolved = true; + } + + enum_type.enum_values[element.identifier->name] = element.value; } + current_enum = nullptr; + member.m_enum->set_datatype(enum_type); } break; case GDScriptParser::ClassNode::Member::FUNCTION: resolve_function_signature(member.function); break; - case GDScriptParser::ClassNode::Member::ENUM_VALUE: - break; // Nothing to do, type and value set in parser. + case GDScriptParser::ClassNode::Member::ENUM_VALUE: { + if (member.enum_value.custom_value) { + current_enum = member.enum_value.parent_enum; + reduce_expression(member.enum_value.custom_value); + current_enum = nullptr; + + if (!member.enum_value.custom_value->is_constant) { + push_error(R"(Enum values must be constant.)", member.enum_value.custom_value); + } else if (member.enum_value.custom_value->reduced_value.get_type() != Variant::INT) { + push_error(R"(Enum values must be integers.)", member.enum_value.custom_value); + } else { + member.enum_value.value = member.enum_value.custom_value->reduced_value; + member.enum_value.resolved = true; + } + } else { + if (member.enum_value.index > 0) { + member.enum_value.value = member.enum_value.parent_enum->values[member.enum_value.index - 1].value + 1; + } else { + member.enum_value.value = 0; + } + member.enum_value.resolved = true; + } + // Also update the original references. + member.enum_value.parent_enum->values.write[member.enum_value.index] = member.enum_value; + p_class->members.write[i].enum_value = member.enum_value; + } break; case GDScriptParser::ClassNode::Member::CLASS: break; // Done later. case GDScriptParser::ClassNode::Member::UNDEFINED: @@ -995,7 +1052,13 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable if (p_variable->initializer != nullptr) { if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true)) { - push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer); + // Try reverse test since it can be a masked subtype. + if (!is_type_compatible(p_variable->initializer->get_datatype(), type, true)) { + push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer); + } else { + // TODO: Add warning. + mark_node_unsafe(p_variable->initializer); + } #ifdef DEBUG_ENABLED } else if (type.builtin_type == Variant::INT && p_variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { parser->push_warning(p_variable->initializer, GDScriptWarning::NARROWING_CONVERSION); @@ -1386,9 +1449,16 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig compatible = is_type_compatible(p_assignment->assignee->get_datatype(), op_type, true); if (!compatible) { if (p_assignment->assignee->get_datatype().is_hard_type()) { - push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", p_assignment->assigned_value->get_datatype().to_string(), p_assignment->assignee->get_datatype().to_string()), p_assignment->assigned_value); + // Try reverse test since it can be a masked subtype. + if (!is_type_compatible(op_type, p_assignment->assignee->get_datatype(), true)) { + push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", p_assignment->assigned_value->get_datatype().to_string(), p_assignment->assignee->get_datatype().to_string()), p_assignment->assigned_value); + } else { + // TODO: Add warning. + mark_node_unsafe(p_assignment); + } } else { // TODO: Warning in this case. + mark_node_unsafe(p_assignment); } } } else { @@ -1635,6 +1705,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa } signature += p_call->arguments[i]->get_datatype().to_string(); } + signature += ")"; push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call->callee); } break; case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: @@ -1685,7 +1756,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa for (int i = 0; i < p_call->arguments.size(); i++) { GDScriptParser::DataType par_type = type_from_property(info.arguments[i]); - if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype())) { + if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype(), true)) { types_match = false; break; #ifdef DEBUG_ENABLED @@ -1712,6 +1783,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa } signature += p_call->arguments[i]->get_datatype().to_string(); } + signature += ")"; push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call); } } @@ -1890,6 +1962,8 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary) { bool all_is_constant = true; + HashMap<Variant, GDScriptParser::ExpressionNode *, VariantHasher, VariantComparator> elements; + for (int i = 0; i < p_dictionary->elements.size(); i++) { const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; if (p_dictionary->style == GDScriptParser::DictionaryNode::PYTHON_DICT) { @@ -1897,6 +1971,14 @@ void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dicti } reduce_expression(element.value); all_is_constant = all_is_constant && element.key->is_constant && element.value->is_constant; + + if (element.key->is_constant) { + if (elements.has(element.key->reduced_value)) { + push_error(vformat(R"(Key "%s" was already used in this dictionary (at line %d).)", element.key->reduced_value, elements[element.key->reduced_value]->start_line), element.key); + } else { + elements[element.key->reduced_value] = element.value; + } + } } if (all_is_constant) { @@ -2105,6 +2187,33 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_identifier, bool can_be_builtin) { // TODO: This is opportunity to further infer types. + + // Check if we are inside and enum. This allows enum values to access other elements of the same enum. + if (current_enum) { + for (int i = 0; i < current_enum->values.size(); i++) { + const GDScriptParser::EnumNode::Value &element = current_enum->values[i]; + if (element.identifier->name == p_identifier->name) { + GDScriptParser::DataType type; + type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + type.kind = element.parent_enum->identifier ? GDScriptParser::DataType::ENUM_VALUE : GDScriptParser::DataType::BUILTIN; + type.builtin_type = Variant::INT; + type.is_constant = true; + if (element.parent_enum->identifier) { + type.enum_type = element.parent_enum->identifier->name; + } + p_identifier->set_datatype(type); + + if (element.resolved) { + p_identifier->is_constant = true; + p_identifier->reduced_value = element.value; + } else { + push_error(R"(Cannot use another enum element before it was declared.)", p_identifier); + } + return; // Found anyway. + } + } + } + // Check if identifier is local. // If that's the case, the declaration already was solved before. switch (p_identifier->source) { @@ -2204,6 +2313,37 @@ void GDScriptAnalyzer::reduce_literal(GDScriptParser::LiteralNode *p_literal) { } void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { + if (!p_preload->path) { + return; + } + + reduce_expression(p_preload->path); + + if (!p_preload->path->is_constant) { + push_error("Preloaded path must be a constant string.", p_preload->path); + return; + } + + if (p_preload->path->reduced_value.get_type() != Variant::STRING) { + push_error("Preloaded path must be a constant string.", p_preload->path); + } else { + p_preload->resolved_path = p_preload->path->reduced_value; + // TODO: Save this as script dependency. + if (p_preload->resolved_path.is_rel_path()) { + p_preload->resolved_path = parser->script_path.get_base_dir().plus_file(p_preload->resolved_path); + } + p_preload->resolved_path = p_preload->resolved_path.simplify_path(); + if (!FileAccess::exists(p_preload->resolved_path)) { + push_error(vformat(R"(Preload file "%s" does not exist.)", p_preload->resolved_path), p_preload->path); + } else { + // TODO: Don't load if validating: use completion cache. + p_preload->resource = ResourceLoader::load(p_preload->resolved_path); + if (p_preload->resource.is_null()) { + push_error(vformat(R"(Could not p_preload resource file "%s".)", p_preload->resolved_path), p_preload->path); + } + } + } + p_preload->is_constant = true; p_preload->reduced_value = p_preload->resource; p_preload->set_datatype(type_from_variant(p_preload->reduced_value)); @@ -2379,7 +2519,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri // Check resulting type if possible. result_type.builtin_type = Variant::NIL; result_type.kind = GDScriptParser::DataType::BUILTIN; - result_type.type_source = GDScriptParser::DataType::INFERRED; + result_type.type_source = base_type.is_hard_type() ? GDScriptParser::DataType::ANNOTATED_INFERRED : GDScriptParser::DataType::INFERRED; switch (base_type.builtin_type) { // Can't index at all. @@ -2510,6 +2650,12 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) GDScriptParser::DataType result; + if (p_unary_op->operand == nullptr) { + result.kind = GDScriptParser::DataType::VARIANT; + p_unary_op->set_datatype(result); + return; + } + if (p_unary_op->operand->is_constant) { p_unary_op->is_constant = true; p_unary_op->reduced_value = Variant::evaluate(p_unary_op->variant_op, p_unary_op->operand->reduced_value, Variant()); @@ -2884,7 +3030,7 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator } // Avoid error in formatting operator (%) where it doesn't find a placeholder. - if (a_type == Variant::STRING) { + if (a_type == Variant::STRING && b_type != Variant::ARRAY) { a = String("%s"); } diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 06d3530cb6..da767522ad 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -41,6 +41,8 @@ class GDScriptAnalyzer { GDScriptParser *parser = nullptr; HashMap<String, Ref<GDScriptParserRef>> depended_parsers; + const GDScriptParser::EnumNode *current_enum = nullptr; + Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true); GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type); diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index 583283ff46..cdb14d6281 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -85,6 +85,17 @@ Error GDScriptParserRef::raise_status(Status p_new_status) { return result; } } + if (result != OK) { + if (parser != nullptr) { + memdelete(parser); + parser = nullptr; + } + if (analyzer != nullptr) { + memdelete(analyzer); + analyzer = nullptr; + } + return result; + } } return result; @@ -118,6 +129,10 @@ Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptP if (singleton->parser_map.has(p_path)) { ref = singleton->parser_map[p_path]; } else { + if (!FileAccess::exists(p_path)) { + r_error = ERR_FILE_NOT_FOUND; + return ref; + } GDScriptParser *parser = memnew(GDScriptParser); ref.instance(); ref->parser = parser; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 0967f74285..aeec1c0379 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -466,44 +466,40 @@ void GDScriptParser::pop_multiline() { } bool GDScriptParser::is_statement_end() { - return check(GDScriptTokenizer::Token::NEWLINE) || check(GDScriptTokenizer::Token::SEMICOLON); + return check(GDScriptTokenizer::Token::NEWLINE) || check(GDScriptTokenizer::Token::SEMICOLON) || check(GDScriptTokenizer::Token::TK_EOF); } void GDScriptParser::end_statement(const String &p_context) { bool found = false; - while (is_statement_end()) { + while (is_statement_end() && !is_at_end()) { // Remove sequential newlines/semicolons. found = true; advance(); } - if (!found) { + if (!found && !is_at_end()) { push_error(vformat(R"(Expected end of statement after %s, found "%s" instead.)", p_context, current.get_name())); } } void GDScriptParser::parse_program() { - if (current.type == GDScriptTokenizer::Token::TK_EOF) { - // Empty file. - push_error("Source file is empty."); - return; - } - head = alloc_node<ClassNode>(); current_class = head; if (match(GDScriptTokenizer::Token::ANNOTATION)) { // Check for @tool annotation. AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::CLASS_LEVEL); - if (annotation->name == "@tool") { - // TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?). - _is_tool = true; - if (previous.type != GDScriptTokenizer::Token::NEWLINE) { - push_error(R"(Expected newline after "@tool" annotation.)"); - } - // @tool annotation has no specific target. - annotation->apply(this, nullptr); - } else { - annotation_stack.push_back(annotation); + if (annotation != nullptr) { + if (annotation->name == "@tool") { + // TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?). + _is_tool = true; + if (previous.type != GDScriptTokenizer::Token::NEWLINE) { + push_error(R"(Expected newline after "@tool" annotation.)"); + } + // @tool annotation has no specific target. + annotation->apply(this, nullptr); + } else { + annotation_stack.push_back(annotation); + } } } @@ -990,9 +986,15 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() { signal->identifier = parse_identifier(); if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { - while (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) { + do { + if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { + // Allow for trailing comma. + break; + } + ParameterNode *parameter = parse_parameter(); if (parameter == nullptr) { + push_error("Expected signal parameter name."); break; } if (parameter->default_value != nullptr) { @@ -1004,7 +1006,8 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() { signal->parameters_indices[parameter->identifier->name] = signal->parameters.size(); signal->parameters.push_back(parameter); } - } + } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end()); + consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after signal parameters.)*"); } @@ -1026,7 +1029,7 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { push_multiline(true); consume(GDScriptTokenizer::Token::BRACE_OPEN, vformat(R"(Expected "{" after %s.)", named ? "enum name" : R"("enum")")); - int current_value = 0; + HashMap<StringName, int> elements; do { if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) { @@ -1035,8 +1038,13 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifer for enum key.)")) { EnumNode::Value item; item.identifier = parse_identifier(); + item.parent_enum = enum_node; + item.line = previous.start_line; + item.leftmost_column = previous.leftmost_column; - if (!named) { + if (elements.has(item.identifier->name)) { + push_error(vformat(R"(Name "%s" was already in this enum (at line %d).)", item.identifier->name, elements[item.identifier->name]), item.identifier); + } else if (!named) { // TODO: Abstract this recursive member check. ClassNode *parent = current_class; while (parent != nullptr) { @@ -1048,19 +1056,18 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { } } - if (match(GDScriptTokenizer::Token::EQUAL)) { - if (consume(GDScriptTokenizer::Token::LITERAL, R"(Expected integer value after "=".)")) { - item.custom_value = parse_literal(); + elements[item.identifier->name] = item.line; - if (item.custom_value->value.get_type() != Variant::INT) { - push_error(R"(Expected integer value after "=".)"); - item.custom_value = nullptr; - } else { - current_value = item.custom_value->value; - } + if (match(GDScriptTokenizer::Token::EQUAL)) { + ExpressionNode *value = parse_expression(false); + if (value == nullptr) { + push_error(R"(Expected expression value after "=".)"); } + item.custom_value = value; } - item.value = current_value++; + item.rightmost_column = previous.rightmost_column; + + item.index = enum_node->values.size(); enum_node->values.push_back(item); if (!named) { // Add as member of current class. @@ -1142,6 +1149,9 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() { if (match(GDScriptTokenizer::Token::FORWARD_ARROW)) { make_completion_context(COMPLETION_TYPE_NAME_OR_VOID, function); function->return_type = parse_type(true); + if (function->return_type == nullptr) { + push_error(R"(Expected return type or "void" after "->".)"); + } } // TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens. @@ -2495,15 +2505,18 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p make_completion_context(COMPLETION_GET_NODE, get_node); get_node->string = parse_literal(); return get_node; - } else if (check(GDScriptTokenizer::Token::IDENTIFIER)) { + } else if (current.is_node_name()) { GetNodeNode *get_node = alloc_node<GetNodeNode>(); int chain_position = 0; do { make_completion_context(COMPLETION_GET_NODE, get_node, chain_position++); - if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expect node identifer after "/".)")) { + if (!current.is_node_name()) { + push_error(R"(Expect node path after "/".)"); return nullptr; } - IdentifierNode *identifier = parse_identifier(); + advance(); + IdentifierNode *identifier = alloc_node<IdentifierNode>(); + identifier->name = previous.get_identifier(); get_node->chain.push_back(identifier); } while (match(GDScriptTokenizer::Token::SLASH)); return get_node; @@ -2527,29 +2540,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_ if (preload->path == nullptr) { push_error(R"(Expected resource path after "(".)"); - } else if (preload->path->type != Node::LITERAL) { - push_error("Preloaded path must be a constant string."); - } else { - LiteralNode *path = static_cast<LiteralNode *>(preload->path); - if (path->value.get_type() != Variant::STRING) { - push_error("Preloaded path must be a constant string."); - } else { - preload->resolved_path = path->value; - // TODO: Save this as script dependency. - if (preload->resolved_path.is_rel_path()) { - preload->resolved_path = script_path.get_base_dir().plus_file(preload->resolved_path); - } - preload->resolved_path = preload->resolved_path.simplify_path(); - if (!FileAccess::exists(preload->resolved_path)) { - push_error(vformat(R"(Preload file "%s" does not exist.)", preload->resolved_path)); - } else { - // TODO: Don't load if validating: use completion cache. - preload->resource = ResourceLoader::load(preload->resolved_path); - if (preload->resource.is_null()) { - push_error(vformat(R"(Could not preload resource file "%s".)", preload->resolved_path)); - } - } - } } pop_completion_call(); diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index edfe330c0c..7d8ae7fc55 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -405,7 +405,10 @@ public: struct EnumNode : public Node { struct Value { IdentifierNode *identifier = nullptr; - LiteralNode *custom_value = nullptr; + ExpressionNode *custom_value = nullptr; + EnumNode *parent_enum = nullptr; + int index = -1; + bool resolved = false; int value = 0; int line = 0; int leftmost_column = 0; diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 7a4bdd88ba..737e920693 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -156,6 +156,64 @@ const char *GDScriptTokenizer::Token::get_name() const { return token_names[type]; } +bool GDScriptTokenizer::Token::is_identifier() const { + // Note: Most keywords should not be recognized as identifiers. + // These are only exceptions for stuff that already is on the engine's API. + switch (type) { + case IDENTIFIER: + case MATCH: // Used in String.match(). + return true; + default: + return false; + } +} + +bool GDScriptTokenizer::Token::is_node_name() const { + // This is meant to allow keywords with the $ notation, but not as general identifiers. + switch (type) { + case IDENTIFIER: + case AND: + case AS: + case ASSERT: + case AWAIT: + case BREAK: + case BREAKPOINT: + case CLASS_NAME: + case CLASS: + case CONST: + case CONTINUE: + case ELIF: + case ELSE: + case ENUM: + case EXTENDS: + case FOR: + case FUNC: + case IF: + case IN: + case IS: + case MATCH: + case NAMESPACE: + case NOT: + case OR: + case PASS: + case PRELOAD: + case RETURN: + case SELF: + case SIGNAL: + case STATIC: + case SUPER: + case TRAIT: + case UNDERSCORE: + case VAR: + case VOID: + case WHILE: + case YIELD: + return true; + default: + return false; + } +} + String GDScriptTokenizer::get_token_name(Token::Type p_token_type) { ERR_FAIL_INDEX_V_MSG(p_token_type, Token::TK_MAX, "<error>", "Using token type out of the enum."); return token_names[p_token_type]; diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index 059a226924..100ed3f132 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -168,9 +168,9 @@ public: String source; const char *get_name() const; - // TODO: Allow some keywords as identifiers? - bool is_identifier() const { return type == IDENTIFIER; } - StringName get_identifier() const { return literal; } + bool is_identifier() const; + bool is_node_name() const; + StringName get_identifier() const { return source; } Token(Type p_type) { type = p_type; diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index ae7898fdf2..4d79d9d395 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -237,7 +237,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p case ClassNode::Member::ENUM_VALUE: { lsp::DocumentSymbol symbol; - symbol.name = m.constant->identifier->name; + symbol.name = m.enum_value.identifier->name; symbol.kind = lsp::SymbolKind::EnumMember; symbol.deprecated = false; symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.enum_value.line); diff --git a/modules/mobile_vr/register_types.cpp b/modules/mobile_vr/register_types.cpp index 75638d47c4..0bb555e780 100644 --- a/modules/mobile_vr/register_types.cpp +++ b/modules/mobile_vr/register_types.cpp @@ -35,9 +35,11 @@ void register_mobile_vr_types() { ClassDB::register_class<MobileVRInterface>(); - Ref<MobileVRInterface> mobile_vr; - mobile_vr.instance(); - XRServer::get_singleton()->add_interface(mobile_vr); + if (XRServer::get_singleton()) { + Ref<MobileVRInterface> mobile_vr; + mobile_vr.instance(); + XRServer::get_singleton()->add_interface(mobile_vr); + } } void unregister_mobile_vr_types() { diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs index c2549b4ad5..5edf72d63e 100644 --- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs @@ -70,13 +70,14 @@ namespace GodotTools.BuildLogger { string line = $"{e.File}({e.LineNumber},{e.ColumnNumber}): error {e.Code}: {e.Message}"; - if (e.ProjectFile.Length > 0) + if (!string.IsNullOrEmpty(e.ProjectFile)) line += $" [{e.ProjectFile}]"; WriteLine(line); string errorLine = $@"error,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," + - $@"{e.Code.CsvEscape()},{e.Message.CsvEscape()},{e.ProjectFile.CsvEscape()}"; + $"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," + + $"{e.ProjectFile?.CsvEscape() ?? string.Empty}"; issuesStreamWriter.WriteLine(errorLine); } @@ -89,8 +90,9 @@ namespace GodotTools.BuildLogger WriteLine(line); - string warningLine = $@"warning,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber},{e.Code.CsvEscape()}," + - $@"{e.Message.CsvEscape()},{(e.ProjectFile != null ? e.ProjectFile.CsvEscape() : string.Empty)}"; + string warningLine = $@"warning,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," + + $"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," + + $"{e.ProjectFile?.CsvEscape() ?? string.Empty}"; issuesStreamWriter.WriteLine(warningLine); } diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs index 012b69032e..e6b0e8f1df 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs @@ -43,7 +43,7 @@ namespace GodotTools.Core path.StartsWith(DriveRoot, StringComparison.Ordinal); } - public static string ToSafeDirName(this string dirName, bool allowDirSeparator) + public static string ToSafeDirName(this string dirName, bool allowDirSeparator = false) { var invalidChars = new List<string> { ":", "*", "?", "\"", "<", ">", "|" }; diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs index 5541876f9e..01d7c99662 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Text; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; @@ -41,7 +42,8 @@ namespace GodotTools.ProjectEditor var root = GenGameProject(name); - root.Save(path); + // Save (without BOM) + root.Save(path, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); return Guid.NewGuid().ToString().ToUpper(); } diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs index 6399991b84..ff7ce97c47 100644 --- a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs @@ -205,23 +205,8 @@ namespace GodotTools if (File.Exists(editorScriptsMetadataPath)) File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath); - var currentPlayRequest = GodotSharpEditor.Instance.CurrentPlaySettings; - - if (currentPlayRequest != null) - { - if (currentPlayRequest.Value.HasDebugger) - { - // Set the environment variable that will tell the player to connect to the IDE debugger - // TODO: We should probably add a better way to do this - Environment.SetEnvironmentVariable("GODOT_MONO_DEBUGGER_AGENT", - "--debugger-agent=transport=dt_socket" + - $",address={currentPlayRequest.Value.DebuggerHost}:{currentPlayRequest.Value.DebuggerPort}" + - ",server=n"); - } - - if (!currentPlayRequest.Value.BuildBeforePlaying) - return true; // Requested play from an external editor/IDE which already built the project - } + if (GodotSharpEditor.Instance.SkipBuildBeforePlaying) + return true; // Requested play from an external editor/IDE which already built the project return BuildProjectBlocking("Debug"); } diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index 554763eecb..e819848212 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -430,7 +430,7 @@ namespace GodotTools.Export private static string DetermineDataDirNameForProject() { var appName = (string)ProjectSettings.GetSetting("application/config/name"); - string appNameSafe = appName.ToSafeDirName(allowDirSeparator: false); + string appNameSafe = appName.ToSafeDirName(); return $"data_{appNameSafe}"; } diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index a363ecc920..3148458d7e 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -38,13 +38,14 @@ namespace GodotTools public BottomPanel BottomPanel { get; private set; } - public PlaySettings? CurrentPlaySettings { get; set; } + public bool SkipBuildBeforePlaying { get; set; } = false; public static string ProjectAssemblyName { get { var projectAssemblyName = (string)ProjectSettings.GetSetting("application/config/name"); + projectAssemblyName = projectAssemblyName.ToSafeDirName(); if (string.IsNullOrEmpty(projectAssemblyName)) projectAssemblyName = "UnnamedProject"; return projectAssemblyName; diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs index 17f3339560..eb34a2d0f7 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs @@ -330,9 +330,10 @@ namespace GodotTools.Ides { DispatchToMainThread(() => { - GodotSharpEditor.Instance.CurrentPlaySettings = new PlaySettings(); + // TODO: Add BuildBeforePlaying flag to PlayRequest + + // Run the game Internal.EditorRunPlay(); - GodotSharpEditor.Instance.CurrentPlaySettings = null; }); return Task.FromResult<Response>(new PlayResponse()); } @@ -341,10 +342,22 @@ namespace GodotTools.Ides { DispatchToMainThread(() => { - GodotSharpEditor.Instance.CurrentPlaySettings = - new PlaySettings(request.DebuggerHost, request.DebuggerPort, request.BuildBeforePlaying ?? true); + // Tell the build callback whether the editor already built the solution or not + GodotSharpEditor.Instance.SkipBuildBeforePlaying = !(request.BuildBeforePlaying ?? true); + + // Pass the debugger agent settings to the player via an environment variables + // TODO: It would be better if this was an argument in EditorRunPlay instead + Environment.SetEnvironmentVariable("GODOT_MONO_DEBUGGER_AGENT", + "--debugger-agent=transport=dt_socket" + + $",address={request.DebuggerHost}:{request.DebuggerPort}" + + ",server=n"); + + // Run the game Internal.EditorRunPlay(); - GodotSharpEditor.Instance.CurrentPlaySettings = null; + + // Restore normal settings + Environment.SetEnvironmentVariable("GODOT_MONO_DEBUGGER_AGENT", ""); + GodotSharpEditor.Instance.SkipBuildBeforePlaying = false; }); return Task.FromResult<Response>(new DebugPlayResponse()); } diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py index f4ef40a0ba..66579c1ad7 100644 --- a/platform/iphone/detect.py +++ b/platform/iphone/detect.py @@ -235,7 +235,10 @@ def configure(env): env.Append(CPPDEFINES=["ICLOUD_ENABLED"]) env.Prepend( - CPPPATH=["$IPHONESDK/usr/include", "$IPHONESDK/System/Library/Frameworks/AudioUnit.framework/Headers",] + CPPPATH=[ + "$IPHONESDK/usr/include", + "$IPHONESDK/System/Library/Frameworks/AudioUnit.framework/Headers", + ] ) env["ENV"]["CODESIGN_ALLOCATE"] = "/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/codesign_allocate" diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp index 4a751488cb..a889717f20 100644 --- a/platform/iphone/export/export.cpp +++ b/platform/iphone/export/export.cpp @@ -86,6 +86,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform { struct IOSExportAsset { String exported_path; bool is_framework; // framework is anything linked to the binary, otherwise it's a resource + bool should_embed; }; String _get_additional_plist_content(); @@ -100,7 +101,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform { Vector<String> _get_preset_architectures(const Ref<EditorExportPreset> &p_preset); void _add_assets_to_project(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets); - Error _export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, Vector<IOSExportAsset> &r_exported_assets); + Error _export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets); Error _export_additional_assets(const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets); bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const { @@ -912,15 +913,6 @@ struct ExportLibsData { }; void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets) { - Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); - Vector<String> frameworks; - for (int i = 0; i < export_plugins.size(); ++i) { - Vector<String> plugin_frameworks = export_plugins[i]->get_ios_frameworks(); - for (int j = 0; j < plugin_frameworks.size(); ++j) { - frameworks.push_back(plugin_frameworks[j]); - } - } - // that is just a random number, we just need Godot IDs not to clash with // existing IDs in the project. PbxId current_id = { 0x58938401, 0, 0 }; @@ -945,15 +937,19 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese String type; if (asset.exported_path.ends_with(".framework")) { - additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n"; - framework_id = (++current_id).str(); - pbx_embeded_frameworks += framework_id + ",\n"; + if (asset.should_embed) { + additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n"; + framework_id = (++current_id).str(); + pbx_embeded_frameworks += framework_id + ",\n"; + } type = "wrapper.framework"; } else if (asset.exported_path.ends_with(".xcframework")) { - additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n"; - framework_id = (++current_id).str(); - pbx_embeded_frameworks += framework_id + ",\n"; + if (asset.should_embed) { + additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n"; + framework_id = (++current_id).str(); + pbx_embeded_frameworks += framework_id + ",\n"; + } type = "wrapper.xcframework"; } else if (asset.exported_path.ends_with(".dylib")) { @@ -1026,7 +1022,7 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese } } -Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, Vector<IOSExportAsset> &r_exported_assets) { +Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets) { DirAccess *filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); String binary_name = p_out_dir.get_file().get_basename(); @@ -1035,7 +1031,7 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir String asset = p_assets[f_idx]; if (!asset.begins_with("res://")) { // either SDK-builtin or already a part of the export template - IOSExportAsset exported_asset = { asset, p_is_framework }; + IOSExportAsset exported_asset = { asset, p_is_framework, p_should_embed }; r_exported_assets.push_back(exported_asset); } else { DirAccess *da = DirAccess::create_for_path(asset); @@ -1101,7 +1097,7 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir memdelete(filesystem_da); return err; } - IOSExportAsset exported_asset = { binary_name.plus_file(asset_path), p_is_framework }; + IOSExportAsset exported_asset = { binary_name.plus_file(asset_path), p_is_framework, p_should_embed }; r_exported_assets.push_back(exported_asset); if (create_framework) { @@ -1165,19 +1161,23 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets) { Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); for (int i = 0; i < export_plugins.size(); i++) { - Vector<String> frameworks = export_plugins[i]->get_ios_frameworks(); - Error err = _export_additional_assets(p_out_dir, frameworks, true, r_exported_assets); + Vector<String> linked_frameworks = export_plugins[i]->get_ios_frameworks(); + Error err = _export_additional_assets(p_out_dir, linked_frameworks, true, false, r_exported_assets); + ERR_FAIL_COND_V(err, err); + + Vector<String> embedded_frameworks = export_plugins[i]->get_ios_embedded_frameworks(); + err = _export_additional_assets(p_out_dir, embedded_frameworks, true, true, r_exported_assets); ERR_FAIL_COND_V(err, err); Vector<String> project_static_libs = export_plugins[i]->get_ios_project_static_libs(); for (int j = 0; j < project_static_libs.size(); j++) { project_static_libs.write[j] = project_static_libs[j].get_file(); // Only the file name as it's copied to the project } - err = _export_additional_assets(p_out_dir, project_static_libs, true, r_exported_assets); + err = _export_additional_assets(p_out_dir, project_static_libs, true, true, r_exported_assets); ERR_FAIL_COND_V(err, err); Vector<String> ios_bundle_files = export_plugins[i]->get_ios_bundle_files(); - err = _export_additional_assets(p_out_dir, ios_bundle_files, false, r_exported_assets); + err = _export_additional_assets(p_out_dir, ios_bundle_files, false, false, r_exported_assets); ERR_FAIL_COND_V(err, err); } @@ -1185,7 +1185,7 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir for (int i = 0; i < p_libraries.size(); ++i) { library_paths.push_back(p_libraries[i].path); } - Error err = _export_additional_assets(p_out_dir, library_paths, true, r_exported_assets); + Error err = _export_additional_assets(p_out_dir, library_paths, true, true, r_exported_assets); ERR_FAIL_COND_V(err, err); return OK; diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index e05d540c50..a0aced26ab 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -681,14 +681,6 @@ DisplayServer::WindowID DisplayServerX11::create_sub_window(WindowMode p_mode, u return id; } -void DisplayServerX11::show_window(WindowID p_id) { - _THREAD_SAFE_METHOD_ - - WindowData &wd = windows[p_id]; - - XMapWindow(x11_display, wd.x11_window); -} - void DisplayServerX11::delete_sub_window(WindowID p_id) { _THREAD_SAFE_METHOD_ @@ -3153,6 +3145,8 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u WindowData wd; wd.x11_window = XCreateWindow(x11_display, RootWindow(x11_display, visualInfo->screen), p_rect.position.x, p_rect.position.y, p_rect.size.width > 0 ? p_rect.size.width : 1, p_rect.size.height > 0 ? p_rect.size.height : 1, 0, visualInfo->depth, InputOutput, visualInfo->visual, valuemask, &windowAttributes); + XMapWindow(x11_display, wd.x11_window); + //associate PID // make PID known to X11 { @@ -3322,7 +3316,6 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u if (cursors[current_cursor] != None) { XDefineCursor(x11_display, wd.x11_window, cursors[current_cursor]); } - return id; } @@ -3562,7 +3555,6 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode window_set_flag(WindowFlags(i), true, main_window); } } - show_window(main_window); //create RenderingDevice if used #if defined(VULKAN_ENABLED) diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h index 8e1f941bbf..fb50b484a0 100644 --- a/platform/linuxbsd/display_server_x11.h +++ b/platform/linuxbsd/display_server_x11.h @@ -277,7 +277,6 @@ public: virtual Vector<DisplayServer::WindowID> get_window_list() const; virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i()); - virtual void show_window(WindowID p_id); virtual void delete_sub_window(WindowID p_id); virtual WindowID get_window_at_screen_position(const Point2i &p_position) const; diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 8c6f3b1167..e00a32e3ba 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -88,7 +88,9 @@ void OS_LinuxBSD::finalize() { #endif #ifdef JOYDEV_ENABLED - memdelete(joypad); + if (joypad) { + memdelete(joypad); + } #endif } diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h index 4295721c68..cd4fbd9db5 100644 --- a/platform/linuxbsd/os_linuxbsd.h +++ b/platform/linuxbsd/os_linuxbsd.h @@ -48,7 +48,7 @@ class OS_LinuxBSD : public OS_Unix { bool force_quit; #ifdef JOYDEV_ENABLED - JoypadLinux *joypad; + JoypadLinux *joypad = nullptr; #endif #ifdef ALSA_ENABLED diff --git a/platform/osx/detect.py b/platform/osx/detect.py index 272ae1b620..a4a382f3a9 100644 --- a/platform/osx/detect.py +++ b/platform/osx/detect.py @@ -76,6 +76,7 @@ def configure(env): elif env["target"] == "debug": env.Prepend(CCFLAGS=["-g3"]) env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) + env.Prepend(LINKFLAGS=["-Xlinker", "-no_deduplicate"]) ## Architecture diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h index d8f3f81ff6..68e8454fd0 100644 --- a/platform/osx/display_server_osx.h +++ b/platform/osx/display_server_osx.h @@ -230,7 +230,6 @@ public: virtual Vector<int> get_window_list() const override; virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i()) override; - virtual void show_window(WindowID p_id) override; virtual void delete_sub_window(WindowID p_id) override; virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index 3b82950224..dfb1783a2c 100644 --- a/platform/osx/display_server_osx.mm +++ b/platform/osx/display_server_osx.mm @@ -2314,23 +2314,18 @@ DisplayServer::WindowID DisplayServerOSX::create_sub_window(WindowMode p_mode, u _THREAD_SAFE_METHOD_ WindowID id = _create_window(p_mode, p_rect); + WindowData &wd = windows[id]; for (int i = 0; i < WINDOW_FLAG_MAX; i++) { if (p_flags & (1 << i)) { window_set_flag(WindowFlags(i), true, id); } } - - return id; -} - -void DisplayServerOSX::show_window(WindowID p_id) { - WindowData &wd = windows[p_id]; - if (wd.no_focus) { [wd.window_object orderFront:nil]; } else { [wd.window_object makeKeyAndOrderFront:nil]; } + return id; } void DisplayServerOSX::_send_window_event(const WindowData &wd, WindowEvent p_event) { @@ -2374,7 +2369,11 @@ void DisplayServerOSX::_update_window(WindowData p_wd) { [p_wd.window_object setHidesOnDeactivate:YES]; } else { // Reset these when our window is not a borderless window that covers up the screen - [p_wd.window_object setLevel:NSNormalWindowLevel]; + if (p_wd.on_top) { + [p_wd.window_object setLevel:NSFloatingWindowLevel]; + } else { + [p_wd.window_object setLevel:NSNormalWindowLevel]; + } [p_wd.window_object setHidesOnDeactivate:NO]; } } @@ -3775,7 +3774,7 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode window_set_flag(WindowFlags(i), true, main_window); } } - show_window(MAIN_WINDOW_ID); + [windows[main_window].window_object makeKeyAndOrderFront:nil]; #if defined(OPENGL_ENABLED) if (rendering_driver == "opengl_es") { diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h index 9204a145bf..5a9e43450f 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -44,7 +44,7 @@ class OS_OSX : public OS_Unix { bool force_quit; - JoypadOSX *joypad_osx; + JoypadOSX *joypad_osx = nullptr; #ifdef COREAUDIO_ENABLED AudioDriverCoreAudio audio_driver; diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index c4eb5407af..399a29cbe0 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -145,7 +145,9 @@ void OS_OSX::finalize() { delete_main_loop(); - memdelete(joypad_osx); + if (joypad_osx) { + memdelete(joypad_osx); + } } void OS_OSX::set_main_loop(MainLoop *p_main_loop) { diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index e4a7814cc1..9469e35536 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -495,17 +495,13 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod _update_window_style(window_id); - return window_id; -} - -void DisplayServerWindows::show_window(WindowID p_id) { - WindowData &wd = windows[p_id]; - - ShowWindow(wd.hWnd, wd.no_focus ? SW_SHOWNOACTIVATE : SW_SHOW); // Show The Window - if (!wd.no_focus) { + ShowWindow(wd.hWnd, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) ? SW_SHOWNOACTIVATE : SW_SHOW); // Show The Window + if (!(p_flags & WINDOW_FLAG_NO_FOCUS_BIT)) { SetForegroundWindow(wd.hWnd); // Slightly Higher Priority SetFocus(wd.hWnd); // Sets Keyboard Focus To } + + return window_id; } void DisplayServerWindows::delete_sub_window(WindowID p_window) { @@ -2037,8 +2033,8 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA Ref<InputEventMouseMotion> mm; mm.instance(); mm->set_window_id(window_id); - mm->set_control(GetKeyState(VK_CONTROL) != 0); - mm->set_shift(GetKeyState(VK_SHIFT) != 0); + mm->set_control(GetKeyState(VK_CONTROL) < 0); + mm->set_shift(GetKeyState(VK_SHIFT) < 0); mm->set_alt(alt_mem); mm->set_pressure(windows[window_id].last_pressure); @@ -2180,8 +2176,8 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm->set_tilt(Vector2((float)pen_info.tiltX / 90, (float)pen_info.tiltY / 90)); } - mm->set_control((wParam & MK_CONTROL) != 0); - mm->set_shift((wParam & MK_SHIFT) != 0); + mm->set_control(GetKeyState(VK_CONTROL) < 0); + mm->set_shift(GetKeyState(VK_SHIFT) < 0); mm->set_alt(alt_mem); mm->set_button_mask(last_button_state); @@ -3143,7 +3139,9 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win } } - show_window(MAIN_WINDOW_ID); + ShowWindow(windows[MAIN_WINDOW_ID].hWnd, SW_SHOW); // Show The Window + SetForegroundWindow(windows[MAIN_WINDOW_ID].hWnd); // Slightly Higher Priority + SetFocus(windows[MAIN_WINDOW_ID].hWnd); // Sets Keyboard Focus To #if defined(VULKAN_ENABLED) diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 52f5b0f4a9..0ad8cd3d07 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -461,7 +461,6 @@ public: virtual Vector<DisplayServer::WindowID> get_window_list() const; virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i()); - virtual void show_window(WindowID p_window); virtual void delete_sub_window(WindowID p_window); virtual WindowID get_window_at_screen_position(const Point2i &p_position) const; diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 5afc1f438e..14167531a5 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -578,7 +578,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { if (handled) { accept_event(); - } else if (!k->get_command()) { + } else if (!k->get_command() || (k->get_command() && k->get_alt())) { if (k->get_unicode() >= 32 && k->get_keycode() != KEY_DELETE) { if (editable) { selection_delete(); diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index 3670f13705..ae2f99e91d 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -62,8 +62,8 @@ void SpinBox::_text_entered(const String &p_string) { Variant value = expr->execute(Array(), nullptr, false); if (value.get_type() != Variant::NIL) { set_value(value); - _value_changed(0); } + _value_changed(0); } LineEdit *SpinBox::get_line_edit() { diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index e17085cafc..d6d8e74748 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -1872,6 +1872,9 @@ void TextEdit::indent_right() { for (int i = start_line; i <= end_line; i++) { String line_text = get_line(i); + if (line_text.size() == 0 && is_selection_active()) { + continue; + } if (indent_using_spaces) { // We don't really care where selection is - we just need to know indentation level at the beginning of the line. int left = _find_first_non_whitespace_column_of_line(line_text); @@ -3604,7 +3607,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { return; } - if (!keycode_handled && !k->get_command()) { // For German keyboards. + if (!keycode_handled && (!k->get_command() || (k->get_command() && k->get_alt()))) { // For German keyboards. if (k->get_unicode() >= 32) { if (readonly) { diff --git a/scene/main/window.cpp b/scene/main/window.cpp index a5c5be8a44..68ffdfe2e8 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -247,7 +247,6 @@ void Window::_make_window() { } RS::get_singleton()->viewport_set_update_mode(get_viewport_rid(), RS::VIEWPORT_UPDATE_WHEN_VISIBLE); - DisplayServer::get_singleton()->show_window(window_id); } void Window::_update_from_window() { diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 47b8b6073b..996d249bbf 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -372,7 +372,11 @@ void register_scene_types() { OS::get_singleton()->yield(); //may take time to init - AcceptDialog::set_swap_cancel_ok(GLOBAL_DEF_NOVAL("gui/common/swap_cancel_ok", bool(DisplayServer::get_singleton()->get_swap_cancel_ok()))); + bool swap_cancel_ok = false; + if (DisplayServer::get_singleton()) { + swap_cancel_ok = GLOBAL_DEF_NOVAL("gui/common/swap_cancel_ok", bool(DisplayServer::get_singleton()->get_swap_cancel_ok())); + } + AcceptDialog::set_swap_cancel_ok(swap_cancel_ok); #endif /* REGISTER 3D */ @@ -912,8 +916,10 @@ void register_scene_types() { } } - // Always make the default theme to avoid invalid default font/icon/style in the given theme - make_default_theme(default_theme_hidpi, font); + // Always make the default theme to avoid invalid default font/icon/style in the given theme. + if (RenderingServer::get_singleton()) { + make_default_theme(default_theme_hidpi, font); + } if (theme_path != String()) { Ref<Theme> theme = ResourceLoader::load(theme_path); diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp index 526d101389..ee8e63266d 100644 --- a/scene/resources/environment.cpp +++ b/scene/resources/environment.cpp @@ -134,6 +134,7 @@ Color Environment::get_ambient_light_color() const { void Environment::set_ambient_source(AmbientSource p_source) { ambient_source = p_source; _update_ambient_light(); + _change_notify(); } Environment::AmbientSource Environment::get_ambient_source() const { @@ -161,6 +162,7 @@ float Environment::get_ambient_light_sky_contribution() const { void Environment::set_reflection_source(ReflectionSource p_source) { reflection_source = p_source; _update_ambient_light(); + _change_notify(); } Environment::ReflectionSource Environment::get_reflection_source() const { @@ -694,150 +696,59 @@ bool Environment::is_fog_enabled() const { return fog_enabled; } -void Environment::set_fog_color(const Color &p_color) { - fog_color = p_color; +void Environment::set_fog_light_color(const Color &p_light_color) { + fog_light_color = p_light_color; _update_fog(); } - -Color Environment::get_fog_color() const { - return fog_color; +Color Environment::get_fog_light_color() const { + return fog_light_color; } - -void Environment::set_fog_sun_color(const Color &p_color) { - fog_sun_color = p_color; +void Environment::set_fog_light_energy(float p_amount) { + fog_light_energy = p_amount; _update_fog(); } - -Color Environment::get_fog_sun_color() const { - return fog_sun_color; +float Environment::get_fog_light_energy() const { + return fog_light_energy; } - -void Environment::set_fog_sun_amount(float p_amount) { - fog_sun_amount = p_amount; +void Environment::set_fog_sun_scatter(float p_amount) { + fog_sun_scatter = p_amount; _update_fog(); } - -float Environment::get_fog_sun_amount() const { - return fog_sun_amount; -} - -void Environment::_update_fog() { - RS::get_singleton()->environment_set_fog( - environment, - fog_enabled, - fog_color, - fog_sun_color, - fog_sun_amount); -} - -void Environment::set_fog_depth_enabled(bool p_enabled) { - fog_depth_enabled = p_enabled; - _update_fog_depth(); -} - -bool Environment::is_fog_depth_enabled() const { - return fog_depth_enabled; -} - -void Environment::set_fog_depth_begin(float p_distance) { - fog_depth_begin = p_distance; - _update_fog_depth(); -} - -float Environment::get_fog_depth_begin() const { - return fog_depth_begin; -} - -void Environment::set_fog_depth_end(float p_distance) { - fog_depth_end = p_distance; - _update_fog_depth(); -} - -float Environment::get_fog_depth_end() const { - return fog_depth_end; -} - -void Environment::set_fog_depth_curve(float p_curve) { - fog_depth_curve = p_curve; - _update_fog_depth(); -} - -float Environment::get_fog_depth_curve() const { - return fog_depth_curve; -} - -void Environment::set_fog_transmit_enabled(bool p_enabled) { - fog_transmit_enabled = p_enabled; - _update_fog_depth(); -} - -bool Environment::is_fog_transmit_enabled() const { - return fog_transmit_enabled; -} - -void Environment::set_fog_transmit_curve(float p_curve) { - fog_transmit_curve = p_curve; - _update_fog_depth(); -} - -float Environment::get_fog_transmit_curve() const { - return fog_transmit_curve; -} - -void Environment::_update_fog_depth() { - RS::get_singleton()->environment_set_fog_depth( - environment, - fog_depth_enabled, - fog_depth_begin, - fog_depth_end, - fog_depth_curve, - fog_transmit_enabled, - fog_transmit_curve); -} - -void Environment::set_fog_height_enabled(bool p_enabled) { - fog_height_enabled = p_enabled; - _update_fog_height(); -} - -bool Environment::is_fog_height_enabled() const { - return fog_height_enabled; +float Environment::get_fog_sun_scatter() const { + return fog_sun_scatter; } - -void Environment::set_fog_height_min(float p_distance) { - fog_height_min = p_distance; - _update_fog_height(); +void Environment::set_fog_density(float p_amount) { + fog_density = p_amount; + _update_fog(); } - -float Environment::get_fog_height_min() const { - return fog_height_min; +float Environment::get_fog_density() const { + return fog_density; } - -void Environment::set_fog_height_max(float p_distance) { - fog_height_max = p_distance; - _update_fog_height(); +void Environment::set_fog_height(float p_amount) { + fog_height = p_amount; + _update_fog(); } - -float Environment::get_fog_height_max() const { - return fog_height_max; +float Environment::get_fog_height() const { + return fog_height; } - -void Environment::set_fog_height_curve(float p_distance) { - fog_height_curve = p_distance; - _update_fog_height(); +void Environment::set_fog_height_density(float p_amount) { + fog_height_density = p_amount; + _update_fog(); } - -float Environment::get_fog_height_curve() const { - return fog_height_curve; +float Environment::get_fog_height_density() const { + return fog_height_density; } -void Environment::_update_fog_height() { - RS::get_singleton()->environment_set_fog_height( +void Environment::_update_fog() { + RS::get_singleton()->environment_set_fog( environment, - fog_height_enabled, - fog_height_min, - fog_height_max, - fog_height_curve); + fog_enabled, + fog_light_color, + fog_light_energy, + fog_sun_scatter, + fog_density, + fog_height, + fog_height_density); } // Volumetric Fog @@ -849,6 +760,7 @@ void Environment::_update_volumetric_fog() { void Environment::set_volumetric_fog_enabled(bool p_enable) { volumetric_fog_enabled = p_enable; _update_volumetric_fog(); + _change_notify(); } bool Environment::is_volumetric_fog_enabled() const { @@ -1003,6 +915,7 @@ void Environment::_validate_property(PropertyInfo &property) const { static const char *hide_prefixes[] = { "fog_", + "volumetric_fog_", "auto_exposure_", "ss_reflections_", "ssao_", @@ -1290,52 +1203,31 @@ void Environment::_bind_methods() { ClassDB::bind_method(D_METHOD("set_fog_enabled", "enabled"), &Environment::set_fog_enabled); ClassDB::bind_method(D_METHOD("is_fog_enabled"), &Environment::is_fog_enabled); - ClassDB::bind_method(D_METHOD("set_fog_color", "color"), &Environment::set_fog_color); - ClassDB::bind_method(D_METHOD("get_fog_color"), &Environment::get_fog_color); - ClassDB::bind_method(D_METHOD("set_fog_sun_color", "color"), &Environment::set_fog_sun_color); - ClassDB::bind_method(D_METHOD("get_fog_sun_color"), &Environment::get_fog_sun_color); - ClassDB::bind_method(D_METHOD("set_fog_sun_amount", "amount"), &Environment::set_fog_sun_amount); - ClassDB::bind_method(D_METHOD("get_fog_sun_amount"), &Environment::get_fog_sun_amount); - - ClassDB::bind_method(D_METHOD("set_fog_depth_enabled", "enabled"), &Environment::set_fog_depth_enabled); - ClassDB::bind_method(D_METHOD("is_fog_depth_enabled"), &Environment::is_fog_depth_enabled); - ClassDB::bind_method(D_METHOD("set_fog_depth_begin", "distance"), &Environment::set_fog_depth_begin); - ClassDB::bind_method(D_METHOD("get_fog_depth_begin"), &Environment::get_fog_depth_begin); - ClassDB::bind_method(D_METHOD("set_fog_depth_end", "distance"), &Environment::set_fog_depth_end); - ClassDB::bind_method(D_METHOD("get_fog_depth_end"), &Environment::get_fog_depth_end); - ClassDB::bind_method(D_METHOD("set_fog_depth_curve", "curve"), &Environment::set_fog_depth_curve); - ClassDB::bind_method(D_METHOD("get_fog_depth_curve"), &Environment::get_fog_depth_curve); - ClassDB::bind_method(D_METHOD("set_fog_transmit_enabled", "enabled"), &Environment::set_fog_transmit_enabled); - ClassDB::bind_method(D_METHOD("is_fog_transmit_enabled"), &Environment::is_fog_transmit_enabled); - ClassDB::bind_method(D_METHOD("set_fog_transmit_curve", "curve"), &Environment::set_fog_transmit_curve); - ClassDB::bind_method(D_METHOD("get_fog_transmit_curve"), &Environment::get_fog_transmit_curve); - - ClassDB::bind_method(D_METHOD("set_fog_height_enabled", "enabled"), &Environment::set_fog_height_enabled); - ClassDB::bind_method(D_METHOD("is_fog_height_enabled"), &Environment::is_fog_height_enabled); - ClassDB::bind_method(D_METHOD("set_fog_height_min", "height"), &Environment::set_fog_height_min); - ClassDB::bind_method(D_METHOD("get_fog_height_min"), &Environment::get_fog_height_min); - ClassDB::bind_method(D_METHOD("set_fog_height_max", "height"), &Environment::set_fog_height_max); - ClassDB::bind_method(D_METHOD("get_fog_height_max"), &Environment::get_fog_height_max); - ClassDB::bind_method(D_METHOD("set_fog_height_curve", "curve"), &Environment::set_fog_height_curve); - ClassDB::bind_method(D_METHOD("get_fog_height_curve"), &Environment::get_fog_height_curve); - - ADD_GROUP("Fixed Fog", "fog_"); + ClassDB::bind_method(D_METHOD("set_fog_light_color", "light_color"), &Environment::set_fog_light_color); + ClassDB::bind_method(D_METHOD("get_fog_light_color"), &Environment::get_fog_light_color); + ClassDB::bind_method(D_METHOD("set_fog_light_energy", "light_energy"), &Environment::set_fog_light_energy); + ClassDB::bind_method(D_METHOD("get_fog_light_energy"), &Environment::get_fog_light_energy); + ClassDB::bind_method(D_METHOD("set_fog_sun_scatter", "sun_scatter"), &Environment::set_fog_sun_scatter); + ClassDB::bind_method(D_METHOD("get_fog_sun_scatter"), &Environment::get_fog_sun_scatter); + + ClassDB::bind_method(D_METHOD("set_fog_density", "density"), &Environment::set_fog_density); + ClassDB::bind_method(D_METHOD("get_fog_density"), &Environment::get_fog_density); + + ClassDB::bind_method(D_METHOD("set_fog_height", "height"), &Environment::set_fog_height); + ClassDB::bind_method(D_METHOD("get_fog_height"), &Environment::get_fog_height); + + ClassDB::bind_method(D_METHOD("set_fog_height_density", "height_density"), &Environment::set_fog_height_density); + ClassDB::bind_method(D_METHOD("get_fog_height_density"), &Environment::get_fog_height_density); + + ADD_GROUP("Fog", "fog_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fog_enabled"), "set_fog_enabled", "is_fog_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::COLOR, "fog_color"), "set_fog_color", "get_fog_color"); - ADD_PROPERTY(PropertyInfo(Variant::COLOR, "fog_sun_color"), "set_fog_sun_color", "get_fog_sun_color"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_sun_amount", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_fog_sun_amount", "get_fog_sun_amount"); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fog_depth_enabled"), "set_fog_depth_enabled", "is_fog_depth_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_depth_begin", PROPERTY_HINT_RANGE, "0,4000,0.1"), "set_fog_depth_begin", "get_fog_depth_begin"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_depth_end", PROPERTY_HINT_RANGE, "0,4000,0.1,or_greater"), "set_fog_depth_end", "get_fog_depth_end"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_depth_curve", PROPERTY_HINT_EXP_EASING), "set_fog_depth_curve", "get_fog_depth_curve"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fog_transmit_enabled"), "set_fog_transmit_enabled", "is_fog_transmit_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_transmit_curve", PROPERTY_HINT_EXP_EASING), "set_fog_transmit_curve", "get_fog_transmit_curve"); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fog_height_enabled"), "set_fog_height_enabled", "is_fog_height_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_height_min", PROPERTY_HINT_RANGE, "-4000,4000,0.1,or_lesser,or_greater"), "set_fog_height_min", "get_fog_height_min"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_height_max", PROPERTY_HINT_RANGE, "-4000,4000,0.1,or_lesser,or_greater"), "set_fog_height_max", "get_fog_height_max"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_height_curve", PROPERTY_HINT_EXP_EASING), "set_fog_height_curve", "get_fog_height_curve"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "fog_light_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_fog_light_color", "get_fog_light_color"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_light_energy", PROPERTY_HINT_RANGE, "0,16,0.01,or_greater"), "set_fog_light_energy", "get_fog_light_energy"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_sun_scatter", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater"), "set_fog_sun_scatter", "get_fog_sun_scatter"); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_density", PROPERTY_HINT_RANGE, "0,16,0.0001"), "set_fog_density", "get_fog_density"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_height", PROPERTY_HINT_RANGE, "-1024,1024,0.01,or_lesser,or_greater"), "set_fog_height", "get_fog_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_height_density", PROPERTY_HINT_RANGE, "0,128,0.001,or_greater"), "set_fog_height_density", "get_fog_height_density"); ClassDB::bind_method(D_METHOD("set_volumetric_fog_enabled", "enabled"), &Environment::set_volumetric_fog_enabled); ClassDB::bind_method(D_METHOD("is_volumetric_fog_enabled"), &Environment::is_volumetric_fog_enabled); @@ -1444,8 +1336,6 @@ Environment::Environment() { _update_sdfgi(); _update_glow(); _update_fog(); - _update_fog_depth(); - _update_fog_height(); _update_adjustment(); _update_volumetric_fog(); _change_notify(); diff --git a/scene/resources/environment.h b/scene/resources/environment.h index 6edb54b187..d4d84f31aa 100644 --- a/scene/resources/environment.h +++ b/scene/resources/environment.h @@ -184,24 +184,14 @@ private: // Fog bool fog_enabled = false; - Color fog_color = Color(0.5, 0.6, 0.7); - Color fog_sun_color = Color(1.0, 0.9, 0.7); - float fog_sun_amount = 0.0; - void _update_fog(); - - bool fog_depth_enabled = true; - float fog_depth_begin = 10.0; - float fog_depth_end = 100.0; - float fog_depth_curve = 1.0; - bool fog_transmit_enabled = false; - float fog_transmit_curve = 1.0; - void _update_fog_depth(); + Color fog_light_color = Color(0.5, 0.6, 0.7); + float fog_light_energy = 1.0; + float fog_sun_scatter = 0.0; + float fog_density = 0.001; + float fog_height = 0.0; + float fog_height_density = 0.0; //can be negative to invert effect - bool fog_height_enabled = false; - float fog_height_min = 10.0; - float fog_height_max = 0.0; - float fog_height_curve = 1.0; - void _update_fog_height(); + void _update_fog(); // Volumetric Fog bool volumetric_fog_enabled = false; @@ -362,36 +352,22 @@ public: float get_glow_hdr_luminance_cap() const; // Fog + void set_fog_enabled(bool p_enabled); bool is_fog_enabled() const; - void set_fog_color(const Color &p_color); - Color get_fog_color() const; - void set_fog_sun_color(const Color &p_color); - Color get_fog_sun_color() const; - void set_fog_sun_amount(float p_amount); - float get_fog_sun_amount() const; - - void set_fog_depth_enabled(bool p_enabled); - bool is_fog_depth_enabled() const; - void set_fog_depth_begin(float p_distance); - float get_fog_depth_begin() const; - void set_fog_depth_end(float p_distance); - float get_fog_depth_end() const; - void set_fog_depth_curve(float p_curve); - float get_fog_depth_curve() const; - void set_fog_transmit_enabled(bool p_enabled); - bool is_fog_transmit_enabled() const; - void set_fog_transmit_curve(float p_curve); - float get_fog_transmit_curve() const; - - void set_fog_height_enabled(bool p_enabled); - bool is_fog_height_enabled() const; - void set_fog_height_min(float p_distance); - float get_fog_height_min() const; - void set_fog_height_max(float p_distance); - float get_fog_height_max() const; - void set_fog_height_curve(float p_distance); - float get_fog_height_curve() const; + void set_fog_light_color(const Color &p_light_color); + Color get_fog_light_color() const; + void set_fog_light_energy(float p_amount); + float get_fog_light_energy() const; + void set_fog_sun_scatter(float p_amount); + float get_fog_sun_scatter() const; + + void set_fog_density(float p_amount); + float get_fog_density() const; + void set_fog_height(float p_amount); + float get_fog_height() const; + void set_fog_height_density(float p_amount); + float get_fog_height_density() const; // Volumetric Fog void set_volumetric_fog_enabled(bool p_enable); diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index 93db8b725f..d4d8018d43 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -572,7 +572,7 @@ Error ResourceLoaderText::load() { } } - if (progress) { + if (progress && resources_total > 0) { *progress = resource_current / float(resources_total); } } @@ -640,7 +640,7 @@ Error ResourceLoaderText::load() { return error; } else { error = OK; - if (progress) { + if (progress && resources_total > 0) { *progress = resource_current / float(resources_total); } @@ -674,7 +674,7 @@ Error ResourceLoaderText::load() { resource_current++; - if (progress) { + if (progress && resources_total > 0) { *progress = resource_current / float(resources_total); } diff --git a/servers/display_server.cpp b/servers/display_server.cpp index 8f6d6d3b99..32d4e9e569 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -186,10 +186,6 @@ DisplayServer::WindowID DisplayServer::create_sub_window(WindowMode p_mode, uint ERR_FAIL_V_MSG(INVALID_WINDOW_ID, "Sub-windows not supported by this display server."); } -void DisplayServer::show_window(WindowID p_id) { - ERR_FAIL_MSG("Sub-windows not supported by this display server."); -} - void DisplayServer::delete_sub_window(WindowID p_id) { ERR_FAIL_MSG("Sub-windows not supported by this display server."); } diff --git a/servers/display_server.h b/servers/display_server.h index b652418244..fc6520fa5e 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -220,7 +220,6 @@ public: }; virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i()); - virtual void show_window(WindowID p_id); virtual void delete_sub_window(WindowID p_id); virtual WindowID get_window_at_screen_position(const Point2i &p_position) const = 0; diff --git a/servers/rendering/rasterizer.h b/servers/rendering/rasterizer.h index d729297209..e1282fdf71 100644 --- a/servers/rendering/rasterizer.h +++ b/servers/rendering/rasterizer.h @@ -86,7 +86,6 @@ public: virtual void environment_set_glow(RID p_env, bool p_enable, int p_level_flags, float p_intensity, float p_strength, float p_mix, float p_bloom_threshold, RS::EnvironmentGlowBlendMode p_blend_mode, float p_hdr_bleed_threshold, float p_hdr_bleed_scale, float p_hdr_luminance_cap) = 0; virtual void environment_glow_set_use_bicubic_upscale(bool p_enable) = 0; - virtual void environment_set_fog(RID p_env, bool p_enable, float p_begin, float p_end, RID p_gradient_texture) = 0; virtual void environment_set_volumetric_fog(RID p_env, bool p_enable, float p_density, const Color &p_light, float p_light_energy, float p_lenght, float p_detail_spread, float p_gi_inject, RS::EnvVolumetricFogShadowFilter p_shadow_filter) = 0; @@ -111,9 +110,7 @@ public: virtual void environment_set_adjustment(RID p_env, bool p_enable, float p_brightness, float p_contrast, float p_saturation, RID p_ramp) = 0; - virtual void environment_set_fog(RID p_env, bool p_enable, const Color &p_color, const Color &p_sun_color, float p_sun_amount) = 0; - virtual void environment_set_fog_depth(RID p_env, bool p_enable, float p_depth_begin, float p_depth_end, float p_depth_curve, bool p_transmit, float p_transmit_curve) = 0; - virtual void environment_set_fog_height(RID p_env, bool p_enable, float p_min_height, float p_max_height, float p_height_curve) = 0; + virtual void environment_set_fog(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density) = 0; virtual Ref<Image> environment_bake_panorama(RID p_env, bool p_bake_irradiance, const Size2i &p_size) = 0; diff --git a/servers/rendering/rasterizer_rd/rasterizer_effects_rd.cpp b/servers/rendering/rasterizer_rd/rasterizer_effects_rd.cpp index 390272b4b8..b3279f041f 100644 --- a/servers/rendering/rasterizer_rd/rasterizer_effects_rd.cpp +++ b/servers/rendering/rasterizer_rd/rasterizer_effects_rd.cpp @@ -1171,7 +1171,7 @@ void RasterizerEffectsRD::cubemap_filter(RID p_source_cubemap, Vector<RID> p_des RD::get_singleton()->compute_list_end(); } -void RasterizerEffectsRD::render_sky(RD::DrawListID p_list, float p_time, RID p_fb, RID p_samplers, RID p_lights, RenderPipelineVertexFormatCacheRD *p_pipeline, RID p_uniform_set, RID p_texture_set, const CameraMatrix &p_camera, const Basis &p_orientation, float p_multiplier, const Vector3 &p_position) { +void RasterizerEffectsRD::render_sky(RD::DrawListID p_list, float p_time, RID p_fb, RID p_samplers, RID p_fog, RenderPipelineVertexFormatCacheRD *p_pipeline, RID p_uniform_set, RID p_texture_set, const CameraMatrix &p_camera, const Basis &p_orientation, float p_multiplier, const Vector3 &p_position) { SkyPushConstant sky_push_constant; zeromem(&sky_push_constant, sizeof(SkyPushConstant)); @@ -1198,7 +1198,7 @@ void RasterizerEffectsRD::render_sky(RD::DrawListID p_list, float p_time, RID p_ RD::get_singleton()->draw_list_bind_uniform_set(draw_list, p_uniform_set, 1); } RD::get_singleton()->draw_list_bind_uniform_set(draw_list, p_texture_set, 2); - RD::get_singleton()->draw_list_bind_uniform_set(draw_list, p_lights, 3); + RD::get_singleton()->draw_list_bind_uniform_set(draw_list, p_fog, 3); RD::get_singleton()->draw_list_bind_index_array(draw_list, index_array); diff --git a/servers/rendering/rasterizer_rd/rasterizer_effects_rd.h b/servers/rendering/rasterizer_rd/rasterizer_effects_rd.h index 2ba9610498..b0964f23e7 100644 --- a/servers/rendering/rasterizer_rd/rasterizer_effects_rd.h +++ b/servers/rendering/rasterizer_rd/rasterizer_effects_rd.h @@ -638,7 +638,7 @@ public: void roughness_limit(RID p_source_normal, RID p_roughness, const Size2i &p_size, float p_curve); void cubemap_downsample(RID p_source_cubemap, RID p_dest_cubemap, const Size2i &p_size); void cubemap_filter(RID p_source_cubemap, Vector<RID> p_dest_cubemap, bool p_use_array); - void render_sky(RD::DrawListID p_list, float p_time, RID p_fb, RID p_samplers, RID p_lights, RenderPipelineVertexFormatCacheRD *p_pipeline, RID p_uniform_set, RID p_texture_set, const CameraMatrix &p_camera, const Basis &p_orientation, float p_multiplier, const Vector3 &p_position); + void render_sky(RD::DrawListID p_list, float p_time, RID p_fb, RID p_samplers, RID p_fog, RenderPipelineVertexFormatCacheRD *p_pipeline, RID p_uniform_set, RID p_texture_set, const CameraMatrix &p_camera, const Basis &p_orientation, float p_multiplier, const Vector3 &p_position); void screen_space_reflection(RID p_diffuse, RID p_normal_roughness, RS::EnvironmentSSRRoughnessQuality p_roughness_quality, RID p_blur_radius, RID p_blur_radius2, RID p_metallic, const Color &p_metallic_mask, RID p_depth, RID p_scale_depth, RID p_scale_normal, RID p_output, RID p_output_blur, const Size2i &p_screen_size, int p_max_steps, float p_fade_in, float p_fade_out, float p_tolerance, const CameraMatrix &p_camera); void merge_specular(RID p_dest_framebuffer, RID p_specular, RID p_base, RID p_reflection); diff --git a/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp b/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp index 78509f9642..11abb8f4a8 100644 --- a/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp +++ b/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp @@ -1146,6 +1146,7 @@ void RasterizerSceneHighEndRD::_setup_environment(RID p_environment, RID p_rende scene_state.ubo.gi_upscale_for_msaa = false; scene_state.ubo.volumetric_fog_enabled = false; + scene_state.ubo.fog_enabled = false; if (p_render_buffers.is_valid()) { RenderBufferDataHighEnd *render_buffers = (RenderBufferDataHighEnd *)render_buffers_get_data(p_render_buffers); @@ -1155,8 +1156,19 @@ void RasterizerSceneHighEndRD::_setup_environment(RID p_environment, RID p_rende if (render_buffers_has_volumetric_fog(p_render_buffers)) { scene_state.ubo.volumetric_fog_enabled = true; - scene_state.ubo.volumetric_fog_inv_length = 1.0 / render_buffers_get_volumetric_fog_end(p_render_buffers); - scene_state.ubo.volumetric_fog_detail_spread = 1.0 / render_buffers_get_volumetric_fog_detail_spread(p_render_buffers); //reverse lookup + float fog_end = render_buffers_get_volumetric_fog_end(p_render_buffers); + if (fog_end > 0.0) { + scene_state.ubo.volumetric_fog_inv_length = 1.0 / fog_end; + } else { + scene_state.ubo.volumetric_fog_inv_length = 1.0; + } + + float fog_detail_spread = render_buffers_get_volumetric_fog_detail_spread(p_render_buffers); //reverse lookup + if (fog_detail_spread > 0.0) { + scene_state.ubo.volumetric_fog_detail_spread = 1.0 / fog_detail_spread; + } else { + scene_state.ubo.volumetric_fog_detail_spread = 1.0; + } } } #if 0 @@ -1272,12 +1284,29 @@ void RasterizerSceneHighEndRD::_setup_environment(RID p_environment, RID p_rende scene_state.ubo.ssao_ao_affect = environment_get_ssao_ao_affect(p_environment); scene_state.ubo.ssao_light_affect = environment_get_ssao_light_affect(p_environment); - Color ao_color = environment_get_ao_color(p_environment); + Color ao_color = environment_get_ao_color(p_environment).to_linear(); scene_state.ubo.ao_color[0] = ao_color.r; scene_state.ubo.ao_color[1] = ao_color.g; scene_state.ubo.ao_color[2] = ao_color.b; scene_state.ubo.ao_color[3] = ao_color.a; + scene_state.ubo.fog_enabled = environment_is_fog_enabled(p_environment); + scene_state.ubo.fog_density = environment_get_fog_density(p_environment); + scene_state.ubo.fog_height = environment_get_fog_height(p_environment); + scene_state.ubo.fog_height_density = environment_get_fog_height_density(p_environment); + if (scene_state.ubo.fog_height_density >= 0.0001) { + scene_state.ubo.fog_height_density = 1.0 / scene_state.ubo.fog_height_density; + } + + Color fog_color = environment_get_fog_light_color(p_environment).to_linear(); + float fog_energy = environment_get_fog_light_energy(p_environment); + + scene_state.ubo.fog_light_color[0] = fog_color.r * fog_energy; + scene_state.ubo.fog_light_color[1] = fog_color.g * fog_energy; + scene_state.ubo.fog_light_color[2] = fog_color.b * fog_energy; + + scene_state.ubo.fog_sun_scatter = environment_get_fog_sun_scatter(p_environment); + } else { if (p_reflection_probe.is_valid() && storage->reflection_probe_is_interior(reflection_probe_instance_get_probe(p_reflection_probe))) { scene_state.ubo.use_ambient_light = false; @@ -1678,6 +1707,7 @@ void RasterizerSceneHighEndRD::_render_scene(RID p_render_buffer, const Transfor } RID radiance_uniform_set; bool draw_sky = false; + bool draw_sky_fog_only = false; Color clear_color; bool keep_color = false; @@ -1693,12 +1723,20 @@ void RasterizerSceneHighEndRD::_render_scene(RID p_render_buffer, const Transfor clear_color.r *= bg_energy; clear_color.g *= bg_energy; clear_color.b *= bg_energy; + if (render_buffers_has_volumetric_fog(p_render_buffer) || environment_is_fog_enabled(p_environment)) { + draw_sky_fog_only = true; + storage->material_set_param(sky_scene_state.fog_material, "clear_color", Variant(clear_color.to_linear())); + } } break; case RS::ENV_BG_COLOR: { clear_color = environment_get_bg_color(p_environment); clear_color.r *= bg_energy; clear_color.g *= bg_energy; clear_color.b *= bg_energy; + if (render_buffers_has_volumetric_fog(p_render_buffer) || environment_is_fog_enabled(p_environment)) { + draw_sky_fog_only = true; + storage->material_set_param(sky_scene_state.fog_material, "clear_color", Variant(clear_color.to_linear())); + } } break; case RS::ENV_BG_SKY: { draw_sky = true; @@ -1715,18 +1753,19 @@ void RasterizerSceneHighEndRD::_render_scene(RID p_render_buffer, const Transfor } } // setup sky if used for ambient, reflections, or background - if (draw_sky || environment_get_reflection_source(p_environment) == RS::ENV_REFLECTION_SOURCE_SKY || environment_get_ambient_source(p_environment) == RS::ENV_AMBIENT_SOURCE_SKY) { + if (draw_sky || draw_sky_fog_only || environment_get_reflection_source(p_environment) == RS::ENV_REFLECTION_SOURCE_SKY || environment_get_ambient_source(p_environment) == RS::ENV_AMBIENT_SOURCE_SKY) { + RENDER_TIMESTAMP("Setup Sky"); + CameraMatrix projection = p_cam_projection; + if (p_reflection_probe.is_valid()) { + CameraMatrix correction; + correction.set_depth_correction(true); + projection = correction * p_cam_projection; + } + + _setup_sky(p_environment, p_render_buffer, projection, p_cam_transform, screen_size); + RID sky = environment_get_sky(p_environment); if (sky.is_valid()) { - RENDER_TIMESTAMP("Setup Sky"); - CameraMatrix projection = p_cam_projection; - if (p_reflection_probe.is_valid()) { - CameraMatrix correction; - correction.set_depth_correction(true); - projection = correction * p_cam_projection; - } - - _setup_sky(p_environment, p_cam_transform.origin, screen_size); _update_sky(p_environment, projection, p_cam_transform); radiance_uniform_set = sky_get_radiance_uniform_set_rd(sky, default_shader_rd, RADIANCE_UNIFORM_SET); } else { @@ -1795,8 +1834,8 @@ void RasterizerSceneHighEndRD::_render_scene(RID p_render_buffer, const Transfor bool can_continue_depth = !scene_state.used_depth_texture && !using_ssr && !using_sss; { - bool will_continue_color = (can_continue_color || draw_sky || debug_giprobes || debug_sdfgi_probes); - bool will_continue_depth = (can_continue_depth || draw_sky || debug_giprobes || debug_sdfgi_probes); + bool will_continue_color = (can_continue_color || draw_sky || draw_sky_fog_only || debug_giprobes || debug_sdfgi_probes); + bool will_continue_depth = (can_continue_depth || draw_sky || draw_sky_fog_only || debug_giprobes || debug_sdfgi_probes); //regular forward for now Vector<Color> c; @@ -1823,8 +1862,8 @@ void RasterizerSceneHighEndRD::_render_scene(RID p_render_buffer, const Transfor if (debug_giprobes) { //debug giprobes - bool will_continue_color = (can_continue_color || draw_sky); - bool will_continue_depth = (can_continue_depth || draw_sky); + bool will_continue_color = (can_continue_color || draw_sky || draw_sky_fog_only); + bool will_continue_depth = (can_continue_depth || draw_sky || draw_sky_fog_only); CameraMatrix dc; dc.set_depth_correction(true); @@ -1838,8 +1877,8 @@ void RasterizerSceneHighEndRD::_render_scene(RID p_render_buffer, const Transfor if (debug_sdfgi_probes) { //debug giprobes - bool will_continue_color = (can_continue_color || draw_sky); - bool will_continue_depth = (can_continue_depth || draw_sky); + bool will_continue_color = (can_continue_color || draw_sky || draw_sky_fog_only); + bool will_continue_depth = (can_continue_depth || draw_sky || draw_sky_fog_only); CameraMatrix dc; dc.set_depth_correction(true); @@ -1849,7 +1888,7 @@ void RasterizerSceneHighEndRD::_render_scene(RID p_render_buffer, const Transfor RD::get_singleton()->draw_list_end(); } - if (draw_sky) { + if (draw_sky || draw_sky_fog_only) { RENDER_TIMESTAMP("Render Sky"); CameraMatrix projection = p_cam_projection; @@ -2514,8 +2553,13 @@ void RasterizerSceneHighEndRD::_update_render_buffers_uniform_set(RID p_render_b RD::Uniform u; u.binding = 10; u.type = RD::UNIFORM_TYPE_TEXTURE; - RID vfog = render_buffers_get_volumetric_fog_texture(p_render_buffers); - if (vfog.is_null()) { + RID vfog = RID(); + if (p_render_buffers.is_valid() && render_buffers_has_volumetric_fog(p_render_buffers)) { + vfog = render_buffers_get_volumetric_fog_texture(p_render_buffers); + if (vfog.is_null()) { + vfog = storage->texture_rd_get_default(RasterizerStorageRD::DEFAULT_RD_TEXTURE_3D_WHITE); + } + } else { vfog = storage->texture_rd_get_default(RasterizerStorageRD::DEFAULT_RD_TEXTURE_3D_WHITE); } u.ids.push_back(vfog); diff --git a/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.h b/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.h index 1fb59453b5..1aad9039ff 100644 --- a/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.h +++ b/servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.h @@ -364,6 +364,16 @@ class RasterizerSceneHighEndRD : public RasterizerSceneRD { float volumetric_fog_inv_length; float volumetric_fog_detail_spread; uint32_t volumetric_fog_pad; + + // Fog + + uint32_t fog_enabled; + float fog_density; + float fog_height; + float fog_height_density; + + float fog_light_color[3]; + float fog_sun_scatter; }; UBO ubo; diff --git a/servers/rendering/rasterizer_rd/rasterizer_scene_rd.cpp b/servers/rendering/rasterizer_rd/rasterizer_scene_rd.cpp index 040124d5ab..34818d2683 100644 --- a/servers/rendering/rasterizer_rd/rasterizer_scene_rd.cpp +++ b/servers/rendering/rasterizer_rd/rasterizer_scene_rd.cpp @@ -2066,22 +2066,33 @@ RID RasterizerSceneRD::sky_get_material(RID p_sky) const { void RasterizerSceneRD::_draw_sky(bool p_can_continue_color, bool p_can_continue_depth, RID p_fb, RID p_environment, const CameraMatrix &p_projection, const Transform &p_transform) { ERR_FAIL_COND(!is_environment(p_environment)); + SkyMaterialData *material = nullptr; + Sky *sky = sky_owner.getornull(environment_get_sky(p_environment)); - ERR_FAIL_COND(!sky); - RID sky_material = sky_get_material(environment_get_sky(p_environment)); + RID sky_material; - SkyMaterialData *material = nullptr; + RS::EnvironmentBG background = environment_get_background(p_environment); - if (sky_material.is_valid()) { - material = (SkyMaterialData *)storage->material_get_data(sky_material, RasterizerStorageRD::SHADER_TYPE_SKY); - if (!material || !material->shader_data->valid) { - material = nullptr; + if (!(background == RS::ENV_BG_CLEAR_COLOR || background == RS::ENV_BG_COLOR) || sky) { + ERR_FAIL_COND(!sky); + sky_material = sky_get_material(environment_get_sky(p_environment)); + + if (sky_material.is_valid()) { + material = (SkyMaterialData *)storage->material_get_data(sky_material, RasterizerStorageRD::SHADER_TYPE_SKY); + if (!material || !material->shader_data->valid) { + material = nullptr; + } + } + + if (!material) { + sky_material = sky_shader.default_material; + material = (SkyMaterialData *)storage->material_get_data(sky_material, RasterizerStorageRD::SHADER_TYPE_SKY); } } - if (!material) { - sky_material = sky_shader.default_material; + if (background == RS::ENV_BG_CLEAR_COLOR || background == RS::ENV_BG_COLOR) { + sky_material = sky_scene_state.fog_material; material = (SkyMaterialData *)storage->material_get_data(sky_material, RasterizerStorageRD::SHADER_TYPE_SKY); } @@ -2121,7 +2132,7 @@ void RasterizerSceneRD::_draw_sky(bool p_can_continue_color, bool p_can_continue clear_colors.push_back(Color(0.0, 0.0, 0.0)); RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(sky->quarter_res_framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); - storage->get_effects()->render_sky(draw_list, time, sky->quarter_res_framebuffer, sky_scene_state.sampler_uniform_set, sky_scene_state.light_uniform_set, pipeline, material->uniform_set, texture_uniform_set, camera, sky_transform, multiplier, p_transform.origin); + storage->get_effects()->render_sky(draw_list, time, sky->quarter_res_framebuffer, sky_scene_state.uniform_set, sky_scene_state.fog_uniform_set, pipeline, material->uniform_set, texture_uniform_set, camera, sky_transform, multiplier, p_transform.origin); RD::get_singleton()->draw_list_end(); } @@ -2134,149 +2145,191 @@ void RasterizerSceneRD::_draw_sky(bool p_can_continue_color, bool p_can_continue clear_colors.push_back(Color(0.0, 0.0, 0.0)); RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(sky->half_res_framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); - storage->get_effects()->render_sky(draw_list, time, sky->half_res_framebuffer, sky_scene_state.sampler_uniform_set, sky_scene_state.light_uniform_set, pipeline, material->uniform_set, texture_uniform_set, camera, sky_transform, multiplier, p_transform.origin); + storage->get_effects()->render_sky(draw_list, time, sky->half_res_framebuffer, sky_scene_state.uniform_set, sky_scene_state.fog_uniform_set, pipeline, material->uniform_set, texture_uniform_set, camera, sky_transform, multiplier, p_transform.origin); RD::get_singleton()->draw_list_end(); } RenderPipelineVertexFormatCacheRD *pipeline = &shader_data->pipelines[SKY_VERSION_BACKGROUND]; - RID texture_uniform_set = _get_sky_textures(sky, SKY_TEXTURE_SET_BACKGROUND); + RID texture_uniform_set; + if (sky) { + texture_uniform_set = _get_sky_textures(sky, SKY_TEXTURE_SET_BACKGROUND); + } else { + texture_uniform_set = sky_scene_state.fog_only_texture_uniform_set; + } RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(p_fb, RD::INITIAL_ACTION_CONTINUE, p_can_continue_color ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CONTINUE, p_can_continue_depth ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ); - storage->get_effects()->render_sky(draw_list, time, p_fb, sky_scene_state.sampler_uniform_set, sky_scene_state.light_uniform_set, pipeline, material->uniform_set, texture_uniform_set, camera, sky_transform, multiplier, p_transform.origin); + storage->get_effects()->render_sky(draw_list, time, p_fb, sky_scene_state.uniform_set, sky_scene_state.fog_uniform_set, pipeline, material->uniform_set, texture_uniform_set, camera, sky_transform, multiplier, p_transform.origin); RD::get_singleton()->draw_list_end(); } -void RasterizerSceneRD::_setup_sky(RID p_environment, const Vector3 &p_position, const Size2i p_screen_size) { +void RasterizerSceneRD::_setup_sky(RID p_environment, RID p_render_buffers, const CameraMatrix &p_projection, const Transform &p_transform, const Size2i p_screen_size) { ERR_FAIL_COND(!is_environment(p_environment)); + SkyMaterialData *material = nullptr; + Sky *sky = sky_owner.getornull(environment_get_sky(p_environment)); - ERR_FAIL_COND(!sky); - RID sky_material = sky_get_material(environment_get_sky(p_environment)); + RID sky_material; - SkyMaterialData *material = nullptr; + SkyShaderData *shader_data = nullptr; - if (sky_material.is_valid()) { - material = (SkyMaterialData *)storage->material_get_data(sky_material, RasterizerStorageRD::SHADER_TYPE_SKY); - if (!material || !material->shader_data->valid) { - material = nullptr; + RS::EnvironmentBG background = environment_get_background(p_environment); + + if (!(background == RS::ENV_BG_CLEAR_COLOR || background == RS::ENV_BG_COLOR) || sky) { + ERR_FAIL_COND(!sky); + sky_material = sky_get_material(environment_get_sky(p_environment)); + + if (sky_material.is_valid()) { + material = (SkyMaterialData *)storage->material_get_data(sky_material, RasterizerStorageRD::SHADER_TYPE_SKY); + if (!material || !material->shader_data->valid) { + material = nullptr; + } } - } - if (!material) { - sky_material = sky_shader.default_material; - material = (SkyMaterialData *)storage->material_get_data(sky_material, RasterizerStorageRD::SHADER_TYPE_SKY); - } + if (!material) { + sky_material = sky_shader.default_material; + material = (SkyMaterialData *)storage->material_get_data(sky_material, RasterizerStorageRD::SHADER_TYPE_SKY); + } - ERR_FAIL_COND(!material); + ERR_FAIL_COND(!material); - SkyShaderData *shader_data = material->shader_data; + shader_data = material->shader_data; - ERR_FAIL_COND(!shader_data); + ERR_FAIL_COND(!shader_data); + } - // Invalidate supbass buffers if screen size changes - if (sky->screen_size != p_screen_size) { - sky->screen_size = p_screen_size; - sky->screen_size.x = sky->screen_size.x < 4 ? 4 : sky->screen_size.x; - sky->screen_size.y = sky->screen_size.y < 4 ? 4 : sky->screen_size.y; - if (shader_data->uses_half_res) { - if (sky->half_res_pass.is_valid()) { - RD::get_singleton()->free(sky->half_res_pass); - sky->half_res_pass = RID(); + if (sky) { + // Invalidate supbass buffers if screen size changes + if (sky->screen_size != p_screen_size) { + sky->screen_size = p_screen_size; + sky->screen_size.x = sky->screen_size.x < 4 ? 4 : sky->screen_size.x; + sky->screen_size.y = sky->screen_size.y < 4 ? 4 : sky->screen_size.y; + if (shader_data->uses_half_res) { + if (sky->half_res_pass.is_valid()) { + RD::get_singleton()->free(sky->half_res_pass); + sky->half_res_pass = RID(); + } + _sky_invalidate(sky); } - _sky_invalidate(sky); - } - if (shader_data->uses_quarter_res) { - if (sky->quarter_res_pass.is_valid()) { - RD::get_singleton()->free(sky->quarter_res_pass); - sky->quarter_res_pass = RID(); + if (shader_data->uses_quarter_res) { + if (sky->quarter_res_pass.is_valid()) { + RD::get_singleton()->free(sky->quarter_res_pass); + sky->quarter_res_pass = RID(); + } + _sky_invalidate(sky); } + } + + // Create new subpass buffers if necessary + if ((shader_data->uses_half_res && sky->half_res_pass.is_null()) || + (shader_data->uses_quarter_res && sky->quarter_res_pass.is_null()) || + sky->radiance.is_null()) { _sky_invalidate(sky); + _update_dirty_skys(); } - } - // Create new subpass buffers if necessary - if ((shader_data->uses_half_res && sky->half_res_pass.is_null()) || - (shader_data->uses_quarter_res && sky->quarter_res_pass.is_null()) || - sky->radiance.is_null()) { - _sky_invalidate(sky); - _update_dirty_skys(); - } + if (shader_data->uses_time && time - sky->prev_time > 0.00001) { + sky->prev_time = time; + sky->reflection.dirty = true; + RenderingServerRaster::redraw_request(); + } - if (shader_data->uses_time && time - sky->prev_time > 0.00001) { - sky->prev_time = time; - sky->reflection.dirty = true; - RenderingServerRaster::redraw_request(); - } + if (material != sky->prev_material) { + sky->prev_material = material; + sky->reflection.dirty = true; + } - if (material != sky->prev_material) { - sky->prev_material = material; - sky->reflection.dirty = true; - } + if (material->uniform_set_updated) { + material->uniform_set_updated = false; + sky->reflection.dirty = true; + } - if (material->uniform_set_updated) { - material->uniform_set_updated = false; - sky->reflection.dirty = true; - } + if (!p_transform.origin.is_equal_approx(sky->prev_position) && shader_data->uses_position) { + sky->prev_position = p_transform.origin; + sky->reflection.dirty = true; + } - if (!p_position.is_equal_approx(sky->prev_position) && shader_data->uses_position) { - sky->prev_position = p_position; - sky->reflection.dirty = true; - } + if (shader_data->uses_light) { + // Check whether the directional_light_buffer changes + bool light_data_dirty = false; - if (shader_data->uses_light || sky_scene_state.light_uniform_set.is_null()) { - // Check whether the directional_light_buffer changes - bool light_data_dirty = false; - - if (sky_scene_state.directional_light_count != sky_scene_state.last_frame_directional_light_count) { - light_data_dirty = true; - for (uint32_t i = sky_scene_state.directional_light_count; i < sky_scene_state.max_directional_lights; i++) { - sky_scene_state.directional_lights[i].enabled = false; - } - } - if (!light_data_dirty) { - for (uint32_t i = 0; i < sky_scene_state.directional_light_count; i++) { - if (sky_scene_state.directional_lights[i].direction[0] != sky_scene_state.last_frame_directional_lights[i].direction[0] || - sky_scene_state.directional_lights[i].direction[1] != sky_scene_state.last_frame_directional_lights[i].direction[1] || - sky_scene_state.directional_lights[i].direction[2] != sky_scene_state.last_frame_directional_lights[i].direction[2] || - sky_scene_state.directional_lights[i].energy != sky_scene_state.last_frame_directional_lights[i].energy || - sky_scene_state.directional_lights[i].color[0] != sky_scene_state.last_frame_directional_lights[i].color[0] || - sky_scene_state.directional_lights[i].color[1] != sky_scene_state.last_frame_directional_lights[i].color[1] || - sky_scene_state.directional_lights[i].color[2] != sky_scene_state.last_frame_directional_lights[i].color[2] || - sky_scene_state.directional_lights[i].enabled != sky_scene_state.last_frame_directional_lights[i].enabled || - sky_scene_state.directional_lights[i].size != sky_scene_state.last_frame_directional_lights[i].size) { - light_data_dirty = true; - break; + if (sky_scene_state.ubo.directional_light_count != sky_scene_state.last_frame_directional_light_count) { + light_data_dirty = true; + for (uint32_t i = sky_scene_state.ubo.directional_light_count; i < sky_scene_state.max_directional_lights; i++) { + sky_scene_state.directional_lights[i].enabled = false; + } + } + if (!light_data_dirty) { + for (uint32_t i = 0; i < sky_scene_state.ubo.directional_light_count; i++) { + if (sky_scene_state.directional_lights[i].direction[0] != sky_scene_state.last_frame_directional_lights[i].direction[0] || + sky_scene_state.directional_lights[i].direction[1] != sky_scene_state.last_frame_directional_lights[i].direction[1] || + sky_scene_state.directional_lights[i].direction[2] != sky_scene_state.last_frame_directional_lights[i].direction[2] || + sky_scene_state.directional_lights[i].energy != sky_scene_state.last_frame_directional_lights[i].energy || + sky_scene_state.directional_lights[i].color[0] != sky_scene_state.last_frame_directional_lights[i].color[0] || + sky_scene_state.directional_lights[i].color[1] != sky_scene_state.last_frame_directional_lights[i].color[1] || + sky_scene_state.directional_lights[i].color[2] != sky_scene_state.last_frame_directional_lights[i].color[2] || + sky_scene_state.directional_lights[i].enabled != sky_scene_state.last_frame_directional_lights[i].enabled || + sky_scene_state.directional_lights[i].size != sky_scene_state.last_frame_directional_lights[i].size) { + light_data_dirty = true; + break; + } } } + + if (light_data_dirty) { + RD::get_singleton()->buffer_update(sky_scene_state.directional_light_buffer, 0, sizeof(SkyDirectionalLightData) * sky_scene_state.max_directional_lights, sky_scene_state.directional_lights, true); + + RasterizerSceneRD::SkyDirectionalLightData *temp = sky_scene_state.last_frame_directional_lights; + sky_scene_state.last_frame_directional_lights = sky_scene_state.directional_lights; + sky_scene_state.directional_lights = temp; + sky_scene_state.last_frame_directional_light_count = sky_scene_state.ubo.directional_light_count; + sky->reflection.dirty = true; + } } + } - if (light_data_dirty || sky_scene_state.light_uniform_set.is_null()) { - RD::get_singleton()->buffer_update(sky_scene_state.directional_light_buffer, 0, sizeof(SkyDirectionalLightData) * sky_scene_state.max_directional_lights, sky_scene_state.directional_lights, true); + //setup fog variables + sky_scene_state.ubo.volumetric_fog_enabled = false; + if (p_render_buffers.is_valid()) { + if (render_buffers_has_volumetric_fog(p_render_buffers)) { + sky_scene_state.ubo.volumetric_fog_enabled = true; - if (sky_scene_state.light_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(sky_scene_state.light_uniform_set)) { - RD::get_singleton()->free(sky_scene_state.light_uniform_set); + float fog_end = render_buffers_get_volumetric_fog_end(p_render_buffers); + if (fog_end > 0.0) { + sky_scene_state.ubo.volumetric_fog_inv_length = 1.0 / fog_end; + } else { + sky_scene_state.ubo.volumetric_fog_inv_length = 1.0; } - Vector<RD::Uniform> uniforms; - { - RD::Uniform u; - u.binding = 0; - u.type = RD::UNIFORM_TYPE_UNIFORM_BUFFER; - u.ids.push_back(sky_scene_state.directional_light_buffer); - uniforms.push_back(u); + float fog_detail_spread = render_buffers_get_volumetric_fog_detail_spread(p_render_buffers); //reverse lookup + if (fog_detail_spread > 0.0) { + sky_scene_state.ubo.volumetric_fog_detail_spread = 1.0 / fog_detail_spread; + } else { + sky_scene_state.ubo.volumetric_fog_detail_spread = 1.0; } + } - sky_scene_state.light_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, sky_shader.default_shader_rd, SKY_SET_LIGHTS); + RID fog_uniform_set = render_buffers_get_volumetric_fog_sky_uniform_set(p_render_buffers); - RasterizerSceneRD::SkyDirectionalLightData *temp = sky_scene_state.last_frame_directional_lights; - sky_scene_state.last_frame_directional_lights = sky_scene_state.directional_lights; - sky_scene_state.directional_lights = temp; - sky_scene_state.last_frame_directional_light_count = sky_scene_state.directional_light_count; - sky->reflection.dirty = true; + if (fog_uniform_set != RID()) { + sky_scene_state.fog_uniform_set = fog_uniform_set; + } else { + sky_scene_state.fog_uniform_set = sky_scene_state.default_fog_uniform_set; } } + + sky_scene_state.ubo.z_far = p_projection.get_z_far(); + sky_scene_state.ubo.fog_enabled = environment_is_fog_enabled(p_environment); + sky_scene_state.ubo.fog_density = environment_get_fog_density(p_environment); + Color fog_color = environment_get_fog_light_color(p_environment).to_linear(); + float fog_energy = environment_get_fog_light_energy(p_environment); + sky_scene_state.ubo.fog_light_color[0] = fog_color.r * fog_energy; + sky_scene_state.ubo.fog_light_color[1] = fog_color.g * fog_energy; + sky_scene_state.ubo.fog_light_color[2] = fog_color.b * fog_energy; + sky_scene_state.ubo.fog_sun_scatter = environment_get_fog_sun_scatter(p_environment); + + RD::get_singleton()->buffer_update(sky_scene_state.uniform_buffer, 0, sizeof(SkySceneState::UBO), &sky_scene_state.ubo, true); } void RasterizerSceneRD::_update_sky(RID p_environment, const CameraMatrix &p_projection, const Transform &p_transform) { @@ -2371,7 +2424,7 @@ void RasterizerSceneRD::_update_sky(RID p_environment, const CameraMatrix &p_pro RID texture_uniform_set = _get_sky_textures(sky, SKY_TEXTURE_SET_CUBEMAP_QUARTER_RES); cubemap_draw_list = RD::get_singleton()->draw_list_begin(sky->reflection.layers[0].mipmaps[2].framebuffers[i], RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_DISCARD); - storage->get_effects()->render_sky(cubemap_draw_list, time, sky->reflection.layers[0].mipmaps[2].framebuffers[i], sky_scene_state.sampler_uniform_set, sky_scene_state.light_uniform_set, pipeline, material->uniform_set, texture_uniform_set, cm, local_view.basis, multiplier, p_transform.origin); + storage->get_effects()->render_sky(cubemap_draw_list, time, sky->reflection.layers[0].mipmaps[2].framebuffers[i], sky_scene_state.uniform_set, sky_scene_state.fog_uniform_set, pipeline, material->uniform_set, texture_uniform_set, cm, local_view.basis, multiplier, p_transform.origin); RD::get_singleton()->draw_list_end(); } } @@ -2389,7 +2442,7 @@ void RasterizerSceneRD::_update_sky(RID p_environment, const CameraMatrix &p_pro RID texture_uniform_set = _get_sky_textures(sky, SKY_TEXTURE_SET_CUBEMAP_HALF_RES); cubemap_draw_list = RD::get_singleton()->draw_list_begin(sky->reflection.layers[0].mipmaps[1].framebuffers[i], RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_DISCARD); - storage->get_effects()->render_sky(cubemap_draw_list, time, sky->reflection.layers[0].mipmaps[1].framebuffers[i], sky_scene_state.sampler_uniform_set, sky_scene_state.light_uniform_set, pipeline, material->uniform_set, texture_uniform_set, cm, local_view.basis, multiplier, p_transform.origin); + storage->get_effects()->render_sky(cubemap_draw_list, time, sky->reflection.layers[0].mipmaps[1].framebuffers[i], sky_scene_state.uniform_set, sky_scene_state.fog_uniform_set, pipeline, material->uniform_set, texture_uniform_set, cm, local_view.basis, multiplier, p_transform.origin); RD::get_singleton()->draw_list_end(); } } @@ -2403,7 +2456,7 @@ void RasterizerSceneRD::_update_sky(RID p_environment, const CameraMatrix &p_pro RID texture_uniform_set = _get_sky_textures(sky, SKY_TEXTURE_SET_CUBEMAP); cubemap_draw_list = RD::get_singleton()->draw_list_begin(sky->reflection.layers[0].mipmaps[0].framebuffers[i], RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_DISCARD); - storage->get_effects()->render_sky(cubemap_draw_list, time, sky->reflection.layers[0].mipmaps[0].framebuffers[i], sky_scene_state.sampler_uniform_set, sky_scene_state.light_uniform_set, pipeline, material->uniform_set, texture_uniform_set, cm, local_view.basis, multiplier, p_transform.origin); + storage->get_effects()->render_sky(cubemap_draw_list, time, sky->reflection.layers[0].mipmaps[0].framebuffers[i], sky_scene_state.uniform_set, sky_scene_state.fog_uniform_set, pipeline, material->uniform_set, texture_uniform_set, cm, local_view.basis, multiplier, p_transform.origin); RD::get_singleton()->draw_list_end(); } @@ -2914,7 +2967,58 @@ void RasterizerSceneRD::environment_set_sdfgi(RID p_env, bool p_enable, RS::Envi env->sdfgi_y_scale = p_y_scale; } -void RasterizerSceneRD::environment_set_volumetric_fog(RID p_env, bool p_enable, float p_density, const Color &p_light, float p_light_energy, float p_lenght, float p_detail_spread, float p_gi_inject, RenderingServer::EnvVolumetricFogShadowFilter p_shadow_filter) { +void RasterizerSceneRD::environment_set_fog(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density) { + Environment *env = environment_owner.getornull(p_env); + ERR_FAIL_COND(!env); + + env->fog_enabled = p_enable; + env->fog_light_color = p_light_color; + env->fog_light_energy = p_light_energy; + env->fog_sun_scatter = p_sun_scatter; + env->fog_density = p_density; + env->fog_height = p_height; + env->fog_height_density = p_height_density; +} + +bool RasterizerSceneRD::environment_is_fog_enabled(RID p_env) const { + const Environment *env = environment_owner.getornull(p_env); + ERR_FAIL_COND_V(!env, false); + + return env->fog_enabled; +} +Color RasterizerSceneRD::environment_get_fog_light_color(RID p_env) const { + const Environment *env = environment_owner.getornull(p_env); + ERR_FAIL_COND_V(!env, Color()); + return env->fog_light_color; +} +float RasterizerSceneRD::environment_get_fog_light_energy(RID p_env) const { + const Environment *env = environment_owner.getornull(p_env); + ERR_FAIL_COND_V(!env, 0); + return env->fog_light_energy; +} +float RasterizerSceneRD::environment_get_fog_sun_scatter(RID p_env) const { + const Environment *env = environment_owner.getornull(p_env); + ERR_FAIL_COND_V(!env, 0); + return env->fog_sun_scatter; +} +float RasterizerSceneRD::environment_get_fog_density(RID p_env) const { + const Environment *env = environment_owner.getornull(p_env); + ERR_FAIL_COND_V(!env, 0); + return env->fog_density; +} +float RasterizerSceneRD::environment_get_fog_height(RID p_env) const { + const Environment *env = environment_owner.getornull(p_env); + ERR_FAIL_COND_V(!env, 0); + + return env->fog_height; +} +float RasterizerSceneRD::environment_get_fog_height_density(RID p_env) const { + const Environment *env = environment_owner.getornull(p_env); + ERR_FAIL_COND_V(!env, 0); + return env->fog_height_density; +} + +void RasterizerSceneRD::environment_set_volumetric_fog(RID p_env, bool p_enable, float p_density, const Color &p_light, float p_light_energy, float p_length, float p_detail_spread, float p_gi_inject, RenderingServer::EnvVolumetricFogShadowFilter p_shadow_filter) { Environment *env = environment_owner.getornull(p_env); ERR_FAIL_COND(!env); @@ -2922,7 +3026,7 @@ void RasterizerSceneRD::environment_set_volumetric_fog(RID p_env, bool p_enable, env->volumetric_fog_density = p_density; env->volumetric_fog_light = p_light; env->volumetric_fog_light_energy = p_light_energy; - env->volumetric_fog_length = p_lenght; + env->volumetric_fog_length = p_length; env->volumetric_fog_detail_spread = p_detail_spread; env->volumetric_fog_shadow_filter = p_shadow_filter; env->volumetric_fog_gi_inject = p_gi_inject; @@ -5556,6 +5660,17 @@ RID RasterizerSceneRD::render_buffers_get_volumetric_fog_texture(RID p_render_bu return rb->volumetric_fog->fog_map; } +RID RasterizerSceneRD::render_buffers_get_volumetric_fog_sky_uniform_set(RID p_render_buffers) { + const RenderBuffers *rb = render_buffers_owner.getornull(p_render_buffers); + ERR_FAIL_COND_V(!rb, RID()); + + if (!rb->volumetric_fog) { + return RID(); + } + + return rb->volumetric_fog->sky_uniform_set; +} + float RasterizerSceneRD::render_buffers_get_volumetric_fog_end(RID p_render_buffers) { const RenderBuffers *rb = render_buffers_owner.getornull(p_render_buffers); ERR_FAIL_COND_V(!rb || !rb->volumetric_fog, 0); @@ -5787,7 +5902,7 @@ void RasterizerSceneRD::_setup_lights(RID *p_light_cull_result, int p_light_cull uint32_t light_count = 0; r_directional_light_count = 0; r_positional_light_count = 0; - sky_scene_state.directional_light_count = 0; + sky_scene_state.ubo.directional_light_count = 0; for (int i = 0; i < p_light_cull_count; i++) { RID li = p_light_cull_result[i]; @@ -5959,7 +6074,7 @@ void RasterizerSceneRD::_setup_lights(RID *p_light_cull_result, int p_light_cull sky_light_data.enabled = true; sky_light_data.size = angular_diameter; - sky_scene_state.directional_light_count++; + sky_scene_state.ubo.directional_light_count++; } r_directional_light_count++; @@ -6266,7 +6381,22 @@ void RasterizerSceneRD::_volumetric_fog_erase(RenderBuffers *rb) { RD::get_singleton()->free(rb->volumetric_fog->light_density_map); RD::get_singleton()->free(rb->volumetric_fog->fog_map); + + if (rb->volumetric_fog->uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(rb->volumetric_fog->uniform_set)) { + RD::get_singleton()->free(rb->volumetric_fog->uniform_set); + } + if (rb->volumetric_fog->uniform_set2.is_valid() && RD::get_singleton()->uniform_set_is_valid(rb->volumetric_fog->uniform_set2)) { + RD::get_singleton()->free(rb->volumetric_fog->uniform_set2); + } + if (rb->volumetric_fog->sdfgi_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(rb->volumetric_fog->sdfgi_uniform_set)) { + RD::get_singleton()->free(rb->volumetric_fog->sdfgi_uniform_set); + } + if (rb->volumetric_fog->sky_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(rb->volumetric_fog->sky_uniform_set)) { + RD::get_singleton()->free(rb->volumetric_fog->sky_uniform_set); + } + memdelete(rb->volumetric_fog); + rb->volumetric_fog = nullptr; } @@ -6356,6 +6486,17 @@ void RasterizerSceneRD::_update_volumetric_fog(RID p_render_buffers, RID p_envir rb->volumetric_fog->fog_map = RD::get_singleton()->texture_create(tf, RD::TextureView()); _render_buffers_uniform_set_changed(p_render_buffers); + + Vector<RD::Uniform> uniforms; + { + RD::Uniform u; + u.binding = 0; + u.type = RD::UNIFORM_TYPE_TEXTURE; + u.ids.push_back(rb->volumetric_fog->fog_map); + uniforms.push_back(u); + } + + rb->volumetric_fog->sky_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, sky_shader.default_shader_rd, SKY_SET_FOG); } //update directional shadow @@ -7856,6 +7997,7 @@ RasterizerSceneRD::RasterizerSceneRD(RasterizerStorageRD *p_storage) { actions.custom_samplers["RADIANCE"] = "material_samplers[3]"; actions.usage_defines["HALF_RES_COLOR"] = "\n#define USES_HALF_RES_COLOR\n"; actions.usage_defines["QUARTER_RES_COLOR"] = "\n#define USES_QUARTER_RES_COLOR\n"; + actions.render_mode_defines["disable_fog"] = "#define DISABLE_FOG\n"; actions.sampler_array_name = "material_samplers"; actions.base_texture_binding_index = 1; @@ -7880,6 +8022,8 @@ RasterizerSceneRD::RasterizerSceneRD(RasterizerStorageRD *p_storage) { SkyMaterialData *md = (SkyMaterialData *)storage->material_get_data(sky_shader.default_material, RasterizerStorageRD::SHADER_TYPE_SKY); sky_shader.default_shader_rd = sky_shader.shader.version_get_shader(md->shader_data->version, SKY_VERSION_BACKGROUND); + sky_scene_state.uniform_buffer = RD::get_singleton()->uniform_buffer_create(sizeof(SkySceneState::UBO)); + Vector<RD::Uniform> uniforms; { @@ -7911,7 +8055,70 @@ RasterizerSceneRD::RasterizerSceneRD(RasterizerStorageRD *p_storage) { uniforms.push_back(u); } - sky_scene_state.sampler_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, sky_shader.default_shader_rd, SKY_SET_SAMPLERS); + { + RD::Uniform u; + u.binding = 2; + u.type = RD::UNIFORM_TYPE_UNIFORM_BUFFER; + u.ids.push_back(sky_scene_state.uniform_buffer); + uniforms.push_back(u); + } + + { + RD::Uniform u; + u.binding = 3; + u.type = RD::UNIFORM_TYPE_UNIFORM_BUFFER; + u.ids.push_back(sky_scene_state.directional_light_buffer); + uniforms.push_back(u); + } + + sky_scene_state.uniform_set = RD::get_singleton()->uniform_set_create(uniforms, sky_shader.default_shader_rd, SKY_SET_UNIFORMS); + } + + { + Vector<RD::Uniform> uniforms; + { + RD::Uniform u; + u.binding = 0; + u.type = RD::UNIFORM_TYPE_TEXTURE; + RID vfog = storage->texture_rd_get_default(RasterizerStorageRD::DEFAULT_RD_TEXTURE_3D_WHITE); + u.ids.push_back(vfog); + uniforms.push_back(u); + } + + sky_scene_state.default_fog_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, sky_shader.default_shader_rd, SKY_SET_FOG); + } + + { + // Need defaults for using fog with clear color + sky_scene_state.fog_shader = storage->shader_create(); + storage->shader_set_code(sky_scene_state.fog_shader, "shader_type sky; uniform vec4 clear_color; void fragment() { COLOR = clear_color.rgb; } \n"); + sky_scene_state.fog_material = storage->material_create(); + storage->material_set_shader(sky_scene_state.fog_material, sky_scene_state.fog_shader); + + Vector<RD::Uniform> uniforms; + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 0; + u.ids.push_back(storage->texture_rd_get_default(RasterizerStorageRD::DEFAULT_RD_TEXTURE_CUBEMAP_BLACK)); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 1; + u.ids.push_back(storage->texture_rd_get_default(RasterizerStorageRD::DEFAULT_RD_TEXTURE_WHITE)); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 2; + u.ids.push_back(storage->texture_rd_get_default(RasterizerStorageRD::DEFAULT_RD_TEXTURE_WHITE)); + uniforms.push_back(u); + } + + sky_scene_state.fog_only_texture_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, sky_shader.default_shader_rd, SKY_SET_TEXTURES); } { @@ -8128,11 +8335,8 @@ RasterizerSceneRD::~RasterizerSceneRD() { RD::get_singleton()->free(E->get().cubemap); } - if (sky_scene_state.sampler_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(sky_scene_state.sampler_uniform_set)) { - RD::get_singleton()->free(sky_scene_state.sampler_uniform_set); - } - if (sky_scene_state.light_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(sky_scene_state.light_uniform_set)) { - RD::get_singleton()->free(sky_scene_state.light_uniform_set); + if (sky_scene_state.uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(sky_scene_state.uniform_set)) { + RD::get_singleton()->free(sky_scene_state.uniform_set); } RD::get_singleton()->free(default_giprobe_buffer); @@ -8148,14 +8352,19 @@ RasterizerSceneRD::~RasterizerSceneRD() { sdfgi_shader.integrate.version_free(sdfgi_shader.integrate_shader); sdfgi_shader.preprocess.version_free(sdfgi_shader.preprocess_shader); + volumetric_fog.shader.version_free(volumetric_fog.shader_version); + memdelete_arr(gi_probe_lights); SkyMaterialData *md = (SkyMaterialData *)storage->material_get_data(sky_shader.default_material, RasterizerStorageRD::SHADER_TYPE_SKY); sky_shader.shader.version_free(md->shader_data->version); RD::get_singleton()->free(sky_scene_state.directional_light_buffer); + RD::get_singleton()->free(sky_scene_state.uniform_buffer); memdelete_arr(sky_scene_state.directional_lights); memdelete_arr(sky_scene_state.last_frame_directional_lights); storage->free(sky_shader.default_shader); storage->free(sky_shader.default_material); + storage->free(sky_scene_state.fog_shader); + storage->free(sky_scene_state.fog_material); memdelete_arr(directional_penumbra_shadow_kernel); memdelete_arr(directional_soft_shadow_kernel); memdelete_arr(penumbra_shadow_kernel); diff --git a/servers/rendering/rasterizer_rd/rasterizer_scene_rd.h b/servers/rendering/rasterizer_rd/rasterizer_scene_rd.h index 7c58a410d0..f504240f50 100644 --- a/servers/rendering/rasterizer_rd/rasterizer_scene_rd.h +++ b/servers/rendering/rasterizer_rd/rasterizer_scene_rd.h @@ -63,14 +63,37 @@ protected: }; struct SkySceneState { + struct UBO { + uint32_t volumetric_fog_enabled; + float volumetric_fog_inv_length; + float volumetric_fog_detail_spread; + uint32_t volumetric_fog_pad; + + float fog_light_color[3]; + float fog_sun_scatter; + + uint32_t fog_enabled; + float fog_density; + + float z_far; + uint32_t directional_light_count; + }; + + UBO ubo; + SkyDirectionalLightData *directional_lights; SkyDirectionalLightData *last_frame_directional_lights; uint32_t max_directional_lights; - uint32_t directional_light_count; uint32_t last_frame_directional_light_count; RID directional_light_buffer; - RID sampler_uniform_set; - RID light_uniform_set; + RID uniform_set; + RID uniform_buffer; + RID fog_uniform_set; + RID default_fog_uniform_set; + + RID fog_shader; + RID fog_material; + RID fog_only_texture_uniform_set; } sky_scene_state; struct RenderBufferData { @@ -105,7 +128,7 @@ protected: void _process_ssr(RID p_render_buffers, RID p_dest_framebuffer, RID p_normal_buffer, RID p_specular_buffer, RID p_metallic, const Color &p_metallic_mask, RID p_environment, const CameraMatrix &p_projection, bool p_use_additive); void _process_sss(RID p_render_buffers, const CameraMatrix &p_camera); - void _setup_sky(RID p_environment, const Vector3 &p_position, const Size2i p_screen_size); + void _setup_sky(RID p_environment, RID p_render_buffers, const CameraMatrix &p_projection, const Transform &p_transform, const Size2i p_screen_size); void _update_sky(RID p_environment, const CameraMatrix &p_projection, const Transform &p_transform); void _draw_sky(bool p_can_continue_color, bool p_can_continue_depth, RID p_fb, RID p_environment, const CameraMatrix &p_projection, const Transform &p_transform); void _process_gi(RID p_render_buffers, RID p_normal_roughness_buffer, RID p_ambient_buffer, RID p_reflection_buffer, RID p_gi_probe_buffer, RID p_environment, const CameraMatrix &p_projection, const Transform &p_transform, RID *p_gi_probe_cull_result, int p_gi_probe_cull_count); @@ -244,10 +267,10 @@ private: }; enum SkySet { - SKY_SET_SAMPLERS, + SKY_SET_UNIFORMS, SKY_SET_MATERIAL, SKY_SET_TEXTURES, - SKY_SET_LIGHTS, + SKY_SET_FOG, SKY_SET_MAX }; @@ -674,6 +697,15 @@ private: float auto_exp_scale = 0.5; uint64_t auto_exposure_version = 0; + // Fog + bool fog_enabled = false; + Color fog_light_color = Color(0.5, 0.6, 0.7); + float fog_light_energy = 1.0; + float fog_sun_scatter = 0.0; + float fog_density = 0.001; + float fog_height = 0.0; + float fog_height_density = 0.0; //can be negative to invert effect + /// Volumetric Fog /// bool volumetric_fog_enabled = false; @@ -1346,6 +1378,7 @@ private: RID uniform_set; RID uniform_set2; RID sdfgi_uniform_set; + RID sky_uniform_set; int last_shadow_filter = -1; }; @@ -1498,7 +1531,15 @@ public: void environment_set_glow(RID p_env, bool p_enable, int p_level_flags, float p_intensity, float p_strength, float p_mix, float p_bloom_threshold, RS::EnvironmentGlowBlendMode p_blend_mode, float p_hdr_bleed_threshold, float p_hdr_bleed_scale, float p_hdr_luminance_cap); void environment_glow_set_use_bicubic_upscale(bool p_enable); - void environment_set_fog(RID p_env, bool p_enable, float p_begin, float p_end, RID p_gradient_texture) {} + void environment_set_fog(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density); + bool environment_is_fog_enabled(RID p_env) const; + Color environment_get_fog_light_color(RID p_env) const; + float environment_get_fog_light_energy(RID p_env) const; + float environment_get_fog_sun_scatter(RID p_env) const; + float environment_get_fog_density(RID p_env) const; + float environment_get_fog_height(RID p_env) const; + float environment_get_fog_height_density(RID p_env) const; + void environment_set_volumetric_fog(RID p_env, bool p_enable, float p_density, const Color &p_light, float p_light_energy, float p_lenght, float p_detail_spread, float p_gi_inject, RS::EnvVolumetricFogShadowFilter p_shadow_filter); virtual void environment_set_volumetric_fog_volume_size(int p_size, int p_depth); @@ -1525,10 +1566,6 @@ public: void environment_set_tonemap(RID p_env, RS::EnvironmentToneMapper p_tone_mapper, float p_exposure, float p_white, bool p_auto_exposure, float p_min_luminance, float p_max_luminance, float p_auto_exp_speed, float p_auto_exp_scale); void environment_set_adjustment(RID p_env, bool p_enable, float p_brightness, float p_contrast, float p_saturation, RID p_ramp) {} - void environment_set_fog(RID p_env, bool p_enable, const Color &p_color, const Color &p_sun_color, float p_sun_amount) {} - void environment_set_fog_depth(RID p_env, bool p_enable, float p_depth_begin, float p_depth_end, float p_depth_curve, bool p_transmit, float p_transmit_curve) {} - void environment_set_fog_height(RID p_env, bool p_enable, float p_min_height, float p_max_height, float p_height_curve) {} - virtual Ref<Image> environment_bake_panorama(RID p_env, bool p_bake_irradiance, const Size2i &p_size); virtual RID camera_effects_create(); @@ -1824,6 +1861,7 @@ public: bool render_buffers_has_volumetric_fog(RID p_render_buffers) const; RID render_buffers_get_volumetric_fog_texture(RID p_render_buffers); + RID render_buffers_get_volumetric_fog_sky_uniform_set(RID p_render_buffers); float render_buffers_get_volumetric_fog_end(RID p_render_buffers); float render_buffers_get_volumetric_fog_detail_spread(RID p_render_buffers); diff --git a/servers/rendering/rasterizer_rd/shaders/scene_high_end.glsl b/servers/rendering/rasterizer_rd/shaders/scene_high_end.glsl index 837d7a3a5d..5993e68317 100644 --- a/servers/rendering/rasterizer_rd/shaders/scene_high_end.glsl +++ b/servers/rendering/rasterizer_rd/shaders/scene_high_end.glsl @@ -1614,6 +1614,36 @@ vec4 volumetric_fog_process(vec2 screen_uv, float z) { return texture(sampler3D(volumetric_fog_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), fog_pos); } +vec4 fog_process(vec3 vertex) { + vec3 fog_color = scene_data.fog_light_color; + + if (scene_data.fog_sun_scatter > 0.001) { + vec4 sun_scatter = vec4(0.0); + float sun_total = 0.0; + vec3 view = normalize(vertex); + + for (uint i = 0; i < scene_data.directional_light_count; i++) { + vec3 light_color = directional_lights.data[i].color * directional_lights.data[i].energy; + float light_amount = pow(max(dot(view, directional_lights.data[i].direction), 0.0), 8.0); + fog_color += light_color * light_amount * scene_data.fog_sun_scatter; + } + } + + float fog_amount = 1.0 - exp(vertex.z * scene_data.fog_density); + + if (abs(scene_data.fog_height_density) > 0.001) { + float y = (scene_data.camera_matrix * vec4(vertex, 1.0)).y; + + float y_dist = scene_data.fog_height - y; + + float vfog_amount = clamp(exp(y_dist * scene_data.fog_height_density), 0.0, 1.0); + + fog_amount = max(vfog_amount, fog_amount); + } + + return vec4(fog_color, fog_amount); +} + #endif void main() { @@ -2696,7 +2726,12 @@ FRAGMENT_SHADER_CODE vec4 fog = volumetric_fog_process(screen_uv, -vertex.z); diffuse_buffer.rgb = mix(diffuse_buffer.rgb, fog.rgb, fog.a); specular_buffer.rgb = mix(specular_buffer.rgb, vec3(0.0), fog.a); - ; + } + + if (scene_data.fog_enabled) { + vec4 fog = fog_process(vertex); + diffuse_buffer.rgb = mix(diffuse_buffer.rgb, fog.rgb, fog.a); + specular_buffer.rgb = mix(specular_buffer.rgb, vec3(0.0), fog.a); } #else //MODE_MULTIPLE_RENDER_TARGETS @@ -2713,6 +2748,11 @@ FRAGMENT_SHADER_CODE frag_color.rgb = mix(frag_color.rgb, fog.rgb, fog.a); } + if (scene_data.fog_enabled) { + vec4 fog = fog_process(vertex); + frag_color.rgb = mix(frag_color.rgb, fog.rgb, fog.a); + } + #endif //MODE_MULTIPLE_RENDER_TARGETS #endif //MODE_RENDER_DEPTH diff --git a/servers/rendering/rasterizer_rd/shaders/scene_high_end_inc.glsl b/servers/rendering/rasterizer_rd/shaders/scene_high_end_inc.glsl index 756cab7350..66bfefbe89 100644 --- a/servers/rendering/rasterizer_rd/shaders/scene_high_end_inc.glsl +++ b/servers/rendering/rasterizer_rd/shaders/scene_high_end_inc.glsl @@ -100,40 +100,14 @@ layout(set = 0, binding = 3, std140) uniform SceneData { float volumetric_fog_inv_length; float volumetric_fog_detail_spread; uint volumetric_fog_pad; -#if 0 - vec4 ambient_light_color; - vec4 bg_color; - vec4 fog_color_enabled; - vec4 fog_sun_color_amount; - - float ambient_energy; - float bg_energy; -#endif - -#if 0 - vec2 shadow_atlas_pixel_size; - vec2 directional_shadow_pixel_size; - - float z_far; - - float subsurface_scatter_width; - float ambient_occlusion_affect_light; - float ambient_occlusion_affect_ao_channel; - float opaque_prepass_threshold; - - bool fog_depth_enabled; - float fog_depth_begin; - float fog_depth_end; + bool fog_enabled; float fog_density; - float fog_depth_curve; - bool fog_transmit_enabled; - float fog_transmit_curve; - bool fog_height_enabled; - float fog_height_min; - float fog_height_max; - float fog_height_curve; -#endif + float fog_height; + float fog_height_density; + + vec3 fog_light_color; + float fog_sun_scatter; } scene_data; diff --git a/servers/rendering/rasterizer_rd/shaders/sky.glsl b/servers/rendering/rasterizer_rd/shaders/sky.glsl index 9c59be6841..7b6de6a555 100644 --- a/servers/rendering/rasterizer_rd/shaders/sky.glsl +++ b/servers/rendering/rasterizer_rd/shaders/sky.glsl @@ -58,6 +58,35 @@ layout(set = 0, binding = 1, std430) restrict readonly buffer GlobalVariableData } global_variables; +layout(set = 0, binding = 2, std140) uniform SceneData { + bool volumetric_fog_enabled; + float volumetric_fog_inv_length; + float volumetric_fog_detail_spread; + uint volumetric_fog_pad; + + vec3 fog_light_color; + float fog_sun_scatter; + + bool fog_enabled; + float fog_density; + + float z_far; + uint directional_light_count; +} +scene_data; + +struct DirectionalLightData { + vec4 direction_energy; + vec4 color_size; + bool enabled; +}; + +layout(set = 0, binding = 3, std140) uniform DirectionalLights { + DirectionalLightData data[MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS]; +} + +directional_lights; + #ifdef USE_MATERIAL_UNIFORMS layout(set = 1, binding = 0, std140) uniform MaterialUniforms{ /* clang-format off */ @@ -77,6 +106,8 @@ layout(set = 2, binding = 1) uniform texture2D half_res; layout(set = 2, binding = 2) uniform texture2D quarter_res; #endif +layout(set = 3, binding = 0) uniform texture3D volumetric_fog_texture; + #ifdef USE_CUBEMAP_PASS #define AT_CUBEMAP_PASS true #else @@ -95,18 +126,6 @@ layout(set = 2, binding = 2) uniform texture2D quarter_res; #define AT_QUARTER_RES_PASS false #endif -struct DirectionalLightData { - vec4 direction_energy; - vec4 color_size; - bool enabled; -}; - -layout(set = 3, binding = 0, std140) uniform DirectionalLights { - DirectionalLightData data[MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS]; -} - -directional_lights; - /* clang-format off */ FRAGMENT_SHADER_GLOBALS @@ -115,6 +134,30 @@ FRAGMENT_SHADER_GLOBALS layout(location = 0) out vec4 frag_color; +vec4 volumetric_fog_process(vec2 screen_uv) { + vec3 fog_pos = vec3(screen_uv, 1.0); + + return texture(sampler3D(volumetric_fog_texture, material_samplers[SAMPLER_LINEAR_CLAMP]), fog_pos); +} + +vec4 fog_process(vec3 view) { + vec3 fog_color = scene_data.fog_light_color; + + if (scene_data.fog_sun_scatter > 0.001) { + vec4 sun_scatter = vec4(0.0); + float sun_total = 0.0; + for (uint i = 0; i < scene_data.directional_light_count; i++) { + vec3 light_color = directional_lights.data[i].color_size.xyz * directional_lights.data[i].direction_energy.w; + float light_amount = pow(max(dot(view, directional_lights.data[i].direction_energy.xyz), 0.0), 8.0); + fog_color += light_color * light_amount * scene_data.fog_sun_scatter; + } + } + + float fog_amount = clamp(1.0 - exp(-scene_data.z_far * scene_data.fog_density), 0.0, 1.0); + + return vec4(fog_color, fog_amount); +} + void main() { vec3 cube_normal; cube_normal.z = -1.0; @@ -178,6 +221,20 @@ FRAGMENT_SHADER_CODE frag_color.rgb = color * params.position_multiplier.w; frag_color.a = alpha; +#if !defined(DISABLE_FOG) && !defined(USE_CUBEMAP_PASS) + + if (scene_data.volumetric_fog_enabled) { + vec4 fog = volumetric_fog_process(uv); + frag_color.rgb = mix(frag_color.rgb, fog.rgb, fog.a); + } + + if (scene_data.fog_enabled) { + vec4 fog = fog_process(cube_normal); + frag_color.rgb = mix(frag_color.rgb, fog.rgb, fog.a); + } + +#endif // DISABLE_FOG + // Blending is disabled for Sky, so alpha doesn't blend // alpha is used for subsurface scattering so make sure it doesn't get applied to Sky if (!AT_CUBEMAP_PASS && !AT_HALF_RES_PASS && !AT_QUARTER_RES_PASS) { diff --git a/servers/rendering/rendering_server_raster.h b/servers/rendering/rendering_server_raster.h index 75d3165c94..b4eac8cd76 100644 --- a/servers/rendering/rendering_server_raster.h +++ b/servers/rendering/rendering_server_raster.h @@ -562,9 +562,7 @@ public: BIND6(environment_set_adjustment, RID, bool, float, float, float, RID) - BIND5(environment_set_fog, RID, bool, const Color &, const Color &, float) - BIND7(environment_set_fog_depth, RID, bool, float, float, float, bool, float) - BIND5(environment_set_fog_height, RID, bool, float, float, float) + BIND8(environment_set_fog, RID, bool, const Color &, float, float, float, float, float) BIND9(environment_set_volumetric_fog, RID, bool, float, const Color &, float, float, float, float, EnvVolumetricFogShadowFilter) BIND2(environment_set_volumetric_fog_volume_size, int, int) diff --git a/servers/rendering/rendering_server_wrap_mt.h b/servers/rendering/rendering_server_wrap_mt.h index d7d0d968a2..fb5c161c8a 100644 --- a/servers/rendering/rendering_server_wrap_mt.h +++ b/servers/rendering/rendering_server_wrap_mt.h @@ -479,9 +479,8 @@ public: FUNC6(environment_set_adjustment, RID, bool, float, float, float, RID) - FUNC5(environment_set_fog, RID, bool, const Color &, const Color &, float) - FUNC7(environment_set_fog_depth, RID, bool, float, float, float, bool, float) - FUNC5(environment_set_fog_height, RID, bool, float, float, float) + FUNC8(environment_set_fog, RID, bool, const Color &, float, float, float, float, float) + FUNC9(environment_set_volumetric_fog, RID, bool, float, const Color &, float, float, float, float, EnvVolumetricFogShadowFilter) FUNC2(environment_set_volumetric_fog_volume_size, int, int) diff --git a/servers/rendering/shader_types.cpp b/servers/rendering/shader_types.cpp index 06cb6171a5..76aeb5989c 100644 --- a/servers/rendering/shader_types.cpp +++ b/servers/rendering/shader_types.cpp @@ -328,6 +328,7 @@ ShaderTypes::ShaderTypes() { shader_modes[RS::SHADER_SKY].modes.push_back("use_half_res_pass"); shader_modes[RS::SHADER_SKY].modes.push_back("use_quarter_res_pass"); + shader_modes[RS::SHADER_SKY].modes.push_back("disable_fog"); shader_types.insert("spatial"); shader_types.insert("canvas_item"); diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index 714fe71d08..3b034da3aa 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -1747,11 +1747,7 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("environment_set_adjustment", "env", "enable", "brightness", "contrast", "saturation", "ramp"), &RenderingServer::environment_set_adjustment); ClassDB::bind_method(D_METHOD("environment_set_ssr", "env", "enable", "max_steps", "fade_in", "fade_out", "depth_tolerance"), &RenderingServer::environment_set_ssr); ClassDB::bind_method(D_METHOD("environment_set_ssao", "env", "enable", "radius", "intensity", "bias", "light_affect", "ao_channel_affect", "blur", "bilateral_sharpness"), &RenderingServer::environment_set_ssao); - ClassDB::bind_method(D_METHOD("environment_set_fog", "env", "enable", "color", "sun_color", "sun_amount"), &RenderingServer::environment_set_fog); - - ClassDB::bind_method(D_METHOD("environment_set_fog_depth", "env", "enable", "depth_begin", "depth_end", "depth_curve", "transmit", "transmit_curve"), &RenderingServer::environment_set_fog_depth); - - ClassDB::bind_method(D_METHOD("environment_set_fog_height", "env", "enable", "min_height", "max_height", "height_curve"), &RenderingServer::environment_set_fog_height); + ClassDB::bind_method(D_METHOD("environment_set_fog", "env", "enable", "light_color", "light_energy", "sun_scatter", "density", "height", "height_density"), &RenderingServer::environment_set_fog); ClassDB::bind_method(D_METHOD("scenario_create"), &RenderingServer::scenario_create); ClassDB::bind_method(D_METHOD("scenario_set_debug", "scenario", "debug_mode"), &RenderingServer::scenario_set_debug); diff --git a/servers/rendering_server.h b/servers/rendering_server.h index 57af53f0c0..a13fd4954b 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -862,9 +862,7 @@ public: virtual void environment_set_sdfgi_frames_to_converge(EnvironmentSDFGIFramesToConverge p_frames) = 0; - virtual void environment_set_fog(RID p_env, bool p_enable, const Color &p_color, const Color &p_sun_color, float p_sun_amount) = 0; - virtual void environment_set_fog_depth(RID p_env, bool p_enable, float p_depth_begin, float p_depth_end, float p_depth_curve, bool p_transmit, float p_transmit_curve) = 0; - virtual void environment_set_fog_height(RID p_env, bool p_enable, float p_min_height, float p_max_height, float p_height_curve) = 0; + virtual void environment_set_fog(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density) = 0; enum EnvVolumetricFogShadowFilter { ENV_VOLUMETRIC_FOG_SHADOW_FILTER_DISABLED, diff --git a/tests/test_class_db.cpp b/tests/test_class_db.cpp deleted file mode 100644 index 3171091402..0000000000 --- a/tests/test_class_db.cpp +++ /dev/null @@ -1,882 +0,0 @@ -/*************************************************************************/ -/* test_class_db.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "test_class_db.h" - -#include "core/global_constants.h" -#include "core/ordered_hash_map.h" -#include "core/os/os.h" -#include "core/string_name.h" -#include "core/ustring.h" -#include "core/variant.h" - -namespace TestClassDB { - -enum class [[nodiscard]] TestResult{ - FAILED, - PASS -}; - -#define TEST_FAIL_COND(m_cond, m_msg) \ - if (unlikely(m_cond)) { \ - ERR_PRINT(m_msg); \ - return TestResult::FAILED; \ - } else \ - ((void)0) - -#define TEST_COND(m_cond, m_msg) \ - if (unlikely(m_cond)) { \ - ERR_PRINT(m_msg); \ - __test_result__ = TestResult::FAILED; \ - } else \ - ((void)0) - -#define TEST_FAIL_CHECK(m_test_expr) \ - if (unlikely((m_test_expr) == TestResult::FAILED)) { \ - return TestResult::FAILED; \ - } else \ - ((void)0) - -#define TEST_CHECK(m_test_expr) \ - if (unlikely((m_test_expr) == TestResult::FAILED)) { \ - __test_result__ = TestResult::FAILED; \ - } else \ - ((void)0) - -#define TEST_START() \ - TestResult __test_result__ = TestResult::PASS; \ - ((void)0) - -#define TEST_END() return __test_result__; - -struct TypeReference { - StringName name; - bool is_enum = false; -}; - -struct ConstantData { - String name; - int value = 0; -}; - -struct EnumData { - StringName name; - List<ConstantData> constants; - - _FORCE_INLINE_ bool operator==(const EnumData &p_enum) const { - return p_enum.name == name; - } -}; - -struct PropertyData { - StringName name; - int index = 0; - - StringName getter; - StringName setter; -}; - -struct ArgumentData { - TypeReference type; - String name; - bool has_defval = false; - Variant defval; -}; - -struct MethodData { - StringName name; - TypeReference return_type; - List<ArgumentData> arguments; - bool is_virtual = false; - bool is_vararg = false; -}; - -struct SignalData { - StringName name; - List<ArgumentData> arguments; -}; - -struct ExposedClass { - StringName name; - StringName base; - - bool is_singleton = false; - bool is_instantiable = false; - bool is_reference = false; - - ClassDB::APIType api_type; - - List<ConstantData> constants; - List<EnumData> enums; - List<PropertyData> properties; - List<MethodData> methods; - List<SignalData> signals_; - - const PropertyData *find_property_by_name(const StringName &p_name) const { - for (const List<PropertyData>::Element *E = properties.front(); E; E = E->next()) { - if (E->get().name == p_name) { - return &E->get(); - } - } - - return nullptr; - } - - const MethodData *find_method_by_name(const StringName &p_name) const { - for (const List<MethodData>::Element *E = methods.front(); E; E = E->next()) { - if (E->get().name == p_name) { - return &E->get(); - } - } - - return nullptr; - } -}; - -struct NamesCache { - StringName variant_type = StaticCString::create("Variant"); - StringName object_class = StaticCString::create("Object"); - StringName reference_class = StaticCString::create("Reference"); - StringName string_type = StaticCString::create("String"); - StringName string_name_type = StaticCString::create("StringName"); - StringName node_path_type = StaticCString::create("NodePath"); - StringName bool_type = StaticCString::create("bool"); - StringName int_type = StaticCString::create("int"); - StringName float_type = StaticCString::create("float"); - StringName void_type = StaticCString::create("void"); - StringName vararg_stub_type = StaticCString::create("@VarArg@"); - StringName vector2_type = StaticCString::create("Vector2"); - StringName rect2_type = StaticCString::create("Rect2"); - StringName vector3_type = StaticCString::create("Vector3"); - - // Object not included as it must be checked for all derived classes - static constexpr int nullable_types_count = 17; - StringName nullable_types[nullable_types_count] = { - string_type, - string_name_type, - node_path_type, - - StaticCString::create(_STR(Array)), - StaticCString::create(_STR(Dictionary)), - StaticCString::create(_STR(Callable)), - StaticCString::create(_STR(Signal)), - - StaticCString::create(_STR(PackedByteArray)), - StaticCString::create(_STR(PackedInt32Array)), - StaticCString::create(_STR(PackedInt64rray)), - StaticCString::create(_STR(PackedFloat32Array)), - StaticCString::create(_STR(PackedFloat64Array)), - StaticCString::create(_STR(PackedStringArray)), - StaticCString::create(_STR(PackedVector2Array)), - StaticCString::create(_STR(PackedVector3Array)), - StaticCString::create(_STR(PackedColorArray)), - }; - - bool is_nullable_type(const StringName &p_type) const { - for (int i = 0; i < nullable_types_count; i++) { - if (p_type == nullable_types[i]) { - return true; - } - } - - return false; - } -}; - -typedef OrderedHashMap<StringName, ExposedClass> ExposedClasses; - -struct Context { - Vector<StringName> enum_types; - Vector<StringName> builtin_types; - ExposedClasses exposed_classes; - List<EnumData> global_enums; - NamesCache names_cache; - - const ExposedClass *find_exposed_class(const StringName &p_name) const { - ExposedClasses::ConstElement elem = exposed_classes.find(p_name); - return elem ? &elem.value() : nullptr; - } - - const ExposedClass *find_exposed_class(const TypeReference &p_type_ref) const { - ExposedClasses::ConstElement elem = exposed_classes.find(p_type_ref.name); - return elem ? &elem.value() : nullptr; - } - - bool has_type(const TypeReference &p_type_ref) const { - if (builtin_types.find(p_type_ref.name) >= 0) { - return true; - } - - if (p_type_ref.is_enum) { - if (enum_types.find(p_type_ref.name) >= 0) { - return true; - } - - // Enum not found. Most likely because none of its constants were bound, so it's empty. That's fine. Use int instead. - return builtin_types.find(names_cache.int_type); - } - - return false; - } -}; - -bool arg_default_value_is_assignable_to_type(const Context &p_context, const Variant &p_val, const TypeReference &p_arg_type) { - if (p_arg_type.name == p_context.names_cache.variant_type) { - // Variant can take anything - return true; - } - - switch (p_val.get_type()) { - case Variant::NIL: - return p_context.find_exposed_class(p_arg_type) || - p_context.names_cache.is_nullable_type(p_arg_type.name); - case Variant::BOOL: - return p_arg_type.name == p_context.names_cache.bool_type; - case Variant::INT: - return p_arg_type.name == p_context.names_cache.int_type || - p_arg_type.name == p_context.names_cache.float_type || - p_arg_type.is_enum; - case Variant::FLOAT: - return p_arg_type.name == p_context.names_cache.float_type; - case Variant::STRING: - case Variant::STRING_NAME: - return p_arg_type.name == p_context.names_cache.string_type || - p_arg_type.name == p_context.names_cache.string_name_type || - p_arg_type.name == p_context.names_cache.node_path_type; - case Variant::NODE_PATH: - return p_arg_type.name == p_context.names_cache.node_path_type; - case Variant::TRANSFORM: - case Variant::TRANSFORM2D: - case Variant::BASIS: - case Variant::QUAT: - case Variant::PLANE: - case Variant::AABB: - case Variant::COLOR: - case Variant::VECTOR2: - case Variant::RECT2: - case Variant::VECTOR3: - case Variant::_RID: - case Variant::ARRAY: - case Variant::DICTIONARY: - case Variant::PACKED_BYTE_ARRAY: - case Variant::PACKED_INT32_ARRAY: - case Variant::PACKED_INT64_ARRAY: - case Variant::PACKED_FLOAT32_ARRAY: - case Variant::PACKED_FLOAT64_ARRAY: - case Variant::PACKED_STRING_ARRAY: - case Variant::PACKED_VECTOR2_ARRAY: - case Variant::PACKED_VECTOR3_ARRAY: - case Variant::PACKED_COLOR_ARRAY: - case Variant::CALLABLE: - case Variant::SIGNAL: - return p_arg_type.name == Variant::get_type_name(p_val.get_type()); - case Variant::OBJECT: - return p_context.find_exposed_class(p_arg_type); - case Variant::VECTOR2I: - return p_arg_type.name == p_context.names_cache.vector2_type || - p_arg_type.name == Variant::get_type_name(p_val.get_type()); - case Variant::RECT2I: - return p_arg_type.name == p_context.names_cache.rect2_type || - p_arg_type.name == Variant::get_type_name(p_val.get_type()); - case Variant::VECTOR3I: - return p_arg_type.name == p_context.names_cache.vector3_type || - p_arg_type.name == Variant::get_type_name(p_val.get_type()); - default: - ERR_PRINT("Unexpected Variant type: " + itos(p_val.get_type())); - break; - } - - return false; -} - -TestResult validate_property(const Context &p_context, const ExposedClass &p_class, const PropertyData &p_prop) { - TEST_START(); - - const MethodData *setter = p_class.find_method_by_name(p_prop.setter); - - // Search it in base classes too - const ExposedClass *top = &p_class; - while (!setter && top->base != StringName()) { - top = p_context.find_exposed_class(top->base); - TEST_FAIL_COND(!top, "Class not found '" + top->base + "'. Inherited by '" + top->name + "'."); - setter = top->find_method_by_name(p_prop.setter); - } - - const MethodData *getter = p_class.find_method_by_name(p_prop.getter); - - // Search it in base classes too - top = &p_class; - while (!getter && top->base != StringName()) { - top = p_context.find_exposed_class(top->base); - TEST_FAIL_COND(!top, "Class not found '" + top->base + "'. Inherited by '" + top->name + "'."); - getter = top->find_method_by_name(p_prop.getter); - } - - TEST_FAIL_COND(!setter && !getter, - "Couldn't find neither the setter nor the getter for property: '" + p_class.name + "." + String(p_prop.name) + "'."); - - if (setter) { - int setter_argc = p_prop.index != -1 ? 2 : 1; - TEST_FAIL_COND(setter->arguments.size() != setter_argc, - "Invalid property setter argument count: '" + p_class.name + "." + String(p_prop.name) + "'."); - } - - if (getter) { - int getter_argc = p_prop.index != -1 ? 1 : 0; - TEST_FAIL_COND(getter->arguments.size() != getter_argc, - "Invalid property setter argument count: '" + p_class.name + "." + String(p_prop.name) + "'."); - } - - if (getter && setter) { - const ArgumentData &setter_first_arg = setter->arguments.back()->get(); - if (getter->return_type.name != setter_first_arg.type.name) { - // Special case for Node::set_name - bool whitelisted = getter->return_type.name == p_context.names_cache.string_name_type && - setter_first_arg.type.name == p_context.names_cache.string_type; - - TEST_FAIL_COND(!whitelisted, - "Return type from getter doesn't match first argument of setter, for property: '" + p_class.name + "." + String(p_prop.name) + "'."); - } - } - - const TypeReference &prop_type_ref = getter ? getter->return_type : setter->arguments.back()->get().type; - - const ExposedClass *prop_class = p_context.find_exposed_class(prop_type_ref); - if (prop_class) { - TEST_COND(prop_class->is_singleton, - "Property type is a singleton: '" + p_class.name + "." + String(p_prop.name) + "'."); - } else { - TEST_FAIL_COND(!p_context.has_type(prop_type_ref), - "Property type '" + prop_type_ref.name + "' not found: '" + p_class.name + "." + String(p_prop.name) + "'."); - } - - if (getter) { - if (p_prop.index != -1) { - const ArgumentData &idx_arg = getter->arguments.front()->get(); - if (idx_arg.type.name != p_context.names_cache.int_type) { - // If not an int, it can be an enum - TEST_COND(p_context.enum_types.find(idx_arg.type.name) < 0, - "Invalid type '" + idx_arg.type.name + "' for index argument of property getter: '" + p_class.name + "." + String(p_prop.name) + "'."); - } - } - } - - if (setter) { - if (p_prop.index != -1) { - const ArgumentData &idx_arg = setter->arguments.front()->get(); - if (idx_arg.type.name != p_context.names_cache.int_type) { - // Assume the index parameter is an enum - // If not an int, it can be an enum - TEST_COND(p_context.enum_types.find(idx_arg.type.name) < 0, - "Invalid type '" + idx_arg.type.name + "' for index argument of property setter: '" + p_class.name + "." + String(p_prop.name) + "'."); - } - } - } - - TEST_END(); -} - -TestResult validate_method(const Context &p_context, const ExposedClass &p_class, const MethodData &p_method) { - TEST_START(); - - const ExposedClass *return_class = p_context.find_exposed_class(p_method.return_type); - if (return_class) { - TEST_COND(return_class->is_singleton, - "Method return type is a singleton: '" + p_class.name + "." + p_method.name + "'."); - } - - for (const List<ArgumentData>::Element *F = p_method.arguments.front(); F; F = F->next()) { - const ArgumentData &arg = F->get(); - - const ExposedClass *arg_class = p_context.find_exposed_class(arg.type); - if (arg_class) { - TEST_COND(arg_class->is_singleton, - "Argument type is a singleton: '" + arg.name + "' of method '" + p_class.name + "." + p_method.name + "'."); - } else { - TEST_FAIL_COND(!p_context.has_type(arg.type), - "Argument type '" + arg.type.name + "' not found: '" + arg.name + "' of method" + p_class.name + "." + p_method.name + "'."); - } - - if (arg.has_defval) { - TEST_COND(!arg_default_value_is_assignable_to_type(p_context, arg.defval, arg.type), - "Invalid default value for parameter '" + arg.name + "' of method '" + p_class.name + "." + p_method.name + "'."); - } - } - - TEST_END(); -} - -TestResult validate_signal(const Context &p_context, const ExposedClass &p_class, const SignalData &p_signal) { - TEST_START(); - - for (const List<ArgumentData>::Element *F = p_signal.arguments.front(); F; F = F->next()) { - const ArgumentData &arg = F->get(); - - const ExposedClass *arg_class = p_context.find_exposed_class(arg.type); - if (arg_class) { - TEST_COND(arg_class->is_singleton, - "Argument class is a singleton: '" + arg.name + "' of signal" + p_class.name + "." + p_signal.name + "'."); - } else { - TEST_FAIL_COND(!p_context.has_type(arg.type), - "Argument type '" + arg.type.name + "' not found: '" + arg.name + "' of signal" + p_class.name + "." + p_signal.name + "'."); - } - } - - TEST_END(); -} - -TestResult validate_class(const Context &p_context, const ExposedClass &p_exposed_class) { - TEST_START(); - - bool is_derived_type = p_exposed_class.base != StringName(); - - if (!is_derived_type) { - // Asserts about the base Object class - TEST_FAIL_COND(p_exposed_class.name != p_context.names_cache.object_class, - "Class '" + p_exposed_class.name + "' has no base class."); - TEST_FAIL_COND(!p_exposed_class.is_instantiable, - "Object class is not instantiable."); - TEST_FAIL_COND(p_exposed_class.api_type != ClassDB::API_CORE, - "Object class is API is not API_CORE."); - TEST_FAIL_COND(p_exposed_class.is_singleton, - "Object class is registered as a singleton."); - } - - TEST_FAIL_COND(p_exposed_class.is_singleton && p_exposed_class.base != p_context.names_cache.object_class, - "Singleton base class '" + String(p_exposed_class.base) + "' is not Object, for class '" + p_exposed_class.name + "'."); - - TEST_FAIL_COND(is_derived_type && !p_context.exposed_classes.has(p_exposed_class.base), - "Base type '" + p_exposed_class.base.operator String() + "' does not exist, for class '" + p_exposed_class.name + "'."); - - for (const List<PropertyData>::Element *F = p_exposed_class.properties.front(); F; F = F->next()) { - TEST_CHECK(validate_property(p_context, p_exposed_class, F->get())); - } - - for (const List<MethodData>::Element *F = p_exposed_class.methods.front(); F; F = F->next()) { - TEST_CHECK(validate_method(p_context, p_exposed_class, F->get())); - } - - for (const List<SignalData>::Element *F = p_exposed_class.signals_.front(); F; F = F->next()) { - TEST_CHECK(validate_signal(p_context, p_exposed_class, F->get())); - } - - TEST_END(); -} - -TestResult add_exposed_classes(Context &r_context) { - TEST_START(); - - List<StringName> class_list; - ClassDB::get_class_list(&class_list); - class_list.sort_custom<StringName::AlphCompare>(); - - while (class_list.size()) { - StringName class_name = class_list.front()->get(); - - ClassDB::APIType api_type = ClassDB::get_api_type(class_name); - - if (api_type == ClassDB::API_NONE) { - class_list.pop_front(); - continue; - } - - if (!ClassDB::is_class_exposed(class_name)) { - OS::get_singleton()->print("Ignoring class '%s' because it's not exposed\n", String(class_name).utf8().get_data()); - class_list.pop_front(); - continue; - } - - if (!ClassDB::is_class_enabled(class_name)) { - OS::get_singleton()->print("Ignoring class '%s' because it's not enabled\n", String(class_name).utf8().get_data()); - class_list.pop_front(); - continue; - } - - ClassDB::ClassInfo *class_info = ClassDB::classes.getptr(class_name); - - ExposedClass exposed_class; - exposed_class.name = class_name; - exposed_class.api_type = api_type; - exposed_class.is_singleton = Engine::get_singleton()->has_singleton(class_name); - exposed_class.is_instantiable = class_info->creation_func && !exposed_class.is_singleton; - exposed_class.is_reference = ClassDB::is_parent_class(class_name, "Reference"); - exposed_class.base = ClassDB::get_parent_class(class_name); - - // Add properties - - List<PropertyInfo> property_list; - ClassDB::get_property_list(class_name, &property_list, true); - - Map<StringName, StringName> accessor_methods; - - for (const List<PropertyInfo>::Element *E = property_list.front(); E; E = E->next()) { - const PropertyInfo &property = E->get(); - - if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY) { - continue; - } - - PropertyData prop; - prop.name = property.name; - prop.setter = ClassDB::get_property_setter(class_name, prop.name); - prop.getter = ClassDB::get_property_getter(class_name, prop.name); - - if (prop.setter != StringName()) { - accessor_methods[prop.setter] = prop.name; - } - if (prop.getter != StringName()) { - accessor_methods[prop.getter] = prop.name; - } - - bool valid = false; - prop.index = ClassDB::get_property_index(class_name, prop.name, &valid); - TEST_FAIL_COND(!valid, "Invalid property: '" + exposed_class.name + "." + String(prop.name) + "'."); - - exposed_class.properties.push_back(prop); - } - - // Add methods - - List<MethodInfo> virtual_method_list; - ClassDB::get_virtual_methods(class_name, &virtual_method_list, true); - - List<MethodInfo> method_list; - ClassDB::get_method_list(class_name, &method_list, true); - method_list.sort(); - - for (List<MethodInfo>::Element *E = method_list.front(); E; E = E->next()) { - const MethodInfo &method_info = E->get(); - - int argc = method_info.arguments.size(); - - if (method_info.name.empty()) { - continue; - } - - MethodData method; - method.name = method_info.name; - - if (method_info.flags & METHOD_FLAG_VIRTUAL) { - method.is_virtual = true; - } - - PropertyInfo return_info = method_info.return_val; - - MethodBind *m = method.is_virtual ? nullptr : ClassDB::get_method(class_name, method_info.name); - - method.is_vararg = m && m->is_vararg(); - - if (!m && !method.is_virtual) { - TEST_FAIL_COND(!virtual_method_list.find(method_info), - "Missing MethodBind for non-virtual method: '" + exposed_class.name + "." + method.name + "'."); - - // A virtual method without the virtual flag. This is a special case. - - // The method Object.free is registered as a virtual method, but without the virtual flag. - // This is because this method is not supposed to be overridden, but called. - // We assume the return type is void. - method.return_type.name = r_context.names_cache.void_type; - - // Actually, more methods like this may be added in the future, which could return - // something different. Let's put this check to notify us if that ever happens. - if (exposed_class.name != r_context.names_cache.object_class || String(method.name) != "free") { - WARN_PRINT("Notification: New unexpected virtual non-overridable method found." - " We only expected Object.free, but found '" + - exposed_class.name + "." + method.name + "'."); - } - } else if (return_info.type == Variant::INT && return_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { - method.return_type.name = return_info.class_name; - method.return_type.is_enum = true; - } else if (return_info.class_name != StringName()) { - method.return_type.name = return_info.class_name; - - bool bad_reference_hint = !method.is_virtual && return_info.hint != PROPERTY_HINT_RESOURCE_TYPE && - ClassDB::is_parent_class(return_info.class_name, r_context.names_cache.reference_class); - TEST_COND(bad_reference_hint, String() + "Return type is reference but hint is not '" _STR(PROPERTY_HINT_RESOURCE_TYPE) "'." + - " Are you returning a reference type by pointer? Method: '" + - exposed_class.name + "." + method.name + "'."); - } else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { - method.return_type.name = return_info.hint_string; - } else if (return_info.type == Variant::NIL && return_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT) { - method.return_type.name = r_context.names_cache.variant_type; - } else if (return_info.type == Variant::NIL) { - method.return_type.name = r_context.names_cache.void_type; - } else { - // NOTE: We don't care about the size and sign of int and float in these tests - method.return_type.name = Variant::get_type_name(return_info.type); - } - - for (int i = 0; i < argc; i++) { - PropertyInfo arg_info = method_info.arguments[i]; - - String orig_arg_name = arg_info.name; - - ArgumentData arg; - arg.name = orig_arg_name; - - if (arg_info.type == Variant::INT && arg_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { - arg.type.name = arg_info.class_name; - arg.type.is_enum = true; - } else if (arg_info.class_name != StringName()) { - arg.type.name = arg_info.class_name; - } else if (arg_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { - arg.type.name = arg_info.hint_string; - } else if (arg_info.type == Variant::NIL) { - arg.type.name = r_context.names_cache.variant_type; - } else { - // NOTE: We don't care about the size and sign of int and float in these tests - arg.type.name = Variant::get_type_name(arg_info.type); - } - - if (m && m->has_default_argument(i)) { - arg.has_defval = true; - arg.defval = m->get_default_argument(i); - } - - method.arguments.push_back(arg); - } - - if (method.is_vararg) { - ArgumentData vararg; - vararg.type.name = r_context.names_cache.vararg_stub_type; - vararg.name = "@varargs@"; - method.arguments.push_back(vararg); - } - - TEST_COND(exposed_class.find_property_by_name(method.name), - "Method name conflicts with property: '" + String(class_name) + "." + String(method.name) + "'."); - - // Classes starting with an underscore are ignored unless they're used as a property setter or getter - if (!method.is_virtual && String(method.name)[0] == '_') { - for (const List<PropertyData>::Element *F = exposed_class.properties.front(); F; F = F->next()) { - const PropertyData &prop = F->get(); - - if (prop.setter == method.name || prop.getter == method.name) { - exposed_class.methods.push_back(method); - break; - } - } - } else { - exposed_class.methods.push_back(method); - } - } - - // Add signals - - const HashMap<StringName, MethodInfo> &signal_map = class_info->signal_map; - const StringName *k = nullptr; - - while ((k = signal_map.next(k))) { - SignalData signal; - - const MethodInfo &method_info = signal_map.get(*k); - - signal.name = method_info.name; - - int argc = method_info.arguments.size(); - - for (int i = 0; i < argc; i++) { - PropertyInfo arg_info = method_info.arguments[i]; - - String orig_arg_name = arg_info.name; - - ArgumentData arg; - arg.name = orig_arg_name; - - if (arg_info.type == Variant::INT && arg_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { - arg.type.name = arg_info.class_name; - arg.type.is_enum = true; - } else if (arg_info.class_name != StringName()) { - arg.type.name = arg_info.class_name; - } else if (arg_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { - arg.type.name = arg_info.hint_string; - } else if (arg_info.type == Variant::NIL) { - arg.type.name = r_context.names_cache.variant_type; - } else { - // NOTE: We don't care about the size and sign of int and float in these tests - arg.type.name = Variant::get_type_name(arg_info.type); - } - - signal.arguments.push_back(arg); - } - - bool method_conflict = exposed_class.find_property_by_name(signal.name); - - if (method_conflict || exposed_class.find_method_by_name(signal.name)) { - // TODO: - // ClassDB allows signal names that conflict with method or property names. - // However registering a signal with a conflicting name is still considered wrong. - // Unfortunately there are some existing cases that are yet to be fixed. - // Until those are fixed we will print a warning instead of failing the test. - WARN_PRINT("Signal name conflicts with " + String(method_conflict ? "method" : "property") + - ": '" + String(class_name) + "." + String(signal.name) + "'."); - } - - exposed_class.signals_.push_back(signal); - } - - // Add enums and constants - - List<String> constants; - ClassDB::get_integer_constant_list(class_name, &constants, true); - - const HashMap<StringName, List<StringName>> &enum_map = class_info->enum_map; - k = nullptr; - - while ((k = enum_map.next(k))) { - EnumData enum_; - enum_.name = *k; - - const List<StringName> &enum_constants = enum_map.get(*k); - for (const List<StringName>::Element *E = enum_constants.front(); E; E = E->next()) { - const StringName &constant_name = E->get(); - int *value = class_info->constant_map.getptr(constant_name); - TEST_FAIL_COND(!value, "Missing enum constant value: '" + - String(class_name) + "." + String(enum_.name) + "." + String(constant_name) + "'."); - constants.erase(constant_name); - - ConstantData constant; - constant.name = constant_name; - constant.value = *value; - - enum_.constants.push_back(constant); - } - - exposed_class.enums.push_back(enum_); - - r_context.enum_types.push_back(String(class_name) + "." + String(*k)); - } - - for (const List<String>::Element *E = constants.front(); E; E = E->next()) { - const String &constant_name = E->get(); - int *value = class_info->constant_map.getptr(StringName(E->get())); - TEST_FAIL_COND(!value, "Missing enum constant value: '" + String(class_name) + "." + String(constant_name) + "'."); - - ConstantData constant; - constant.name = constant_name; - constant.value = *value; - - exposed_class.constants.push_back(constant); - } - - r_context.exposed_classes.insert(class_name, exposed_class); - class_list.pop_front(); - } - - TEST_END(); -} - -void add_builtin_types(Context &r_context) { - // NOTE: We don't care about the size and sign of int and float in these tests - for (int i = 0; i < Variant::VARIANT_MAX; i++) { - r_context.builtin_types.push_back(Variant::get_type_name(Variant::Type(i))); - } - - r_context.builtin_types.push_back(_STR(Variant)); - r_context.builtin_types.push_back(r_context.names_cache.vararg_stub_type); - r_context.builtin_types.push_back("void"); -} - -void add_global_enums(Context &r_context) { - int global_constants_count = GlobalConstants::get_global_constant_count(); - - if (global_constants_count > 0) { - for (int i = 0; i < global_constants_count; i++) { - StringName enum_name = GlobalConstants::get_global_constant_enum(i); - - if (enum_name != StringName()) { - ConstantData constant; - constant.name = GlobalConstants::get_global_constant_name(i); - constant.value = GlobalConstants::get_global_constant_value(i); - - EnumData enum_; - enum_.name = enum_name; - List<EnumData>::Element *enum_match = r_context.global_enums.find(enum_); - if (enum_match) { - enum_match->get().constants.push_back(constant); - } else { - enum_.constants.push_back(constant); - r_context.global_enums.push_back(enum_); - } - } - } - - for (List<EnumData>::Element *E = r_context.global_enums.front(); E; E = E->next()) { - r_context.enum_types.push_back(E->get().name); - } - } - - // HARDCODED - List<StringName> hardcoded_enums; - hardcoded_enums.push_back("Vector2.Axis"); - hardcoded_enums.push_back("Vector2i.Axis"); - hardcoded_enums.push_back("Vector3.Axis"); - hardcoded_enums.push_back("Vector3i.Axis"); - for (List<StringName>::Element *E = hardcoded_enums.front(); E; E = E->next()) { - // These enums are not generated and must be written manually (e.g.: Vector3.Axis) - // Here, we assume core types do not begin with underscore - r_context.enum_types.push_back(E->get()); - } -} - -TestResult run_class_db_tests() { - TEST_START(); - - Context context; - - TEST_FAIL_CHECK(add_exposed_classes(context)); - add_builtin_types(context); - add_global_enums(context); - - const ExposedClass *object_class = context.find_exposed_class(context.names_cache.object_class); - TEST_FAIL_COND(!object_class, "Object class not found."); - TEST_FAIL_COND(object_class->base != StringName(), - "Object class derives from another class: '" + object_class->base + "'."); - - for (ExposedClasses::Element E = context.exposed_classes.front(); E; E = E.next()) { - TEST_CHECK(validate_class(context, E.value())); - } - - TEST_END(); -} - -MainLoop *test() { - TestResult pass = run_class_db_tests(); - - OS::get_singleton()->print("ClassDB tests: %s\n", pass == TestResult::PASS ? "PASS" : "FAILED"); - - if (pass == TestResult::FAILED) { - OS::get_singleton()->set_exit_code(pass == TestResult::PASS ? 0 : 1); - } - - return nullptr; -} - -} // namespace TestClassDB diff --git a/tests/test_class_db.h b/tests/test_class_db.h index 1a31cfb01b..d0d8136874 100644 --- a/tests/test_class_db.h +++ b/tests/test_class_db.h @@ -31,12 +31,806 @@ #ifndef GODOT_TEST_CLASS_DB_H #define GODOT_TEST_CLASS_DB_H -#include "core/os/main_loop.h" +#include "core/register_core_types.h" + +#include "core/global_constants.h" +#include "core/ordered_hash_map.h" +#include "core/os/os.h" +#include "core/string_name.h" +#include "core/ustring.h" +#include "core/variant.h" + +#include "tests/test_macros.h" + +#define TEST_COND DOCTEST_CHECK_FALSE_MESSAGE +#define TEST_FAIL DOCTEST_FAIL +#define TEST_FAIL_COND DOCTEST_REQUIRE_FALSE_MESSAGE +#define TEST_FAIL_COND_WARN DOCTEST_WARN_FALSE_MESSAGE namespace TestClassDB { -MainLoop *test(); +struct TypeReference { + StringName name; + bool is_enum = false; +}; + +struct ConstantData { + String name; + int value = 0; +}; + +struct EnumData { + StringName name; + List<ConstantData> constants; + + _FORCE_INLINE_ bool operator==(const EnumData &p_enum) const { + return p_enum.name == name; + } +}; + +struct PropertyData { + StringName name; + int index = 0; + + StringName getter; + StringName setter; +}; + +struct ArgumentData { + TypeReference type; + String name; + bool has_defval = false; + Variant defval; +}; + +struct MethodData { + StringName name; + TypeReference return_type; + List<ArgumentData> arguments; + bool is_virtual = false; + bool is_vararg = false; +}; + +struct SignalData { + StringName name; + List<ArgumentData> arguments; +}; + +struct ExposedClass { + StringName name; + StringName base; + + bool is_singleton = false; + bool is_instantiable = false; + bool is_reference = false; + + ClassDB::APIType api_type; + + List<ConstantData> constants; + List<EnumData> enums; + List<PropertyData> properties; + List<MethodData> methods; + List<SignalData> signals_; + + const PropertyData *find_property_by_name(const StringName &p_name) const { + for (const List<PropertyData>::Element *E = properties.front(); E; E = E->next()) { + if (E->get().name == p_name) { + return &E->get(); + } + } + + return nullptr; + } + + const MethodData *find_method_by_name(const StringName &p_name) const { + for (const List<MethodData>::Element *E = methods.front(); E; E = E->next()) { + if (E->get().name == p_name) { + return &E->get(); + } + } + + return nullptr; + } +}; + +struct NamesCache { + StringName variant_type = StaticCString::create("Variant"); + StringName object_class = StaticCString::create("Object"); + StringName reference_class = StaticCString::create("Reference"); + StringName string_type = StaticCString::create("String"); + StringName string_name_type = StaticCString::create("StringName"); + StringName node_path_type = StaticCString::create("NodePath"); + StringName bool_type = StaticCString::create("bool"); + StringName int_type = StaticCString::create("int"); + StringName float_type = StaticCString::create("float"); + StringName void_type = StaticCString::create("void"); + StringName vararg_stub_type = StaticCString::create("@VarArg@"); + StringName vector2_type = StaticCString::create("Vector2"); + StringName rect2_type = StaticCString::create("Rect2"); + StringName vector3_type = StaticCString::create("Vector3"); + // Object not included as it must be checked for all derived classes + static constexpr int nullable_types_count = 17; + StringName nullable_types[nullable_types_count] = { + string_type, + string_name_type, + node_path_type, + + StaticCString::create(_STR(Array)), + StaticCString::create(_STR(Dictionary)), + StaticCString::create(_STR(Callable)), + StaticCString::create(_STR(Signal)), + + StaticCString::create(_STR(PackedByteArray)), + StaticCString::create(_STR(PackedInt32Array)), + StaticCString::create(_STR(PackedInt64rray)), + StaticCString::create(_STR(PackedFloat32Array)), + StaticCString::create(_STR(PackedFloat64Array)), + StaticCString::create(_STR(PackedStringArray)), + StaticCString::create(_STR(PackedVector2Array)), + StaticCString::create(_STR(PackedVector3Array)), + StaticCString::create(_STR(PackedColorArray)), + }; + + bool is_nullable_type(const StringName &p_type) const { + for (int i = 0; i < nullable_types_count; i++) { + if (p_type == nullable_types[i]) { + return true; + } + } + + return false; + } +}; + +typedef OrderedHashMap<StringName, ExposedClass> ExposedClasses; + +struct Context { + Vector<StringName> enum_types; + Vector<StringName> builtin_types; + ExposedClasses exposed_classes; + List<EnumData> global_enums; + NamesCache names_cache; + + const ExposedClass *find_exposed_class(const StringName &p_name) const { + ExposedClasses::ConstElement elem = exposed_classes.find(p_name); + return elem ? &elem.value() : nullptr; + } + + const ExposedClass *find_exposed_class(const TypeReference &p_type_ref) const { + ExposedClasses::ConstElement elem = exposed_classes.find(p_type_ref.name); + return elem ? &elem.value() : nullptr; + } + + bool has_type(const TypeReference &p_type_ref) const { + if (builtin_types.find(p_type_ref.name) >= 0) { + return true; + } + + if (p_type_ref.is_enum) { + if (enum_types.find(p_type_ref.name) >= 0) { + return true; + } + + // Enum not found. Most likely because none of its constants were bound, so it's empty. That's fine. Use int instead. + return builtin_types.find(names_cache.int_type); + } + + return false; + } +}; + +bool arg_default_value_is_assignable_to_type(const Context &p_context, const Variant &p_val, const TypeReference &p_arg_type, String *r_err_msg = nullptr) { + if (p_arg_type.name == p_context.names_cache.variant_type) { + // Variant can take anything + return true; + } + + switch (p_val.get_type()) { + case Variant::NIL: + return p_context.find_exposed_class(p_arg_type) || + p_context.names_cache.is_nullable_type(p_arg_type.name); + case Variant::BOOL: + return p_arg_type.name == p_context.names_cache.bool_type; + case Variant::INT: + return p_arg_type.name == p_context.names_cache.int_type || + p_arg_type.name == p_context.names_cache.float_type || + p_arg_type.is_enum; + case Variant::FLOAT: + return p_arg_type.name == p_context.names_cache.float_type; + case Variant::STRING: + case Variant::STRING_NAME: + return p_arg_type.name == p_context.names_cache.string_type || + p_arg_type.name == p_context.names_cache.string_name_type || + p_arg_type.name == p_context.names_cache.node_path_type; + case Variant::NODE_PATH: + return p_arg_type.name == p_context.names_cache.node_path_type; + case Variant::TRANSFORM: + case Variant::TRANSFORM2D: + case Variant::BASIS: + case Variant::QUAT: + case Variant::PLANE: + case Variant::AABB: + case Variant::COLOR: + case Variant::VECTOR2: + case Variant::RECT2: + case Variant::VECTOR3: + case Variant::_RID: + case Variant::ARRAY: + case Variant::DICTIONARY: + case Variant::PACKED_BYTE_ARRAY: + case Variant::PACKED_INT32_ARRAY: + case Variant::PACKED_INT64_ARRAY: + case Variant::PACKED_FLOAT32_ARRAY: + case Variant::PACKED_FLOAT64_ARRAY: + case Variant::PACKED_STRING_ARRAY: + case Variant::PACKED_VECTOR2_ARRAY: + case Variant::PACKED_VECTOR3_ARRAY: + case Variant::PACKED_COLOR_ARRAY: + case Variant::CALLABLE: + case Variant::SIGNAL: + return p_arg_type.name == Variant::get_type_name(p_val.get_type()); + case Variant::OBJECT: + return p_context.find_exposed_class(p_arg_type); + case Variant::VECTOR2I: + return p_arg_type.name == p_context.names_cache.vector2_type || + p_arg_type.name == Variant::get_type_name(p_val.get_type()); + case Variant::RECT2I: + return p_arg_type.name == p_context.names_cache.rect2_type || + p_arg_type.name == Variant::get_type_name(p_val.get_type()); + case Variant::VECTOR3I: + return p_arg_type.name == p_context.names_cache.vector3_type || + p_arg_type.name == Variant::get_type_name(p_val.get_type()); + default: + if (r_err_msg) { + *r_err_msg = "Unexpected Variant type: " + itos(p_val.get_type()); + } + break; + } + + return false; } +void validate_property(const Context &p_context, const ExposedClass &p_class, const PropertyData &p_prop) { + const MethodData *setter = p_class.find_method_by_name(p_prop.setter); + + // Search it in base classes too + const ExposedClass *top = &p_class; + while (!setter && top->base != StringName()) { + top = p_context.find_exposed_class(top->base); + TEST_FAIL_COND(!top, "Class not found '" + top->base + "'. Inherited by '" + top->name + "'."); + setter = top->find_method_by_name(p_prop.setter); + } + + const MethodData *getter = p_class.find_method_by_name(p_prop.getter); + + // Search it in base classes too + top = &p_class; + while (!getter && top->base != StringName()) { + top = p_context.find_exposed_class(top->base); + TEST_FAIL_COND(!top, "Class not found '" + top->base + "'. Inherited by '" + top->name + "'."); + getter = top->find_method_by_name(p_prop.getter); + } + + TEST_FAIL_COND((!setter && !getter), + "Couldn't find neither the setter nor the getter for property: '" + p_class.name + "." + String(p_prop.name) + "'."); + + if (setter) { + int setter_argc = p_prop.index != -1 ? 2 : 1; + TEST_FAIL_COND(setter->arguments.size() != setter_argc, + "Invalid property setter argument count: '" + p_class.name + "." + String(p_prop.name) + "'."); + } + + if (getter) { + int getter_argc = p_prop.index != -1 ? 1 : 0; + TEST_FAIL_COND(getter->arguments.size() != getter_argc, + "Invalid property setter argument count: '" + p_class.name + "." + String(p_prop.name) + "'."); + } + + if (getter && setter) { + const ArgumentData &setter_first_arg = setter->arguments.back()->get(); + if (getter->return_type.name != setter_first_arg.type.name) { + // Special case for Node::set_name + bool whitelisted = getter->return_type.name == p_context.names_cache.string_name_type && + setter_first_arg.type.name == p_context.names_cache.string_type; + + TEST_FAIL_COND(!whitelisted, + "Return type from getter doesn't match first argument of setter, for property: '" + p_class.name + "." + String(p_prop.name) + "'."); + } + } + + const TypeReference &prop_type_ref = getter ? getter->return_type : setter->arguments.back()->get().type; + + const ExposedClass *prop_class = p_context.find_exposed_class(prop_type_ref); + if (prop_class) { + TEST_COND(prop_class->is_singleton, + "Property type is a singleton: '" + p_class.name + "." + String(p_prop.name) + "'."); + } else { + TEST_FAIL_COND(!p_context.has_type(prop_type_ref), + "Property type '" + prop_type_ref.name + "' not found: '" + p_class.name + "." + String(p_prop.name) + "'."); + } + + if (getter) { + if (p_prop.index != -1) { + const ArgumentData &idx_arg = getter->arguments.front()->get(); + if (idx_arg.type.name != p_context.names_cache.int_type) { + // If not an int, it can be an enum + TEST_COND(p_context.enum_types.find(idx_arg.type.name) < 0, + "Invalid type '" + idx_arg.type.name + "' for index argument of property getter: '" + p_class.name + "." + String(p_prop.name) + "'."); + } + } + } + + if (setter) { + if (p_prop.index != -1) { + const ArgumentData &idx_arg = setter->arguments.front()->get(); + if (idx_arg.type.name != p_context.names_cache.int_type) { + // Assume the index parameter is an enum + // If not an int, it can be an enum + TEST_COND(p_context.enum_types.find(idx_arg.type.name) < 0, + "Invalid type '" + idx_arg.type.name + "' for index argument of property setter: '" + p_class.name + "." + String(p_prop.name) + "'."); + } + } + } +} + +void validate_method(const Context &p_context, const ExposedClass &p_class, const MethodData &p_method) { + const ExposedClass *return_class = p_context.find_exposed_class(p_method.return_type); + if (return_class) { + TEST_COND(return_class->is_singleton, + "Method return type is a singleton: '" + p_class.name + "." + p_method.name + "'."); + } + + for (const List<ArgumentData>::Element *F = p_method.arguments.front(); F; F = F->next()) { + const ArgumentData &arg = F->get(); + + const ExposedClass *arg_class = p_context.find_exposed_class(arg.type); + if (arg_class) { + TEST_COND(arg_class->is_singleton, + "Argument type is a singleton: '" + arg.name + "' of method '" + p_class.name + "." + p_method.name + "'."); + } else { + TEST_FAIL_COND(!p_context.has_type(arg.type), + "Argument type '" + arg.type.name + "' not found: '" + arg.name + "' of method" + p_class.name + "." + p_method.name + "'."); + } + + if (arg.has_defval) { + String type_error_msg; + bool arg_defval_assignable_to_type = arg_default_value_is_assignable_to_type(p_context, arg.defval, arg.type, &type_error_msg); + String err_msg = vformat("Invalid default value for parameter '%s' of method '%s.%s'.", arg.name, p_class.name, p_method.name); + if (!type_error_msg.empty()) { + err_msg += " " + type_error_msg; + } + TEST_COND(!arg_defval_assignable_to_type, err_msg.utf8().get_data()); + } + } +} + +void validate_signal(const Context &p_context, const ExposedClass &p_class, const SignalData &p_signal) { + for (const List<ArgumentData>::Element *F = p_signal.arguments.front(); F; F = F->next()) { + const ArgumentData &arg = F->get(); + + const ExposedClass *arg_class = p_context.find_exposed_class(arg.type); + if (arg_class) { + TEST_COND(arg_class->is_singleton, + "Argument class is a singleton: '" + arg.name + "' of signal" + p_class.name + "." + p_signal.name + "'."); + } else { + TEST_FAIL_COND(!p_context.has_type(arg.type), + "Argument type '" + arg.type.name + "' not found: '" + arg.name + "' of signal" + p_class.name + "." + p_signal.name + "'."); + } + } +} + +void validate_class(const Context &p_context, const ExposedClass &p_exposed_class) { + bool is_derived_type = p_exposed_class.base != StringName(); + + if (!is_derived_type) { + // Asserts about the base Object class + TEST_FAIL_COND(p_exposed_class.name != p_context.names_cache.object_class, + "Class '" + p_exposed_class.name + "' has no base class."); + TEST_FAIL_COND(!p_exposed_class.is_instantiable, + "Object class is not instantiable."); + TEST_FAIL_COND(p_exposed_class.api_type != ClassDB::API_CORE, + "Object class is API is not API_CORE."); + TEST_FAIL_COND(p_exposed_class.is_singleton, + "Object class is registered as a singleton."); + } + + TEST_FAIL_COND((p_exposed_class.is_singleton && p_exposed_class.base != p_context.names_cache.object_class), + "Singleton base class '" + String(p_exposed_class.base) + "' is not Object, for class '" + p_exposed_class.name + "'."); + + TEST_FAIL_COND((is_derived_type && !p_context.exposed_classes.has(p_exposed_class.base)), + "Base type '" + p_exposed_class.base.operator String() + "' does not exist, for class '" + p_exposed_class.name + "'."); + + for (const List<PropertyData>::Element *F = p_exposed_class.properties.front(); F; F = F->next()) { + validate_property(p_context, p_exposed_class, F->get()); + } + + for (const List<MethodData>::Element *F = p_exposed_class.methods.front(); F; F = F->next()) { + validate_method(p_context, p_exposed_class, F->get()); + } + + for (const List<SignalData>::Element *F = p_exposed_class.signals_.front(); F; F = F->next()) { + validate_signal(p_context, p_exposed_class, F->get()); + } +} + +void add_exposed_classes(Context &r_context) { + List<StringName> class_list; + ClassDB::get_class_list(&class_list); + class_list.sort_custom<StringName::AlphCompare>(); + + while (class_list.size()) { + StringName class_name = class_list.front()->get(); + + ClassDB::APIType api_type = ClassDB::get_api_type(class_name); + + if (api_type == ClassDB::API_NONE) { + class_list.pop_front(); + continue; + } + + if (!ClassDB::is_class_exposed(class_name)) { + MESSAGE(vformat("Ignoring class '%s' because it's not exposed.", class_name).utf8().get_data()); + class_list.pop_front(); + continue; + } + + if (!ClassDB::is_class_enabled(class_name)) { + MESSAGE(vformat("Ignoring class '%s' because it's not enabled.", class_name).utf8().get_data()); + class_list.pop_front(); + continue; + } + + ClassDB::ClassInfo *class_info = ClassDB::classes.getptr(class_name); + + ExposedClass exposed_class; + exposed_class.name = class_name; + exposed_class.api_type = api_type; + exposed_class.is_singleton = Engine::get_singleton()->has_singleton(class_name); + exposed_class.is_instantiable = class_info->creation_func && !exposed_class.is_singleton; + exposed_class.is_reference = ClassDB::is_parent_class(class_name, "Reference"); + exposed_class.base = ClassDB::get_parent_class(class_name); + + // Add properties + + List<PropertyInfo> property_list; + ClassDB::get_property_list(class_name, &property_list, true); + + Map<StringName, StringName> accessor_methods; + + for (const List<PropertyInfo>::Element *E = property_list.front(); E; E = E->next()) { + const PropertyInfo &property = E->get(); + + if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY) { + continue; + } + + PropertyData prop; + prop.name = property.name; + prop.setter = ClassDB::get_property_setter(class_name, prop.name); + prop.getter = ClassDB::get_property_getter(class_name, prop.name); + + if (prop.setter != StringName()) { + accessor_methods[prop.setter] = prop.name; + } + if (prop.getter != StringName()) { + accessor_methods[prop.getter] = prop.name; + } + + bool valid = false; + prop.index = ClassDB::get_property_index(class_name, prop.name, &valid); + TEST_FAIL_COND(!valid, "Invalid property: '" + exposed_class.name + "." + String(prop.name) + "'."); + + exposed_class.properties.push_back(prop); + } + + // Add methods + + List<MethodInfo> virtual_method_list; + ClassDB::get_virtual_methods(class_name, &virtual_method_list, true); + + List<MethodInfo> method_list; + ClassDB::get_method_list(class_name, &method_list, true); + method_list.sort(); + + for (List<MethodInfo>::Element *E = method_list.front(); E; E = E->next()) { + const MethodInfo &method_info = E->get(); + + int argc = method_info.arguments.size(); + + if (method_info.name.empty()) { + continue; + } + + MethodData method; + method.name = method_info.name; + + if (method_info.flags & METHOD_FLAG_VIRTUAL) { + method.is_virtual = true; + } + + PropertyInfo return_info = method_info.return_val; + + MethodBind *m = method.is_virtual ? nullptr : ClassDB::get_method(class_name, method_info.name); + + method.is_vararg = m && m->is_vararg(); + + if (!m && !method.is_virtual) { + TEST_FAIL_COND(!virtual_method_list.find(method_info), + "Missing MethodBind for non-virtual method: '" + exposed_class.name + "." + method.name + "'."); + + // A virtual method without the virtual flag. This is a special case. + + // The method Object.free is registered as a virtual method, but without the virtual flag. + // This is because this method is not supposed to be overridden, but called. + // We assume the return type is void. + method.return_type.name = r_context.names_cache.void_type; + + // Actually, more methods like this may be added in the future, which could return + // something different. Let's put this check to notify us if that ever happens. + String warn_msg = vformat( + "Notification: New unexpected virtual non-overridable method found. " + "We only expected Object.free, but found '%s.%s'.", + exposed_class.name, method.name); + TEST_FAIL_COND_WARN( + (exposed_class.name != r_context.names_cache.object_class || String(method.name) != "free"), + warn_msg.utf8().get_data()); + + } else if (return_info.type == Variant::INT && return_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { + method.return_type.name = return_info.class_name; + method.return_type.is_enum = true; + } else if (return_info.class_name != StringName()) { + method.return_type.name = return_info.class_name; + + bool bad_reference_hint = !method.is_virtual && return_info.hint != PROPERTY_HINT_RESOURCE_TYPE && + ClassDB::is_parent_class(return_info.class_name, r_context.names_cache.reference_class); + TEST_COND(bad_reference_hint, String() + "Return type is reference but hint is not '" _STR(PROPERTY_HINT_RESOURCE_TYPE) "'." + + " Are you returning a reference type by pointer? Method: '" + + exposed_class.name + "." + method.name + "'."); + } else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { + method.return_type.name = return_info.hint_string; + } else if (return_info.type == Variant::NIL && return_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT) { + method.return_type.name = r_context.names_cache.variant_type; + } else if (return_info.type == Variant::NIL) { + method.return_type.name = r_context.names_cache.void_type; + } else { + // NOTE: We don't care about the size and sign of int and float in these tests + method.return_type.name = Variant::get_type_name(return_info.type); + } + + for (int i = 0; i < argc; i++) { + PropertyInfo arg_info = method_info.arguments[i]; + + String orig_arg_name = arg_info.name; + + ArgumentData arg; + arg.name = orig_arg_name; + + if (arg_info.type == Variant::INT && arg_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { + arg.type.name = arg_info.class_name; + arg.type.is_enum = true; + } else if (arg_info.class_name != StringName()) { + arg.type.name = arg_info.class_name; + } else if (arg_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { + arg.type.name = arg_info.hint_string; + } else if (arg_info.type == Variant::NIL) { + arg.type.name = r_context.names_cache.variant_type; + } else { + // NOTE: We don't care about the size and sign of int and float in these tests + arg.type.name = Variant::get_type_name(arg_info.type); + } + + if (m && m->has_default_argument(i)) { + arg.has_defval = true; + arg.defval = m->get_default_argument(i); + } + + method.arguments.push_back(arg); + } + + if (method.is_vararg) { + ArgumentData vararg; + vararg.type.name = r_context.names_cache.vararg_stub_type; + vararg.name = "@varargs@"; + method.arguments.push_back(vararg); + } + + TEST_COND(exposed_class.find_property_by_name(method.name), + "Method name conflicts with property: '" + String(class_name) + "." + String(method.name) + "'."); + + // Classes starting with an underscore are ignored unless they're used as a property setter or getter + if (!method.is_virtual && String(method.name)[0] == '_') { + for (const List<PropertyData>::Element *F = exposed_class.properties.front(); F; F = F->next()) { + const PropertyData &prop = F->get(); + + if (prop.setter == method.name || prop.getter == method.name) { + exposed_class.methods.push_back(method); + break; + } + } + } else { + exposed_class.methods.push_back(method); + } + } + + // Add signals + + const HashMap<StringName, MethodInfo> &signal_map = class_info->signal_map; + const StringName *k = nullptr; + + while ((k = signal_map.next(k))) { + SignalData signal; + + const MethodInfo &method_info = signal_map.get(*k); + + signal.name = method_info.name; + + int argc = method_info.arguments.size(); + + for (int i = 0; i < argc; i++) { + PropertyInfo arg_info = method_info.arguments[i]; + + String orig_arg_name = arg_info.name; + + ArgumentData arg; + arg.name = orig_arg_name; + + if (arg_info.type == Variant::INT && arg_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { + arg.type.name = arg_info.class_name; + arg.type.is_enum = true; + } else if (arg_info.class_name != StringName()) { + arg.type.name = arg_info.class_name; + } else if (arg_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { + arg.type.name = arg_info.hint_string; + } else if (arg_info.type == Variant::NIL) { + arg.type.name = r_context.names_cache.variant_type; + } else { + // NOTE: We don't care about the size and sign of int and float in these tests + arg.type.name = Variant::get_type_name(arg_info.type); + } + + signal.arguments.push_back(arg); + } + + bool method_conflict = exposed_class.find_property_by_name(signal.name); + + // TODO: + // ClassDB allows signal names that conflict with method or property names. + // However registering a signal with a conflicting name is still considered wrong. + // Unfortunately there are some existing cases that are yet to be fixed. + // Until those are fixed we will print a warning instead of failing the test. + String warn_msg = vformat( + "Signal name conflicts with %s: '%s.%s.", + method_conflict ? "method" : "property", class_name, signal.name); + TEST_FAIL_COND_WARN((method_conflict || exposed_class.find_method_by_name(signal.name)), + warn_msg.utf8().get_data()); + + exposed_class.signals_.push_back(signal); + } + + // Add enums and constants + + List<String> constants; + ClassDB::get_integer_constant_list(class_name, &constants, true); + + const HashMap<StringName, List<StringName>> &enum_map = class_info->enum_map; + k = nullptr; + + while ((k = enum_map.next(k))) { + EnumData enum_; + enum_.name = *k; + + const List<StringName> &enum_constants = enum_map.get(*k); + for (const List<StringName>::Element *E = enum_constants.front(); E; E = E->next()) { + const StringName &constant_name = E->get(); + int *value = class_info->constant_map.getptr(constant_name); + TEST_FAIL_COND(!value, "Missing enum constant value: '" + + String(class_name) + "." + String(enum_.name) + "." + String(constant_name) + "'."); + constants.erase(constant_name); + + ConstantData constant; + constant.name = constant_name; + constant.value = *value; + + enum_.constants.push_back(constant); + } + + exposed_class.enums.push_back(enum_); + + r_context.enum_types.push_back(String(class_name) + "." + String(*k)); + } + + for (const List<String>::Element *E = constants.front(); E; E = E->next()) { + const String &constant_name = E->get(); + int *value = class_info->constant_map.getptr(StringName(E->get())); + TEST_FAIL_COND(!value, "Missing enum constant value: '" + String(class_name) + "." + String(constant_name) + "'."); + + ConstantData constant; + constant.name = constant_name; + constant.value = *value; + + exposed_class.constants.push_back(constant); + } + + r_context.exposed_classes.insert(class_name, exposed_class); + class_list.pop_front(); + } +} + +void add_builtin_types(Context &r_context) { + // NOTE: We don't care about the size and sign of int and float in these tests + for (int i = 0; i < Variant::VARIANT_MAX; i++) { + r_context.builtin_types.push_back(Variant::get_type_name(Variant::Type(i))); + } + + r_context.builtin_types.push_back(_STR(Variant)); + r_context.builtin_types.push_back(r_context.names_cache.vararg_stub_type); + r_context.builtin_types.push_back("void"); +} + +void add_global_enums(Context &r_context) { + int global_constants_count = GlobalConstants::get_global_constant_count(); + + if (global_constants_count > 0) { + for (int i = 0; i < global_constants_count; i++) { + StringName enum_name = GlobalConstants::get_global_constant_enum(i); + + if (enum_name != StringName()) { + ConstantData constant; + constant.name = GlobalConstants::get_global_constant_name(i); + constant.value = GlobalConstants::get_global_constant_value(i); + + EnumData enum_; + enum_.name = enum_name; + List<EnumData>::Element *enum_match = r_context.global_enums.find(enum_); + if (enum_match) { + enum_match->get().constants.push_back(constant); + } else { + enum_.constants.push_back(constant); + r_context.global_enums.push_back(enum_); + } + } + } + + for (List<EnumData>::Element *E = r_context.global_enums.front(); E; E = E->next()) { + r_context.enum_types.push_back(E->get().name); + } + } + + // HARDCODED + List<StringName> hardcoded_enums; + hardcoded_enums.push_back("Vector2.Axis"); + hardcoded_enums.push_back("Vector2i.Axis"); + hardcoded_enums.push_back("Vector3.Axis"); + hardcoded_enums.push_back("Vector3i.Axis"); + for (List<StringName>::Element *E = hardcoded_enums.front(); E; E = E->next()) { + // These enums are not generated and must be written manually (e.g.: Vector3.Axis) + // Here, we assume core types do not begin with underscore + r_context.enum_types.push_back(E->get()); + } +} + +TEST_SUITE("[ClassDB]") { + TEST_CASE("[ClassDB] Add exposed classes, builtin types, and global enums") { + Context context; + + add_exposed_classes(context); + add_builtin_types(context); + add_global_enums(context); + + SUBCASE("[ClassDB] Find exposed class") { + const ExposedClass *object_class = context.find_exposed_class(context.names_cache.object_class); + TEST_FAIL_COND(!object_class, "Object class not found."); + TEST_FAIL_COND(object_class->base != StringName(), + "Object class derives from another class: '" + object_class->base + "'."); + + for (ExposedClasses::Element E = context.exposed_classes.front(); E; E = E.next()) { + validate_class(context, E.value()); + } + } + } +} + +} // namespace TestClassDB + #endif //GODOT_TEST_CLASS_DB_H diff --git a/tests/test_gradient.h b/tests/test_gradient.h new file mode 100644 index 0000000000..88fe06b3ec --- /dev/null +++ b/tests/test_gradient.h @@ -0,0 +1,152 @@ +/*************************************************************************/ +/* test_gradient.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TEST_GRADIENT_H +#define TEST_GRADIENT_H + +#include "core/class_db.h" +#include "core/color.h" +#include "scene/resources/gradient.h" + +#include "thirdparty/doctest/doctest.h" + +namespace TestGradient { + +TEST_CASE("[Gradient] Default gradient") { + // Black-white gradient. + Ref<Gradient> gradient = memnew(Gradient); + + CHECK_MESSAGE( + gradient->get_points_count() == 2, + "Default gradient should contain the expected number of points."); + + CHECK_MESSAGE( + gradient->get_color_at_offset(0.0).is_equal_approx(Color(0, 0, 0)), + "Default gradient should return the expected interpolated value at offset 0.0."); + CHECK_MESSAGE( + gradient->get_color_at_offset(0.4).is_equal_approx(Color(0.4, 0.4, 0.4)), + "Default gradient should return the expected interpolated value at offset 0.4."); + CHECK_MESSAGE( + gradient->get_color_at_offset(0.8).is_equal_approx(Color(0.8, 0.8, 0.8)), + "Default gradient should return the expected interpolated value at offset 0.8."); + CHECK_MESSAGE( + gradient->get_color_at_offset(1.0).is_equal_approx(Color(1, 1, 1)), + "Default gradient should return the expected interpolated value at offset 1.0."); + + // Out of bounds checks. + CHECK_MESSAGE( + gradient->get_color_at_offset(-1.0).is_equal_approx(Color(0, 0, 0)), + "Default gradient should return the expected interpolated value at offset -1.0."); + CHECK_MESSAGE( + gradient->get_color_at_offset(1234.0).is_equal_approx(Color(1, 1, 1)), + "Default gradient should return the expected interpolated value at offset 1234.0."); +} + +TEST_CASE("[Gradient] Custom gradient (points specified in order)") { + // Red-yellow-green gradient (with overbright green). + Ref<Gradient> gradient = memnew(Gradient); + Vector<Gradient::Point> points; + points.push_back({ 0.0, Color(1, 0, 0) }); + points.push_back({ 0.5, Color(1, 1, 0) }); + points.push_back({ 1.0, Color(0, 2, 0) }); + gradient->set_points(points); + + CHECK_MESSAGE( + gradient->get_points_count() == 3, + "Custom gradient should contain the expected number of points."); + + CHECK_MESSAGE( + gradient->get_color_at_offset(0.0).is_equal_approx(Color(1, 0, 0)), + "Custom gradient should return the expected interpolated value at offset 0.0."); + CHECK_MESSAGE( + gradient->get_color_at_offset(0.25).is_equal_approx(Color(1, 0.5, 0)), + "Custom gradient should return the expected interpolated value at offset 0.25."); + CHECK_MESSAGE( + gradient->get_color_at_offset(0.5).is_equal_approx(Color(1, 1, 0)), + "Custom gradient should return the expected interpolated value at offset 0.5."); + CHECK_MESSAGE( + gradient->get_color_at_offset(0.75).is_equal_approx(Color(0.5, 1.5, 0)), + "Custom gradient should return the expected interpolated value at offset 0.75."); + CHECK_MESSAGE( + gradient->get_color_at_offset(1.0).is_equal_approx(Color(0, 2, 0)), + "Custom gradient should return the expected interpolated value at offset 1.0."); + + gradient->remove_point(1); + CHECK_MESSAGE( + gradient->get_points_count() == 2, + "Custom gradient should contain the expected number of points after removing one point."); + CHECK_MESSAGE( + gradient->get_color_at_offset(0.5).is_equal_approx(Color(0.5, 1, 0)), + "Custom gradient should return the expected interpolated value at offset 0.5 after removing point at index 1."); +} + +TEST_CASE("[Gradient] Custom gradient (points specified out-of-order)") { + // HSL rainbow with points specified out of order. + // These should be sorted automatically when adding points. + Ref<Gradient> gradient = memnew(Gradient); + Vector<Gradient::Point> points; + points.push_back({ 0.2, Color(1, 0, 0) }); + points.push_back({ 0.0, Color(1, 1, 0) }); + points.push_back({ 0.8, Color(0, 1, 0) }); + points.push_back({ 0.4, Color(0, 1, 1) }); + points.push_back({ 1.0, Color(0, 0, 1) }); + points.push_back({ 0.6, Color(1, 0, 1) }); + gradient->set_points(points); + + CHECK_MESSAGE( + gradient->get_points_count() == 6, + "Custom out-of-order gradient should contain the expected number of points."); + + CHECK_MESSAGE( + gradient->get_color_at_offset(0.0).is_equal_approx(Color(1, 1, 0)), + "Custom out-of-order gradient should return the expected interpolated value at offset 0.0."); + CHECK_MESSAGE( + gradient->get_color_at_offset(0.3).is_equal_approx(Color(0.5, 0.5, 0.5)), + "Custom out-of-order gradient should return the expected interpolated value at offset 0.3."); + CHECK_MESSAGE( + gradient->get_color_at_offset(0.6).is_equal_approx(Color(1, 0, 1)), + "Custom out-of-order gradient should return the expected interpolated value at offset 0.6."); + CHECK_MESSAGE( + gradient->get_color_at_offset(1.0).is_equal_approx(Color(0, 0, 1)), + "Custom out-of-order gradient should return the expected interpolated value at offset 1.0."); + + gradient->remove_point(0); + CHECK_MESSAGE( + gradient->get_points_count() == 5, + "Custom out-of-order gradient should contain the expected number of points after removing one point."); + // The color will be clamped to the nearest point (which is at offset 0.2). + CHECK_MESSAGE( + gradient->get_color_at_offset(0.1).is_equal_approx(Color(1, 0, 0)), + "Custom out-of-order gradient should return the expected interpolated value at offset 0.1 after removing point at index 0."); +} + +} // namespace TestGradient + +#endif // TEST_GRADIENT_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 137882841c..892cc26a51 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -37,6 +37,7 @@ #include "test_class_db.h" #include "test_color.h" #include "test_gdscript.h" +#include "test_gradient.h" #include "test_gui.h" #include "test_math.h" #include "test_oa_hash_map.h" diff --git a/tests/test_ordered_hash_map.cpp b/tests/test_ordered_hash_map.cpp deleted file mode 100644 index d18a3784be..0000000000 --- a/tests/test_ordered_hash_map.cpp +++ /dev/null @@ -1,175 +0,0 @@ -/*************************************************************************/ -/* test_ordered_hash_map.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "test_ordered_hash_map.h" - -#include "core/ordered_hash_map.h" -#include "core/os/os.h" -#include "core/pair.h" -#include "core/vector.h" - -namespace TestOrderedHashMap { - -bool test_insert() { - OrderedHashMap<int, int> map; - OrderedHashMap<int, int>::Element e = map.insert(42, 84); - - return e && e.key() == 42 && e.get() == 84 && e.value() == 84 && map[42] == 84 && map.has(42) && map.find(42); -} - -bool test_insert_overwrite() { - OrderedHashMap<int, int> map; - map.insert(42, 84); - map.insert(42, 1234); - - return map[42] == 1234; -} - -bool test_erase_via_element() { - OrderedHashMap<int, int> map; - OrderedHashMap<int, int>::Element e = map.insert(42, 84); - - map.erase(e); - return !e && !map.has(42) && !map.find(42); -} - -bool test_erase_via_key() { - OrderedHashMap<int, int> map; - map.insert(42, 84); - map.erase(42); - return !map.has(42) && !map.find(42); -} - -bool test_size() { - OrderedHashMap<int, int> map; - map.insert(42, 84); - map.insert(123, 84); - map.insert(123, 84); - map.insert(0, 84); - map.insert(123485, 84); - - return map.size() == 4; -} - -bool test_iteration() { - OrderedHashMap<int, int> map; - map.insert(42, 84); - map.insert(123, 12385); - map.insert(0, 12934); - map.insert(123485, 1238888); - map.insert(123, 111111); - - Vector<Pair<int, int>> expected; - expected.push_back(Pair<int, int>(42, 84)); - expected.push_back(Pair<int, int>(123, 111111)); - expected.push_back(Pair<int, int>(0, 12934)); - expected.push_back(Pair<int, int>(123485, 1238888)); - - int idx = 0; - for (OrderedHashMap<int, int>::Element E = map.front(); E; E = E.next()) { - if (expected[idx] != Pair<int, int>(E.key(), E.value())) { - return false; - } - ++idx; - } - return true; -} - -bool test_const_iteration(const OrderedHashMap<int, int> &map) { - Vector<Pair<int, int>> expected; - expected.push_back(Pair<int, int>(42, 84)); - expected.push_back(Pair<int, int>(123, 111111)); - expected.push_back(Pair<int, int>(0, 12934)); - expected.push_back(Pair<int, int>(123485, 1238888)); - - int idx = 0; - for (OrderedHashMap<int, int>::ConstElement E = map.front(); E; E = E.next()) { - if (expected[idx] != Pair<int, int>(E.key(), E.value())) { - return false; - } - ++idx; - } - return true; -} - -bool test_const_iteration() { - OrderedHashMap<int, int> map; - map.insert(42, 84); - map.insert(123, 12385); - map.insert(0, 12934); - map.insert(123485, 1238888); - map.insert(123, 111111); - - return test_const_iteration(map); -} - -typedef bool (*TestFunc)(); - -TestFunc test_funcs[] = { - - test_insert, - test_insert_overwrite, - test_erase_via_element, - test_erase_via_key, - test_size, - test_iteration, - test_const_iteration, - nullptr - -}; - -MainLoop *test() { - int count = 0; - int passed = 0; - - while (true) { - if (!test_funcs[count]) { - break; - } - bool pass = test_funcs[count](); - if (pass) { - passed++; - } - OS::get_singleton()->print("\t%s\n", pass ? "PASS" : "FAILED"); - - count++; - } - - OS::get_singleton()->print("\n\n\n"); - OS::get_singleton()->print("*************\n"); - OS::get_singleton()->print("***TOTALS!***\n"); - OS::get_singleton()->print("*************\n"); - - OS::get_singleton()->print("Passed %i of %i tests\n", passed, count); - - return nullptr; -} - -} // namespace TestOrderedHashMap diff --git a/tests/test_ordered_hash_map.h b/tests/test_ordered_hash_map.h index f251da0ba2..3182c391cb 100644 --- a/tests/test_ordered_hash_map.h +++ b/tests/test_ordered_hash_map.h @@ -31,11 +31,109 @@ #ifndef TEST_ORDERED_HASH_MAP_H #define TEST_ORDERED_HASH_MAP_H -#include "core/os/main_loop.h" +#include "core/ordered_hash_map.h" +#include "core/os/os.h" +#include "core/pair.h" +#include "core/vector.h" + +#include "tests/test_macros.h" namespace TestOrderedHashMap { -MainLoop *test(); +TEST_CASE("[OrderedHashMap] Insert element") { + OrderedHashMap<int, int> map; + OrderedHashMap<int, int>::Element e = map.insert(42, 84); + + CHECK(e); + CHECK(e.key() == 42); + CHECK(e.get() == 84); + CHECK(e.value() == 84); + CHECK(map[42] == 84); + CHECK(map.has(42)); + CHECK(map.find(42)); +} + +TEST_CASE("[OrderedHashMap] Overwrite element") { + OrderedHashMap<int, int> map; + map.insert(42, 84); + map.insert(42, 1234); + + CHECK(map[42] == 1234); +} + +TEST_CASE("[OrderedHashMap] Erase via element") { + OrderedHashMap<int, int> map; + OrderedHashMap<int, int>::Element e = map.insert(42, 84); + + map.erase(e); + CHECK(!e); + CHECK(!map.has(42)); + CHECK(!map.find(42)); +} + +TEST_CASE("[OrderedHashMap] Erase via key") { + OrderedHashMap<int, int> map; + map.insert(42, 84); + map.erase(42); + CHECK(!map.has(42)); + CHECK(!map.find(42)); } +TEST_CASE("[OrderedHashMap] Size") { + OrderedHashMap<int, int> map; + map.insert(42, 84); + map.insert(123, 84); + map.insert(123, 84); + map.insert(0, 84); + map.insert(123485, 84); + + CHECK(map.size() == 4); +} + +TEST_CASE("[OrderedHashMap] Iteration") { + OrderedHashMap<int, int> map; + map.insert(42, 84); + map.insert(123, 12385); + map.insert(0, 12934); + map.insert(123485, 1238888); + map.insert(123, 111111); + + Vector<Pair<int, int>> expected; + expected.push_back(Pair<int, int>(42, 84)); + expected.push_back(Pair<int, int>(123, 111111)); + expected.push_back(Pair<int, int>(0, 12934)); + expected.push_back(Pair<int, int>(123485, 1238888)); + + int idx = 0; + for (OrderedHashMap<int, int>::Element E = map.front(); E; E = E.next()) { + CHECK(expected[idx] == Pair<int, int>(E.key(), E.value())); + ++idx; + } +} + +TEST_CASE("[OrderedHashMap] Const iteration") { + OrderedHashMap<int, int> map; + map.insert(42, 84); + map.insert(123, 12385); + map.insert(0, 12934); + map.insert(123485, 1238888); + map.insert(123, 111111); + + const OrderedHashMap<int, int> const_map = map; + + Vector<Pair<int, int>> expected; + expected.push_back(Pair<int, int>(42, 84)); + expected.push_back(Pair<int, int>(123, 111111)); + expected.push_back(Pair<int, int>(0, 12934)); + expected.push_back(Pair<int, int>(123485, 1238888)); + + int idx = 0; + for (OrderedHashMap<int, int>::ConstElement E = const_map.front(); E; E = E.next()) { + CHECK(expected[idx] == Pair<int, int>(E.key(), E.value())); + ++idx; + } +} + +} // namespace TestOrderedHashMap + #endif // TEST_ORDERED_HASH_MAP_H diff --git a/tests/test_validate_testing.h b/tests/test_validate_testing.h index 4bcc57d9c5..b4ea6eb576 100644 --- a/tests/test_validate_testing.h +++ b/tests/test_validate_testing.h @@ -54,8 +54,6 @@ TEST_SUITE("Validate tests") { CHECK_MESSAGE(_print_error_enabled, "Error printing should be re-enabled."); } TEST_CASE("Stringify Variant types") { - ClassDB::init(); // For objects. - Variant var; INFO(var); @@ -185,8 +183,6 @@ TEST_SUITE("Validate tests") { << var << " " << vec2 << " " << rect2 << " " << color); CHECK(true); // So all above prints. - - ClassDB::cleanup(); } } |