diff --git a/.gitignore b/.gitignore index 744f54575..4cf92b549 100644 --- a/.gitignore +++ b/.gitignore @@ -85,13 +85,3 @@ moc_*.cxx *.[ao] *.so venv/ - -## recastnavigation unused files -extern/recastnavigation/.travis.yml -extern/recastnavigation/CONTRIBUTING.md -extern/recastnavigation/Docs/ -extern/recastnavigation/Doxyfile -extern/recastnavigation/README.md -extern/recastnavigation/RecastDemo/ -extern/recastnavigation/Tests/ -extern/recastnavigation/appveyor.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index acfdc9e50..0891ecba9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,6 @@ 0.47.0 ------ - Bug #832: OpenMW-CS: Handle deleted references Bug #1662: Qt4 and Windows binaries crash if there's a non-ASCII character in a file path/config path Bug #1901: Actors colliding behaviour is different from vanilla Bug #1952: Incorrect particle lighting @@ -9,7 +8,7 @@ Bug #2311: Targeted scripts are not properly supported on non-unique RefIDs Bug #2473: Unable to overstock merchants Bug #2798: Mutable ESM records - Bug #2976 [reopened]: Issues combining settings from the command line and both config files + Bug #2976: [reopened]: Issues combining settings from the command line and both config files Bug #3137: Walking into a wall prevents jumping Bug #3372: Projectiles and magic bolts go through moving targets Bug #3676: NiParticleColorModifier isn't applied properly @@ -24,7 +23,7 @@ Bug #4201: Projectile-projectile collision Bug #4247: Cannot walk up stairs in Ebonheart docks Bug #4357: OpenMW-CS: TopicInfos index sorting and rearranging isn't fully functional - Bug #4363: Editor: Defect in Clone Function for Dialogue Info records + Bug #4363: OpenMW-CS: Defect in Clone Function for Dialogue Info records Bug #4447: Actor collision capsule shape allows looking through some walls Bug #4465: Collision shape overlapping causes twitching Bug #4476: Abot Gondoliers: player hangs in air during scenic travel @@ -34,6 +33,7 @@ Bug #4764: Data race in osg ParticleSystem Bug #4765: Data race in ChunkManager -> Array::setBinding Bug #4774: Guards are ignorant of an invisible player that tries to attack them + Bug #5026: Data races with rain intensity uniform set by sky and used by water Bug #5101: Hostile followers travel with the player Bug #5108: Savegame bloating due to inefficient fog textures format Bug #5165: Active spells should use real time intead of timestamps @@ -44,11 +44,11 @@ Bug #5367: Selecting a spell on an enchanted item per hotkey always plays the equip sound Bug #5369: Spawnpoint in the Grazelands doesn't produce oversized creatures Bug #5370: Opening an unlocked but trapped door uses the key - Bug #5384: openmw-cs: deleting an instance requires reload of scene window to show in editor + Bug #5384: OpenMW-CS: Deleting an instance requires reload of scene window to show in editor Bug #5387: Move/MoveWorld don't update the object's cell properly Bug #5391: Races Redone 1.2 bodies don't show on the inventory Bug #5397: NPC greeting does not reset if you leave + reenter area - Bug #5400: Editor: Verifier checks race of non-skin bodyparts + Bug #5400: OpenMW-CS: Verifier checks race of non-skin bodyparts Bug #5403: Enchantment effect doesn't show on an enemy during death animation Bug #5415: Environment maps in ebony cuirass and HiRez Armors Indoril cuirass don't work Bug #5416: Junk non-node records before the root node are not handled gracefully @@ -70,6 +70,7 @@ Bug #5499: Faction advance is available when requirements not met Bug #5502: Dead zone for analogue stick movement is too small Bug #5507: Sound volume is not clamped on ingame settings update + Bug #5525: Case-insensitive search in the inventory window does not work with non-ASCII characters Bug #5531: Actors flee using current rotation by axis x Bug #5539: Window resize breaks when going from a lower resolution to full screen resolution Bug #5548: Certain exhausted topics can be highlighted again even though there's no new dialogue @@ -93,26 +94,35 @@ Bug #5703: OpenMW-CS menu system crashing on XFCE Bug #5706: AI sequences stop looping after the saved game is reloaded Bug #5713: OpenMW-CS: Collada models are corrupted in Qt-based scene view - Bug #5731: Editor: skirts are invisible on characters + Bug #5731: OpenMW-CS: skirts are invisible on characters Bug #5739: Saving and loading the save a second or two before hitting the ground doesn't count fall damage Bug #5758: Paralyzed actors behavior is inconsistent with vanilla Bug #5762: Movement solver is insufficiently robust + Bug #5807: Video decoding crash on ARM Bug #5821: NPCs from mods getting removed if mod order was changed Bug #5835: OpenMW doesn't accept negative values for NPC's hello, alarm, fight, and flee Bug #5836: OpenMW dialogue/greeting/voice filter doesn't accept negative Ai values for NPC's hello, alarm, fight, and flee Bug #5838: Local map and other menus become blank in some locations while playing Wizards' Islands mod. Bug #5840: GetSoundPlaying "Health Damage" doesn't play when NPC hits target with shield effect ( vanilla engine behavior ) Bug #5841: Can't Cast Zero Cost Spells When Magicka is < 0 + Bug #5869: Guards can initiate arrest dialogue behind locked doors + Bug #5871: The console appears if you type the Russian letter "Ё" in the name of the enchantment + Bug #5877: Effects appearing with empty icon + Bug #5899: Visible modal windows and dropdowns crashing game on exit + Bug #5902: NiZBufferProperty is unable to disable the depth test + Bug #5906: Sunglare doesn't work with Mesa drivers and AMD GPUs Feature #390: 3rd person look "over the shoulder" + Feature #832: OpenMW-CS: Handle deleted references Feature #1536: Show more information about level on menu Feature #2386: Distant Statics in the form of Object Paging Feature #2404: Levelled List can not be placed into a container Feature #2686: Timestamps in openmw.log Feature #3171: OpenMW-CS: Instance drag selection Feature #4894: Consider actors as obstacles for pathfinding + Feature #4899: Alpha-To-Coverage Anti-Aliasing for alpha testing Feature #4977: Use the "default icon.tga" when an item's icon is not found Feature #5043: Head Bobbing - Feature #5199: Improve Scene Colors + Feature #5199: OpenMW-CS: Improve scene view colors Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher Feature #5362: Show the soul gems' trapped soul in count dialog Feature #5445: Handle NiLines @@ -121,7 +131,6 @@ Feature #5486: Fixes trainers to choose their training skills based on their base skill points Feature #5519: Code Patch tab in launcher Feature #5524: Resume failed script execution after reload - Feature #5525: Search fields tweaks (utf-8) Feature #5545: Option to allow stealing from an unconscious NPC during combat Feature #5563: Run physics update in background thread Feature #5579: MCP SetAngle enhancement diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index ba0ff00ca..6dad0abc1 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -1,13 +1,19 @@ -#!/bin/sh -e +#!/bin/sh -ex # workaround python issue on travis HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.8 || true HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.9 || true +HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies qt@6 || true # Some of these tools can come from places other than brew, so check before installing command -v ccache >/dev/null 2>&1 || brew install ccache command -v cmake >/dev/null 2>&1 || brew install cmake -command -v qmake >/dev/null 2>&1 || brew install qt +command -v qmake >/dev/null 2>&1 || brew install qt@5 +export PATH="/usr/local/opt/qt@5/bin:$PATH" # needed to use qmake in none default path as qt now points to qt6 + +ccache --version +cmake --version +qmake --version curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-f8918dd.zip -o ~/openmw-deps.zip unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 0c26801cc..dba87128a 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -325,6 +325,16 @@ add_qt_platform_dlls() { QT_PLATFORMS[$CONFIG]="${QT_PLATFORMS[$CONFIG]} $@" } +declare -A QT_STYLES +QT_STYLES["Release"]="" +QT_STYLES["Debug"]="" +QT_STYLES["RelWithDebInfo"]="" +add_qt_style_dlls() { + local CONFIG=$1 + shift + QT_STYLES[$CONFIG]="${QT_STYLES[$CONFIG]} $@" +} + if [ -z $PLATFORM ]; then PLATFORM="$(uname -m)" fi @@ -868,6 +878,7 @@ fi fi add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll add_qt_platform_dlls $CONFIGURATION "$(pwd)/plugins/platforms/qwindows${DLLSUFFIX}.dll" + add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll" done echo Done. else @@ -883,6 +894,7 @@ fi DIR=$(windowsPathAsUnix "${QT_SDK}") add_runtime_dlls $CONFIGURATION "${DIR}/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll add_qt_platform_dlls $CONFIGURATION "${DIR}/plugins/platforms/qwindows${DLLSUFFIX}.dll" + add_qt_style_dlls $CONFIGURATION "${DIR}/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll" done echo Done. fi @@ -1060,6 +1072,13 @@ fi cp "$DLL" "${DLL_PREFIX}platforms" done echo + echo "- Qt Style DLLs..." + mkdir -p ${DLL_PREFIX}styles + for DLL in ${QT_STYLES[$CONFIGURATION]}; do + echo " $(basename $DLL)" + cp "$DLL" "${DLL_PREFIX}styles" + done + echo done #fi @@ -1067,7 +1086,13 @@ if [ -n "$ACTIVATE_MSVC" ]; then echo -n "- Activating MSVC in the current shell... " command -v vswhere >/dev/null 2>&1 || { echo "Error: vswhere is not on the path."; wrappedExit 1; } - MSVC_INSTALLATION_PATH=$(vswhere -products '*' -version "[$MSVC_REAL_VER,$(awk "BEGIN { print $MSVC_REAL_VER + 1; exit }"))" -property installationPath) + # There are so many arguments now that I'm going to document them: + # * products: allow Visual Studio or standalone build tools + # * version: obvious. Awk helps make a version range by adding one. + # * property installationPath: only give the installation path. + # * latest: return only one result if several candidates exist. Prefer the last installed/updated + # * requires: make sure it's got the MSVC compiler instead of, for example, just the .NET compiler. The .x86.x64 suffix means it's for either, not that it's the x64 on x86 cross compiler as you always get both + MSVC_INSTALLATION_PATH=$(vswhere -products '*' -version "[$MSVC_REAL_VER,$(awk "BEGIN { print $MSVC_REAL_VER + 1; exit }"))" -property installationPath -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64) if [ -z "$MSVC_INSTALLATION_PATH" ]; then echo "vswhere was unable to find MSVC $MSVC_DISPLAY_YEAR" wrappedExit 1 diff --git a/CI/before_script.osx.sh b/CI/before_script.osx.sh index 8f9be16e1..36fa79299 100755 --- a/CI/before_script.osx.sh +++ b/CI/before_script.osx.sh @@ -4,7 +4,7 @@ export CXX=clang++ export CC=clang DEPENDENCIES_ROOT="/private/tmp/openmw-deps/openmw-deps" -QT_PATH=$(brew --prefix qt) +QT_PATH=$(brew --prefix qt@5) CCACHE_EXECUTABLE=$(brew --prefix ccache)/bin/ccache mkdir build cd build diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index 82d2ff681..3e7ab7fca 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -23,6 +23,7 @@ declare -rA GROUPED_DEPS=( libsdl2-dev libqt5opengl5-dev libopenal-dev libunshield-dev libtinyxml-dev libbullet-dev liblz4-dev libpng-dev libjpeg-dev " + # TODO: add librecastnavigation-dev when debian is ready # These dependencies can alternatively be built and linked statically. [openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev" diff --git a/CMakeLists.txt b/CMakeLists.txt index 853cf5108..01c666a49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,11 @@ endif() # Detect OS include(cmake/OSIdentity.cmake) +option(OPENMW_GL4ES_MANUAL_INIT "Manually initialize gl4es. This is more reliable on platforms without a windowing system. Requires gl4es to be configured with -DNOEGL=ON -DNO_LOADER=ON -DNO_INIT_CONSTRUCTOR=ON." OFF) +if(OPENMW_GL4ES_MANUAL_INIT) + add_definitions(-DOPENMW_GL4ES_MANUAL_INIT) +endif() + # Apps and tools option(BUILD_OPENMW "Build OpenMW" ON) option(BUILD_LAUNCHER "Build Launcher" ON) @@ -113,6 +118,12 @@ option(SDL2_STATIC "Link static build of SDL into the binaries" FALSE) option(QT_STATIC "Link static build of QT into the binaries" FALSE) option(OPENMW_USE_SYSTEM_BULLET "Use system provided bullet physics library" ON) +if(OPENMW_USE_SYSTEM_BULLET) + set(_bullet_static_default OFF) +else() + set(_bullet_static_default ON) +endif() +option(BULLET_STATIC "Link static build of Bullet into the binaries" ${_bullet_static_default}) option(OPENMW_USE_SYSTEM_OSG "Use system provided OpenSceneGraph libraries" ON) if(OPENMW_USE_SYSTEM_OSG) @@ -133,6 +144,7 @@ option(MYGUI_STATIC "Link static build of Mygui into the binaries" ${_mygui_stat option(OPENMW_USE_SYSTEM_RECASTNAVIGATION "Use system provided recastnavigation library" OFF) if(OPENMW_USE_SYSTEM_RECASTNAVIGATION) set(_recastnavigation_static_default OFF) + find_package(RecastNavigation REQUIRED) else() set(_recastnavigation_static_default ON) endif() @@ -369,7 +381,9 @@ set(BOOST_COMPONENTS system filesystem program_options iostreams) if(WIN32) set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale) if(MSVC) - set(BOOST_COMPONENTS ${BOOST_COMPONENTS} zlib) + # boost-zlib is not present (nor needed) in vcpkg version of boost. + # there, it is part of boost-iostreams instead. + set(BOOST_OPTIONAL_COMPONENTS zlib) endif(MSVC) endif(WIN32) @@ -379,7 +393,7 @@ endif() set(Boost_NO_BOOST_CMAKE ON) -find_package(Boost 1.6.2 REQUIRED COMPONENTS ${BOOST_COMPONENTS}) +find_package(Boost 1.6.2 REQUIRED COMPONENTS ${BOOST_COMPONENTS} OPTIONAL_COMPONENTS ${BOOST_OPTIONAL_COMPONENTS}) if(OPENMW_USE_SYSTEM_MYGUI) find_package(MyGUI 3.2.2 REQUIRED) endif() diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 17b3375f5..65575580a 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -215,12 +215,16 @@ if(APPLE) endif(APPLE) target_link_libraries(openmw-cs - ${OSG_LIBRARIES} - ${OSGTEXT_LIBRARIES} - ${OSGUTIL_LIBRARIES} + # CMake's built-in OSG finder does not use pkgconfig, so we have to + # manually ensure the order is correct for inter-library dependencies. + # This only makes a difference with `-DOPENMW_USE_SYSTEM_OSG=ON -DOSG_STATIC=ON`. + # https://gitlab.kitware.com/cmake/cmake/-/issues/21701 ${OSGVIEWER_LIBRARIES} - ${OSGGA_LIBRARIES} ${OSGFX_LIBRARIES} + ${OSGGA_LIBRARIES} + ${OSGUTIL_LIBRARIES} + ${OSGTEXT_LIBRARIES} + ${OSG_LIBRARIES} ${EXTERN_OSGQT_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} @@ -233,13 +237,24 @@ if(OSG_STATIC) add_library(openmw_cs_osg_plugins INTERFACE) foreach(_plugin ${USED_OSG_PLUGINS}) string(TOUPPER ${_plugin} _plugin_uc) - list(APPEND _osg_plugins_static_files $) - target_link_libraries(openmw_cs_osg_plugins INTERFACE ${${_plugin_uc}_LIBRARY}) + if(OPENMW_USE_SYSTEM_OSG) + list(APPEND _osg_plugins_static_files ${${_plugin_uc}_LIBRARY}) + else() + list(APPEND _osg_plugins_static_files $) + target_link_libraries(openmw_cs_osg_plugins INTERFACE $) + add_dependencies(openmw_cs_osg_plugins ${${_plugin_uc}_LIBRARY}) + endif() endforeach() # We use --whole-archive because OSG plugins use registration. get_whole_archive_options(_opts ${_osg_plugins_static_files}) target_link_options(openmw_cs_osg_plugins INTERFACE ${_opts}) target_link_libraries(openmw-cs openmw_cs_osg_plugins) + + if(OPENMW_USE_SYSTEM_OSG) + # OSG plugin pkgconfig files are missing these dependencies. + # https://github.com/openscenegraph/OpenSceneGraph/issues/1052 + target_link_libraries(openmw freetype jpeg png) + endif() endif(OSG_STATIC) target_link_libraries(openmw-cs Qt5::Widgets Qt5::Core Qt5::Network Qt5::OpenGL) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 5aa2bd541..d5122892c 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -169,13 +169,18 @@ include_directories( ) target_link_libraries(tes3mp - ${OSG_LIBRARIES} + # CMake's built-in OSG finder does not use pkgconfig, so we have to + # manually ensure the order is correct for inter-library dependencies. + # This only makes a difference with `-DOPENMW_USE_SYSTEM_OSG=ON -DOSG_STATIC=ON`. + # https://gitlab.kitware.com/cmake/cmake/-/issues/21701 ${OSGPARTICLE_LIBRARIES} - ${OSGUTIL_LIBRARIES} - ${OSGDB_LIBRARIES} ${OSGVIEWER_LIBRARIES} ${OSGGA_LIBRARIES} ${OSGSHADOW_LIBRARIES} + ${OSGDB_LIBRARIES} + ${OSGUTIL_LIBRARIES} + ${OSG_LIBRARIES} + ${Boost_SYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} @@ -196,13 +201,24 @@ if(OSG_STATIC) add_library(openmw_osg_plugins INTERFACE) foreach(_plugin ${USED_OSG_PLUGINS}) string(TOUPPER ${_plugin} _plugin_uc) - list(APPEND _osg_plugins_static_files $) - target_link_libraries(openmw_osg_plugins INTERFACE ${${_plugin_uc}_LIBRARY}) + if(OPENMW_USE_SYSTEM_OSG) + list(APPEND _osg_plugins_static_files ${${_plugin_uc}_LIBRARY}) + else() + list(APPEND _osg_plugins_static_files $) + target_link_libraries(openmw_osg_plugins INTERFACE $) + add_dependencies(openmw_osg_plugins ${${_plugin_uc}_LIBRARY}) + endif() endforeach() # We use --whole-archive because OSG plugins use registration. get_whole_archive_options(_opts ${_osg_plugins_static_files}) target_link_options(openmw_osg_plugins INTERFACE ${_opts}) target_link_libraries(tes3mp openmw_osg_plugins) + + if(OPENMW_USE_SYSTEM_OSG) + # OSG plugin pkgconfig files are missing these dependencies. + # https://github.com/openscenegraph/OpenSceneGraph/issues/1052 + target_link_libraries(tes3mp freetype jpeg png) + endif() endif(OSG_STATIC) if (ANDROID) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 67f0eac31..14a8c7647 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -733,6 +733,7 @@ namespace MWBase virtual void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) = 0; virtual void launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile, const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) = 0; + virtual void updateProjectilesCasters() = 0; virtual void applyLoopingParticles(const MWWorld::Ptr& ptr) = 0; diff --git a/apps/openmw/mwgui/bookpage.hpp b/apps/openmw/mwgui/bookpage.hpp index 4e49b8f67..f9fd93089 100644 --- a/apps/openmw/mwgui/bookpage.hpp +++ b/apps/openmw/mwgui/bookpage.hpp @@ -53,7 +53,7 @@ namespace MWGui { static const int fontHeight = MWBase::Environment::get().getWindowManager()->getFontHeight(); - MyGUI::GlyphInfo* gi = font->getGlyphInfo(ch); + const MyGUI::GlyphInfo* gi = font->getGlyphInfo(ch); if (gi) { const float scale = font->getDefaultHeight() / (float) fontHeight; diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index 872955905..e2e85bb9d 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -293,7 +293,7 @@ namespace MWGui size_t length = mCommandLine->getTextCursor() - max; if(length > 0) { - std::string text = caption; + auto text = caption; text.erase(max, length); mCommandLine->setCaption(text); mCommandLine->setTextCursor(max); @@ -303,7 +303,7 @@ namespace MWGui { if(mCommandLine->getTextCursor() > 0) { - std::string text = mCommandLine->getCaption(); + auto text = mCommandLine->getCaption(); text.erase(0, mCommandLine->getTextCursor()); mCommandLine->setCaption(text); mCommandLine->setTextCursor(0); diff --git a/apps/openmw/mwgui/layout.cpp b/apps/openmw/mwgui/layout.cpp index ae1c09659..9b9b9537f 100644 --- a/apps/openmw/mwgui/layout.cpp +++ b/apps/openmw/mwgui/layout.cpp @@ -35,6 +35,7 @@ namespace MWGui void Layout::shutdown() { + setVisible(false); MyGUI::Gui::getInstance().destroyWidget(mMainWidget); mListWindowRoot.clear(); } diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index e6a10ee32..405abfbae 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -181,7 +181,9 @@ namespace MWGui } else if (mWidgetMap.find(effectId) != mWidgetMap.end()) { - mWidgetMap[effectId]->setVisible(false); + MyGUI::ImageBox* image = mWidgetMap[effectId]; + image->setVisible(false); + image->setAlpha(1.f); } } diff --git a/apps/openmw/mwgui/statswatcher.hpp b/apps/openmw/mwgui/statswatcher.hpp index 41ab4fd25..353779d87 100644 --- a/apps/openmw/mwgui/statswatcher.hpp +++ b/apps/openmw/mwgui/statswatcher.hpp @@ -63,6 +63,8 @@ namespace MWGui void watchActor(const MWWorld::Ptr& ptr); MWWorld::Ptr getWatchedActor() const { return mWatched; } + + void forceUpdate() { mWatchedStatsEmpty = true; } }; } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 81e18eb1c..1b2a03dcd 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -184,7 +184,7 @@ namespace MWGui , mWerewolfOverlayEnabled(Settings::Manager::getBool ("werewolf overlay", "GUI")) , mHudEnabled(true) , mCursorVisible(true) - , mCursorActive(false) + , mCursorActive(true) , mPlayerBounty(-1) , mGui(nullptr) , mGuiModes() @@ -201,7 +201,7 @@ namespace MWGui { float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); mGuiPlatform = new osgMyGUI::Platform(viewer, guiRoot, resourceSystem->getImageManager(), uiScale); - mGuiPlatform->initialise(resourcePath, logpath); + mGuiPlatform->initialise(resourcePath, (boost::filesystem::path(logpath) / "MyGUI.log").generic_string()); mGui = new MyGUI::Gui; mGui->initialise(""); @@ -516,6 +516,8 @@ namespace MWGui } else allow(GW_ALL); + + mStatsWatcher->forceUpdate(); } WindowManager::~WindowManager() @@ -524,8 +526,6 @@ namespace MWGui { mStatsWatcher.reset(); - mKeyboardNavigation.reset(); - MyGUI::LanguageManager::getInstance().eventRequestTag.clear(); MyGUI::PointerManager::getInstance().eventChangeMousePointer.clear(); MyGUI::InputManager::getInstance().eventChangeKeyFocus.clear(); @@ -544,6 +544,8 @@ namespace MWGui delete mCursorManager; delete mToolTips; + mKeyboardNavigation.reset(); + cleanupGarbage(); mFontLoader.reset(); diff --git a/apps/openmw/mwinput/keyboardmanager.cpp b/apps/openmw/mwinput/keyboardmanager.cpp index db047a342..854085846 100644 --- a/apps/openmw/mwinput/keyboardmanager.cpp +++ b/apps/openmw/mwinput/keyboardmanager.cpp @@ -39,12 +39,14 @@ namespace MWInput && MWBase::Environment::get().getWindowManager()->isConsoleMode()) SDL_StopTextInput(); - bool consumed = false; + bool consumed = SDL_IsTextInputActive() && // Little trick to check if key is printable + (!(SDLK_SCANCODE_MASK & arg.keysym.sym) && + (std::isprint(arg.keysym.sym) || + // Don't trust isprint for symbols outside the extended ASCII range + (kc == MyGUI::KeyCode::None && arg.keysym.sym > 0xff))); if (kc != MyGUI::KeyCode::None && !mBindingsManager->isDetectingBindingState()) { - consumed = MWBase::Environment::get().getWindowManager()->injectKeyPress(kc, 0, arg.repeat); - if (SDL_IsTextInputActive() && // Little trick to check if key is printable - (!(SDLK_SCANCODE_MASK & arg.keysym.sym) && std::isprint(arg.keysym.sym))) + if (MWBase::Environment::get().getWindowManager()->injectKeyPress(kc, 0, arg.repeat)) consumed = true; mBindingsManager->setPlayerControlsEnabled(!consumed); } diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 77de517eb..1e4a005f6 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -8,6 +8,7 @@ #include #include +#include /* Start of tes3mp addition @@ -142,10 +143,11 @@ namespace MWMechanics { //Update every frame. UpdateLOS uses a timer, so the LOS check does not happen every frame. updateLOS(actor, target, duration, storage); - float targetReachedTolerance = 0.0f; - if (storage.mLOS) - targetReachedTolerance = storage.mAttackRange; - const bool is_target_reached = pathTo(actor, target.getRefData().getPosition().asVec3(), duration, targetReachedTolerance); + const float targetReachedTolerance = storage.mLOS && !storage.mUseCustomDestination + ? storage.mAttackRange : 0.0f; + const osg::Vec3f destination = storage.mUseCustomDestination + ? storage.mCustomDestination : target.getRefData().getPosition().asVec3(); + const bool is_target_reached = pathTo(actor, destination, duration, targetReachedTolerance); if (is_target_reached) storage.mReadyToAttack = true; } @@ -300,8 +302,8 @@ namespace MWMechanics const ESM::Weapon* weapon = currentAction->getWeapon(); ESM::Position pos = actor.getRefData().getPosition(); - osg::Vec3f vActorPos(pos.asVec3()); - osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3()); + const osg::Vec3f vActorPos(pos.asVec3()); + const osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3()); osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target); float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target); @@ -311,9 +313,7 @@ namespace MWMechanics if (isRangedCombat) { // rotate actor taking into account target movement direction and projectile speed - osg::Vec3f& lastTargetPos = storage.mLastTargetPos; - vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength); - lastTargetPos = vTargetPos; + vAimDir = AimDirToMovingTarget(actor, target, storage.mLastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength); storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir); @@ -324,12 +324,69 @@ namespace MWMechanics storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated } + storage.mLastTargetPos = vTargetPos; + if (storage.mReadyToAttack) { storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target); // start new attack storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat); } + + // If actor uses custom destination it has to try to rebuild path because environment can change + // (door is opened between actor and target) or target position has changed and current custom destination + // is not good enough to attack target. + if (storage.mCurrentAction->isAttackingOrSpell() + && ((!storage.mReadyToAttack && !mPathFinder.isPathConstructed()) + || (storage.mUseCustomDestination && (storage.mCustomDestination - vTargetPos).length() > rangeAttack))) + { + // Try to build path to the target. + const auto halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor); + const auto navigatorFlags = getNavigatorFlags(actor); + const auto areaCosts = getAreaCosts(actor); + const auto pathGridGraph = getPathGridGraph(actor.getCell()); + mPathFinder.buildPath(actor, vActorPos, vTargetPos, actor.getCell(), pathGridGraph, halfExtents, navigatorFlags, areaCosts); + + if (!mPathFinder.isPathConstructed()) + { + // If there is no path, try to find a point on a line from the actor position to target projected + // on navmesh to attack the target from there. + const MWBase::World* world = MWBase::Environment::get().getWorld(); + const auto halfExtents = world->getPathfindingHalfExtents(actor); + const auto navigator = world->getNavigator(); + const auto navigatorFlags = getNavigatorFlags(actor); + const auto areaCosts = getAreaCosts(actor); + const auto hit = navigator->raycast(halfExtents, vActorPos, vTargetPos, navigatorFlags); + + if (hit.has_value() && (*hit - vTargetPos).length() <= rangeAttack) + { + // If the point is close enough, try to find a path to that point. + mPathFinder.buildPath(actor, vActorPos, *hit, actor.getCell(), pathGridGraph, halfExtents, navigatorFlags, areaCosts); + if (mPathFinder.isPathConstructed()) + { + // If path to that point is found use it as custom destination. + storage.mCustomDestination = *hit; + storage.mUseCustomDestination = true; + } + } + + if (!mPathFinder.isPathConstructed()) + { + storage.mUseCustomDestination = false; + storage.stopAttack(); + characterController.setAttackingOrSpell(false); + currentAction.reset(new ActionFlee()); + actionCooldown = currentAction->getActionCooldown(); + storage.startFleeing(); + MWBase::Environment::get().getDialogueManager()->say(actor, "flee"); + } + } + else + { + storage.mUseCustomDestination = false; + } + } + return false; } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 64645ca94..3a77aa8e8 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -55,6 +55,9 @@ namespace MWMechanics float mFleeBlindRunTimer; ESM::Pathgrid::Point mFleeDest; + bool mUseCustomDestination; + osg::Vec3f mCustomDestination; + AiCombatStorage(): mAttackCooldown(0.0f), mTimerReact(AI_REACTION_TIME), @@ -74,7 +77,9 @@ namespace MWMechanics mFleeState(FleeState_None), mLOS(false), mUpdateLOSTimer(0.0f), - mFleeBlindRunTimer(0.0f) + mFleeBlindRunTimer(0.0f), + mUseCustomDestination(false), + mCustomDestination() {} void startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 45ccf2f0b..50d79d55c 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -86,9 +86,15 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte const float pathTolerance = 100.f; - if (pathTo(actor, dest, duration, pathTolerance) && - std::abs(dest.z() - actorPos.z()) < pathTolerance) // check the true distance in case the target is far away in Z-direction + // check the true distance in case the target is far away in Z-direction + bool reached = pathTo(actor, dest, duration, pathTolerance) && + std::abs(dest.z() - actorPos.z()) < pathTolerance; + + if (reached) { + if (!MWBase::Environment::get().getWorld()->getLOS(target, actor)) + return false; + /* Start of tes3mp addition @@ -102,6 +108,7 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte /* End of tes3mp addition */ + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Dialogue, actor); //Arrest player when reached return true; } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index f10c41d9e..b26d24bef 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -3154,8 +3154,8 @@ void CharacterController::updateHeadTracking(float duration) if (!head) return; - float zAngleRadians = 0.f; - float xAngleRadians = 0.f; + double zAngleRadians = 0.f; + double xAngleRadians = 0.f; if (!mHeadTrackTarget.isEmpty()) { @@ -3188,15 +3188,16 @@ void CharacterController::updateHeadTracking(float duration) const osg::Vec3f actorDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); zAngleRadians = std::atan2(actorDirection.x(), actorDirection.y()) - std::atan2(direction.x(), direction.y()); + zAngleRadians = Misc::normalizeAngle(zAngleRadians - mAnimation->getHeadYaw()) + mAnimation->getHeadYaw(); + zAngleRadians *= (1 - direction.z() * direction.z()); xAngleRadians = std::asin(direction.z()); } const double xLimit = osg::DegreesToRadians(40.0); const double zLimit = osg::DegreesToRadians(30.0); double zLimitOffset = mAnimation->getUpperBodyYawRadians(); - xAngleRadians = osg::clampBetween(Misc::normalizeAngle(xAngleRadians), -xLimit, xLimit); - zAngleRadians = osg::clampBetween(Misc::normalizeAngle(zAngleRadians), - -zLimit + zLimitOffset, zLimit + zLimitOffset); + xAngleRadians = osg::clampBetween(xAngleRadians, -xLimit, xLimit); + zAngleRadians = osg::clampBetween(zAngleRadians, -zLimit + zLimitOffset, zLimit + zLimitOffset); float factor = duration*5; factor = std::min(factor, 1.f); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index f330d5128..c563da0f7 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -412,6 +412,10 @@ namespace MWMechanics void applyElementalShields(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim) { + // Don't let elemental shields harm the player in god mode. + bool godmode = attacker == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); + if (godmode) + return; for (int i=0; i<3; ++i) { float magnitude = victim.getClass().getCreatureStats(victim).getMagicEffects().get(ESM::MagicEffect::FireShield+i).getMagnitude(); diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index 5ce50b544..1f74602a1 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -35,6 +35,14 @@ namespace MWMechanics { } + Spells::Spells(const Spells& spells) : mSpellList(spells.mSpellList), mSpells(spells.mSpells), + mSelectedSpell(spells.mSelectedSpell), mUsedPowers(spells.mUsedPowers), + mSpellsChanged(spells.mSpellsChanged), mEffects(spells.mEffects), mSourcedEffects(spells.mSourcedEffects) + { + if(mSpellList) + mSpellList->addListener(this); + } + std::map::const_iterator Spells::begin() const { return mSpells.begin(); diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index 2f4049d2e..3df89a537 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -57,6 +57,10 @@ namespace MWMechanics Spells(); + Spells(const Spells&); + + Spells(const Spells&&) = delete; + ~Spells(); static bool hasCorprusEffect(const ESM::Spell *spell); diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index db0cf06a9..94f844193 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -209,6 +209,9 @@ osg::Vec3f Actor::getCollisionObjectPosition() const bool Actor::setPosition(const osg::Vec3f& position) { std::scoped_lock lock(mPositionMutex); + // position is being forced, ignore simulation results until we sync up + if (mSkipSimulation) + return false; bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged; mPreviousPosition = mPosition + mPositionOffset; mPosition = position + mPositionOffset; @@ -222,6 +225,17 @@ void Actor::adjustPosition(const osg::Vec3f& offset) mPositionOffset += offset; } +void Actor::applyOffsetChange() +{ + if (mPositionOffset.length() == 0) + return; + mWorldPosition += mPositionOffset; + mPosition += mPositionOffset; + mPreviousPosition += mPositionOffset; + mPositionOffset = osg::Vec3f(); + mWorldPositionChanged = true; +} + osg::Vec3f Actor::getPosition() const { return mPosition; diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 9d129f2ba..031125f40 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -96,9 +96,16 @@ namespace MWPhysics * Returns true if the new position is different. */ bool setPosition(const osg::Vec3f& position); + + // force set actor position to be as in Ptr::RefData void updatePosition(); + + // register a position offset that will be applied during simulation. void adjustPosition(const osg::Vec3f& offset); + // apply position offset. Can't be called during simulation + void applyOffsetChange(); + osg::Vec3f getPosition() const; osg::Vec3f getPreviousPosition() const; diff --git a/apps/openmw/mwphysics/heightfield.cpp b/apps/openmw/mwphysics/heightfield.cpp index 436cdfe8f..e210bc390 100644 --- a/apps/openmw/mwphysics/heightfield.cpp +++ b/apps/openmw/mwphysics/heightfield.cpp @@ -10,6 +10,12 @@ #include +#if BT_BULLET_VERSION < 310 +// Older Bullet versions only support `btScalar` heightfields. +// Our heightfield data is `float`. +// +// These functions handle conversion from `float` to `double` when +// `btScalar` is `double` (`BT_USE_DOUBLE_PRECISION`). namespace { template @@ -40,14 +46,18 @@ namespace return btScalarHeights.data(); } } +#endif namespace MWPhysics { HeightField::HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject, PhysicsTaskScheduler* scheduler) : mHoldObject(holdObject) +#if BT_BULLET_VERSION < 310 , mHeights(makeHeights(heights, sqrtVerts)) +#endif , mTaskScheduler(scheduler) { +#if BT_BULLET_VERSION < 310 mShape = std::make_unique( sqrtVerts, sqrtVerts, getHeights(heights, mHeights), @@ -55,9 +65,22 @@ namespace MWPhysics minH, maxH, 2, PHY_FLOAT, false ); +#else + mShape = std::make_unique( + sqrtVerts, sqrtVerts, heights, minH, maxH, 2, false); +#endif mShape->setUseDiamondSubdivision(true); mShape->setLocalScaling(btVector3(triSize, triSize, 1)); +#if BT_BULLET_VERSION >= 289 + // Accelerates some collision tests. + // + // Note: The accelerator data structure in Bullet is only used + // in some operations. This could be improved, see: + // https://github.com/bulletphysics/bullet3/issues/3276 + mShape->buildAccelerator(); +#endif + btTransform transform(btQuaternion::getIdentity(), btVector3((x+0.5f) * triSize * (sqrtVerts-1), (y+0.5f) * triSize * (sqrtVerts-1), diff --git a/apps/openmw/mwphysics/heightfield.hpp b/apps/openmw/mwphysics/heightfield.hpp index c76f8b943..93b2733f3 100644 --- a/apps/openmw/mwphysics/heightfield.hpp +++ b/apps/openmw/mwphysics/heightfield.hpp @@ -34,7 +34,9 @@ namespace MWPhysics std::unique_ptr mShape; std::unique_ptr mCollisionObject; osg::ref_ptr mHoldObject; +#if BT_BULLET_VERSION < 310 std::vector mHeights; +#endif PhysicsTaskScheduler* mTaskScheduler; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 754bb60af..1fa6251f8 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -186,9 +186,11 @@ namespace MWPhysics mPostStepBarrier = std::make_unique(mNumThreads, [&]() { if (mRemainingSteps) + { --mRemainingSteps; + updateActorsPositions(); + } mNextJob.store(0, std::memory_order_release); - updateActorsPositions(); }); mPostSimBarrier = std::make_unique(mNumThreads, [&]() @@ -492,6 +494,7 @@ namespace MWPhysics if (actor->setPosition(actorData.mPosition)) { std::scoped_lock lock(mCollisionWorldMutex); + actorData.mPosition = actor->getPosition(); // account for potential position change made by script actor->updateCollisionObjectPosition(); mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 3706b2235..ba989c6e7 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -628,7 +628,9 @@ namespace MWPhysics return object->getCollisionObject(); return nullptr; }(); - assert(caster); + + if (caster == nullptr) + Log(Debug::Warning) << "No caster for projectile " << projectileId; ProjectileConvexCallback resultCallback(caster, btFrom, btTo, projectile); resultCallback.m_collisionFilterMask = 0xff; @@ -719,6 +721,15 @@ namespace MWPhysics return mProjectileId; } + void PhysicsSystem::setCaster(int projectileId, const MWWorld::Ptr& caster) + { + const auto foundProjectile = mProjectiles.find(projectileId); + assert(foundProjectile != mProjectiles.end()); + auto* projectile = foundProjectile->second.get(); + + projectile->setCaster(caster); + } + bool PhysicsSystem::toggleCollisionMode() { ActorMap::iterator found = mActors.find(MWMechanics::getPlayer()); @@ -974,6 +985,10 @@ namespace MWPhysics void ActorFrameData::updatePosition() { mActorRaw->updateWorldPosition(); + // If physics runs "fast enough", position are interpolated without simulation + // By calling this here, we are sure that offsets are applied at least once per frame, + // regardless of simulation speed. + mActorRaw->applyOffsetChange(); mPosition = mActorRaw->getPosition(); if (mMoveToWaterSurface) { diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 6254d2706..97dc7dde2 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -125,6 +125,7 @@ namespace MWPhysics void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius, bool canTraverseWater); + void setCaster(int projectileId, const MWWorld::Ptr& caster); void updateProjectile(const int projectileId, const osg::Vec3f &position) const; void removeProjectile(const int projectileId); diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 633c8fcb7..2c9b28e78 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -20,6 +21,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -84,7 +86,8 @@ namespace MWRender }; - // Set up alpha blending to Additive mode to avoid issues caused by transparent objects writing onto the alpha value of the FBO + // Set up alpha blending mode to avoid issues caused by transparent objects writing onto the alpha value of the FBO + // This makes the RTT have premultiplied alpha, though, so the source blend factor must be GL_ONE when it's applied class SetUpBlendVisitor : public osg::NodeVisitor { public: @@ -94,22 +97,40 @@ namespace MWRender void apply(osg::Node& node) override { - if (osg::StateSet* stateset = node.getStateSet()) + if (osg::ref_ptr stateset = node.getStateSet()) { + osg::ref_ptr newStateSet; if (stateset->getAttribute(osg::StateAttribute::BLENDFUNC) || stateset->getBinNumber() == osg::StateSet::TRANSPARENT_BIN) { - osg::ref_ptr newStateSet = new osg::StateSet(*stateset, osg::CopyOp::SHALLOW_COPY); osg::BlendFunc* blendFunc = static_cast(stateset->getAttribute(osg::StateAttribute::BLENDFUNC)); - osg::ref_ptr newBlendFunc = blendFunc ? new osg::BlendFunc(*blendFunc) : new osg::BlendFunc; - newBlendFunc->setDestinationAlpha(osg::BlendFunc::ONE); - newStateSet->setAttribute(newBlendFunc, osg::StateAttribute::ON); - node.setStateSet(newStateSet); + + if (blendFunc) + { + newStateSet = new osg::StateSet(*stateset, osg::CopyOp::SHALLOW_COPY); + node.setStateSet(newStateSet); + osg::ref_ptr newBlendFunc = new osg::BlendFunc(*blendFunc); + newStateSet->setAttribute(newBlendFunc, osg::StateAttribute::ON); + // I *think* (based on some by-hand maths) that the RGB and dest alpha factors are unchanged, and only dest determines source alpha factor + // This has the benefit of being idempotent if we assume nothing used glBlendFuncSeparate before we touched it + if (blendFunc->getDestination() == osg::BlendFunc::ONE_MINUS_SRC_ALPHA) + newBlendFunc->setSourceAlpha(osg::BlendFunc::ONE); + else if (blendFunc->getDestination() == osg::BlendFunc::ONE) + newBlendFunc->setSourceAlpha(osg::BlendFunc::ZERO); + // Other setups barely exist in the wild and aren't worth supporting as they're not equippable gear + else + Log(Debug::Info) << "Unable to adjust blend mode for character preview. Source factor 0x" << std::hex << blendFunc->getSource() << ", destination factor 0x" << blendFunc->getDestination() << std::dec; + } } if (stateset->getMode(GL_BLEND) & osg::StateAttribute::ON) { + if (!newStateSet) + { + newStateSet = new osg::StateSet(*stateset, osg::CopyOp::SHALLOW_COPY); + node.setStateSet(newStateSet); + } // Disable noBlendAlphaEnv - stateset->setTextureMode(7, GL_TEXTURE_2D, osg::StateAttribute::OFF); - stateset->addUniform(mNoAlphaUniform); + newStateSet->setTextureMode(7, GL_TEXTURE_2D, osg::StateAttribute::OFF); + newStateSet->addUniform(mNoAlphaUniform); } } traverse(node); @@ -134,6 +155,7 @@ namespace MWRender mTexture->setInternalFormat(GL_RGBA); mTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + mTexture->setUserValue("premultiplied alpha", true); mCamera = new osg::Camera; // hints that the camera is not relative to the master camera @@ -145,7 +167,7 @@ namespace MWRender mCamera->setProjectionMatrixAsPerspective(fovYDegrees, sizeX/static_cast(sizeY), 0.1f, 10000.f); // zNear and zFar are autocomputed mCamera->setViewport(0, 0, sizeX, sizeY); mCamera->setRenderOrder(osg::Camera::PRE_RENDER); - mCamera->attach(osg::Camera::COLOR_BUFFER, mTexture); + mCamera->attach(osg::Camera::COLOR_BUFFER, mTexture, 0, 0, false, Settings::Manager::getInt("antialiasing", "Video")); mCamera->setName("CharacterPreview"); mCamera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); mCamera->setCullMask(~(Mask_UpdateVisitor)); diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 049118c90..0baa85c52 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -1,5 +1,6 @@ #include "groundcover.hpp" +#include #include #include @@ -258,11 +259,16 @@ namespace MWRender // Keep link to original mesh to keep it in cache group->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(temp)); + mSceneManager->reinstateRemovedState(node); + InstancingVisitor visitor(pair.second, worldCenter); node->accept(visitor); group->addChild(node); } + // Force a unified alpha handling instead of data from meshes + osg::ref_ptr alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f / 255.f); + group->getOrCreateStateSet()->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON); group->getBound(); group->setNodeMask(Mask_Groundcover); mSceneManager->recreateShaders(group, "groundcover", false, true); diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 5fa1a0e29..64931aa88 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "../mwbase/environment.hpp" @@ -237,7 +238,7 @@ void LocalMap::setupRenderToTexture(osg::ref_ptr camera, int x, int texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - camera->attach(osg::Camera::COLOR_BUFFER, texture); + SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, texture); camera->addChild(mSceneRoot); mRoot->addChild(camera); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index a3aee5c0f..331927155 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include #include @@ -26,6 +25,7 @@ #include #include +#include #include #include @@ -212,6 +212,7 @@ namespace MWRender resourceSystem->getSceneManager()->setAutoUseSpecularMaps(Settings::Manager::getBool("auto use object specular maps", "Shaders")); resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders")); resourceSystem->getSceneManager()->setApplyLightingToEnvMaps(Settings::Manager::getBool("apply lighting to environment maps", "Shaders")); + resourceSystem->getSceneManager()->setConvertAlphaTestToAlphaToCoverage(Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1); osg::ref_ptr sceneRoot = new SceneUtil::LightManager; sceneRoot->setLightingMask(Mask_Lighting); @@ -244,6 +245,7 @@ namespace MWRender globalDefines["clamp"] = Settings::Manager::getBool("clamp lighting", "Shaders") ? "1" : "0"; globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0"; globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; + globalDefines["useGPUShader4"] = "0"; float groundcoverDistance = (Constants::CellSizeInUnits * std::max(1, Settings::Manager::getInt("distance", "Groundcover")) - 1024) * 0.93; globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * 0.9f); @@ -310,10 +312,6 @@ namespace MWRender groundcoverRoot->setName("Groundcover Root"); sceneRoot->addChild(groundcoverRoot); - // Force a unified alpha handling instead of data from meshes - osg::ref_ptr alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f/255.f); - groundcoverRoot->getOrCreateStateSet()->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON); - mGroundcoverUpdater = new GroundcoverUpdater; groundcoverRoot->addUpdateCallback(mGroundcoverUpdater); @@ -370,7 +368,6 @@ namespace MWRender mSky.reset(new SkyManager(sceneRoot, resourceSystem->getSceneManager())); mSky->setCamera(mViewer->getCamera()); - mSky->setRainIntensityUniform(mWater->getRainIntensityUniform()); source->setStateSetModes(*mRootNode->getOrCreateStateSet(), osg::StateAttribute::ON); @@ -409,6 +406,11 @@ namespace MWRender mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("simpleWater", false)); + // Hopefully, anything genuinely requiring the default alpha func of GL_ALWAYS explicitly sets it + mRootNode->getOrCreateStateSet()->setAttribute(Shader::RemovedAlphaFunc::getInstance(GL_ALWAYS)); + // The transparent renderbin sets alpha testing on because that was faster on old GPUs. It's now slower and breaks things. + mRootNode->getOrCreateStateSet()->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF); + mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near"); mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far"); updateProjectionMatrix(); @@ -658,6 +660,9 @@ namespace MWRender mUnrefQueue->flush(mWorkQueue.get()); + float rainIntensity = mSky->getPrecipitationAlpha(); + mWater->setRainIntensity(rainIntensity); + if (!paused) { mEffectManager->update(dt); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 389944000..5a6ec06e5 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -497,8 +497,6 @@ public: // Disable writing to the color buffer. We are using this geometry for visibility tests only. osg::ref_ptr colormask (new osg::ColorMask(0, 0, 0, 0)); stateset->setAttributeAndModes(colormask, osg::StateAttribute::ON); - osg::ref_ptr po (new osg::PolygonOffset( -1., -1. )); - stateset->setAttributeAndModes(po, osg::StateAttribute::ON); mTransform->addChild(queryNode); @@ -595,7 +593,7 @@ private: if (queryVisible) { osg::ref_ptr depth (new osg::Depth); - depth->setFunction(osg::Depth::LESS); + depth->setFunction(osg::Depth::LEQUAL); // This is a trick to make fragments written by the query always use the maximum depth value, // without having to retrieve the current far clipping distance. // We want the sun glare to be "infinitely" far away. @@ -1113,7 +1111,6 @@ private: SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager) : mSceneManager(sceneManager) , mCamera(nullptr) - , mRainIntensityUniform(nullptr) , mAtmosphereNightRoll(0.f) , mCreated(false) , mIsStorm(false) @@ -1139,7 +1136,7 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana , mBaseWindSpeed(0.f) , mEnabled(true) , mSunEnabled(true) - , mEffectFade(0.f) + , mPrecipitationAlpha(0.f) { osg::ref_ptr skyroot (new CameraRelativeTransform); skyroot->setName("Sky Root"); @@ -1163,11 +1160,6 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana mUnderwaterSwitch = new UnderwaterSwitchCallback(skyroot); } -void SkyManager::setRainIntensityUniform(osg::Uniform *uniform) -{ - mRainIntensityUniform = uniform; -} - void SkyManager::create() { assert(!mCreated); @@ -1522,7 +1514,7 @@ void SkyManager::createRain() osg::ref_ptr program (new osgParticle::ModularProgram); program->addOperator(new WrapAroundOperator(mCamera,rainRange)); - program->addOperator(new WeatherAlphaOperator(mEffectFade, true)); + program->addOperator(new WeatherAlphaOperator(mPrecipitationAlpha, true)); program->setParticleSystem(mRainParticleSystem); mRainNode->addChild(program); @@ -1576,29 +1568,23 @@ bool SkyManager::isEnabled() return mEnabled; } -bool SkyManager::hasRain() +bool SkyManager::hasRain() const { return mRainNode != nullptr; } +float SkyManager::getPrecipitationAlpha() const +{ + if (mEnabled && !mIsStorm && (hasRain() || mParticleNode)) + return mPrecipitationAlpha; + + return 0.f; +} + void SkyManager::update(float duration) { if (!mEnabled) - { - if (mRainIntensityUniform) - mRainIntensityUniform->set(0.f); - return; - } - - if (mRainIntensityUniform) - { - float rainIntensity = 0.f; - if (!mIsStorm && (hasRain() || mParticleNode)) - rainIntensity = mEffectFade; - - mRainIntensityUniform->set(rainIntensity); - } switchUnderwaterRain(); @@ -1737,7 +1723,7 @@ void SkyManager::setWeather(const WeatherResult& weather) SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::shared_ptr(new SceneUtil::FrameTimeSource)); mParticleEffect->accept(assignVisitor); - AlphaFader::SetupVisitor alphaFaderSetupVisitor(mEffectFade); + AlphaFader::SetupVisitor alphaFaderSetupVisitor(mPrecipitationAlpha); mParticleEffect->accept(alphaFaderSetupVisitor); @@ -1754,7 +1740,7 @@ void SkyManager::setWeather(const WeatherResult& weather) osg::ref_ptr program (new osgParticle::ModularProgram); if (!mIsStorm) program->addOperator(new WrapAroundOperator(mCamera,osg::Vec3(1024,1024,800))); - program->addOperator(new WeatherAlphaOperator(mEffectFade, false)); + program->addOperator(new WeatherAlphaOperator(mPrecipitationAlpha, false)); program->setParticleSystem(ps); mParticleNode->addChild(program); } @@ -1843,7 +1829,7 @@ void SkyManager::setWeather(const WeatherResult& weather) mAtmosphereNightNode->setNodeMask(weather.mNight ? ~0 : 0); - mEffectFade = weather.mEffectFade; + mPrecipitationAlpha = weather.mPrecipitationAlpha; } float SkyManager::getBaseWindSpeed() const diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index f32c40192..f8c501dda 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -7,7 +7,6 @@ #include #include -#include namespace osg { @@ -88,7 +87,7 @@ namespace MWRender std::string mParticleEffect; std::string mRainEffect; - float mEffectFade; + float mPrecipitationAlpha; float mRainDiameter; float mRainMinHeight; @@ -157,7 +156,9 @@ namespace MWRender bool isEnabled(); - bool hasRain(); + bool hasRain() const; + + float getPrecipitationAlpha() const; void setRainSpeed(float speed); @@ -180,8 +181,6 @@ namespace MWRender void setCamera(osg::Camera *camera); - void setRainIntensityUniform(osg::Uniform *uniform); - float getBaseWindSpeed() const; private: @@ -196,7 +195,6 @@ namespace MWRender Resource::SceneManager* mSceneManager; osg::Camera *mCamera; - osg::Uniform *mRainIntensityUniform; osg::ref_ptr mRootNode; osg::ref_ptr mEarlyRenderBinRoot; @@ -271,7 +269,7 @@ namespace MWRender bool mEnabled; bool mSunEnabled; - float mEffectFade; + float mPrecipitationAlpha; osg::Vec4f mMoonScriptColor; }; diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 1cc5a3cb7..caa0af434 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -208,6 +209,37 @@ public: } }; +class RainIntensityUpdater : public SceneUtil::StateSetUpdater +{ +public: + RainIntensityUpdater() + : mRainIntensity(0.f) + { + } + + void setRainIntensity(float rainIntensity) + { + mRainIntensity = rainIntensity; + } + +protected: + void setDefaults(osg::StateSet* stateset) override + { + osg::ref_ptr rainIntensityUniform = new osg::Uniform("rainIntensity", 0.0f); + stateset->addUniform(rainIntensityUniform.get()); + } + + void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override + { + osg::ref_ptr rainIntensityUniform = stateset->getUniform("rainIntensity"); + if (rainIntensityUniform != nullptr) + rainIntensityUniform->set(mRainIntensity); + } + +private: + float mRainIntensity; +}; + osg::ref_ptr readPngImage (const std::string& file) { // use boost in favor of osgDB::readImage, to handle utf-8 path issues on Windows @@ -271,7 +303,7 @@ public: mRefractionTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mRefractionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - attach(osg::Camera::COLOR_BUFFER, mRefractionTexture); + SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(this, osg::Camera::COLOR_BUFFER, mRefractionTexture); mRefractionDepthTexture = new osg::Texture2D; mRefractionDepthTexture->setTextureSize(rttSize, rttSize); @@ -356,7 +388,7 @@ public: mReflectionTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mReflectionTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - attach(osg::Camera::COLOR_BUFFER, mReflectionTexture); + SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(this, osg::Camera::COLOR_BUFFER, mReflectionTexture); // XXX: should really flip the FrontFace on each renderable instead of forcing clockwise. osg::ref_ptr frontFace (new osg::FrontFace); @@ -431,7 +463,8 @@ public: Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem *resourceSystem, osgUtil::IncrementalCompileOperation *ico, const std::string& resourcePath) - : mParent(parent) + : mRainIntensityUpdater(nullptr) + , mParent(parent) , mSceneRoot(sceneRoot) , mResourceSystem(resourceSystem) , mResourcePath(resourcePath) @@ -463,8 +496,6 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem setHeight(mTop); - mRainIntensityUniform = new osg::Uniform("rainIntensity",(float) 0.0); - updateWaterMaterial(); if (ico) @@ -494,11 +525,6 @@ void Water::setCullCallback(osg::Callback* callback) } } -osg::Uniform *Water::getRainIntensityUniform() -{ - return mRainIntensityUniform.get(); -} - void Water::updateWaterMaterial() { if (mReflection) @@ -556,6 +582,8 @@ void Water::createSimpleWaterStateSet(osg::Node* node, float alpha) osg::ref_ptr stateset = SceneUtil::createSimpleWaterStateSet(alpha, MWRender::RenderBin_Water); node->setStateSet(stateset); + node->setUpdateCallback(nullptr); + mRainIntensityUpdater = nullptr; // Add animated textures std::vector > textures; @@ -639,15 +667,15 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R shaderStateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); - shaderStateset->addUniform(mRainIntensityUniform.get()); - osg::ref_ptr program (new osg::Program); program->addShader(vertexShader); program->addShader(fragmentShader); shaderStateset->setAttributeAndModes(program, osg::StateAttribute::ON); node->setStateSet(shaderStateset); - node->setUpdateCallback(nullptr); + + mRainIntensityUpdater = new RainIntensityUpdater(); + node->setUpdateCallback(mRainIntensityUpdater); } void Water::processChangedSettings(const Settings::CategorySettingVector& settings) @@ -730,6 +758,12 @@ void Water::setHeight(const float height) mRefraction->setWaterLevel(mTop); } +void Water::setRainIntensity(float rainIntensity) +{ + if (mRainIntensityUpdater) + mRainIntensityUpdater->setRainIntensity(rainIntensity); +} + void Water::update(float dt) { mSimulation->update(dt); diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index 3787ef426..ec7dc95db 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -6,7 +6,6 @@ #include #include -#include #include #include @@ -46,11 +45,12 @@ namespace MWRender class Refraction; class Reflection; class RippleSimulation; + class RainIntensityUpdater; /// Water rendering class Water { - osg::ref_ptr mRainIntensityUniform; + osg::ref_ptr mRainIntensityUpdater; osg::ref_ptr mParent; osg::ref_ptr mSceneRoot; @@ -112,6 +112,7 @@ namespace MWRender void changeCell(const MWWorld::CellStore* store); void setHeight(const float height); + void setRainIntensity(const float rainIntensity); void update(float dt); @@ -119,8 +120,6 @@ namespace MWRender osg::Camera *getRefractionCamera(); void processChangedSettings(const Settings::CategorySettingVector& settings); - - osg::Uniform *getRainIntensityUniform(); }; } diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 251844641..913b7d7b6 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -330,20 +330,17 @@ namespace MWScript Interpreter::Type_Float pos = runtime[0].mFloat; runtime.pop(); - float ax = ptr.getRefData().getPosition().pos[0]; - float ay = ptr.getRefData().getPosition().pos[1]; - float az = ptr.getRefData().getPosition().pos[2]; - // Note: SetPos does not skip weather transitions in vanilla engine, so we do not call setTeleported(true) here. - MWWorld::Ptr updated = ptr; + const auto curPos = ptr.getRefData().getPosition().asVec3(); + auto newPos = curPos; if(axis == "x") { - updated = MWBase::Environment::get().getWorld()->moveObject(ptr,pos,ay,az,true); + newPos[0] = pos; } else if(axis == "y") { - updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,pos,az,true); + newPos[1] = pos; } else if(axis == "z") { @@ -352,20 +349,21 @@ namespace MWScript { float terrainHeight = -std::numeric_limits::max(); if (ptr.getCell()->isExterior()) - terrainHeight = MWBase::Environment::get().getWorld()->getTerrainHeightAt(osg::Vec3f(ax, ay, az)); + terrainHeight = MWBase::Environment::get().getWorld()->getTerrainHeightAt(curPos); if (pos < terrainHeight) pos = terrainHeight; } - updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,ay,pos,true); + newPos[2] = pos; } else { return; } - dynamic_cast(runtime.getContext()).updatePtr(ptr,updated); + dynamic_cast(runtime.getContext()).updatePtr(ptr, + MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos)); } }; diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 95ed9eeed..ec36a5cdf 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -287,9 +287,9 @@ void FFmpeg_Decoder::close() mStream = nullptr; av_packet_unref(&mPacket); - av_freep(&mFrame); - swr_free(&mSwr); av_freep(&mDataBuf); + av_frame_free(&mFrame); + swr_free(&mSwr); if(mFormatCtx) { @@ -302,11 +302,13 @@ void FFmpeg_Decoder::close() // if (mFormatCtx->pb->buffer != nullptr) { - av_free(mFormatCtx->pb->buffer); - mFormatCtx->pb->buffer = nullptr; + av_freep(&mFormatCtx->pb->buffer); } - av_free(mFormatCtx->pb); - mFormatCtx->pb = nullptr; +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 80, 100) + avio_context_free(&mFormatCtx->pb); +#else + av_freep(&mFormatCtx->pb); +#endif } avformat_close_input(&mFormatCtx); } diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index fb7479486..429662201 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -549,6 +549,8 @@ void MWState::StateManager::loadGame (const Character *character, const std::str MWBase::Environment::get().getWorld()->changeToCell(cell->getCell()->getCellId(), pos, true, false); } + MWBase::Environment::get().getWorld()->updateProjectilesCasters(); + // Vanilla MW will restart startup scripts when a save game is loaded. This is unintuitive, // but some mods may be using it as a reload detector. MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 8453ba90a..8640b3fcc 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -117,6 +117,45 @@ namespace } } + template + void fixRestockingImpl(const T* base, RecordType& state) + { + // Workaround for old saves not containing negative quantities + for(const auto& baseItem : base->mInventory.mList) + { + if(baseItem.mCount < 0) + { + for(auto& item : state.mInventory.mItems) + { + if(item.mCount > 0 && Misc::StringUtils::ciEqual(baseItem.mItem, item.mRef.mRefID)) + item.mCount = -item.mCount; + } + } + } + } + + template + void fixRestocking(const T* base, RecordType& state) + {} + + template<> + void fixRestocking<>(const ESM::Creature* base, ESM::CreatureState& state) + { + fixRestockingImpl(base, state); + } + + template<> + void fixRestocking<>(const ESM::NPC* base, ESM::NpcState& state) + { + fixRestockingImpl(base, state); + } + + template<> + void fixRestocking<>(const ESM::Container* base, ESM::ContainerState& state) + { + fixRestockingImpl(base, state); + } + template void readReferenceCollection (ESM::ESMReader& reader, MWWorld::CellRefList& collection, const ESM::CellRef& cref, const std::map& contentFileMap, MWWorld::CellStore* cellstore) @@ -147,6 +186,9 @@ namespace if (!record) return; + if (state.mVersion < 15) + fixRestocking(record, state); + if (state.mRef.mRefNum.hasContentFile()) { for (typename MWWorld::CellRefList::List::iterator iter (collection.mList.begin()); diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 3ffc7bb95..f483905dd 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -352,6 +352,29 @@ namespace MWWorld mProjectiles.push_back(state); } + void ProjectileManager::updateCasters() + { + for (auto& state : mProjectiles) + mPhysics->setCaster(state.mProjectileId, state.getCaster()); + + for (auto& state : mMagicBolts) + { + // casters are identified by actor id in the savegame. objects doesn't have one so they can't be identified back. + // TODO: should object-type caster be restored from savegame? + if (state.mActorId == -1) + continue; + + auto caster = state.getCaster(); + if (caster.isEmpty()) + { + Log(Debug::Error) << "Couldn't find caster with ID " << state.mActorId; + cleanupMagicBolt(state); + continue; + } + mPhysics->setCaster(state.mProjectileId, caster); + } + } + void ProjectileManager::update(float dt) { periodicCleanup(dt); diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index c047d90dd..e4bcae1ae 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -54,6 +54,8 @@ namespace MWWorld void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, const osg::Vec3f& pos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength); + void updateCasters(); + void update(float dt); void processHits(); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 2d0f52028..45e6135cb 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -479,11 +479,17 @@ namespace MWWorld mPhysics->disableWater(); const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + + // By default the player is grounded, with the scene fully loaded, we validate and correct this. + if (player.mCell == cell) // Only run once, during initial cell load. + { + mPhysics->traceDown(player, player.getRefData().getPosition().asVec3(), 10.f); + } + navigator->update(player.getRefData().getPosition().asVec3()); if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) { - mRendering.configureAmbient(cell->getCell()); } diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 8b279f763..d8e0a8dc4 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -1353,7 +1353,7 @@ inline void WeatherManager::calculateResult(const int weatherID, const float gam mResult.mGlareView = current.mGlareView; mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; mResult.mAmbientSoundVolume = 1.f; - mResult.mEffectFade = 1.f; + mResult.mPrecipitationAlpha = 1.f; mResult.mIsStorm = current.mIsStorm; @@ -1460,7 +1460,7 @@ inline void WeatherManager::calculateTransitionResult(const float factor, const mResult.mRainSpeed = current.mRainSpeed; mResult.mRainEntranceSpeed = current.mRainEntranceSpeed; mResult.mAmbientSoundVolume = 1 - factor / threshold; - mResult.mEffectFade = mResult.mAmbientSoundVolume; + mResult.mPrecipitationAlpha = mResult.mAmbientSoundVolume; mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; mResult.mRainDiameter = current.mRainDiameter; mResult.mRainMinHeight = current.mRainMinHeight; @@ -1475,7 +1475,7 @@ inline void WeatherManager::calculateTransitionResult(const float factor, const mResult.mRainSpeed = other.mRainSpeed; mResult.mRainEntranceSpeed = other.mRainEntranceSpeed; mResult.mAmbientSoundVolume = (factor - threshold) / (1 - threshold); - mResult.mEffectFade = mResult.mAmbientSoundVolume; + mResult.mPrecipitationAlpha = mResult.mAmbientSoundVolume; mResult.mAmbientLoopSoundID = other.mAmbientLoopSoundID; mResult.mRainDiameter = other.mRainDiameter; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 7d7132ace..70c187ae0 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3662,6 +3662,11 @@ namespace MWWorld mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection); } + void World::updateProjectilesCasters() + { + mProjectileManager->updateCasters(); + } + class ApplyLoopingParticlesVisitor : public MWMechanics::EffectSourceVisitor { private: diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 894bba733..37a45f4b5 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -817,6 +817,7 @@ namespace MWWorld void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) override; void launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile, const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) override; + void updateProjectilesCasters() override; void applyLoopingParticles(const MWWorld::Ptr& ptr) override; diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index ae345d187..d377aed1e 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -76,6 +76,17 @@ namespace } }; + template + btHeightfieldTerrainShape makeSquareHeightfieldTerrainShape(const std::array& values, + btScalar heightScale = 1, int upAxis = 2, PHY_ScalarType heightDataType = PHY_FLOAT, bool flipQuadEdges = false) + { + const int width = static_cast(std::sqrt(size)); + const btScalar min = *std::min_element(values.begin(), values.end()); + const btScalar max = *std::max_element(values.begin(), values.end()); + const btScalar greater = std::max(std::abs(min), std::abs(max)); + return btHeightfieldTerrainShape(width, width, values.data(), heightScale, -greater, greater, upAxis, heightDataType, flipQuadEdges); + } + TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_return_empty) { EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), @@ -108,7 +119,7 @@ namespace 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; - btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData); shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); @@ -154,7 +165,7 @@ namespace 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; - btHeightfieldTerrainShape heightfieldShape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + btHeightfieldTerrainShape heightfieldShape = makeSquareHeightfieldTerrainShape(heightfieldData); heightfieldShape.setLocalScaling(btVector3(128, 128, 1)); btBoxShape boxShape(btVector3(20, 20, 100)); @@ -238,7 +249,7 @@ namespace 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; - btHeightfieldTerrainShape heightfieldShape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + btHeightfieldTerrainShape heightfieldShape = makeSquareHeightfieldTerrainShape(heightfieldData); heightfieldShape.setLocalScaling(btVector3(128, 128, 1)); btBoxShape boxShape(btVector3(20, 20, 100)); @@ -325,7 +336,7 @@ namespace 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; - btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData); shape.setLocalScaling(btVector3(128, 128, 1)); const std::array heightfieldData2 {{ @@ -335,7 +346,7 @@ namespace -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, }}; - btHeightfieldTerrainShape shape2(5, 5, heightfieldData2.data(), 1, 0, 0, 2, PHY_FLOAT, false); + btHeightfieldTerrainShape shape2 = makeSquareHeightfieldTerrainShape(heightfieldData2); shape2.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); @@ -382,7 +393,7 @@ namespace 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; - btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData); shape.setLocalScaling(btVector3(128, 128, 1)); std::array heightfieldDataAvoid {{ @@ -392,7 +403,7 @@ namespace -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, }}; - btHeightfieldTerrainShape shapeAvoid(5, 5, heightfieldDataAvoid.data(), 1, 0, 0, 2, PHY_FLOAT, false); + btHeightfieldTerrainShape shapeAvoid = makeSquareHeightfieldTerrainShape(heightfieldDataAvoid); shapeAvoid.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); @@ -439,7 +450,7 @@ namespace -50, -100, -150, -100, -100, 0, -50, -100, -100, -100, }}; - btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData); shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); @@ -487,7 +498,7 @@ namespace 0, -100, -100, -100, -100, -100, 0, 0, 0, 0, 0, 0, 0, 0, }}; - btHeightfieldTerrainShape shape(7, 7, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData); shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); @@ -534,7 +545,7 @@ namespace 0, -100, -100, -100, -100, -100, 0, 0, 0, 0, 0, 0, 0, 0, }}; - btHeightfieldTerrainShape shape(7, 7, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData); shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); @@ -581,7 +592,7 @@ namespace 0, -100, -100, -100, -100, -100, 0, 0, 0, 0, 0, 0, 0, 0, }}; - btHeightfieldTerrainShape shape(7, 7, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData); shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); @@ -626,7 +637,7 @@ namespace 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; - btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData); shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); @@ -680,7 +691,7 @@ namespace 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; - btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData); shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); @@ -711,7 +722,7 @@ namespace 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; - btHeightfieldTerrainShape heightfieldShape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + btHeightfieldTerrainShape heightfieldShape = makeSquareHeightfieldTerrainShape(heightfieldData); heightfieldShape.setLocalScaling(btVector3(128, 128, 1)); const std::vector boxShapes(100, btVector3(20, 20, 100)); @@ -802,4 +813,26 @@ namespace EXPECT_GT(duration, mSettings.mMinUpdateInterval) << std::chrono::duration_cast>(duration).count() << " ms"; } + + TEST_F(DetourNavigatorNavigatorTest, update_then_raycast_should_return_position) + { + const std::array heightfieldData {{ + 0, 0, 0, 0, 0, + 0, -25, -25, -25, -25, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + }}; + btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData); + shape.setLocalScaling(btVector3(128, 128, 1)); + + mNavigator->addAgent(mAgentHalfExtents); + mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); + mNavigator->update(mPlayerPosition); + mNavigator->wait(); + + const auto result = mNavigator->raycast(mAgentHalfExtents, mStart, mEnd, Flag_walk); + + ASSERT_THAT(result, Optional(Vec3fEq(mEnd.x(), mEnd.y(), 1.87719))); + } } diff --git a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp index eac3c024f..be5209001 100644 --- a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp @@ -4,10 +4,6 @@ #include #include -#include -#include -#include -#include #include #include diff --git a/cmake/FindRecastNavigation.cmake b/cmake/FindRecastNavigation.cmake new file mode 100644 index 000000000..8728a5e94 --- /dev/null +++ b/cmake/FindRecastNavigation.cmake @@ -0,0 +1,211 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. +# Copyright 2021 Bret Curtis for OpenMW +#[=======================================================================[.rst: +FindRecastNavigation +------- + +Find the RecastNavigation include directory and library. + +Use this module by invoking find_package with the form:: + +.. code-block:: cmake + + find_package(RecastNavigation + [version] # Minimum version e.g. 1.8.0 + [REQUIRED] # Fail with error if RECAST is not found + ) + +Imported targets +^^^^^^^^^^^^^^^^ + +This module defines the following :prop_tgt:`IMPORTED` targets: + +.. variable:: RecastNavigation::Recast + + Imported target for using the RECAST library, if found. + +Result variables +^^^^^^^^^^^^^^^^ + +.. variable:: RECAST_FOUND + + Set to true if RECAST library found, otherwise false or undefined. + +.. variable:: RECAST_INCLUDE_DIRS + + Paths to include directories listed in one variable for use by RECAST client. + +.. variable:: RECAST_LIBRARIES + + Paths to libraries to linked against to use RECAST. + +.. variable:: RECAST_VERSION + + The version string of RECAST found. + +Cache variables +^^^^^^^^^^^^^^^ + +For users who wish to edit and control the module behavior, this module +reads hints about search locations from the following variables:: + +.. variable:: RECAST_INCLUDE_DIR + + Path to RECAST include directory with ``Recast.h`` header. + +.. variable:: RECAST_LIBRARY + + Path to RECAST library to be linked. + +NOTE: The variables above should not usually be used in CMakeLists.txt files! + +#]=======================================================================] + +### Find libraries ############################################################## + +if(NOT RECAST_LIBRARY) + find_library(RECAST_LIBRARY_RELEASE NAMES Recast HINTS ${RECASTNAVIGATION_ROOT} PATH_SUFFIXES lib) + find_library(RECAST_LIBRARY_DEBUG NAMES Recast-d HINTS ${RECASTNAVIGATION_ROOT} PATH_SUFFIXES lib) + include(SelectLibraryConfigurations) + select_library_configurations(RECAST) + mark_as_advanced(RECAST_LIBRARY_RELEASE RECAST_LIBRARY_DEBUG) +else() + file(TO_CMAKE_PATH "${RECAST_LIBRARY}" RECAST_LIBRARY) +endif() + +if(NOT DETOUR_LIBRARY) + find_library(DETOUR_LIBRARY_RELEASE NAMES Detour HINTS ${RECASTNAVIGATION_ROOT} PATH_SUFFIXES lib) + find_library(DETOUR_LIBRARY_DEBUG NAMES Detour-d HINTS ${RECASTNAVIGATION_ROOT} PATH_SUFFIXES lib) + include(SelectLibraryConfigurations) + select_library_configurations(DETOUR) + mark_as_advanced(DETOUR_LIBRARY_RELEASE DETOUR_LIBRARY_DEBUG) +else() + file(TO_CMAKE_PATH "${DETOUR_LIBRARY}" DETOUR_LIBRARY) +endif() + +if(NOT DEBUGUTILS_LIBRARY) + find_library(DEBUGUTILS_LIBRARY_RELEASE NAMES DebugUtils HINTS ${RECASTNAVIGATION_ROOT} PATH_SUFFIXES lib) + find_library(DEBUGUTILS_LIBRARY_DEBUG NAMES DebugUtils-d HINTS ${RECASTNAVIGATION_ROOT} PATH_SUFFIXES lib) + include(SelectLibraryConfigurations) + select_library_configurations(DEBUGUTILS) + mark_as_advanced(DEBUGUTILS_LIBRARY_RELEASE DEBUGUTILS_LIBRARY_DEBUG) +else() + file(TO_CMAKE_PATH "${DEBUGUTILS_LIBRARY}" DEBUGUTILS_LIBRARY) +endif() + +### Find include directory #################################################### +find_path(RECAST_INCLUDE_DIR NAMES Recast.h HINTS ${RECASTNAVIGATION_ROOT} PATH_SUFFIXES include RECAST include/recastnavigation) +mark_as_advanced(RECAST_INCLUDE_DIR) + +if(RECAST_INCLUDE_DIR AND EXISTS "${RECAST_INCLUDE_DIR}/Recast.h") + file(STRINGS "${RECAST_INCLUDE_DIR}/Recast.h" _Recast_h_contents + REGEX "#define RECAST_VERSION_[A-Z]+[ ]+[0-9]+") + string(REGEX REPLACE "#define RECAST_VERSION_MAJOR[ ]+([0-9]+).+" "\\1" + RECAST_VERSION_MAJOR "${_Recast_h_contents}") + string(REGEX REPLACE ".+#define RECAST_VERSION_MINOR[ ]+([0-9]+).+" "\\1" + RECAST_VERSION_MINOR "${_Recast_h_contents}") + string(REGEX REPLACE ".+#define RECAST_VERSION_RELEASE[ ]+([0-9]+).*" "\\1" + RECAST_VERSION_RELEASE "${_Recast_h_contents}") + set(RECAST_VERSION "${RECAST_VERSION_MAJOR}.${RECAST_VERSION_MINOR}.${RECAST_VERSION_RELEASE}") + unset(_Recast_h_contents) +endif() + +#TODO: they don't include a version yet +set(RECAST_VERSION "1.5.1") + +### Set result variables ###################################################### +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(RecastNavigation DEFAULT_MSG + RECAST_LIBRARY RECAST_INCLUDE_DIR RECAST_VERSION) + +set(RECAST_LIBRARIES ${RECAST_LIBRARY}) +set(RECAST_INCLUDE_DIRS ${RECAST_INCLUDE_DIR}) + +set(DETOUR_LIBRARIES ${DETOUR_LIBRARY}) +set(DETOUR_INCLUDE_DIRS ${RECAST_INCLUDE_DIR}) + +set(DEBUGUTILS_LIBRARIES ${DEBUGUTILS_LIBRARY}) +set(DEBUGUTILS_INCLUDE_DIRS ${RECAST_INCLUDE_DIR}) + +### Import targets ############################################################ +if(RecastNavigation_FOUND) + if(NOT TARGET RecastNavigation::Recast) + add_library(RecastNavigation::Recast UNKNOWN IMPORTED) + set_target_properties(RecastNavigation::Recast PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + INTERFACE_INCLUDE_DIRECTORIES "${RECAST_INCLUDE_DIR}") + + if(RECAST_LIBRARY_RELEASE) + set_property(TARGET RecastNavigation::Recast APPEND PROPERTY + IMPORTED_CONFIGURATIONS RELEASE) + set_target_properties(RecastNavigation::Recast PROPERTIES + IMPORTED_LOCATION_RELEASE "${RECAST_LIBRARY_RELEASE}") + endif() + + if(RECAST_LIBRARY_DEBUG) + set_property(TARGET RecastNavigation::Recast APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) + set_target_properties(RecastNavigation::Recast PROPERTIES + IMPORTED_LOCATION_DEBUG "${RECAST_LIBRARY_DEBUG}") + endif() + + if(NOT RECAST_LIBRARY_RELEASE AND NOT RECAST_LIBRARY_DEBUG) + set_property(TARGET RecastNavigation::Recast APPEND PROPERTY + IMPORTED_LOCATION "${RECAST_LIBRARY}") + endif() + endif() + + if(NOT TARGET RecastNavigation::Detour) + add_library(RecastNavigation::Detour UNKNOWN IMPORTED) + set_target_properties(RecastNavigation::Detour PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + INTERFACE_INCLUDE_DIRECTORIES "${DETOUR_INCLUDE_DIR}") + + if(DETOUR_LIBRARY_RELEASE) + set_property(TARGET RecastNavigation::Detour APPEND PROPERTY + IMPORTED_CONFIGURATIONS RELEASE) + set_target_properties(RecastNavigation::Detour PROPERTIES + IMPORTED_LOCATION_RELEASE "${DETOUR_LIBRARY_RELEASE}") + endif() + + if(DETOUR_LIBRARY_DEBUG) + set_property(TARGET RecastNavigation::Detour APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) + set_target_properties(RecastNavigation::Detour PROPERTIES + IMPORTED_LOCATION_DEBUG "${DETOUR_LIBRARY_DEBUG}") + endif() + + if(NOT DETOUR_LIBRARY_RELEASE AND NOT DETOUR_LIBRARY_DEBUG) + set_property(TARGET RecastNavigation::Detour APPEND PROPERTY + IMPORTED_LOCATION "${DETOUR_LIBRARY}") + endif() + endif() + + if(NOT TARGET RecastNavigation::DebugUtils) + add_library(RecastNavigation::DebugUtils UNKNOWN IMPORTED) + set_target_properties(RecastNavigation::DebugUtils PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + INTERFACE_INCLUDE_DIRECTORIES "${DEBUGUTILS_INCLUDE_DIR}") + + if(DEBUGUTILS_LIBRARY_RELEASE) + set_property(TARGET RecastNavigation::DebugUtils APPEND PROPERTY + IMPORTED_CONFIGURATIONS RELEASE) + set_target_properties(RecastNavigation::DebugUtils PROPERTIES + IMPORTED_LOCATION_RELEASE "${DEBUGUTILS_LIBRARY_RELEASE}") + endif() + + if(DEBUGUTILS_LIBRARY_DEBUG) + set_property(TARGET RecastNavigation::DebugUtils APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) + set_target_properties(RecastNavigation::DebugUtils PROPERTIES + IMPORTED_LOCATION_DEBUG "${DEBUGUTILS_LIBRARY_DEBUG}") + endif() + + if(NOT DEBUGUTILS_LIBRARY_RELEASE AND NOT DEBUGUTILS_LIBRARY_DEBUG) + set_property(TARGET RecastNavigation::DebugUtils APPEND PROPERTY + IMPORTED_LOCATION "${DEBUGUTILS_LIBRARY}") + endif() + endif() + +endif() diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index e01cc10f5..00adfe0c1 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -47,7 +47,7 @@ add_component_dir (resource ) add_component_dir (shader - shadermanager shadervisitor + shadermanager shadervisitor removedalphafunc ) add_component_dir (sceneutil @@ -143,7 +143,7 @@ add_component_dir (fontloader ) add_component_dir (sdlutil - sdlgraphicswindow imagetosurface sdlinputwrapper sdlvideowrapper events sdlcursormanager + gl4es_init sdlgraphicswindow imagetosurface sdlinputwrapper sdlvideowrapper events sdlcursormanager ) ENDIF(BUILD_OPENMW OR BUILD_OPENCS) @@ -242,6 +242,9 @@ if (BUILD_OPENMW OR BUILD_OPENCS) endif() # End of tes3mp change (major) +# Start of tes3mp change (major) +# +# Don't require the DetourNavigator when building the server if (BUILD_OPENMW OR BUILD_OPENCS) add_component_dir(detournavigator debug @@ -261,8 +264,10 @@ if (BUILD_OPENMW OR BUILD_OPENCS) settings navigator findrandompointaroundcircle + raycast ) endif() +# End of tes3mp change (major) set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui ) @@ -305,20 +310,26 @@ include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) target_link_libraries(components - ${Boost_SYSTEM_LIBRARY} - ${Boost_FILESYSTEM_LIBRARY} - ${Boost_PROGRAM_OPTIONS_LIBRARY} - ${Boost_IOSTREAMS_LIBRARY} - ${OSG_LIBRARIES} - ${OPENTHREADS_LIBRARIES} + # CMake's built-in OSG finder does not use pkgconfig, so we have to + # manually ensure the order is correct for inter-library dependencies. + # This only makes a difference with `-DOPENMW_USE_SYSTEM_OSG=ON -DOSG_STATIC=ON`. + # https://gitlab.kitware.com/cmake/cmake/-/issues/21701 ${OSGPARTICLE_LIBRARIES} - ${OSGUTIL_LIBRARIES} - ${OSGDB_LIBRARIES} ${OSGVIEWER_LIBRARIES} - ${OSGTEXT_LIBRARIES} - ${OSGGA_LIBRARIES} ${OSGSHADOW_LIBRARIES} ${OSGANIMATION_LIBRARIES} + ${OSGGA_LIBRARIES} + ${OSGTEXT_LIBRARIES} + ${OSGDB_LIBRARIES} + ${OSGUTIL_LIBRARIES} + ${OSG_LIBRARIES} + ${OPENTHREADS_LIBRARIES} + + ${Boost_SYSTEM_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_IOSTREAMS_LIBRARY} + ${SDL2_LIBRARIES} ${OPENGL_gl_LIBRARY} ${MyGUI_LIBRARIES} diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index 3fd74dd83..f6220b7ce 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -28,12 +28,11 @@ #include #include -using namespace std; using namespace Bsa; /// Error handling -void BSAFile::fail(const string &msg) +void BSAFile::fail(const std::string &msg) { throw std::runtime_error("BSA Error: " + msg + "\nArchive: " + mFilename); } @@ -160,7 +159,7 @@ int BSAFile::getIndex(const char *str) const } /// Open an archive file. -void BSAFile::open(const string &file) +void BSAFile::open(const std::string &file) { mFilename = file; readHeader(); @@ -171,7 +170,7 @@ Files::IStreamPtr BSAFile::getFile(const char *file) assert(file); int i = getIndex(file); if(i == -1) - fail("File not found: " + string(file)); + fail("File not found: " + std::string(file)); const FileStruct &fs = mFiles[i]; diff --git a/components/bullethelpers/operators.hpp b/components/bullethelpers/operators.hpp index dd2ec8017..9250563ae 100644 --- a/components/bullethelpers/operators.hpp +++ b/components/bullethelpers/operators.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include diff --git a/components/debug/gldebug.cpp b/components/debug/gldebug.cpp index 3c5ec728a..37829a8c1 100644 --- a/components/debug/gldebug.cpp +++ b/components/debug/gldebug.cpp @@ -32,133 +32,246 @@ either expressed or implied, of the FreeBSD Project. #include "gldebug.hpp" #include +#include #include // OpenGL constants not provided by OSG: #include -void debugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) +namespace Debug { -#ifdef GL_DEBUG_OUTPUT - std::string srcStr; - switch (source) + + void debugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) { - case GL_DEBUG_SOURCE_API: - srcStr = "API"; - break; - case GL_DEBUG_SOURCE_WINDOW_SYSTEM: - srcStr = "WINDOW_SYSTEM"; - break; - case GL_DEBUG_SOURCE_SHADER_COMPILER: - srcStr = "SHADER_COMPILER"; - break; - case GL_DEBUG_SOURCE_THIRD_PARTY: - srcStr = "THIRD_PARTY"; - break; - case GL_DEBUG_SOURCE_APPLICATION: - srcStr = "APPLICATION"; - break; - case GL_DEBUG_SOURCE_OTHER: - srcStr = "OTHER"; - break; - default: - srcStr = "UNDEFINED"; - break; +#ifdef GL_DEBUG_OUTPUT + std::string srcStr; + switch (source) + { + case GL_DEBUG_SOURCE_API: + srcStr = "API"; + break; + case GL_DEBUG_SOURCE_WINDOW_SYSTEM: + srcStr = "WINDOW_SYSTEM"; + break; + case GL_DEBUG_SOURCE_SHADER_COMPILER: + srcStr = "SHADER_COMPILER"; + break; + case GL_DEBUG_SOURCE_THIRD_PARTY: + srcStr = "THIRD_PARTY"; + break; + case GL_DEBUG_SOURCE_APPLICATION: + srcStr = "APPLICATION"; + break; + case GL_DEBUG_SOURCE_OTHER: + srcStr = "OTHER"; + break; + default: + srcStr = "UNDEFINED"; + break; + } + + std::string typeStr; + + Level logSeverity = Warning; + switch (type) + { + case GL_DEBUG_TYPE_ERROR: + typeStr = "ERROR"; + logSeverity = Error; + break; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: + typeStr = "DEPRECATED_BEHAVIOR"; + break; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: + typeStr = "UNDEFINED_BEHAVIOR"; + break; + case GL_DEBUG_TYPE_PORTABILITY: + typeStr = "PORTABILITY"; + break; + case GL_DEBUG_TYPE_PERFORMANCE: + typeStr = "PERFORMANCE"; + break; + case GL_DEBUG_TYPE_OTHER: + typeStr = "OTHER"; + break; + default: + typeStr = "UNDEFINED"; + break; + } + + Log(logSeverity) << "OpenGL " << typeStr << " [" << srcStr << "]: " << message; +#endif } - std::string typeStr; + class PushDebugGroup + { + public: + static std::unique_ptr sInstance; + + void (GL_APIENTRY * glPushDebugGroup) (GLenum source, GLuint id, GLsizei length, const GLchar * message); + + void (GL_APIENTRY * glPopDebugGroup) (void); - Debug::Level logSeverity = Debug::Warning; - switch (type) + bool valid() + { + return glPushDebugGroup && glPopDebugGroup; + } + }; + + std::unique_ptr PushDebugGroup::sInstance{ std::make_unique() }; + + EnableGLDebugOperation::EnableGLDebugOperation() : osg::GraphicsOperation("EnableGLDebugOperation", false) { - case GL_DEBUG_TYPE_ERROR: - typeStr = "ERROR"; - logSeverity = Debug::Error; - break; - case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: - typeStr = "DEPRECATED_BEHAVIOR"; - break; - case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: - typeStr = "UNDEFINED_BEHAVIOR"; - break; - case GL_DEBUG_TYPE_PORTABILITY: - typeStr = "PORTABILITY"; - break; - case GL_DEBUG_TYPE_PERFORMANCE: - typeStr = "PERFORMANCE"; - break; - case GL_DEBUG_TYPE_OTHER: - typeStr = "OTHER"; - break; - default: - typeStr = "UNDEFINED"; - break; } - Log(logSeverity) << "OpenGL " << typeStr << " [" << srcStr << "]: " << message; + void EnableGLDebugOperation::operator()(osg::GraphicsContext* graphicsContext) + { +#ifdef GL_DEBUG_OUTPUT + OpenThreads::ScopedLock lock(mMutex); + + unsigned int contextID = graphicsContext->getState()->getContextID(); + + typedef void (GL_APIENTRY *DEBUGPROC)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam); + typedef void (GL_APIENTRY *GLDebugMessageControlFunction)(GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); + typedef void (GL_APIENTRY *GLDebugMessageCallbackFunction)(DEBUGPROC, const void* userParam); + + GLDebugMessageControlFunction glDebugMessageControl = nullptr; + GLDebugMessageCallbackFunction glDebugMessageCallback = nullptr; + + if (osg::isGLExtensionSupported(contextID, "GL_KHR_debug")) + { + osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallback"); + osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControl"); + osg::setGLExtensionFuncPtr(PushDebugGroup::sInstance->glPushDebugGroup, "glPushDebugGroup"); + osg::setGLExtensionFuncPtr(PushDebugGroup::sInstance->glPopDebugGroup, "glPopDebugGroup"); + } + else if (osg::isGLExtensionSupported(contextID, "GL_ARB_debug_output")) + { + osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallbackARB"); + osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControlARB"); + } + else if (osg::isGLExtensionSupported(contextID, "GL_AMD_debug_output")) + { + osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallbackAMD"); + osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControlAMD"); + } + + if (glDebugMessageCallback && glDebugMessageControl) + { + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_ERROR, GL_DEBUG_SEVERITY_MEDIUM, 0, nullptr, true); + glDebugMessageCallback(debugCallback, nullptr); + + Log(Info) << "OpenGL debug callback attached."; + } + else #endif -} + Log(Error) << "Unable to attach OpenGL debug callback."; + } -void enableGLDebugExtension(unsigned int contextID) -{ -#ifdef GL_DEBUG_OUTPUT - typedef void (GL_APIENTRY *DEBUGPROC)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam); - typedef void (GL_APIENTRY *GLDebugMessageControlFunction)(GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); - typedef void (GL_APIENTRY *GLDebugMessageCallbackFunction)(DEBUGPROC, const void* userParam); - - GLDebugMessageControlFunction glDebugMessageControl = nullptr; - GLDebugMessageCallbackFunction glDebugMessageCallback = nullptr; - - if (osg::isGLExtensionSupported(contextID, "GL_KHR_debug")) + bool shouldDebugOpenGL() + { + const char* env = std::getenv("OPENMW_DEBUG_OPENGL"); + if (!env) + return false; + std::string str(env); + if (str.length() == 0) + return true; + + return str.find("OFF") == std::string::npos && str.find("0") == std::string::npos && str.find("NO") == std::string::npos; + } + + DebugGroup::DebugGroup(const std::string & message, GLuint id) + #ifdef GL_DEBUG_OUTPUT + : mSource(GL_DEBUG_SOURCE_APPLICATION) + #else + : mSource(0x824A) + #endif + , mId(id) + , mMessage(message) { - osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallback"); - osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControl"); } - else if (osg::isGLExtensionSupported(contextID, "GL_ARB_debug_output")) + + DebugGroup::DebugGroup(const DebugGroup & debugGroup, const osg::CopyOp & copyop) + : osg::StateAttribute(debugGroup, copyop) + , mSource(debugGroup.mSource) + , mId(debugGroup.mId) + , mMessage(debugGroup.mMessage) { - osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallbackARB"); - osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControlARB"); } - else if (osg::isGLExtensionSupported(contextID, "GL_AMD_debug_output")) + + void DebugGroup::apply(osg::State & state) const { - osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallbackAMD"); - osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControlAMD"); + if (!PushDebugGroup::sInstance->valid()) + { + Log(Error) << "OpenGL debug groups not supported on this system, or OPENMW_DEBUG_OPENGL environment variable not set."; + return; + } + + auto& attributeVec = state.getAttributeVec(this); + auto& lastAppliedStack = sLastAppliedStack[state.getContextID()]; + + size_t firstNonMatch = 0; + while (firstNonMatch < lastAppliedStack.size() + && ((firstNonMatch < attributeVec.size() && lastAppliedStack[firstNonMatch] == attributeVec[firstNonMatch].first) + || lastAppliedStack[firstNonMatch] == this)) + firstNonMatch++; + + for (size_t i = lastAppliedStack.size(); i > firstNonMatch; --i) + lastAppliedStack[i - 1]->pop(state); + lastAppliedStack.resize(firstNonMatch); + + lastAppliedStack.reserve(attributeVec.size()); + for (size_t i = firstNonMatch; i < attributeVec.size(); ++i) + { + const DebugGroup* group = static_cast(attributeVec[i].first); + group->push(state); + lastAppliedStack.push_back(group); + } + if (!(lastAppliedStack.back() == this)) + { + push(state); + lastAppliedStack.push_back(this); + } } - if (glDebugMessageCallback && glDebugMessageControl) + int DebugGroup::compare(const StateAttribute & sa) const { - glEnable(GL_DEBUG_OUTPUT); - glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_ERROR, GL_DEBUG_SEVERITY_MEDIUM, 0, nullptr, true); - glDebugMessageCallback(debugCallback, nullptr); + COMPARE_StateAttribute_Types(DebugGroup, sa); + + COMPARE_StateAttribute_Parameter(mSource); + COMPARE_StateAttribute_Parameter(mId); + COMPARE_StateAttribute_Parameter(mMessage); - Log(Debug::Info) << "OpenGL debug callback attached."; + return 0; } - else -#endif - Log(Debug::Error) << "Unable to attach OpenGL debug callback."; -} -Debug::EnableGLDebugOperation::EnableGLDebugOperation() : osg::GraphicsOperation("EnableGLDebugOperation", false) -{ -} + void DebugGroup::releaseGLObjects(osg::State * state) const + { + if (state) + sLastAppliedStack.erase(state->getContextID()); + else + sLastAppliedStack.clear(); + } -void Debug::EnableGLDebugOperation::operator()(osg::GraphicsContext* graphicsContext) -{ - OpenThreads::ScopedLock lock(mMutex); + bool DebugGroup::isValid() const + { + return mSource || mId || mMessage.length(); + } - unsigned int contextID = graphicsContext->getState()->getContextID(); - enableGLDebugExtension(contextID); -} + void DebugGroup::push(osg::State & state) const + { + if (isValid()) + PushDebugGroup::sInstance->glPushDebugGroup(mSource, mId, mMessage.size(), mMessage.c_str()); + } + + void DebugGroup::pop(osg::State & state) const + { + if (isValid()) + PushDebugGroup::sInstance->glPopDebugGroup(); + } + + std::map> DebugGroup::sLastAppliedStack{}; -bool Debug::shouldDebugOpenGL() -{ - const char* env = std::getenv("OPENMW_DEBUG_OPENGL"); - if (!env) - return false; - std::string str(env); - if (str.length() == 0) - return true; - - return str.find("OFF") == std::string::npos && str.find("0") == std::string::npos && str.find("NO") == std::string::npos; } diff --git a/components/debug/gldebug.hpp b/components/debug/gldebug.hpp index 8be747afe..b6f32c9cf 100644 --- a/components/debug/gldebug.hpp +++ b/components/debug/gldebug.hpp @@ -17,5 +17,65 @@ namespace Debug }; bool shouldDebugOpenGL(); + + + /* + Debug groups allow rendering to be annotated, making debugging via APITrace/CodeXL/NSight etc. much clearer. + + Because I've not thought of a quick and clean way of doing it without incurring a small performance cost, + there are no uses of this class checked in. For now, add annotations locally when you need them. + + To use this class, add it to a StateSet just like any other StateAttribute. Prefer the string-only constructor. + You'll need OPENMW_DEBUG_OPENGL set to true, or shouldDebugOpenGL() redefined to just return true as otherwise + the extension function pointers won't get set up. That can maybe be cleaned up in the future. + + Beware that consecutive identical debug groups (i.e. pointers match) won't always get applied due to OSG thinking + it's already applied them. Either avoid nesting the same object, add dummy groups so they're not consecutive, or + ensure the leaf group isn't identical to its parent. + */ + class DebugGroup : public osg::StateAttribute + { + public: + DebugGroup() + : mSource(0) + , mId(0) + , mMessage("") + {} + + DebugGroup(GLenum source, GLuint id, const std::string& message) + : mSource(source) + , mId(id) + , mMessage(message) + {} + + DebugGroup(const std::string& message, GLuint id = 0); + + DebugGroup(const DebugGroup& debugGroup, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); + + META_StateAttribute(Debug, DebugGroup, osg::StateAttribute::Type(101)); + + void apply(osg::State& state) const override; + + int compare(const StateAttribute& sa) const override; + + void releaseGLObjects(osg::State* state = nullptr) const override; + + virtual bool isValid() const; + + protected: + virtual ~DebugGroup() = default; + + virtual void push(osg::State& state) const; + + virtual void pop(osg::State& state) const; + + GLenum mSource; + GLuint mId; + std::string mMessage; + + static std::map> sLastAppliedStack; + + friend EnableGLDebugOperation; + }; } #endif diff --git a/components/detournavigator/navigator.cpp b/components/detournavigator/navigator.cpp index 658e539ad..700217c52 100644 --- a/components/detournavigator/navigator.cpp +++ b/components/detournavigator/navigator.cpp @@ -1,5 +1,6 @@ #include "findrandompointaroundcircle.hpp" #include "navigator.hpp" +#include "raycast.hpp" namespace DetourNavigator { @@ -17,4 +18,19 @@ namespace DetourNavigator return std::optional(); return std::optional(fromNavMeshCoordinates(settings, *result)); } + + std::optional Navigator::raycast(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start, + const osg::Vec3f& end, const Flags includeFlags) const + { + const auto navMesh = getNavMesh(agentHalfExtents); + if (navMesh == nullptr) + return {}; + const auto settings = getSettings(); + const auto result = DetourNavigator::raycast(navMesh->lockConst()->getImpl(), + toNavMeshCoordinates(settings, agentHalfExtents), toNavMeshCoordinates(settings, start), + toNavMeshCoordinates(settings, end), includeFlags, settings); + if (!result) + return {}; + return fromNavMeshCoordinates(settings, *result); + } } diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index a79aa59d4..ef61f78c6 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -223,6 +223,17 @@ namespace DetourNavigator std::optional findRandomPointAroundCircle(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start, const float maxRadius, const Flags includeFlags) const; + /** + * @brief raycast finds farest navmesh point from start on a line from start to end that has path from start. + * @param agentHalfExtents allows to find navmesh for given actor. + * @param start of the line + * @param end of the line + * @param includeFlags setup allowed surfaces for actor to walk. + * @return not empty optional with position if point is found and empty optional if point is not found. + */ + std::optional raycast(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start, + const osg::Vec3f& end, const Flags includeFlags) const; + virtual RecastMeshTiles getRecastMeshTiles() = 0; }; } diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index c47cf9766..142ba590d 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -18,6 +18,8 @@ namespace DetourNavigator void NavigatorImpl::addAgent(const osg::Vec3f& agentHalfExtents) { + if(agentHalfExtents.length2() <= 0) + return; ++mAgents[agentHalfExtents]; mNavMeshManager.addAgent(agentHalfExtents); } diff --git a/components/detournavigator/raycast.cpp b/components/detournavigator/raycast.cpp new file mode 100644 index 000000000..86fabe9c1 --- /dev/null +++ b/components/detournavigator/raycast.cpp @@ -0,0 +1,44 @@ +#include "raycast.hpp" +#include "settings.hpp" +#include "findsmoothpath.hpp" + +#include +#include +#include + +#include + +namespace DetourNavigator +{ + std::optional raycast(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, + const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const Settings& settings) + { + dtNavMeshQuery navMeshQuery; + if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes)) + return {}; + + dtQueryFilter queryFilter; + queryFilter.setIncludeFlags(includeFlags); + + dtPolyRef ref = 0; + if (dtStatus status = navMeshQuery.findNearestPoly(start.ptr(), halfExtents.ptr(), &queryFilter, &ref, nullptr); + dtStatusFailed(status) || ref == 0) + return {}; + + const unsigned options = 0; + std::array path; + dtRaycastHit hit; + hit.path = path.data(); + hit.maxPath = path.size(); + if (dtStatus status = navMeshQuery.raycast(ref, start.ptr(), end.ptr(), &queryFilter, options, &hit); + dtStatusFailed(status) || hit.pathCount == 0) + return {}; + + osg::Vec3f hitPosition; + if (dtStatus status = navMeshQuery.closestPointOnPoly(path[hit.pathCount - 1], end.ptr(), hitPosition.ptr(), nullptr); + dtStatusFailed(status)) + return {}; + + return hitPosition; + } +} diff --git a/components/detournavigator/raycast.hpp b/components/detournavigator/raycast.hpp new file mode 100644 index 000000000..ddf61b49f --- /dev/null +++ b/components/detournavigator/raycast.hpp @@ -0,0 +1,19 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RAYCAST_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RAYCAST_H + +#include "flags.hpp" + +#include +#include + +class dtNavMesh; + +namespace DetourNavigator +{ + struct Settings; + + std::optional raycast(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, + const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const Settings& settings); +} + +#endif diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 1b6eca734..4e7dce876 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -357,16 +357,14 @@ std::string ESMReader::getString(int size) void ESMReader::fail(const std::string &msg) { - using namespace std; - - stringstream ss; + std::stringstream ss; ss << "ESM Error: " << msg; ss << "\n File: " << mCtx.filename; ss << "\n Record: " << mCtx.recName.toString(); ss << "\n Subrecord: " << mCtx.subName.toString(); if (mEsm.get()) - ss << "\n Offset: 0x" << hex << mEsm->tellg(); + ss << "\n Offset: 0x" << std::hex << mEsm->tellg(); throw std::runtime_error(ss.str()); } diff --git a/components/esm/variant.cpp b/components/esm/variant.cpp index 6ff31dc44..cbcb6e002 100644 --- a/components/esm/variant.cpp +++ b/components/esm/variant.cpp @@ -62,10 +62,31 @@ ESM::Variant& ESM::Variant::operator= (const Variant& variant) return *this; } +ESM::Variant& ESM::Variant::operator= (Variant&& variant) +{ + if (&variant!=this) + { + delete mData; + + mType = variant.mType; + mData = variant.mData; + + variant.mData = nullptr; + } + + return *this; +} + ESM::Variant::Variant (const Variant& variant) : mType (variant.mType), mData (variant.mData ? variant.mData->clone() : nullptr) {} +ESM::Variant::Variant(Variant&& variant) +: mType (variant.mType), mData (variant.mData) +{ + variant.mData = nullptr; +} + ESM::VarType ESM::Variant::getType() const { return mType; diff --git a/components/esm/variant.hpp b/components/esm/variant.hpp index 5f179a7bd..20b3aa76e 100644 --- a/components/esm/variant.hpp +++ b/components/esm/variant.hpp @@ -46,8 +46,10 @@ namespace ESM ~Variant(); Variant& operator= (const Variant& variant); + Variant& operator= (Variant && variant); Variant (const Variant& variant); + Variant (Variant&& variant); VarType getType() const; diff --git a/components/myguiplatform/myguicompat.h b/components/myguiplatform/myguicompat.h new file mode 100644 index 000000000..04ca11a79 --- /dev/null +++ b/components/myguiplatform/myguicompat.h @@ -0,0 +1,12 @@ +#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUICOMPAT_H +#define OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUICOMPAT_H + +#include + +#if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0) + #define OPENMW_MYGUI_CONST_GETTER_3_4_1 const +#else + #define OPENMW_MYGUI_CONST_GETTER_3_4_1 +#endif + +#endif // OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUICOMPAT_H diff --git a/components/myguiplatform/myguidatamanager.cpp b/components/myguiplatform/myguidatamanager.cpp index c90e09221..0310e996b 100644 --- a/components/myguiplatform/myguidatamanager.cpp +++ b/components/myguiplatform/myguidatamanager.cpp @@ -15,7 +15,7 @@ void DataManager::setResourcePath(const std::string &path) mResourcePath = path; } -MyGUI::IDataStream *DataManager::getData(const std::string &name) +MyGUI::IDataStream *DataManager::getData(const std::string &name) OPENMW_MYGUI_CONST_GETTER_3_4_1 { std::string fullpath = getDataPath(name); std::unique_ptr stream; @@ -34,13 +34,13 @@ void DataManager::freeData(MyGUI::IDataStream *data) delete data; } -bool DataManager::isDataExist(const std::string &name) +bool DataManager::isDataExist(const std::string &name) OPENMW_MYGUI_CONST_GETTER_3_4_1 { std::string fullpath = mResourcePath + "/" + name; return boost::filesystem::exists(fullpath); } -const MyGUI::VectorString &DataManager::getDataListNames(const std::string &pattern) +const MyGUI::VectorString &DataManager::getDataListNames(const std::string &pattern) OPENMW_MYGUI_CONST_GETTER_3_4_1 { // TODO: pattern matching (unused?) static MyGUI::VectorString strings; @@ -49,7 +49,7 @@ const MyGUI::VectorString &DataManager::getDataListNames(const std::string &patt return strings; } -const std::string &DataManager::getDataPath(const std::string &name) +const std::string &DataManager::getDataPath(const std::string &name) OPENMW_MYGUI_CONST_GETTER_3_4_1 { static std::string result; result.clear(); diff --git a/components/myguiplatform/myguidatamanager.hpp b/components/myguiplatform/myguidatamanager.hpp index a97c6ad2e..ca2f94899 100644 --- a/components/myguiplatform/myguidatamanager.hpp +++ b/components/myguiplatform/myguidatamanager.hpp @@ -3,10 +3,11 @@ #include +#include "myguicompat.h" + namespace osgMyGUI { - class DataManager : public MyGUI::DataManager { public: @@ -18,7 +19,7 @@ public: /** Get data stream from specified resource name. @param _name Resource name (usually file name). */ - MyGUI::IDataStream* getData(const std::string& _name) override; + MyGUI::IDataStream* getData(const std::string& _name) OPENMW_MYGUI_CONST_GETTER_3_4_1 override; /** Free data stream. @param _data Data stream. @@ -28,18 +29,18 @@ public: /** Is data with specified name exist. @param _name Resource name. */ - bool isDataExist(const std::string& _name) override; + bool isDataExist(const std::string& _name) OPENMW_MYGUI_CONST_GETTER_3_4_1 override; /** Get all data names with names that matches pattern. @param _pattern Pattern to match (for example "*.layout"). */ - const MyGUI::VectorString& getDataListNames(const std::string& _pattern) override; + const MyGUI::VectorString& getDataListNames(const std::string& _pattern) OPENMW_MYGUI_CONST_GETTER_3_4_1 override; /** Get full path to data. @param _name Resource name. @return Return full path to specified data. */ - const std::string& getDataPath(const std::string& _name) override; + const std::string& getDataPath(const std::string& _name) OPENMW_MYGUI_CONST_GETTER_3_4_1 override; private: std::string mResourcePath; diff --git a/components/myguiplatform/myguiloglistener.cpp b/components/myguiplatform/myguiloglistener.cpp index a22ac8fc5..74b4b3081 100644 --- a/components/myguiplatform/myguiloglistener.cpp +++ b/components/myguiplatform/myguiloglistener.cpp @@ -2,11 +2,15 @@ #include +#include + namespace osgMyGUI { void CustomLogListener::open() { mStream.open(boost::filesystem::path(mFileName), std::ios_base::out); + if (!mStream.is_open()) + Log(Debug::Error) << "Unable to create MyGUI log with path " << mFileName; } void CustomLogListener::close() diff --git a/components/myguiplatform/myguirendermanager.cpp b/components/myguiplatform/myguirendermanager.cpp index dc771f11f..77a5ee533 100644 --- a/components/myguiplatform/myguirendermanager.cpp +++ b/components/myguiplatform/myguirendermanager.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -14,6 +15,9 @@ #include +#include + +#include "myguicompat.h" #include "myguitexture.hpp" #define MYGUI_PLATFORM_LOG_SECTION "Platform" @@ -263,7 +267,7 @@ public: osg::VertexBufferObject* getVertexBuffer(); void setVertexCount(size_t count) override; - size_t getVertexCount() override; + size_t getVertexCount() OPENMW_MYGUI_CONST_GETTER_3_4_1 override; MyGUI::Vertex *lock() override; void unlock() override; @@ -290,7 +294,7 @@ void OSGVertexBuffer::setVertexCount(size_t count) mNeedVertexCount = count; } -size_t OSGVertexBuffer::getVertexCount() +size_t OSGVertexBuffer::getVertexCount() OPENMW_MYGUI_CONST_GETTER_3_4_1 { return mNeedVertexCount; } @@ -438,14 +442,23 @@ void RenderManager::doRender(MyGUI::IVertexBuffer *buffer, MyGUI::ITexture *text batch.mVertexBuffer = static_cast(buffer)->getVertexBuffer(); batch.mArray = static_cast(buffer)->getVertexArray(); static_cast(buffer)->markUsed(); + bool premultipliedAlpha = false; if (texture) { batch.mTexture = static_cast(texture)->getTexture(); if (batch.mTexture->getDataVariance() == osg::Object::DYNAMIC) mDrawable->setDataVariance(osg::Object::DYNAMIC); // only for this frame, reset in begin() + batch.mTexture->getUserValue("premultiplied alpha", premultipliedAlpha); } if (mInjectState) batch.mStateSet = mInjectState; + else if (premultipliedAlpha) + { + // This is hacky, but MyGUI made it impossible to use a custom layer for a nested node, so state couldn't be injected 'properly' + osg::ref_ptr stateSet = new osg::StateSet(); + stateSet->setAttribute(new osg::BlendFunc(osg::BlendFunc::ONE, osg::BlendFunc::ONE_MINUS_SRC_ALPHA)); + batch.mStateSet = stateSet; + } mDrawable->addBatch(batch); } @@ -560,4 +573,14 @@ bool RenderManager::checkTexture(MyGUI::ITexture* _texture) return true; } +#if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0) +void RenderManager::registerShader( + const std::string& _shaderName, + const std::string& _vertexProgramFile, + const std::string& _fragmentProgramFile) +{ + MYGUI_PLATFORM_LOG(Warning, "osgMyGUI::RenderManager::registerShader is not implemented"); +} +#endif + } diff --git a/components/myguiplatform/myguirendermanager.hpp b/components/myguiplatform/myguirendermanager.hpp index 72abebd18..3c3fb672d 100644 --- a/components/myguiplatform/myguirendermanager.hpp +++ b/components/myguiplatform/myguirendermanager.hpp @@ -5,6 +5,8 @@ #include +#include "myguicompat.h" + namespace Resource { class ImageManager; @@ -70,7 +72,8 @@ public: const MyGUI::IntSize& getViewSize() const override { return mViewSize; } /** @see RenderManager::getVertexFormat */ - MyGUI::VertexColourType getVertexFormat() override { return mVertexFormat; } + MyGUI::VertexColourType getVertexFormat() OPENMW_MYGUI_CONST_GETTER_3_4_1 override + { return mVertexFormat; } /** @see RenderManager::isFormatSupported */ bool isFormatSupported(MyGUI::PixelFormat format, MyGUI::TextureUsage usage) override; @@ -102,17 +105,22 @@ public: void setInjectState(osg::StateSet* stateSet); /** @see IRenderTarget::getInfo */ - const MyGUI::RenderTargetInfo& getInfo() override { return mInfo; } + const MyGUI::RenderTargetInfo& getInfo() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mInfo; } bool checkTexture(MyGUI::ITexture* _texture); // setViewSize() is a part of MyGUI::RenderManager interface since 3.4.0 release -#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,4,0) +#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3, 4, 0) void setViewSize(int width, int height); #else void setViewSize(int width, int height) override; #endif + // registerShader() is a part of MyGUI::RenderManager interface since 3.4.1 release +#if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0) + void registerShader(const std::string& _shaderName, const std::string& _vertexProgramFile, const std::string& _fragmentProgramFile) override; +#endif + /*internal:*/ void collectDrawCalls(); diff --git a/components/myguiplatform/myguitexture.cpp b/components/myguiplatform/myguitexture.cpp index 598f5a14e..ce7332cc7 100644 --- a/components/myguiplatform/myguitexture.cpp +++ b/components/myguiplatform/myguitexture.cpp @@ -115,16 +115,6 @@ namespace osgMyGUI Log(Debug::Warning) << "Would save image to file " << fname; } - int OSGTexture::getWidth() - { - return mWidth; - } - - int OSGTexture::getHeight() - { - return mHeight; - } - void *OSGTexture::lock(MyGUI::TextureUsage /*access*/) { if (!mTexture.valid()) @@ -167,15 +157,14 @@ namespace osgMyGUI mLockedImage = nullptr; } - bool OSGTexture::isLocked() - { - return mLockedImage.valid(); - } - // Render-to-texture not currently implemented. MyGUI::IRenderTarget* OSGTexture::getRenderTarget() { return nullptr; } +#if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0) + void OSGTexture::setShader(const std::string& _shaderName) + { Log(Debug::Warning) << "OSGTexture::setShader is not implemented"; } +#endif } diff --git a/components/myguiplatform/myguitexture.hpp b/components/myguiplatform/myguitexture.hpp index 6baeb7459..a34f1b762 100644 --- a/components/myguiplatform/myguitexture.hpp +++ b/components/myguiplatform/myguitexture.hpp @@ -5,6 +5,12 @@ #include +#if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0) + #define OPENMW_MYGUI_CONST_GETTER_3_4_1 const +#else + #define OPENMW_MYGUI_CONST_GETTER_3_4_1 +#endif + namespace osg { class Image; @@ -47,17 +53,22 @@ namespace osgMyGUI void* lock(MyGUI::TextureUsage access) override; void unlock() override; - bool isLocked() override; + bool isLocked() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mLockedImage.valid(); } - int getWidth() override; - int getHeight() override; + int getWidth() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mWidth; } + int getHeight() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mHeight; } - MyGUI::PixelFormat getFormat() override { return mFormat; } - MyGUI::TextureUsage getUsage() override { return mUsage; } - size_t getNumElemBytes() override { return mNumElemBytes; } + MyGUI::PixelFormat getFormat() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mFormat; } + MyGUI::TextureUsage getUsage() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mUsage; } + size_t getNumElemBytes() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mNumElemBytes; } MyGUI::IRenderTarget *getRenderTarget() override; + // setShader() is a part of MyGUI::RenderManager interface since 3.4.1 release +#if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0) + void setShader(const std::string& _shaderName) override; +#endif + /*internal:*/ osg::Texture2D *getTexture() const { return mTexture.get(); } }; diff --git a/components/myguiplatform/scalinglayer.cpp b/components/myguiplatform/scalinglayer.cpp index 07a5161b2..99ed6d07a 100644 --- a/components/myguiplatform/scalinglayer.cpp +++ b/components/myguiplatform/scalinglayer.cpp @@ -3,6 +3,8 @@ #include #include +#include "myguicompat.h" + namespace osgMyGUI { @@ -37,7 +39,7 @@ namespace osgMyGUI mTarget->doRender(_buffer, _texture, _count); } - const MyGUI::RenderTargetInfo& getInfo() override + const MyGUI::RenderTargetInfo& getInfo() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { mInfo = mTarget->getInfo(); mInfo.hOffset = mHOffset; @@ -51,7 +53,7 @@ namespace osgMyGUI MyGUI::IRenderTarget* mTarget; MyGUI::IntSize mViewSize; float mHOffset, mVOffset; - MyGUI::RenderTargetInfo mInfo; + mutable MyGUI::RenderTargetInfo mInfo; }; MyGUI::ILayerItem *ScalingLayer::getLayerItemByPoint(int _left, int _top) const diff --git a/components/nif/nifkey.hpp b/components/nif/nifkey.hpp index de2fa31c8..91869ff84 100644 --- a/components/nif/nifkey.hpp +++ b/components/nif/nifkey.hpp @@ -77,7 +77,7 @@ struct KeyMapT { mInterpolationType = nif->getUInt(); - KeyT key; + KeyType key = {}; NIFStream &nifReference = *nif; if (mInterpolationType == InterpolationType_Linear diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 902f15fb3..3e34969f4 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1725,11 +1725,16 @@ namespace NifOsg case Nif::RC_NiZBufferProperty: { const Nif::NiZBufferProperty* zprop = static_cast(property); - // VER_MW doesn't support a DepthFunction according to NifSkope + osg::StateSet* stateset = node->getOrCreateStateSet(); + // Depth test flag + stateset->setMode(GL_DEPTH_TEST, zprop->flags&1 ? osg::StateAttribute::ON + : osg::StateAttribute::OFF); osg::ref_ptr depth = new osg::Depth; + // Depth write flag depth->setWriteMask((zprop->flags>>1)&1); + // Morrowind ignores depth test function depth = shareAttribute(depth); - node->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); break; } // OSG groups the material properties that NIFs have separate, so we have to parse them all again when one changed diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 19cc96433..e46ce2016 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -257,6 +257,12 @@ namespace Resource node->accept(*shaderVisitor); } + void SceneManager::reinstateRemovedState(osg::ref_ptr node) + { + osg::ref_ptr reinstateRemovedStateVisitor = new Shader::ReinstateRemovedStateVisitor(false); + node->accept(*reinstateRemovedStateVisitor); + } + void SceneManager::setClampLighting(bool clamp) { mClampLighting = clamp; @@ -297,6 +303,11 @@ namespace Resource mApplyLightingToEnvMaps = apply; } + void SceneManager::setConvertAlphaTestToAlphaToCoverage(bool convert) + { + mConvertAlphaTestToAlphaToCoverage = convert; + } + SceneManager::~SceneManager() { // this has to be defined in the .cpp file as we can't delete incomplete types @@ -771,6 +782,7 @@ namespace Resource shaderVisitor->setAutoUseSpecularMaps(mAutoUseSpecularMaps); shaderVisitor->setSpecularMapPattern(mSpecularMapPattern); shaderVisitor->setApplyLightingToEnvMaps(mApplyLightingToEnvMaps); + shaderVisitor->setConvertAlphaTestToAlphaToCoverage(mConvertAlphaTestToAlphaToCoverage); shaderVisitor->setTranslucentFramebuffer(translucentFramebuffer); return shaderVisitor; } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 9da6bc500..bf69a8c4b 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -75,9 +75,14 @@ namespace Resource Shader::ShaderManager& getShaderManager(); - /// Re-create shaders for this node, need to call this if texture stages or vertex color mode have changed. + /// Re-create shaders for this node, need to call this if alpha testing, texture stages or vertex color mode have changed. void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false, bool forceShadersForNode = false); + /// Applying shaders to a node may replace some fixed-function state. + /// This restores it. + /// When editing such state, it should be reinstated before the edits, and shaders should be recreated afterwards. + void reinstateRemovedState(osg::ref_ptr node); + /// @see ShaderVisitor::setForceShaders void setForceShaders(bool force); bool getForceShaders() const; @@ -100,6 +105,8 @@ namespace Resource void setApplyLightingToEnvMaps(bool apply); + void setConvertAlphaTestToAlphaToCoverage(bool convert); + void setShaderPath(const std::string& path); /// Check if a given scene is loaded and if so, update its usage timestamp to prevent it from being unloaded @@ -184,6 +191,7 @@ namespace Resource bool mAutoUseSpecularMaps; std::string mSpecularMapPattern; bool mApplyLightingToEnvMaps; + bool mConvertAlphaTestToAlphaToCoverage; osg::ref_ptr mInstanceCache; diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index ba903f6db..89e001a98 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -278,7 +278,7 @@ void VDSMCameraCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) static osg::ref_ptr ss; if (!ss) { - ShadowsBinAdder adder("ShadowsBin"); + ShadowsBinAdder adder("ShadowsBin", _vdsm->getCastingPrograms()); ss = new osg::StateSet; ss->setRenderBinDetails(osg::StateSet::OPAQUE_BIN, "ShadowsBin", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS); } @@ -782,7 +782,8 @@ void MWShadowTechnique::ViewDependentData::releaseGLObjects(osg::State* state) c MWShadowTechnique::MWShadowTechnique(): ShadowTechnique(), _enableShadows(false), - _debugHud(nullptr) + _debugHud(nullptr), + _castingPrograms{ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr } { _shadowRecievingPlaceholderStateSet = new osg::StateSet; mSetDummyStateWhenDisabled = false; @@ -790,6 +791,7 @@ MWShadowTechnique::MWShadowTechnique(): MWShadowTechnique::MWShadowTechnique(const MWShadowTechnique& vdsm, const osg::CopyOp& copyop): ShadowTechnique(vdsm,copyop) + , _castingPrograms(vdsm._castingPrograms) { _shadowRecievingPlaceholderStateSet = new osg::StateSet; _enableShadows = vdsm._enableShadows; @@ -870,7 +872,10 @@ void SceneUtil::MWShadowTechnique::enableFrontFaceCulling() _useFrontFaceCulling = true; if (_shadowCastingStateSet) + { _shadowCastingStateSet->setAttribute(new osg::CullFace(osg::CullFace::FRONT), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + _shadowCastingStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + } } void SceneUtil::MWShadowTechnique::disableFrontFaceCulling() @@ -878,17 +883,29 @@ void SceneUtil::MWShadowTechnique::disableFrontFaceCulling() _useFrontFaceCulling = false; if (_shadowCastingStateSet) + { + _shadowCastingStateSet->removeAttribute(osg::StateAttribute::CULLFACE); _shadowCastingStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); + } } void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & shaderManager) { // This can't be part of the constructor as OSG mandates that there be a trivial constructor available - - _castingProgram = new osg::Program(); - _castingProgram->addShader(shaderManager.getShader("shadowcasting_vertex.glsl", Shader::ShaderManager::DefineMap(), osg::Shader::VERTEX)); - _castingProgram->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", Shader::ShaderManager::DefineMap(), osg::Shader::FRAGMENT)); + osg::ref_ptr castingVertexShader = shaderManager.getShader("shadowcasting_vertex.glsl", {}, osg::Shader::VERTEX); + osg::ref_ptr exts = osg::GLExtensions::Get(0, false); + std::string useGPUShader4 = exts && exts->isGpuShader4Supported ? "1" : "0"; + for (int alphaFunc = GL_NEVER; alphaFunc <= GL_ALWAYS; ++alphaFunc) + { + auto& program = _castingPrograms[alphaFunc - GL_NEVER]; + program = new osg::Program(); + program->addShader(castingVertexShader); + program->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", { {"alphaFunc", std::to_string(alphaFunc)}, + {"alphaToCoverage", "0"}, + {"useGPUShader4", useGPUShader4} + }, osg::Shader::FRAGMENT)); + } } MWShadowTechnique::ViewDependentData* MWShadowTechnique::createViewDependentData(osgUtil::CullVisitor* /*cv*/) @@ -1606,10 +1623,11 @@ void MWShadowTechnique::createShaders() } - if (!_castingProgram) + if (!_castingPrograms[GL_ALWAYS - GL_NEVER]) OSG_NOTICE << "Shadow casting shader has not been set up. Remember to call setupCastingShader(Shader::ShaderManager &)" << std::endl; - _shadowCastingStateSet->setAttributeAndModes(_castingProgram, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + // Always use the GL_ALWAYS shader as the shadows bin will change it if necessary + _shadowCastingStateSet->setAttributeAndModes(_castingPrograms[GL_ALWAYS - GL_NEVER], osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); // The casting program uses a sampler, so to avoid undefined behaviour, we must bind a dummy texture in case no other is supplied _shadowCastingStateSet->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON); _shadowCastingStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); diff --git a/components/sceneutil/mwshadowtechnique.hpp b/components/sceneutil/mwshadowtechnique.hpp index 7b934b798..de57bf1fd 100644 --- a/components/sceneutil/mwshadowtechnique.hpp +++ b/components/sceneutil/mwshadowtechnique.hpp @@ -215,6 +215,8 @@ namespace SceneUtil { virtual void createShaders(); + virtual std::array, GL_ALWAYS - GL_NEVER + 1> getCastingPrograms() const { return _castingPrograms; } + virtual bool selectActiveLights(osgUtil::CullVisitor* cv, ViewDependentData* vdd) const; virtual osg::Polytope computeLightViewFrustumPolytope(Frustum& frustum, LightData& positionedLight); @@ -288,7 +290,7 @@ namespace SceneUtil { }; osg::ref_ptr _debugHud; - osg::ref_ptr _castingProgram; + std::array, GL_ALWAYS - GL_NEVER + 1> _castingPrograms; }; } diff --git a/components/sceneutil/shadowsbin.cpp b/components/sceneutil/shadowsbin.cpp index 520ad0362..5a4096f5c 100644 --- a/components/sceneutil/shadowsbin.cpp +++ b/components/sceneutil/shadowsbin.cpp @@ -1,7 +1,9 @@ #include "shadowsbin.hpp" #include #include +#include #include +#include #include using namespace osgUtil; @@ -25,9 +27,9 @@ namespace osg::StateSet::ModeList::const_iterator mf = l.find(mode); if (mf == l.end()) return; - int flags = mf->second; + unsigned int flags = mf->second; bool newValue = flags & osg::StateAttribute::ON; - accumulateState(currentValue, newValue, isOverride, ss->getMode(mode)); + accumulateState(currentValue, newValue, isOverride, flags); } inline bool materialNeedShadows(osg::Material* m) @@ -40,6 +42,10 @@ namespace namespace SceneUtil { +std::array, GL_ALWAYS - GL_NEVER + 1> ShadowsBin::sCastingPrograms = { + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr +}; + ShadowsBin::ShadowsBin() { mNoTestStateSet = new osg::StateSet; @@ -48,10 +54,16 @@ ShadowsBin::ShadowsBin() mShaderAlphaTestStateSet = new osg::StateSet; mShaderAlphaTestStateSet->addUniform(new osg::Uniform("alphaTestShadows", true)); - mShaderAlphaTestStateSet->setMode(GL_BLEND, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); + mShaderAlphaTestStateSet->setMode(GL_BLEND, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED | osg::StateAttribute::OVERRIDE); + + for (size_t i = 0; i < sCastingPrograms.size(); ++i) + { + mAlphaFuncShaders[i] = new osg::StateSet; + mAlphaFuncShaders[i]->setAttribute(sCastingPrograms[i], osg::StateAttribute::ON | osg::StateAttribute::PROTECTED | osg::StateAttribute::OVERRIDE); + } } -StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::unordered_set& uninterestingCache) +StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::unordered_set& uninterestingCache, bool cullFaceOverridden) { std::vector return_path; State state; @@ -71,7 +83,6 @@ StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::un continue; accumulateModeState(ss, state.mAlphaBlend, state.mAlphaBlendOverride, GL_BLEND); - accumulateModeState(ss, state.mAlphaTest, state.mAlphaTestOverride, GL_ALPHA_TEST); const osg::StateSet::AttributeList& attributes = ss->getAttributeList(); osg::StateSet::AttributeList::const_iterator found = attributes.find(std::make_pair(osg::StateAttribute::MATERIAL, 0)); @@ -83,10 +94,21 @@ StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::un state.mMaterial = nullptr; } - // osg::FrontFace specifies triangle winding, not front-face culling. We can't safely reparent anything under it. - found = attributes.find(std::make_pair(osg::StateAttribute::FRONTFACE, 0)); + found = attributes.find(std::make_pair(osg::StateAttribute::ALPHAFUNC, 0)); if (found != attributes.end()) - state.mImportantState = true; + { + // As force shaders is on, we know this is really a RemovedAlphaFunc + const osg::StateSet::RefAttributePair& rap = found->second; + accumulateState(state.mAlphaFunc, static_cast(rap.first.get()), state.mAlphaFuncOverride, rap.second); + } + + if (!cullFaceOverridden) + { + // osg::FrontFace specifies triangle winding, not front-face culling. We can't safely reparent anything under it unless GL_CULL_FACE is off or we flip face culling. + found = attributes.find(std::make_pair(osg::StateAttribute::FRONTFACE, 0)); + if (found != attributes.end()) + state.mImportantState = true; + } if ((*itr) != sg && !state.interesting()) uninterestingCache.insert(*itr); @@ -108,21 +130,45 @@ StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::un if (state.mAlphaBlend) { sg_new = sg->find_or_insert(mShaderAlphaTestStateSet); - for (RenderLeaf* leaf : sg->_leaves) - { + sg_new->_leaves = std::move(sg->_leaves); + for (RenderLeaf* leaf : sg_new->_leaves) leaf->_parent = sg_new; - sg_new->_leaves.push_back(leaf); - } - return sg_new; + sg = sg_new; + } + + // GL_ALWAYS is set by default by mwshadowtechnique + if (state.mAlphaFunc && state.mAlphaFunc->getFunction() != GL_ALWAYS) + { + sg_new = sg->find_or_insert(mAlphaFuncShaders[state.mAlphaFunc->getFunction() - GL_NEVER]); + sg_new->_leaves = std::move(sg->_leaves); + for (RenderLeaf* leaf : sg_new->_leaves) + leaf->_parent = sg_new; + sg = sg_new; } + return sg; } +void ShadowsBin::addPrototype(const std::string & name, const std::array, GL_ALWAYS - GL_NEVER + 1>& castingPrograms) +{ + sCastingPrograms = castingPrograms; + osg::ref_ptr bin(new ShadowsBin); + osgUtil::RenderBin::addRenderBinPrototype(name, bin); +} + +inline bool ShadowsBin::State::needTexture() const +{ + return mAlphaBlend || (mAlphaFunc && mAlphaFunc->getFunction() != GL_ALWAYS); +} + bool ShadowsBin::State::needShadows() const { - if (!mMaterial) - return true; - return materialNeedShadows(mMaterial); + if (mAlphaFunc && mAlphaFunc->getFunction() == GL_NEVER) + return false; + // other alpha func + material combinations might be skippable + if (mAlphaBlend && mMaterial) + return materialNeedShadows(mMaterial); + return true; } void ShadowsBin::sortImplementation() @@ -139,13 +185,27 @@ void ShadowsBin::sortImplementation() root = root->_parent; const osg::StateSet* ss = root->getStateSet(); if (ss->getMode(GL_NORMALIZE) & osg::StateAttribute::ON // that is root stategraph of renderingmanager cpp - || ss->getAttribute(osg::StateAttribute::VIEWPORT)) // fallback to rendertargets sg just in case + || ss->getAttribute(osg::StateAttribute::VIEWPORT)) // fallback to rendertarget's sg just in case break; if (!root->_parent) return; } StateGraph* noTestRoot = root->find_or_insert(mNoTestStateSet.get()); - // root is now a stategraph with useDiffuseMapForShadowAlpha disabled but minimal other state + // noTestRoot is now a stategraph with useDiffuseMapForShadowAlpha disabled but minimal other state + + bool cullFaceOverridden = false; + while ((root = root->_parent)) + { + if (!root->getStateSet()) + continue; + unsigned int cullFaceFlags = root->getStateSet()->getMode(GL_CULL_FACE); + if (cullFaceFlags & osg::StateAttribute::OVERRIDE && !(cullFaceFlags & osg::StateAttribute::ON)) + { + cullFaceOverridden = true; + break; + } + } + noTestRoot->_leaves.reserve(_stateGraphList.size()); StateGraphList newList; std::unordered_set uninterestingCache; @@ -154,7 +214,7 @@ void ShadowsBin::sortImplementation() // Render leaves which shouldn't use the diffuse map for shadow alpha but do cast shadows become children of root, so graph is now empty. Don't add to newList. // Graphs containing just render leaves which don't cast shadows are discarded. Don't add to newList. // Graphs containing other leaves need to be in newList. - StateGraph* graphToAdd = cullStateGraph(graph, noTestRoot, uninterestingCache); + StateGraph* graphToAdd = cullStateGraph(graph, noTestRoot, uninterestingCache, cullFaceOverridden); if (graphToAdd) newList.push_back(graphToAdd); } diff --git a/components/sceneutil/shadowsbin.hpp b/components/sceneutil/shadowsbin.hpp index cc6fd3525..1c63caf4b 100644 --- a/components/sceneutil/shadowsbin.hpp +++ b/components/sceneutil/shadowsbin.hpp @@ -1,11 +1,13 @@ #ifndef OPENMW_COMPONENTS_SCENEUTIL_SHADOWBIN_H #define OPENMW_COMPONENTS_SCENEUTIL_SHADOWBIN_H +#include #include #include namespace osg { class Material; + class AlphaFunc; } namespace SceneUtil @@ -15,8 +17,12 @@ namespace SceneUtil class ShadowsBin : public osgUtil::RenderBin { private: + static std::array, GL_ALWAYS - GL_NEVER + 1> sCastingPrograms; + osg::ref_ptr mNoTestStateSet; osg::ref_ptr mShaderAlphaTestStateSet; + + std::array, GL_ALWAYS - GL_NEVER + 1> mAlphaFuncShaders; public: META_Object(SceneUtil, ShadowsBin) ShadowsBin(); @@ -24,6 +30,7 @@ namespace SceneUtil : osgUtil::RenderBin(rhs, copyop) , mNoTestStateSet(rhs.mNoTestStateSet) , mShaderAlphaTestStateSet(rhs.mShaderAlphaTestStateSet) + , mAlphaFuncShaders(rhs.mAlphaFuncShaders) {} void sortImplementation() override; @@ -33,8 +40,8 @@ namespace SceneUtil State() : mAlphaBlend(false) , mAlphaBlendOverride(false) - , mAlphaTest(false) - , mAlphaTestOverride(false) + , mAlphaFunc(nullptr) + , mAlphaFuncOverride(false) , mMaterial(nullptr) , mMaterialOverride(false) , mImportantState(false) @@ -42,33 +49,29 @@ namespace SceneUtil bool mAlphaBlend; bool mAlphaBlendOverride; - bool mAlphaTest; - bool mAlphaTestOverride; + osg::AlphaFunc* mAlphaFunc; + bool mAlphaFuncOverride; osg::Material* mMaterial; bool mMaterialOverride; bool mImportantState; - bool needTexture() const { return mAlphaBlend || mAlphaTest; } + bool needTexture() const; bool needShadows() const; // A state is interesting if there's anything about it that might affect whether we can optimise child state bool interesting() const { - return !needShadows() || needTexture() || mAlphaBlendOverride || mAlphaTestOverride || mMaterialOverride || mImportantState; + return !needShadows() || needTexture() || mAlphaBlendOverride || mAlphaFuncOverride || mMaterialOverride || mImportantState; } }; - osgUtil::StateGraph* cullStateGraph(osgUtil::StateGraph* sg, osgUtil::StateGraph* root, std::unordered_set& uninteresting); + osgUtil::StateGraph* cullStateGraph(osgUtil::StateGraph* sg, osgUtil::StateGraph* root, std::unordered_set& uninteresting, bool cullFaceOverridden); - static void addPrototype(const std::string& name) - { - osg::ref_ptr bin (new ShadowsBin); - osgUtil::RenderBin::addRenderBinPrototype(name, bin); - } + static void addPrototype(const std::string& name, const std::array, GL_ALWAYS - GL_NEVER + 1>& castingPrograms); }; class ShadowsBinAdder { public: - ShadowsBinAdder(const std::string& name){ ShadowsBin::addPrototype(name); } + ShadowsBinAdder(const std::string& name, const std::array, GL_ALWAYS - GL_NEVER + 1>& castingPrograms){ ShadowsBin::addPrototype(name, castingPrograms); } }; } diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index 2c0d8efa0..fa3c7d26d 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -11,6 +11,7 @@ #include #include +#include namespace SceneUtil { @@ -260,4 +261,21 @@ osg::ref_ptr addEnchantedGlow(osg::ref_ptr node, Resourc return glowUpdater; } +bool attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg::Camera::BufferComponent buffer, osg::Texture * texture, unsigned int level, unsigned int face, bool mipMapGeneration) +{ + unsigned int samples = 0; + unsigned int colourSamples = 0; + bool addMSAAIntermediateTarget = Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1; + if (addMSAAIntermediateTarget) + { + // Alpha-to-coverage requires a multisampled framebuffer. + // OSG will set that up automatically and resolve it to the specified single-sample texture for us. + // For some reason, two samples are needed, at least with some drivers. + samples = 2; + colourSamples = 1; + } + camera->attach(buffer, texture, level, face, mipMapGeneration, samples, colourSamples); + return addMSAAIntermediateTarget; +} + } diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp index 303d609f5..8103ed87a 100644 --- a/components/sceneutil/util.hpp +++ b/components/sceneutil/util.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -60,6 +61,9 @@ namespace SceneUtil bool hasUserDescription(const osg::Node* node, const std::string pattern); osg::ref_ptr addEnchantedGlow(osg::ref_ptr node, Resource::ResourceSystem* resourceSystem, osg::Vec4f glowColor, float glowDuration=-1); + + // Alpha-to-coverage requires a multisampled framebuffer, so we need to set that up for RTTs + bool attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg::Camera::BufferComponent buffer, osg::Texture* texture, unsigned int level = 0, unsigned int face = 0, bool mipMapGeneration = false); } #endif diff --git a/components/sdlutil/gl4es_init.cpp b/components/sdlutil/gl4es_init.cpp new file mode 100644 index 000000000..bf9e007b6 --- /dev/null +++ b/components/sdlutil/gl4es_init.cpp @@ -0,0 +1,36 @@ +// EGL does not work reliably for feature detection. +// Instead, we initialize gl4es manually. +#ifdef OPENMW_GL4ES_MANUAL_INIT +#include "gl4es_init.h" + +// For glHint +#include + +extern "C" { + +#include +#include + +static SDL_Window *gWindow; + +void openmw_gl4es_GetMainFBSize(int *width, int *height) +{ + SDL_GetWindowSize(gWindow, width, height); +} + +void openmw_gl4es_init(SDL_Window *window) +{ + gWindow = window; + set_getprocaddress(SDL_GL_GetProcAddress); + set_getmainfbsize(openmw_gl4es_GetMainFBSize); + initialize_gl4es(); + + // merge glBegin/glEnd in beams and console + glHint(GL_BEGINEND_HINT_GL4ES, 1); + // dxt unpacked to 16-bit looks ugly + glHint(GL_AVOID16BITS_HINT_GL4ES, 1); +} + +} // extern "C" + +#endif // OPENMW_GL4ES_MANUAL_INIT diff --git a/components/sdlutil/gl4es_init.h b/components/sdlutil/gl4es_init.h new file mode 100644 index 000000000..11371e7ae --- /dev/null +++ b/components/sdlutil/gl4es_init.h @@ -0,0 +1,13 @@ +#ifndef OPENMW_COMPONENTS_SDLUTIL_GL4ES_INIT_H +#define OPENMW_COMPONENTS_SDLUTIL_GL4ES_INIT_H +#ifdef OPENMW_GL4ES_MANUAL_INIT +#include + +// Must be called once SDL video mode has been set, +// which creates a context. +// +// GL4ES can then query the context for features and extensions. +extern "C" void openmw_gl4es_init(SDL_Window *window); + +#endif // OPENMW_GL4ES_MANUAL_INIT +#endif // OPENMW_COMPONENTS_SDLUTIL_GL4ES_INIT_H diff --git a/components/sdlutil/sdlgraphicswindow.cpp b/components/sdlutil/sdlgraphicswindow.cpp index ad7ecd9ae..43284c216 100644 --- a/components/sdlutil/sdlgraphicswindow.cpp +++ b/components/sdlutil/sdlgraphicswindow.cpp @@ -2,6 +2,10 @@ #include +#ifdef OPENMW_GL4ES_MANUAL_INIT +#include "gl4es_init.h" +#endif + namespace SDLUtil { @@ -91,7 +95,7 @@ void GraphicsWindowSDL2::init() SDL_Window *oldWin = SDL_GL_GetCurrentWindow(); SDL_GLContext oldCtx = SDL_GL_GetCurrentContext(); -#if defined(ANDROID) +#if defined(ANDROID) || defined(OPENMW_GL4ES_MANUAL_INIT) int major = 1; int minor = 1; char *ver = getenv("OPENMW_GLES_VERSION"); @@ -116,6 +120,10 @@ void GraphicsWindowSDL2::init() return; } +#ifdef OPENMW_GL4ES_MANUAL_INIT + openmw_gl4es_init(mWindow); +#endif + setSwapInterval(_traits->vsync); // Update traits with what we've actually been given diff --git a/components/shader/removedalphafunc.cpp b/components/shader/removedalphafunc.cpp new file mode 100644 index 000000000..6b701b890 --- /dev/null +++ b/components/shader/removedalphafunc.cpp @@ -0,0 +1,20 @@ +#include "removedalphafunc.hpp" + +#include + +#include + +namespace Shader +{ + std::array, GL_ALWAYS - GL_NEVER + 1> RemovedAlphaFunc::sInstances{ + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr + }; + + osg::ref_ptr RemovedAlphaFunc::getInstance(GLenum func) + { + assert(func >= GL_NEVER && func <= GL_ALWAYS); + if (!sInstances[func - GL_NEVER]) + sInstances[func - GL_NEVER] = new RemovedAlphaFunc(static_cast(func), 1.0); + return sInstances[func - GL_NEVER]; + } +} diff --git a/components/shader/removedalphafunc.hpp b/components/shader/removedalphafunc.hpp new file mode 100644 index 000000000..c744391e5 --- /dev/null +++ b/components/shader/removedalphafunc.hpp @@ -0,0 +1,40 @@ +#ifndef OPENMW_COMPONENTS_REMOVEDALPHAFUNC_H +#define OPENMW_COMPONENTS_REMOVEDALPHAFUNC_H + +#include + +#include + +namespace Shader +{ + // State attribute used when shader visitor replaces the deprecated alpha function with a shader + // Prevents redundant glAlphaFunc calls and lets the shadowsbin know the stateset had alpha testing + class RemovedAlphaFunc : public osg::AlphaFunc + { + public: + // Get a singleton-like instance with the right func (but a default threshold) + static osg::ref_ptr getInstance(GLenum func); + + RemovedAlphaFunc() + : osg::AlphaFunc() + {} + + RemovedAlphaFunc(ComparisonFunction func, float ref) + : osg::AlphaFunc(func, ref) + {} + + RemovedAlphaFunc(const RemovedAlphaFunc& raf, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY) + : osg::AlphaFunc(raf, copyop) + {} + + META_StateAttribute(Shader, RemovedAlphaFunc, ALPHAFUNC); + + void apply(osg::State& state) const override {} + + protected: + virtual ~RemovedAlphaFunc() = default; + + static std::array, GL_ALWAYS - GL_NEVER + 1> sInstances; + }; +} +#endif //OPENMW_COMPONENTS_REMOVEDALPHAFUNC_H diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 9dec5522c..eae9ad2db 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -1,7 +1,10 @@ #include "shadervisitor.hpp" +#include #include +#include #include +#include #include #include @@ -13,6 +16,7 @@ #include #include +#include "removedalphafunc.hpp" #include "shadermanager.hpp" namespace Shader @@ -22,6 +26,11 @@ namespace Shader : mShaderRequired(false) , mColorMode(0) , mMaterialOverridden(false) + , mAlphaTestOverridden(false) + , mAlphaBlendOverridden(false) + , mAlphaFunc(GL_ALWAYS) + , mAlphaRef(1.0) + , mAlphaBlend(false) , mNormalHeight(false) , mTexStageRequiringTangents(-1) , mNode(nullptr) @@ -77,6 +86,34 @@ namespace Shader return newStateSet.get(); } + osg::UserDataContainer* getWritableUserDataContainer(osg::Object& object) + { + if (!object.getUserDataContainer()) + return object.getOrCreateUserDataContainer(); + + osg::ref_ptr newUserData = static_cast(object.getUserDataContainer()->clone(osg::CopyOp::SHALLOW_COPY)); + object.setUserDataContainer(newUserData); + return newUserData.get(); + } + + osg::StateSet* getRemovedState(osg::StateSet& stateSet) + { + if (!stateSet.getUserDataContainer()) + return nullptr; + + return static_cast(stateSet.getUserDataContainer()->getUserObject("removedState")); + } + + void updateRemovedState(osg::UserDataContainer& userData, osg::StateSet* stateSet) + { + unsigned int index = userData.getUserObjectIndex("removedState"); + if (index < userData.getNumUserObjects()) + userData.setUserObject(index, stateSet); + else + userData.addUserObject(stateSet); + stateSet->setName("removedState"); + } + const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap", "envMap", "specularMap", "decalMap", "bumpMap" }; bool isTextureNameRecognized(const std::string& name) { @@ -235,49 +272,76 @@ namespace Shader } const osg::StateSet::AttributeList& attributes = stateset->getAttributeList(); - for (osg::StateSet::AttributeList::const_iterator it = attributes.begin(); it != attributes.end(); ++it) + osg::StateSet::AttributeList removedAttributes; + osg::ref_ptr removedState; + if (removedState = getRemovedState(*stateset)) + removedAttributes = removedState->getAttributeList(); + for (const auto& attributeMap : { attributes, removedAttributes }) { - if (it->first.first == osg::StateAttribute::MATERIAL) + for (osg::StateSet::AttributeList::const_iterator it = attributeMap.begin(); it != attributeMap.end(); ++it) { - // This should probably be moved out of ShaderRequirements and be applied directly now it's a uniform instead of a define - if (!mRequirements.back().mMaterialOverridden || it->second.second & osg::StateAttribute::PROTECTED) + if (it->first.first == osg::StateAttribute::MATERIAL) { - if (it->second.second & osg::StateAttribute::OVERRIDE) - mRequirements.back().mMaterialOverridden = true; + // This should probably be moved out of ShaderRequirements and be applied directly now it's a uniform instead of a define + if (!mRequirements.back().mMaterialOverridden || it->second.second & osg::StateAttribute::PROTECTED) + { + if (it->second.second & osg::StateAttribute::OVERRIDE) + mRequirements.back().mMaterialOverridden = true; - const osg::Material* mat = static_cast(it->second.first.get()); + const osg::Material* mat = static_cast(it->second.first.get()); - if (!writableStateSet) - writableStateSet = getWritableStateSet(node); + if (!writableStateSet) + writableStateSet = getWritableStateSet(node); - int colorMode; - switch (mat->getColorMode()) - { - case osg::Material::OFF: - colorMode = 0; - break; - case osg::Material::EMISSION: - colorMode = 1; - break; - default: - case osg::Material::AMBIENT_AND_DIFFUSE: - colorMode = 2; - break; - case osg::Material::AMBIENT: - colorMode = 3; - break; - case osg::Material::DIFFUSE: - colorMode = 4; - break; - case osg::Material::SPECULAR: - colorMode = 5; - break; + int colorMode; + switch (mat->getColorMode()) + { + case osg::Material::OFF: + colorMode = 0; + break; + case osg::Material::EMISSION: + colorMode = 1; + break; + default: + case osg::Material::AMBIENT_AND_DIFFUSE: + colorMode = 2; + break; + case osg::Material::AMBIENT: + colorMode = 3; + break; + case osg::Material::DIFFUSE: + colorMode = 4; + break; + case osg::Material::SPECULAR: + colorMode = 5; + break; + } + + mRequirements.back().mColorMode = colorMode; } + } + else if (it->first.first == osg::StateAttribute::ALPHAFUNC) + { + if (!mRequirements.back().mAlphaTestOverridden || it->second.second & osg::StateAttribute::PROTECTED) + { + if (it->second.second & osg::StateAttribute::OVERRIDE) + mRequirements.back().mAlphaTestOverridden = true; - mRequirements.back().mColorMode = colorMode; + const osg::AlphaFunc* alpha = static_cast(it->second.first.get()); + mRequirements.back().mAlphaFunc = alpha->getFunction(); + mRequirements.back().mAlphaRef = alpha->getReferenceValue(); + } } } - // Eventually, move alpha testing to discard in shader adn remove deprecated state here + } + + unsigned int alphaBlend = stateset->getMode(GL_BLEND); + if (alphaBlend != osg::StateAttribute::INHERIT && (!mRequirements.back().mAlphaBlendOverridden || alphaBlend & osg::StateAttribute::PROTECTED)) + { + if (alphaBlend & osg::StateAttribute::OVERRIDE) + mRequirements.back().mAlphaBlendOverridden = true; + + mRequirements.back().mAlphaBlend = alphaBlend & osg::StateAttribute::ON; } } @@ -323,6 +387,57 @@ namespace Shader writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); + defineMap["alphaFunc"] = std::to_string(reqs.mAlphaFunc); + + // back up removed state in case recreateShaders gets rid of the shader later + osg::ref_ptr removedState; + if ((removedState = getRemovedState(*writableStateSet)) && !mAllowedToModifyStateSets) + removedState = new osg::StateSet(*removedState, osg::CopyOp::SHALLOW_COPY); + if (!removedState) + removedState = new osg::StateSet(); + + defineMap["alphaToCoverage"] = "0"; + if (reqs.mAlphaFunc != osg::AlphaFunc::ALWAYS) + { + writableStateSet->addUniform(new osg::Uniform("alphaRef", reqs.mAlphaRef)); + + const auto* alphaFunc = writableStateSet->getAttributePair(osg::StateAttribute::ALPHAFUNC); + if (alphaFunc) + removedState->setAttribute(alphaFunc->first, alphaFunc->second); + // This prevents redundant glAlphaFunc calls while letting the shadows bin still see the test + writableStateSet->setAttribute(RemovedAlphaFunc::getInstance(reqs.mAlphaFunc), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + // Blending won't work with A2C as we use the alpha channel for coverage. gl_SampleCoverage from ARB_sample_shading would save the day, but requires GLSL 130 + if (mConvertAlphaTestToAlphaToCoverage && !reqs.mAlphaBlend) + { + writableStateSet->setMode(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB, osg::StateAttribute::ON); + defineMap["alphaToCoverage"] = "1"; + } + + // Preventing alpha tested stuff shrinking as lower mip levels are used requires knowing the texture size + osg::ref_ptr exts = osg::GLExtensions::Get(0, false); + if (exts && exts->isGpuShader4Supported) + defineMap["useGPUShader4"] = "1"; + // We could fall back to a texture size uniform if EXT_gpu_shader4 is missing + } + + if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT) + removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST)); + // This disables the deprecated fixed-function alpha test + writableStateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED); + + if (!removedState->getModeList().empty() || !removedState->getAttributeList().empty()) + { + // user data is normally shallow copied so shared with the original stateset + osg::ref_ptr writableUserData; + if (mAllowedToModifyStateSets) + writableUserData = writableStateSet->getOrCreateUserDataContainer(); + else + writableUserData = getWritableUserDataContainer(*writableStateSet); + + updateRemovedState(*writableUserData, removedState); + } + defineMap["translucentFramebuffer"] = mTranslucentFramebuffer ? "1" : "0"; osg::ref_ptr vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX)); @@ -350,6 +465,25 @@ namespace Shader writableStateSet = getWritableStateSet(node); writableStateSet->removeAttribute(osg::StateAttribute::PROGRAM); + + osg::ref_ptr removedState; + if (removedState = getRemovedState(*writableStateSet)) + { + // user data is normally shallow copied so shared with the original stateset + osg::ref_ptr writableUserData; + if (mAllowedToModifyStateSets) + writableUserData = writableStateSet->getUserDataContainer(); + else + writableUserData = getWritableUserDataContainer(*writableStateSet); + unsigned int index = writableUserData->getUserObjectIndex("removedState"); + writableUserData->removeUserObject(index); + + for (const auto& [mode, value] : removedState->getModeList()) + writableStateSet->setMode(mode, value); + + for (const auto& attribute : removedState->getAttributeList()) + writableStateSet->setAttribute(attribute.second.first, attribute.second.second); + } } bool ShaderVisitor::adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs) @@ -477,9 +611,53 @@ namespace Shader mApplyLightingToEnvMaps = apply; } + void ShaderVisitor::setConvertAlphaTestToAlphaToCoverage(bool convert) + { + mConvertAlphaTestToAlphaToCoverage = convert; + } + void ShaderVisitor::setTranslucentFramebuffer(bool translucent) { mTranslucentFramebuffer = translucent; } + ReinstateRemovedStateVisitor::ReinstateRemovedStateVisitor(bool allowedToModifyStateSets) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mAllowedToModifyStateSets(allowedToModifyStateSets) + { + } + + void ReinstateRemovedStateVisitor::apply(osg::Node& node) + { + if (node.getStateSet()) + { + osg::ref_ptr removedState = getRemovedState(*node.getStateSet()); + if (removedState) + { + osg::ref_ptr writableStateSet; + if (mAllowedToModifyStateSets) + writableStateSet = node.getStateSet(); + else + writableStateSet = getWritableStateSet(node); + + // user data is normally shallow copied so shared with the original stateset + osg::ref_ptr writableUserData; + if (mAllowedToModifyStateSets) + writableUserData = writableStateSet->getUserDataContainer(); + else + writableUserData = getWritableUserDataContainer(*writableStateSet); + unsigned int index = writableUserData->getUserObjectIndex("removedState"); + writableUserData->removeUserObject(index); + + for (const auto&[mode, value] : removedState->getModeList()) + writableStateSet->setMode(mode, value); + + for (const auto& attribute : removedState->getAttributeList()) + writableStateSet->setAttribute(attribute.second.first, attribute.second.second); + } + } + + traverse(node); + } + } diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 11b37c923..f7c6f8312 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -40,6 +40,8 @@ namespace Shader void setApplyLightingToEnvMaps(bool apply); + void setConvertAlphaTestToAlphaToCoverage(bool convert); + void setTranslucentFramebuffer(bool translucent); void apply(osg::Node& node) override; @@ -65,6 +67,8 @@ namespace Shader bool mApplyLightingToEnvMaps; + bool mConvertAlphaTestToAlphaToCoverage; + bool mTranslucentFramebuffer; ShaderManager& mShaderManager; @@ -83,6 +87,12 @@ namespace Shader int mColorMode; bool mMaterialOverridden; + bool mAlphaTestOverridden; + bool mAlphaBlendOverridden; + + GLenum mAlphaFunc; + float mAlphaRef; + bool mAlphaBlend; bool mNormalHeight; // true if normal map has height info in alpha channel @@ -102,6 +112,17 @@ namespace Shader bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs); }; + class ReinstateRemovedStateVisitor : public osg::NodeVisitor + { + public: + ReinstateRemovedStateVisitor(bool allowedToModifyStateSets); + + void apply(osg::Node& node) override; + + private: + bool mAllowedToModifyStateSets; + }; + } #endif diff --git a/components/to_utf8/gen_iconv.cpp b/components/to_utf8/gen_iconv.cpp index 8198b305d..f2d9a42f1 100644 --- a/components/to_utf8/gen_iconv.cpp +++ b/components/to_utf8/gen_iconv.cpp @@ -1,20 +1,19 @@ // This program generates the file tables_gen.hpp #include -using namespace std; #include #include -void tab() { cout << " "; } +void tab() { std::cout << " "; } // write one number with a space in front of it and a comma after it void num(char i, bool last) { // Convert i to its integer value, i.e. -128 to 127. Printing it directly // would result in non-printable characters in the source code, which is bad. - cout << " " << static_cast(i); - if(!last) cout << ","; + std::cout << " " << static_cast(i); + if(!last) std::cout << ","; } // Write one table entry (UTF8 value), 1-5 bytes @@ -27,9 +26,9 @@ void writeChar(char *value, int length, bool last, const std::string &comment="" num(value[i], last && i==4); if(comment != "") - cout << " // " << comment; + std::cout << " // " << comment; - cout << endl; + std::cout << std::endl; } // What to write on missing characters @@ -46,7 +45,7 @@ void writeMissing(bool last) int write_table(const std::string &charset, const std::string &tableName) { // Write table header - cout << "static signed char " << tableName << "[] =\n{\n"; + std::cout << "static signed char " << tableName << "[] =\n{\n"; // Open conversion system iconv_t cd = iconv_open ("UTF-8", charset.c_str()); @@ -74,7 +73,7 @@ int write_table(const std::string &charset, const std::string &tableName) iconv_close (cd); // Finish table - cout << "};\n"; + std::cout << "};\n"; return 0; } @@ -82,37 +81,37 @@ int write_table(const std::string &charset, const std::string &tableName) int main() { // Write header guard - cout << "#ifndef COMPONENTS_TOUTF8_TABLE_GEN_H\n#define COMPONENTS_TOUTF8_TABLE_GEN_H\n\n"; + std::cout << "#ifndef COMPONENTS_TOUTF8_TABLE_GEN_H\n#define COMPONENTS_TOUTF8_TABLE_GEN_H\n\n"; // Write namespace - cout << "namespace ToUTF8\n{\n\n"; + std::cout << "namespace ToUTF8\n{\n\n"; // Central European and Eastern European languages that use Latin script, such as // Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian. - cout << "\n/// Central European and Eastern European languages that use Latin script," - "\n/// such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian," - "\n/// Serbian (Latin script), Romanian and Albanian." - "\n"; + std::cout << "\n/// Central European and Eastern European languages that use Latin script," + "\n/// such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian," + "\n/// Serbian (Latin script), Romanian and Albanian." + "\n"; write_table("WINDOWS-1250", "windows_1250"); // Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages - cout << "\n/// Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic" - "\n/// and other languages" - "\n"; + std::cout << "\n/// Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic" + "\n/// and other languages" + "\n"; write_table("WINDOWS-1251", "windows_1251"); // English - cout << "\n/// Latin alphabet used by English and some other Western languages" - "\n"; + std::cout << "\n/// Latin alphabet used by English and some other Western languages" + "\n"; write_table("WINDOWS-1252", "windows_1252"); write_table("CP437", "cp437"); // Close namespace - cout << "\n}\n\n"; + std::cout << "\n}\n\n"; // Close header guard - cout << "#endif\n\n"; + std::cout << "#endif\n\n"; return 0; } diff --git a/docs/source/reference/modding/extended.rst b/docs/source/reference/modding/extended.rst index 98b3e7f00..cd739cb1b 100644 --- a/docs/source/reference/modding/extended.rst +++ b/docs/source/reference/modding/extended.rst @@ -306,8 +306,11 @@ For example, we do not need to have collision or animation objects for groundcov do not need to render groundcover on the map, do not need to render it for the whole visible area (which can be very large with Distant Terrain). It allows to increase performance a lot. General advices to create assets for this feature: + 1. Alpha properties from Nif files are not used, a unified alpha settings are used (alpha testing, "greater of equal" function, 128/255 threshold). + 2. Use a single NiTriShape in groundocver mesh, or at least use same properties (texture, alpha, material, etc), so OpenMW can merge them on the fly. Otherwise animations may not work properly. + 3. Smooth fading does not work for meshes, which have textures without alpha (e.g. rock). Groundcover mods can be registered in the openmw.cfg via "groundcover" entries instead of "content" ones: diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index ed43b19a2..acc848299 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -147,3 +147,14 @@ radial fog By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV. Note that the rendering will act as if you have 'force shaders' option enabled with this on, which means that shaders will be used to render all objects and the terrain. + +antialias alpha test +--------------------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +Convert the alpha test (cutout/punchthrough alpha) to alpha-to-coverage when :ref:`antialiasing` is on. +This allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. +When MSAA is off, this setting will have no visible effect, but might have a performance cost. diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index b4182a105..8766e51cc 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -31,15 +31,20 @@ if(NOT OPENMW_USE_SYSTEM_BULLET) set(USE_DOUBLE_PRECISION ${BULLET_USE_DOUBLES} CACHE BOOL "") set(BULLET2_MULTITHREADING ON CACHE BOOL "") - # Version 3.08 with the following changes: - # 1. Fixes the linking of Threads: - # https://github.com/bulletphysics/bullet3/pull/3237 - # 2. Removes ~300 MiB of files not used here: - # rm -rf build3 data docs examples test Doxyfile + if(BULLET_STATIC) + set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) + else() + set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE) + if(MSVC) + set(USE_MSVC_RUNTIME_LIBRARY_DLL ON CACHE BOOL "" FORCE) + endif() + endif() + + # master on 12 Mar 2021 include(FetchContent) FetchContent_Declare(bullet - URL https://github.com/glebm/bullet3/archive/ed5256454f4f84bd2c1728c88ddb0405d614e7d2.zip - URL_HASH MD5=e3c94fac35a7be885ad8843f828a0f96 + URL https://github.com/bulletphysics/bullet3/archive/87e668f6b2a883b4ef63db8a07c8e9283916e9d9.zip + URL_HASH MD5=9f13246439968494c2b595cf412d83c8 SOURCE_DIR fetched/bullet ) FetchContent_MakeAvailableExcludeFromAll(bullet) @@ -60,16 +65,20 @@ if(NOT OPENMW_USE_SYSTEM_MYGUI) set(MYGUI_BUILD_PLUGINS OFF CACHE BOOL "") set(MYGUI_BUILD_TOOLS OFF CACHE BOOL "") + # We appear to be using some obsolete properties in the XML. + # See https://gitlab.com/OpenMW/openmw/-/issues/5896 + set(MYGUI_DONT_USE_OBSOLETE OFF CACHE BOOL "") + if(MYGUI_STATIC) - set(BUILD_SHARED_LIBS OFF) + set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) else() - set(BUILD_SHARED_LIBS ON) + set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE) endif() include(FetchContent) FetchContent_Declare(mygui - URL https://github.com/MyGUI/mygui/archive/MyGUI3.4.0.zip - URL_HASH MD5=9e990a4240430cbf567bfe73488a274e + URL https://github.com/MyGUI/mygui/archive/MyGUI3.4.1.zip + URL_HASH MD5=952d4033854612c99a5d9bf4b8550c26 SOURCE_DIR fetched/mygui ) FetchContent_MakeAvailableExcludeFromAll(mygui) @@ -81,8 +90,6 @@ endif() if(NOT OPENMW_USE_SYSTEM_OSG) cmake_minimum_required(VERSION 3.11) # for FetchContent - set(DYNAMIC_OPENTHREADS OFF CACHE BOOL "") - set(DYNAMIC_OPENSCENEGRAPH OFF CACHE BOOL "") set(BUILD_OSG_APPLICATIONS OFF CACHE BOOL "") set(BUILD_OSG_DEPRECATED_SERIALIZERS OFF CACHE BOOL "") set(OSG_FIND_3RD_PARTY_DEPS OFF CACHE BOOL "") @@ -104,9 +111,33 @@ if(NOT OPENMW_USE_SYSTEM_OSG) set(OPENGL_PROFILE "GL2" CACHE STRING "") if(OSG_STATIC) - set(BUILD_SHARED_LIBS OFF) + set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) + set(DYNAMIC_OPENTHREADS OFF CACHE BOOL "" FORCE) + set(DYNAMIC_OPENSCENEGRAPH OFF CACHE BOOL "" FORCE) else() - set(BUILD_SHARED_LIBS ON) + set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE) + set(DYNAMIC_OPENTHREADS ON CACHE BOOL "" FORCE) + set(DYNAMIC_OPENSCENEGRAPH ON CACHE BOOL "" FORCE) + endif() + mark_as_advanced(DYNAMIC_OPENTHREADS DYNAMIC_OPENSCENEGRAPH) + + if(WIN32) + # OSG here inherits C++17 language level because it doesn't specify its own. + # + # OSG's `using namespace std` interferes with Windows header files. + # + # See https://developercommunity.visualstudio.com/t/error-c2872-byte-ambiguous-symbol/93889 + # + # An alternative way to work around this without changing the language level is: + # + # add_compile_definitions(_HAS_STD_BYTE=0) + # + # TODO: Put OSG into its own scope so that this does not leak into Recast below. + set(CMAKE_CXX_STANDARD 11) + + if(MSVC) + set(OSG_MSVC_VERSIONED_DLL OFF CACHE BOOL "") + endif() endif() # branch OpenSceneGraph-3.6 on 23 Jan 2021. diff --git a/extern/osg-ffmpeg-videoplayer/audiodecoder.cpp b/extern/osg-ffmpeg-videoplayer/audiodecoder.cpp index 337382679..c32794d2a 100644 --- a/extern/osg-ffmpeg-videoplayer/audiodecoder.cpp +++ b/extern/osg-ffmpeg-videoplayer/audiodecoder.cpp @@ -91,7 +91,7 @@ MovieAudioDecoder::~MovieAudioDecoder() if(mAudioContext) avcodec_free_context(&mAudioContext); - av_freep(&mFrame); + av_frame_free(&mFrame); av_freep(&mDataBuf); } @@ -222,7 +222,7 @@ int MovieAudioDecoder::audio_decode_frame(AVFrame *frame, int &sample_skip) return result; } - av_packet_unref(&mPacket); + av_packet_unref(pkt); mGetNextPacket = true; /* next packet */ diff --git a/extern/osg-ffmpeg-videoplayer/videostate.cpp b/extern/osg-ffmpeg-videoplayer/videostate.cpp index 5858d985a..7c4bddb01 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.cpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -49,7 +50,7 @@ VideoState::VideoState() , av_sync_type(AV_SYNC_DEFAULT) , audio_st(nullptr) , video_st(nullptr), frame_last_pts(0.0) - , video_clock(0.0), sws_context(nullptr), rgbaFrame(nullptr), pictq_size(0) + , video_clock(0.0), sws_context(nullptr), pictq_size(0) , pictq_rindex(0), pictq_windex(0) , mSeekRequested(false) , mSeekPos(0) @@ -82,10 +83,11 @@ void PacketQueue::put(AVPacket *pkt) pkt1 = (AVPacketList*)av_malloc(sizeof(AVPacketList)); if(!pkt1) throw std::bad_alloc(); - if(pkt != &flush_pkt && !pkt->buf && av_packet_ref(&pkt1->pkt, pkt) < 0) - throw std::runtime_error("Failed to duplicate packet"); + if(pkt == &flush_pkt) + pkt1->pkt = *pkt; + else + av_packet_move_ref(&pkt1->pkt, pkt); - pkt1->pkt = *pkt; pkt1->next = nullptr; this->mutex.lock (); @@ -116,7 +118,8 @@ int PacketQueue::get(AVPacket *pkt, VideoState *is) this->nb_packets--; this->size -= pkt1->pkt.size; - *pkt = pkt1->pkt; + av_packet_unref(pkt); + av_packet_move_ref(pkt, &pkt1->pkt); av_free(pkt1); return 1; @@ -155,6 +158,39 @@ void PacketQueue::clear() this->mutex.unlock (); } +int VideoPicture::set_dimensions(int w, int h) { + if (this->rgbaFrame != nullptr && this->rgbaFrame->width == w && + this->rgbaFrame->height == h) { + return 0; + } + + std::unique_ptr frame{ + av_frame_alloc()}; + if (frame == nullptr) { + std::cerr << "av_frame_alloc failed" << std::endl; + return -1; + } + + constexpr AVPixelFormat kPixFmt = AV_PIX_FMT_RGBA; + frame->format = kPixFmt; + frame->width = w; + frame->height = h; + if (av_image_alloc(frame->data, frame->linesize, frame->width, frame->height, + kPixFmt, 1) < 0) { + std::cerr << "av_image_alloc failed" << std::endl; + return -1; + } + + this->rgbaFrame = std::move(frame); + return 0; +} + +void VideoPicture::AVFrameDeleter::operator()(AVFrame* frame) const +{ + av_freep(frame->data); + av_frame_free(&frame); +} + int VideoState::istream_read(void *user_data, uint8_t *buf, int buf_size) { try @@ -220,7 +256,7 @@ void VideoState::video_display(VideoPicture *vp) osg::ref_ptr image = new osg::Image; image->setImage(this->video_ctx->width, this->video_ctx->height, - 1, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, &vp->data[0], osg::Image::NO_DELETE); + 1, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, vp->rgbaFrame->data[0], osg::Image::NO_DELETE); mTexture->setImage(image); } @@ -296,23 +332,27 @@ int VideoState::queue_picture(AVFrame *pFrame, double pts) // Convert the image into RGBA format // TODO: we could do this in a pixel shader instead, if the source format // matches a commonly used format (ie YUV420P) - if(this->sws_context == nullptr) + const int w = pFrame->width; + const int h = pFrame->height; + if(this->sws_context == nullptr || this->sws_context_w != w || this->sws_context_h != h) { - int w = this->video_ctx->width; - int h = this->video_ctx->height; + if (this->sws_context != nullptr) + sws_freeContext(this->sws_context); this->sws_context = sws_getContext(w, h, this->video_ctx->pix_fmt, w, h, AV_PIX_FMT_RGBA, SWS_BICUBIC, nullptr, nullptr, nullptr); if(this->sws_context == nullptr) throw std::runtime_error("Cannot initialize the conversion context!\n"); + this->sws_context_w = w; + this->sws_context_h = h; } vp->pts = pts; - vp->data.resize(this->video_ctx->width * this->video_ctx->height * 4); + if (vp->set_dimensions(w, h) < 0) + return -1; - uint8_t *dst[4] = { &vp->data[0], nullptr, nullptr, nullptr }; sws_scale(this->sws_context, pFrame->data, pFrame->linesize, - 0, this->video_ctx->height, dst, this->rgbaFrame->linesize); + 0, this->video_ctx->height, vp->rgbaFrame->data, vp->rgbaFrame->linesize); // now we inform our display thread that we have a pic ready this->pictq_windex = (this->pictq_windex+1) % VIDEO_PICTURE_ARRAY_SIZE; @@ -360,13 +400,11 @@ public: { VideoState* self = mVideoState; AVPacket pkt1, *packet = &pkt1; + av_init_packet(packet); AVFrame *pFrame; pFrame = av_frame_alloc(); - self->rgbaFrame = av_frame_alloc(); - av_image_alloc(self->rgbaFrame->data, self->rgbaFrame->linesize, self->video_ctx->width, self->video_ctx->height, AV_PIX_FMT_RGBA, 1); - while(self->videoq.get(packet, self) >= 0) { if(packet->data == flush_pkt.data) @@ -407,10 +445,7 @@ public: av_packet_unref(packet); - av_free(pFrame); - - av_freep(&self->rgbaFrame->data[0]); - av_free(self->rgbaFrame); + av_frame_free(&pFrame); } private: @@ -438,6 +473,7 @@ public: AVFormatContext *pFormatCtx = self->format_ctx; AVPacket pkt1, *packet = &pkt1; + av_init_packet(packet); try { @@ -673,16 +709,21 @@ void VideoState::init(std::shared_ptr inputstream, const std::stri { if (this->format_ctx->pb != nullptr) { - av_free(this->format_ctx->pb->buffer); - this->format_ctx->pb->buffer = nullptr; - - av_free(this->format_ctx->pb); - this->format_ctx->pb = nullptr; + av_freep(&this->format_ctx->pb->buffer); +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 80, 100) + avio_context_free(&this->format_ctx->pb); +#else + av_freep(&this->format_ctx->pb); +#endif } } // "Note that a user-supplied AVFormatContext will be freed on failure." this->format_ctx = nullptr; - av_free(ioCtx); +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 80, 100) + avio_context_free(&ioCtx); +#else + av_freep(&ioCtx); +#endif throw std::runtime_error("Failed to open video input"); } @@ -756,11 +797,12 @@ void VideoState::deinit() /// if (this->format_ctx->pb != nullptr) { - av_free(this->format_ctx->pb->buffer); - this->format_ctx->pb->buffer = nullptr; - - av_free(this->format_ctx->pb); - this->format_ctx->pb = nullptr; + av_freep(&this->format_ctx->pb->buffer); +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 80, 100) + avio_context_free(&this->format_ctx->pb); +#else + av_freep(&this->format_ctx->pb); +#endif } avformat_close_input(&this->format_ctx); } @@ -771,6 +813,11 @@ void VideoState::deinit() mTexture->setImage(nullptr); mTexture = nullptr; } + + // Dellocate RGBA frame queue. + for (std::size_t i = 0; i < VIDEO_PICTURE_ARRAY_SIZE; ++i) + this->pictq[i].rgbaFrame = nullptr; + } double VideoState::get_external_clock() diff --git a/extern/osg-ffmpeg-videoplayer/videostate.hpp b/extern/osg-ffmpeg-videoplayer/videostate.hpp index 641fce04b..015656084 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.hpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.hpp @@ -95,7 +95,16 @@ struct VideoPicture { VideoPicture() : pts(0.0) { } - std::vector data; + struct AVFrameDeleter { + void operator()(AVFrame* frame) const; + }; + + // Sets frame dimensions. + // Must be called before writing to `rgbaFrame`. + // Return -1 on error. + int set_dimensions(int w, int h); + + std::unique_ptr rgbaFrame; double pts; }; @@ -159,8 +168,8 @@ struct VideoState { double video_clock; /// - diff --git a/files/mygui/openmw_tooltips.layout b/files/mygui/openmw_tooltips.layout index bb505d253..ce927038c 100644 --- a/files/mygui/openmw_tooltips.layout +++ b/files/mygui/openmw_tooltips.layout @@ -193,7 +193,6 @@ - @@ -248,7 +247,6 @@ - @@ -269,9 +267,6 @@ - - - diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 67d944d19..d439c4635 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -442,6 +442,11 @@ apply lighting to environment maps = false # This makes fogging independent from the viewing angle. Shaders will be used to render all objects. radial fog = false +# Convert the alpha test (cutout/punchthrough alpha) to alpha-to-coverage. +# This allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. +# When MSAA is off, this setting will have no visible effect, but might have a performance cost. +antialias alpha test = false + [Input] # Capture control of the cursor prevent movement outside the window. diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index a4e898e4b..7250aa372 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -12,6 +12,7 @@ set(SHADER_FILES water_vertex.glsl water_fragment.glsl water_nm.png + alpha.glsl objects_vertex.glsl objects_fragment.glsl terrain_vertex.glsl diff --git a/files/shaders/alpha.glsl b/files/shaders/alpha.glsl new file mode 100644 index 000000000..05be801e9 --- /dev/null +++ b/files/shaders/alpha.glsl @@ -0,0 +1,85 @@ + +#define FUNC_NEVER 512 // 0x0200 +#define FUNC_LESS 513 // 0x0201 +#define FUNC_EQUAL 514 // 0x0202 +#define FUNC_LEQUAL 515 // 0x0203 +#define FUNC_GREATER 516 // 0x0204 +#define FUNC_NOTEQUAL 517 // 0x0205 +#define FUNC_GEQUAL 518 // 0x0206 +#define FUNC_ALWAYS 519 // 0x0207 + +#if @alphaFunc != FUNC_ALWAYS && @alphaFunc != FUNC_NEVER +uniform float alphaRef; +#endif + +float mipmapLevel(vec2 scaleduv) +{ + vec2 dUVdx = dFdx(scaleduv); + vec2 dUVdy = dFdy(scaleduv); + float maxDUVSquared = max(dot(dUVdx, dUVdx), dot(dUVdy, dUVdy)); + return max(0.0, 0.5 * log2(maxDUVSquared)); +} + +float coveragePreservingAlphaScale(sampler2D diffuseMap, vec2 uv) +{ + #if @alphaFunc != FUNC_ALWAYS && @alphaFunc != FUNC_NEVER + vec2 textureSize; + #if @useGPUShader4 + textureSize = textureSize2D(diffuseMap, 0); + #else + textureSize = vec2(256.0); + #endif + return 1.0 + mipmapLevel(uv * textureSize) * 0.25; + #else + return 1.0; + #endif +} + +void alphaTest() +{ + #if @alphaToCoverage + float coverageAlpha = (gl_FragData[0].a - clamp(alphaRef, 0.0001, 0.9999)) / max(fwidth(gl_FragData[0].a), 0.0001) + 0.5; + + // Some functions don't make sense with A2C or are a pain to think about and no meshes use them anyway + // Use regular alpha testing in such cases until someone complains. + #if @alphaFunc == FUNC_NEVER + discard; + #elif @alphaFunc == FUNC_LESS + gl_FragData[0].a = 1.0 - coverageAlpha; + #elif @alphaFunc == FUNC_EQUAL + if (gl_FragData[0].a != alphaRef) + discard; + #elif @alphaFunc == FUNC_LEQUAL + gl_FragData[0].a = 1.0 - coverageAlpha; + #elif @alphaFunc == FUNC_GREATER + gl_FragData[0].a = coverageAlpha; + #elif @alphaFunc == FUNC_NOTEQUAL + if (gl_FragData[0].a == alphaRef) + discard; + #elif @alphaFunc == FUNC_GEQUAL + gl_FragData[0].a = coverageAlpha; + #endif + #else + #if @alphaFunc == FUNC_NEVER + discard; + #elif @alphaFunc == FUNC_LESS + if (gl_FragData[0].a >= alphaRef) + discard; + #elif @alphaFunc == FUNC_EQUAL + if (gl_FragData[0].a != alphaRef) + discard; + #elif @alphaFunc == FUNC_LEQUAL + if (gl_FragData[0].a > alphaRef) + discard; + #elif @alphaFunc == FUNC_GREATER + if (gl_FragData[0].a <= alphaRef) + discard; + #elif @alphaFunc == FUNC_NOTEQUAL + if (gl_FragData[0].a == alphaRef) + discard; + #elif @alphaFunc == FUNC_GEQUAL + if (gl_FragData[0].a < alphaRef) + discard; + #endif + #endif +} \ No newline at end of file diff --git a/files/shaders/groundcover_fragment.glsl b/files/shaders/groundcover_fragment.glsl index 77fd32e58..f25b7d487 100644 --- a/files/shaders/groundcover_fragment.glsl +++ b/files/shaders/groundcover_fragment.glsl @@ -1,5 +1,9 @@ #version 120 +#if @useGPUShader4 + #extension GL_EXT_gpu_shader4: require +#endif + #define GROUNDCOVER #if @diffuseMap @@ -30,11 +34,7 @@ centroid varying vec3 shadowDiffuseLighting; #include "shadows_fragment.glsl" #include "lighting.glsl" - -float calc_coverage(float a, float alpha_ref, float falloff_rate) -{ - return clamp(falloff_rate * (a - alpha_ref) + alpha_ref, 0.0, 1.0); -} +#include "alpha.glsl" void main() { @@ -55,12 +55,13 @@ void main() gl_FragData[0] = vec4(1.0); #endif - gl_FragData[0].a = calc_coverage(gl_FragData[0].a, 128.0/255.0, 4.0); - - float shadowing = unshadowedLightRatio(linearDepth); if (euclideanDepth > @groundcoverFadeStart) gl_FragData[0].a *= 1.0-smoothstep(@groundcoverFadeStart, @groundcoverFadeEnd, euclideanDepth); + alphaTest(); + + float shadowing = unshadowedLightRatio(linearDepth); + vec3 lighting; #if !PER_PIXEL_LIGHTING lighting = passLighting + shadowDiffuseLighting * shadowing; diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 50bb77145..e0d7833c9 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -1,5 +1,9 @@ #version 120 +#if @useGPUShader4 + #extension GL_EXT_gpu_shader4: require +#endif + #if @diffuseMap uniform sampler2D diffuseMap; varying vec2 diffuseMapUV; @@ -68,6 +72,7 @@ varying vec3 passNormal; #include "shadows_fragment.glsl" #include "lighting.glsl" #include "parallax.glsl" +#include "alpha.glsl" void main() { @@ -109,10 +114,15 @@ void main() #if @diffuseMap gl_FragData[0] = texture2D(diffuseMap, adjustedDiffuseUV); + gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, adjustedDiffuseUV); #else gl_FragData[0] = vec4(1.0); #endif + vec4 diffuseColor = getDiffuseColor(); + gl_FragData[0].a *= diffuseColor.a; + alphaTest(); + #if @detailMap gl_FragData[0].xyz *= texture2D(detailMap, detailMapUV).xyz * 2.0; #endif @@ -151,9 +161,6 @@ void main() #endif - vec4 diffuseColor = getDiffuseColor(); - gl_FragData[0].a *= diffuseColor.a; - float shadowing = unshadowedLightRatio(linearDepth); vec3 lighting; #if !PER_PIXEL_LIGHTING diff --git a/files/shaders/shadowcasting_fragment.glsl b/files/shaders/shadowcasting_fragment.glsl index a5410d008..07fad047e 100644 --- a/files/shaders/shadowcasting_fragment.glsl +++ b/files/shaders/shadowcasting_fragment.glsl @@ -1,5 +1,9 @@ #version 120 +#if @useGPUShader4 + #extension GL_EXT_gpu_shader4: require +#endif + uniform sampler2D diffuseMap; varying vec2 diffuseMapUV; @@ -8,6 +12,8 @@ varying float alphaPassthrough; uniform bool useDiffuseMapForShadowAlpha; uniform bool alphaTestShadows; +#include "alpha.glsl" + void main() { gl_FragData[0].rgb = vec3(1.0); @@ -16,7 +22,10 @@ void main() else gl_FragData[0].a = alphaPassthrough; - // Prevent translucent things casting shadow (including the player using an invisibility effect). For now, rely on the deprecated FF test for non-blended stuff. + alphaTest(); + + // Prevent translucent things casting shadow (including the player using an invisibility effect). + // This replaces alpha blending, which obviously doesn't work with depth buffers if (alphaTestShadows && gl_FragData[0].a <= 0.5) discard; }