summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/linux_builds.yml61
-rw-r--r--.mailmap9
-rw-r--r--AUTHORS.md9
-rw-r--r--DONORS.md220
-rw-r--r--core/SCsub8
-rw-r--r--core/core_builders.py6
-rw-r--r--core/engine.cpp4
-rw-r--r--core/project_settings.h4
-rw-r--r--core/rid_owner.h2
-rw-r--r--doc/classes/AnimatedSprite2D.xml1
-rw-r--r--doc/classes/AnimatedSprite3D.xml1
-rw-r--r--doc/classes/AnimatedTexture.xml3
-rw-r--r--doc/classes/AnimationPlayer.xml1
-rw-r--r--doc/classes/CollisionShape2D.xml2
-rw-r--r--doc/classes/DynamicFont.xml2
-rw-r--r--doc/classes/EditorExportPlugin.xml12
-rw-r--r--doc/classes/Engine.xml2
-rw-r--r--doc/classes/HSlider.xml1
-rw-r--r--doc/classes/Line2D.xml2
-rw-r--r--doc/classes/PhysicsServer2D.xml1
-rw-r--r--doc/classes/RichTextLabel.xml1
-rw-r--r--doc/classes/Slider.xml1
-rw-r--r--doc/classes/VSlider.xml1
-rw-r--r--editor/connections_dialog.cpp58
-rw-r--r--editor/connections_dialog.h3
-rw-r--r--editor/editor_about.cpp7
-rw-r--r--editor/editor_export.cpp9
-rw-r--r--editor/editor_export.h4
-rw-r--r--editor/editor_properties.cpp33
-rw-r--r--editor/editor_settings.cpp1
-rw-r--r--editor/filesystem_dock.cpp47
-rw-r--r--editor/filesystem_dock.h1
-rw-r--r--editor/icons/GuiToggleOff.svg2
-rw-r--r--editor/icons/GuiToggleOn.svg2
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp34
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.cpp186
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.h25
-rw-r--r--editor/plugins/tile_set_editor_plugin.cpp18
-rw-r--r--editor/project_settings_editor.cpp36
-rw-r--r--editor/project_settings_editor.h4
-rw-r--r--editor/property_selector.cpp27
-rw-r--r--editor/rename_dialog.cpp29
-rw-r--r--main/SCsub8
-rw-r--r--main/main.cpp88
-rw-r--r--main/main.h6
-rw-r--r--methods.py9
-rwxr-xr-xmisc/dist/osx_tools.app/Contents/Info.plist2
-rw-r--r--modules/bmp/image_loader_bmp.cpp31
-rw-r--r--modules/gdscript/gdscript.cpp15
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp164
-rw-r--r--modules/gdscript/gdscript_analyzer.h2
-rw-r--r--modules/gdscript/gdscript_cache.cpp15
-rw-r--r--modules/gdscript/gdscript_parser.cpp108
-rw-r--r--modules/gdscript/gdscript_parser.h5
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp58
-rw-r--r--modules/gdscript/gdscript_tokenizer.h6
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.cpp2
-rw-r--r--modules/mobile_vr/register_types.cpp8
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs10
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/BuildManager.cs19
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs3
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs23
-rw-r--r--platform/iphone/detect.py5
-rw-r--r--platform/iphone/export/export.cpp48
-rw-r--r--platform/linuxbsd/display_server_x11.cpp12
-rw-r--r--platform/linuxbsd/display_server_x11.h1
-rw-r--r--platform/linuxbsd/os_linuxbsd.cpp4
-rw-r--r--platform/linuxbsd/os_linuxbsd.h2
-rw-r--r--platform/osx/detect.py1
-rw-r--r--platform/osx/display_server_osx.h1
-rw-r--r--platform/osx/display_server_osx.mm17
-rw-r--r--platform/osx/os_osx.h2
-rw-r--r--platform/osx/os_osx.mm4
-rw-r--r--platform/windows/display_server_windows.cpp24
-rw-r--r--platform/windows/display_server_windows.h1
-rw-r--r--scene/gui/line_edit.cpp2
-rw-r--r--scene/gui/spin_box.cpp2
-rw-r--r--scene/gui/text_edit.cpp5
-rw-r--r--scene/main/window.cpp1
-rw-r--r--scene/register_scene_types.cpp12
-rw-r--r--scene/resources/environment.cpp238
-rw-r--r--scene/resources/environment.h66
-rw-r--r--scene/resources/resource_format_text.cpp6
-rw-r--r--servers/display_server.cpp4
-rw-r--r--servers/display_server.h1
-rw-r--r--servers/rendering/rasterizer.h5
-rw-r--r--servers/rendering/rasterizer_rd/rasterizer_effects_rd.cpp4
-rw-r--r--servers/rendering/rasterizer_rd/rasterizer_effects_rd.h2
-rw-r--r--servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp88
-rw-r--r--servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.h10
-rw-r--r--servers/rendering/rasterizer_rd/rasterizer_scene_rd.cpp453
-rw-r--r--servers/rendering/rasterizer_rd/rasterizer_scene_rd.h60
-rw-r--r--servers/rendering/rasterizer_rd/shaders/scene_high_end.glsl42
-rw-r--r--servers/rendering/rasterizer_rd/shaders/scene_high_end_inc.glsl38
-rw-r--r--servers/rendering/rasterizer_rd/shaders/sky.glsl81
-rw-r--r--servers/rendering/rendering_server_raster.h4
-rw-r--r--servers/rendering/rendering_server_wrap_mt.h5
-rw-r--r--servers/rendering/shader_types.cpp1
-rw-r--r--servers/rendering_server.cpp6
-rw-r--r--servers/rendering_server.h4
-rw-r--r--tests/test_class_db.cpp882
-rw-r--r--tests/test_class_db.h798
-rw-r--r--tests/test_gradient.h152
-rw-r--r--tests/test_main.cpp1
-rw-r--r--tests/test_ordered_hash_map.cpp175
-rw-r--r--tests/test_ordered_hash_map.h102
-rw-r--r--tests/test_validate_testing.h4
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)
diff --git a/.mailmap b/.mailmap
index 4b427a8a5e..f2f69eb9da 100644
--- a/.mailmap
+++ b/.mailmap
@@ -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)
diff --git a/DONORS.md b/DONORS.md
index 16a30b9489..88e674f043 100644
--- a/DONORS.md
+++ b/DONORS.md
@@ -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 &gt; 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 &gt; 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();
}
}