From 04843fed6d5fd7f784912480536b44f6d3c40cec Mon Sep 17 00:00:00 2001 From: cody glassman Date: Fri, 13 May 2022 18:58:00 -0700 Subject: [PATCH] moddable post-processing pipeline --- CHANGELOG.md | 1 + apps/launcher/advancedpage.cpp | 19 + apps/launcher/advancedpage.hpp | 1 + apps/opencs/model/world/data.cpp | 1 + apps/openmw/CMakeLists.txt | 6 +- apps/openmw/engine.cpp | 5 + apps/openmw/mwbase/windowmanager.hpp | 3 + apps/openmw/mwbase/world.hpp | 7 + apps/openmw/mwgui/postprocessorhud.cpp | 454 +++++++ apps/openmw/mwgui/postprocessorhud.hpp | 107 ++ apps/openmw/mwgui/windowmanagerimp.cpp | 32 +- apps/openmw/mwgui/windowmanagerimp.hpp | 4 + apps/openmw/mwinput/actionmanager.cpp | 3 + apps/openmw/mwinput/actions.hpp | 2 + apps/openmw/mwinput/bindingsmanager.cpp | 6 +- apps/openmw/mwlua/inputbindings.cpp | 1 + apps/openmw/mwlua/luabindings.hpp | 1 + apps/openmw/mwlua/luamanagerimp.cpp | 2 + apps/openmw/mwlua/luamanagerimp.hpp | 1 + apps/openmw/mwlua/postprocessingbindings.cpp | 140 +++ apps/openmw/mwrender/animation.cpp | 4 +- apps/openmw/mwrender/characterpreview.cpp | 22 +- apps/openmw/mwrender/hdr.cpp | 123 ++ apps/openmw/mwrender/hdr.hpp | 71 ++ apps/openmw/mwrender/localmap.cpp | 1 + apps/openmw/mwrender/npcanimation.cpp | 25 +- apps/openmw/mwrender/pingpongcanvas.cpp | 260 ++++ apps/openmw/mwrender/pingpongcanvas.hpp | 88 ++ apps/openmw/mwrender/pingpongcull.cpp | 47 + apps/openmw/mwrender/pingpongcull.hpp | 22 + apps/openmw/mwrender/postprocessor.cpp | 1018 +++++++++++----- apps/openmw/mwrender/postprocessor.hpp | 210 +++- apps/openmw/mwrender/renderingmanager.cpp | 85 +- apps/openmw/mwrender/renderingmanager.hpp | 8 +- apps/openmw/mwrender/screenshotmanager.cpp | 11 +- apps/openmw/mwrender/sky.cpp | 10 + apps/openmw/mwrender/sky.hpp | 3 + apps/openmw/mwrender/skyutil.cpp | 6 + apps/openmw/mwrender/skyutil.hpp | 1 + apps/openmw/mwrender/transparentpass.cpp | 89 ++ apps/openmw/mwrender/transparentpass.hpp | 38 + apps/openmw/mwrender/water.cpp | 2 + apps/openmw/mwworld/scene.cpp | 5 + apps/openmw/mwworld/weather.cpp | 8 +- apps/openmw/mwworld/weather.hpp | 6 +- apps/openmw/mwworld/worldimp.cpp | 15 + apps/openmw/mwworld/worldimp.hpp | 7 + apps/openmw_test_suite/CMakeLists.txt | 4 + apps/openmw_test_suite/fx/lexer.cpp | 216 ++++ apps/openmw_test_suite/fx/technique.cpp | 204 ++++ apps/openmw_test_suite/lua/testing_util.hpp | 5 + .../settings/shadermanager.cpp | 66 ++ components/CMakeLists.txt | 4 + components/fx/lexer.cpp | 301 +++++ components/fx/lexer.hpp | 75 ++ components/fx/lexer_types.hpp | 56 + components/fx/parse_constants.hpp | 133 +++ components/fx/pass.cpp | 253 ++++ components/fx/pass.hpp | 79 ++ components/fx/stateupdater.cpp | 60 + components/fx/stateupdater.hpp | 192 +++ components/fx/technique.cpp | 1049 +++++++++++++++++ components/fx/technique.hpp | 300 +++++ components/fx/types.hpp | 259 ++++ components/fx/widgets.cpp | 164 +++ components/fx/widgets.hpp | 266 +++++ components/resource/scenemanager.cpp | 21 +- components/resource/scenemanager.hpp | 15 +- components/sceneutil/clearcolor.hpp | 42 + components/sceneutil/depth.cpp | 12 - components/sceneutil/depth.hpp | 3 - components/sceneutil/rtt.cpp | 2 +- components/sceneutil/util.cpp | 36 - components/serialization/osgyaml.hpp | 64 + components/settings/shadermanager.hpp | 174 +++ components/shader/shadervisitor.cpp | 74 +- components/shader/shadervisitor.hpp | 12 +- components/std140/ubo.hpp | 162 +++ components/stereo/multiview.cpp | 32 +- components/stereo/multiview.hpp | 5 +- components/stereo/stereomanager.hpp | 1 - components/terrain/chunkmanager.cpp | 4 +- components/terrain/material.cpp | 17 +- components/terrain/material.hpp | 6 +- components/vfs/archive.hpp | 2 + components/vfs/bsaarchive.hpp | 4 + components/vfs/filesystemarchive.hpp | 2 + components/vfs/manager.cpp | 11 + components/vfs/manager.hpp | 5 + docs/source/reference/index.rst | 1 + docs/source/reference/lua-scripting/api.rst | 61 +- .../lua-scripting/openmw_postprocessing.rst | 5 + .../reference/lua-scripting/overview.rst | 60 +- .../reference/modding/settings/index.rst | 1 + .../modding/settings/postprocessing.rst | 65 + .../reference/modding/settings/windows.rst | 14 + .../source/reference/postprocessing/index.rst | 11 + .../source/reference/postprocessing/omwfx.rst | 862 ++++++++++++++ .../reference/postprocessing/overview.rst | 45 + files/lua_api/CMakeLists.txt | 1 + files/lua_api/openmw/input.lua | 1 + files/lua_api/openmw/postprocessing.lua | 92 ++ files/mygui/CMakeLists.txt | 2 + files/mygui/openmw_list.skin.xml | 8 + files/mygui/openmw_postprocessor_hud.layout | 138 +++ files/mygui/openmw_postprocessor_hud.skin.xml | 79 ++ files/mygui/openmw_resources.xml | 11 + files/mygui/openmw_settings_window.layout | 33 +- files/mygui/skins.xml | 1 + files/settings-default.cfg | 30 +- files/shaders/CMakeLists.txt | 6 + .../blended_depth_postpass_fragment.glsl | 19 + .../blended_depth_postpass_vertex.glsl | 20 + files/shaders/fullscreen_tri_fragment.glsl | 10 + files/shaders/fullscreen_tri_vertex.glsl | 9 + files/shaders/groundcover_fragment.glsl | 14 +- files/shaders/groundcover_vertex.glsl | 5 +- files/shaders/hdr_fragment.glsl | 14 + files/shaders/hdr_luminance_fragment.glsl | 17 + files/shaders/nv_default_fragment.glsl | 13 +- files/shaders/objects_fragment.glsl | 19 +- files/shaders/openmw_fragment.glsl | 9 +- files/shaders/openmw_fragment.h.glsl | 4 +- files/shaders/openmw_fragment_multiview.glsl | 9 +- files/shaders/terrain_fragment.glsl | 21 +- files/shaders/water_fragment.glsl | 4 + files/ui/advancedpage.ui | 88 ++ files/vfs/CMakeLists.txt | 7 + files/vfs/shaders/displaydepth.omwfx | 26 + files/vfs/shaders/main.omwfx | 45 + 130 files changed, 8645 insertions(+), 608 deletions(-) create mode 100644 apps/openmw/mwgui/postprocessorhud.cpp create mode 100644 apps/openmw/mwgui/postprocessorhud.hpp create mode 100644 apps/openmw/mwlua/postprocessingbindings.cpp create mode 100644 apps/openmw/mwrender/hdr.cpp create mode 100644 apps/openmw/mwrender/hdr.hpp create mode 100644 apps/openmw/mwrender/pingpongcanvas.cpp create mode 100644 apps/openmw/mwrender/pingpongcanvas.hpp create mode 100644 apps/openmw/mwrender/pingpongcull.cpp create mode 100644 apps/openmw/mwrender/pingpongcull.hpp create mode 100644 apps/openmw/mwrender/transparentpass.cpp create mode 100644 apps/openmw/mwrender/transparentpass.hpp create mode 100644 apps/openmw_test_suite/fx/lexer.cpp create mode 100644 apps/openmw_test_suite/fx/technique.cpp create mode 100644 apps/openmw_test_suite/settings/shadermanager.cpp create mode 100644 components/fx/lexer.cpp create mode 100644 components/fx/lexer.hpp create mode 100644 components/fx/lexer_types.hpp create mode 100644 components/fx/parse_constants.hpp create mode 100644 components/fx/pass.cpp create mode 100644 components/fx/pass.hpp create mode 100644 components/fx/stateupdater.cpp create mode 100644 components/fx/stateupdater.hpp create mode 100644 components/fx/technique.cpp create mode 100644 components/fx/technique.hpp create mode 100644 components/fx/types.hpp create mode 100644 components/fx/widgets.cpp create mode 100644 components/fx/widgets.hpp create mode 100755 components/sceneutil/clearcolor.hpp create mode 100644 components/serialization/osgyaml.hpp create mode 100644 components/settings/shadermanager.hpp create mode 100644 components/std140/ubo.hpp create mode 100644 docs/source/reference/lua-scripting/openmw_postprocessing.rst create mode 100644 docs/source/reference/modding/settings/postprocessing.rst create mode 100644 docs/source/reference/postprocessing/index.rst create mode 100644 docs/source/reference/postprocessing/omwfx.rst create mode 100644 docs/source/reference/postprocessing/overview.rst create mode 100644 files/lua_api/openmw/postprocessing.lua create mode 100644 files/mygui/openmw_postprocessor_hud.layout create mode 100644 files/mygui/openmw_postprocessor_hud.skin.xml create mode 100644 files/shaders/blended_depth_postpass_fragment.glsl create mode 100644 files/shaders/blended_depth_postpass_vertex.glsl create mode 100644 files/shaders/fullscreen_tri_fragment.glsl create mode 100644 files/shaders/fullscreen_tri_vertex.glsl create mode 100644 files/shaders/hdr_fragment.glsl create mode 100644 files/shaders/hdr_luminance_fragment.glsl create mode 100644 files/vfs/shaders/displaydepth.omwfx create mode 100644 files/vfs/shaders/main.omwfx diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d264b5f02..5c64b831bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -128,6 +128,7 @@ Feature #2858: Add a tab to the launcher for handling datafolders Feature #3245: Grid and angle snapping for the OpenMW-CS Feature #3616: Allow Zoom levels on the World Map + Feature #4067: Post Processing Feature #4297: Implement APPLIED_ONCE flag for magic effects Feature #4414: Handle duration of EXTRA SPELL magic effect Feature #4595: Unique object identifier diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index fc1d84a61f..6f4ca60edf 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -145,6 +145,12 @@ bool Launcher::AdvancedPage::loadSettings() objectPagingMinSizeComboBox->setValue(Settings::Manager::getDouble("object paging min size", "Terrain")); loadSettingBool(nightDaySwitchesCheckBox, "day night switches", "Game"); + + connect(postprocessEnabledCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotPostProcessToggled(bool))); + loadSettingBool(postprocessEnabledCheckBox, "enabled", "Post Processing"); + loadSettingBool(postprocessLiveReloadCheckBox, "live reload", "Post Processing"); + loadSettingBool(postprocessTransparentPostpassCheckBox, "transparent postpass", "Post Processing"); + postprocessHDRTimeComboBox->setValue(Settings::Manager::getDouble("hdr exposure time", "Post Processing")); } // Audio @@ -302,6 +308,11 @@ void Launcher::AdvancedPage::saveSettings() Settings::Manager::setDouble("object paging min size", "Terrain", objectPagingMinSize); saveSettingBool(nightDaySwitchesCheckBox, "day night switches", "Game"); + + saveSettingBool(postprocessEnabledCheckBox, "enabled", "Post Processing"); + saveSettingBool(postprocessLiveReloadCheckBox, "live reload", "Post Processing"); + saveSettingBool(postprocessTransparentPostpassCheckBox, "transparent postpass", "Post Processing"); + Settings::Manager::setDouble("hdr exposure time", "Post Processing", postprocessHDRTimeComboBox->value()); } // Audio @@ -464,3 +475,11 @@ void Launcher::AdvancedPage::slotViewOverShoulderToggled(bool checked) { viewOverShoulderVerticalLayout->setEnabled(viewOverShoulderCheckBox->checkState()); } + +void Launcher::AdvancedPage::slotPostProcessToggled(bool checked) +{ + postprocessLiveReloadCheckBox->setEnabled(checked); + postprocessTransparentPostpassCheckBox->setEnabled(checked); + postprocessHDRTimeComboBox->setEnabled(checked); + postprocessHDRTimeLabel->setEnabled(checked); +} diff --git a/apps/launcher/advancedpage.hpp b/apps/launcher/advancedpage.hpp index 1d16fae706..6a3f8319d2 100644 --- a/apps/launcher/advancedpage.hpp +++ b/apps/launcher/advancedpage.hpp @@ -30,6 +30,7 @@ namespace Launcher void on_runScriptAfterStartupBrowseButton_clicked(); void slotAnimSourcesToggled(bool checked); void slotViewOverShoulderToggled(bool checked); + void slotPostProcessToggled(bool checked); private: Config::GameSettings &mGameSettings; diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 12274b912f..b10efaa3a4 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -84,6 +84,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat defines["radialFog"] = "0"; defines["lightingModel"] = "0"; defines["reverseZ"] = "0"; + defines["refraction_enabled"] = "0"; for (const auto& define : shadowDefines) defines[define.first] = define.second; mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines); diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 7bbf179d95..1756824164 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -22,7 +22,8 @@ add_openmw_dir (mwrender actors objects renderingmanager animation rotatecontroller sky skyutil npcanimation vismask creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation - renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover postprocessor + renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover + postprocessor pingpongcull hdr pingpongcanvas transparentpass ) add_openmw_dir (mwinput @@ -43,6 +44,7 @@ add_openmw_dir (mwgui tradeitemmodel companionitemmodel pickpocketitemmodel controllers savegamedialog recharge mode videowidget backgroundimage itemwidget screenfader debugwindow spellmodel spellview draganddrop timeadvancer jailscreen itemchargeview keyboardnavigation textcolours statswatcher + postprocessorhud ) add_openmw_dir (mwdialogue @@ -59,7 +61,7 @@ add_openmw_dir (mwscript add_openmw_dir (mwlua luamanagerimp object worldview userdataserializer eventqueue luabindings localscripts playerscripts objectbindings cellbindings asyncbindings settingsbindings - camerabindings uibindings inputbindings nearbybindings stats + camerabindings uibindings inputbindings nearbybindings postprocessingbindings stats types/types types/door types/actor types/container types/weapon types/npc types/creature ) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 982ae92f11..af3db12b5f 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -46,6 +46,8 @@ #include #include +#include + #include "mwinput/inputmanagerimp.hpp" #include "mwgui/windowmanagerimp.hpp" @@ -987,6 +989,8 @@ void OMW::Engine::go() Settings::Manager settings; std::string settingspath = settings.load(mCfgMgr); + Settings::ShaderManager::get().load((mCfgMgr.getUserConfigPath() / "shaders.yaml").string()); + MWClass::registerClasses(); // Create encoder @@ -1110,6 +1114,7 @@ void OMW::Engine::go() // Save user settings settings.saveUser(settingspath); + Settings::ShaderManager::get().save(); mLuaManager->savePermanentStorage(mCfgMgr.getUserConfigPath().string()); Log(Debug::Info) << "Quitting peacefully."; diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index b0076b02f2..c1c99436cb 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -70,6 +70,7 @@ namespace MWGui class WindowModal; class JailScreen; class MessageBox; + class PostProcessorHud; enum ShowInDialogueMode { ShowInDialogueMode_IfPossible, @@ -147,6 +148,7 @@ namespace MWBase virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0; virtual MWGui::TradeWindow* getTradeWindow() = 0; virtual const std::vector getActiveMessageBoxes() = 0; + virtual MWGui::PostProcessorHud* getPostProcessorHud() = 0; /// Make the player use an item, while updating GUI state accordingly virtual void useItem(const MWWorld::Ptr& item, bool force=false) = 0; @@ -326,6 +328,7 @@ namespace MWBase virtual void toggleConsole() = 0; virtual void toggleDebugWindow() = 0; + virtual void togglePostProcessorHud() = 0; /// Cycle to next or previous spell virtual void cycleSpell(bool next) = 0; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 336af37a89..15177b4301 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -67,6 +67,7 @@ namespace MWRender class Animation; class Camera; class RenderingManager; + class PostProcessor; } namespace MWMechanics @@ -238,6 +239,10 @@ namespace MWBase virtual int getCurrentWeather() const = 0; + virtual int getNextWeather() const = 0; + + virtual float getWeatherTransition() const = 0; + virtual unsigned int getNightDayMode() const = 0; virtual int getMasserPhase() const = 0; @@ -664,6 +669,8 @@ namespace MWBase virtual Misc::Rng::Generator& getPrng() = 0; virtual MWRender::RenderingManager* getRenderingManager() = 0; + + virtual MWRender::PostProcessor* getPostProcessor() = 0; }; } diff --git a/apps/openmw/mwgui/postprocessorhud.cpp b/apps/openmw/mwgui/postprocessorhud.cpp new file mode 100644 index 0000000000..cfdbfda7e8 --- /dev/null +++ b/apps/openmw/mwgui/postprocessorhud.cpp @@ -0,0 +1,454 @@ +#include "postprocessorhud.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "../mwrender/postprocessor.hpp" + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" + +namespace +{ + void saveChain() + { + auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); + + std::ostringstream chain; + + for (size_t i = 1; i < processor->getTechniques().size(); ++i) + { + auto technique = processor->getTechniques()[i]; + + if (!technique) + continue; + + chain << technique->getName(); + + if (i < processor-> getTechniques().size() - 1) + chain << ","; + } + + Settings::Manager::setString("chain", "Post Processing", chain.str()); + } +} + +namespace MWGui +{ + void PostProcessorHud::ListWrapper::onKeyButtonPressed(MyGUI::KeyCode key, MyGUI::Char ch) + { + if (MyGUI::InputManager::getInstance().isShiftPressed() && (key == MyGUI::KeyCode::ArrowUp || key == MyGUI::KeyCode::ArrowDown)) + return; + + MyGUI::ListBox::onKeyButtonPressed(key, ch); + } + + PostProcessorHud::PostProcessorHud() + : WindowBase("openmw_postprocessor_hud.layout") + { + getWidget(mTabConfiguration, "TabConfiguration"); + getWidget(mActiveList, "ActiveList"); + getWidget(mInactiveList, "InactiveList"); + getWidget(mModeToggle, "ModeToggle"); + getWidget(mConfigLayout, "ConfigLayout"); + getWidget(mFilter, "Filter"); + getWidget(mButtonActivate, "ButtonActivate"); + getWidget(mButtonDeactivate, "ButtonDeactivate"); + getWidget(mButtonUp, "ButtonUp"); + getWidget(mButtonDown, "ButtonDown"); + + mButtonActivate->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyActivatePressed); + mButtonDeactivate->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyDeactivatePressed); + mButtonUp->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyShaderUpPressed); + mButtonDown->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyShaderDownPressed); + + mActiveList->eventKeyButtonPressed += MyGUI::newDelegate(this, &PostProcessorHud::notifyKeyButtonPressed); + mInactiveList->eventKeyButtonPressed += MyGUI::newDelegate(this, &PostProcessorHud::notifyKeyButtonPressed); + + mActiveList->eventListChangePosition += MyGUI::newDelegate(this, &PostProcessorHud::notifyListChangePosition); + mInactiveList->eventListChangePosition += MyGUI::newDelegate(this, &PostProcessorHud::notifyListChangePosition); + + mModeToggle->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyModeToggle); + + mFilter->eventEditTextChange += MyGUI::newDelegate(this, &PostProcessorHud::notifyFilterChanged); + + mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &PostProcessorHud::notifyWindowResize); + + mShaderInfo = mConfigLayout->createWidget("HeaderText", {}, MyGUI::Align::Default); + mShaderInfo->setUserString("VStretch", "true"); + mShaderInfo->setUserString("HStretch", "true"); + mShaderInfo->setTextAlign(MyGUI::Align::Left | MyGUI::Align::Top); + mShaderInfo->setEditReadOnly(true); + mShaderInfo->setEditWordWrap(true); + mShaderInfo->setEditMultiLine(true); + + mConfigLayout->setVisibleVScroll(true); + + mConfigArea = mConfigLayout->createWidget("", {}, MyGUI::Align::Default); + } + + void PostProcessorHud::notifyFilterChanged(MyGUI::EditBox* sender) + { + updateTechniques(); + } + + void PostProcessorHud::notifyWindowResize(MyGUI::Window* sender) + { + layout(); + } + + void PostProcessorHud::notifyResetButtonClicked(MyGUI::Widget* sender) + { + for (size_t i = 1; i < mConfigArea->getChildCount(); ++i) + { + if (auto* child = dynamic_cast(mConfigArea->getChildAt(i))) + child->toDefault(); + } + } + + void PostProcessorHud::notifyListChangePosition(MyGUI::ListBox* sender, size_t index) + { + if (sender == mActiveList) + mInactiveList->clearIndexSelected(); + else if (sender == mInactiveList) + mActiveList->clearIndexSelected(); + + if (index >= sender->getItemCount()) + return; + + updateConfigView(sender->getItemNameAt(index)); + } + + void PostProcessorHud::toggleTechnique(bool enabled) + { + auto* list = enabled ? mInactiveList : mActiveList; + + size_t selected = list->getIndexSelected(); + + if (selected != MyGUI::ITEM_NONE) + { + auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); + mOverrideHint = list->getItemNameAt(selected); + if (enabled) + processor->enableTechnique(*list->getItemDataAt>(selected)); + else + processor->disableTechnique(*list->getItemDataAt>(selected)); + saveChain(); + } + } + + void PostProcessorHud::notifyActivatePressed(MyGUI::Widget* sender) + { + toggleTechnique(true); + } + + void PostProcessorHud::notifyDeactivatePressed(MyGUI::Widget* sender) + { + toggleTechnique(false); + } + + void PostProcessorHud::moveShader(Direction direction) + { + auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); + + size_t selected = mActiveList->getIndexSelected(); + + if (selected == MyGUI::ITEM_NONE) + return; + + int index = direction == Direction::Up ? static_cast(selected) - 1 : selected + 1; + index = std::clamp(index, 0, mActiveList->getItemCount() - 1); + + if (static_cast(index) != selected) + { + if (processor->enableTechnique(*mActiveList->getItemDataAt>(selected), index)) + saveChain(); + } + } + + void PostProcessorHud::notifyShaderUpPressed(MyGUI::Widget* sender) + { + moveShader(Direction::Up); + } + + void PostProcessorHud::notifyShaderDownPressed(MyGUI::Widget* sender) + { + moveShader(Direction::Down); + } + + void PostProcessorHud::notifyKeyButtonPressed(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char ch) + { + MyGUI::ListBox* list = static_cast(sender); + + if (list->getIndexSelected() == MyGUI::ITEM_NONE) + return; + + if (key == MyGUI::KeyCode::ArrowLeft && list == mActiveList) + { + if (MyGUI::InputManager::getInstance().isShiftPressed()) + { + toggleTechnique(false); + } + else + { + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mInactiveList); + mActiveList->clearIndexSelected(); + select(mInactiveList, 0); + } + } + else if (key == MyGUI::KeyCode::ArrowRight && list == mInactiveList) + { + if (MyGUI::InputManager::getInstance().isShiftPressed()) + { + toggleTechnique(true); + } + else + { + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mActiveList); + mInactiveList->clearIndexSelected(); + select(mActiveList, 0); + } + } + else if (list == mActiveList && MyGUI::InputManager::getInstance().isShiftPressed() && (key == MyGUI::KeyCode::ArrowUp || key == MyGUI::KeyCode::ArrowDown)) + { + moveShader(key == MyGUI::KeyCode::ArrowUp ? Direction::Up : Direction::Down); + } + } + + void PostProcessorHud::notifyModeToggle(MyGUI::Widget* sender) + { + Settings::ShaderManager::Mode prev = Settings::ShaderManager::get().getMode(); + toggleMode(prev == Settings::ShaderManager::Mode::Debug ? Settings::ShaderManager::Mode::Normal : Settings::ShaderManager::Mode::Debug); + } + + void PostProcessorHud::onOpen() + { + toggleMode(Settings::ShaderManager::Mode::Debug); + updateTechniques(); + } + + void PostProcessorHud::onClose() + { + toggleMode(Settings::ShaderManager::Mode::Normal); + } + + void PostProcessorHud::layout() + { + constexpr int padding = 12; + constexpr int padding2 = padding * 2; + mShaderInfo->setCoord(padding, padding, mConfigLayout->getSize().width - padding2 - padding, mShaderInfo->getTextSize().height); + + int totalHeight = mShaderInfo->getTop() + mShaderInfo->getTextSize().height + padding; + + mConfigArea->setCoord({padding, totalHeight, mShaderInfo->getSize().width, mConfigLayout->getHeight()}); + + int childHeights = 0; + MyGUI::EnumeratorWidgetPtr enumerator = mConfigArea->getEnumerator(); + while (enumerator.next()) + { + enumerator.current()->setCoord(padding, childHeights + padding, mShaderInfo->getSize().width - padding2, enumerator.current()->getHeight()); + childHeights += enumerator.current()->getHeight() + padding; + } + totalHeight += childHeights; + + mConfigArea->setSize(mConfigArea->getWidth(), childHeights); + + mConfigLayout->setCanvasSize(mConfigLayout->getWidth() - padding2, totalHeight); + mConfigLayout->setSize(mConfigLayout->getWidth(), mConfigLayout->getParentSize().height - padding2); + } + + void PostProcessorHud::select(ListWrapper* list, size_t index) + { + list->setIndexSelected(index); + notifyListChangePosition(list, index); + } + + void PostProcessorHud::toggleMode(Settings::ShaderManager::Mode mode) + { + Settings::ShaderManager::get().setMode(mode); + + mModeToggle->setCaptionWithReplacing(mode == Settings::ShaderManager::Mode::Debug ? "#{sOn}" :"#{sOff}"); + + MWBase::Environment::get().getWorld()->getPostProcessor()->toggleMode(); + + if (!isVisible()) + return; + + if (mInactiveList->getIndexSelected() != MyGUI::ITEM_NONE) + updateConfigView(mInactiveList->getItemNameAt(mInactiveList->getIndexSelected())); + else if (mActiveList->getIndexSelected() != MyGUI::ITEM_NONE) + updateConfigView(mActiveList->getItemNameAt(mActiveList->getIndexSelected())); + } + + void PostProcessorHud::updateConfigView(const std::string& name) + { + auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); + + auto technique = processor->loadTechnique(name); + + if (!technique) + return; + + while (mConfigArea->getChildCount() > 0) + MyGUI::Gui::getInstance().destroyWidget(mConfigArea->getChildAt(0)); + + mShaderInfo->setCaption(""); + + if (!technique) + return; + + std::ostringstream ss; + + const std::string NA = "NA"; + const std::string endl = "\n"; + + std::string author = technique->getAuthor().empty() ? NA : std::string(technique->getAuthor()); + std::string version = technique->getVersion().empty() ? NA : std::string(technique->getVersion()); + std::string description = technique->getDescription().empty() ? NA : std::string(technique->getDescription()); + + auto serializeBool = [](bool value) { + return value ? "#{sYes}" : "#{sNo}"; + }; + + const auto flags = technique->getFlags(); + + const auto flag_interior = serializeBool (!(flags & fx::Technique::Flag_Disable_Interiors)); + const auto flag_exterior = serializeBool (!(flags & fx::Technique::Flag_Disable_Exteriors)); + const auto flag_underwater = serializeBool(!(flags & fx::Technique::Flag_Disable_Underwater)); + const auto flag_abovewater = serializeBool(!(flags & fx::Technique::Flag_Disable_Abovewater)); + + switch (technique->getStatus()) + { + case fx::Technique::Status::Success: + case fx::Technique::Status::Uncompiled: + ss << "#{fontcolourhtml=header}Author: #{fontcolourhtml=normal} " << author << endl << endl + << "#{fontcolourhtml=header}Version: #{fontcolourhtml=normal} " << version << endl << endl + << "#{fontcolourhtml=header}Description: #{fontcolourhtml=normal} " << description << endl << endl + << "#{fontcolourhtml=header}Interiors: #{fontcolourhtml=normal} " << flag_interior + << "#{fontcolourhtml=header} Exteriors: #{fontcolourhtml=normal} " << flag_exterior + << "#{fontcolourhtml=header} Underwater: #{fontcolourhtml=normal} " << flag_underwater + << "#{fontcolourhtml=header} Abovewater: #{fontcolourhtml=normal} " << flag_abovewater; + break; + case fx::Technique::Status::File_Not_exists: + ss << "#{fontcolourhtml=negative}Shader Error: #{fontcolourhtml=header} <" << std::string(technique->getFileName()) << ">#{fontcolourhtml=normal} not found." << endl << endl + << "Ensure the shader file is in a 'Shaders/' sub directory in a data files directory"; + break; + case fx::Technique::Status::Parse_Error: + ss << "#{fontcolourhtml=negative}Shader Compile Error: #{fontcolourhtml=normal} <" << std::string(technique->getName()) << "> failed to compile." << endl << endl + << technique->getLastError(); + break; + } + + mShaderInfo->setCaptionWithReplacing(ss.str()); + + if (Settings::ShaderManager::get().getMode() == Settings::ShaderManager::Mode::Debug) + { + if (technique->getUniformMap().size() > 0) + { + MyGUI::Button* resetButton = mConfigArea->createWidget("MW_Button", {0,0,0,24}, MyGUI::Align::Default); + resetButton->setCaption("Reset all to default"); + resetButton->setTextAlign(MyGUI::Align::Center); + resetButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyResetButtonClicked); + } + + for (const auto& uniform : technique->getUniformMap()) + { + if (!uniform->mStatic || uniform->mSamplerType) + continue; + + if (!uniform->mHeader.empty()) + mConfigArea->createWidget("MW_UniformGroup", {0,0,0,34}, MyGUI::Align::Default)->setCaption(uniform->mHeader); + + fx::Widgets::UniformBase* uwidget = mConfigArea->createWidget("MW_UniformEdit", {0,0,0,22}, MyGUI::Align::Default); + uwidget->init(uniform); + } + } + + layout(); + } + + void PostProcessorHud::updateTechniques() + { + if (!isVisible()) + return; + + std::string hint; + ListWrapper* hintWidget = nullptr; + if (mInactiveList->getIndexSelected() != MyGUI::ITEM_NONE) + { + hint = mInactiveList->getItemNameAt(mInactiveList->getIndexSelected()); + hintWidget = mInactiveList; + } + else if (mActiveList->getIndexSelected() != MyGUI::ITEM_NONE) + { + hint = mActiveList->getItemNameAt(mActiveList->getIndexSelected()); + hintWidget = mActiveList; + } + + mInactiveList->removeAllItems(); + mActiveList->removeAllItems(); + + auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); + + for (const auto& [name, _] : processor->getTechniqueMap()) + { + auto technique = processor->loadTechnique(name); + + if (!technique) + continue; + + if (!technique->getHidden() && !processor->isTechniqueEnabled(technique) && name.find(mFilter->getCaption()) != std::string::npos) + mInactiveList->addItem(name, technique); + } + + for (auto technique : processor->getTechniques()) + { + if (!technique->getHidden()) + mActiveList->addItem(technique->getName(), technique); + } + + auto tryFocus = [this](ListWrapper* widget, const std::string& hint) + { + size_t index = widget->findItemIndexWith(hint); + + if (index != MyGUI::ITEM_NONE) + { + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(widget); + select(widget, index); + } + }; + + if (!mOverrideHint.empty()) + { + tryFocus(mActiveList, mOverrideHint); + tryFocus(mInactiveList, mOverrideHint); + + mOverrideHint.clear(); + } + else if (hintWidget && !hint.empty()) + tryFocus(hintWidget, hint); + } + + void PostProcessorHud::registerMyGUIComponents() + { + MyGUI::FactoryManager& factory = MyGUI::FactoryManager::getInstance(); + factory.registerFactory("Widget"); + factory.registerFactory("Widget"); + factory.registerFactory("Widget"); + factory.registerFactory("Widget"); + factory.registerFactory("Widget"); + factory.registerFactory("Widget"); + factory.registerFactory("Widget"); + factory.registerFactory("Widget"); + } +} diff --git a/apps/openmw/mwgui/postprocessorhud.hpp b/apps/openmw/mwgui/postprocessorhud.hpp new file mode 100644 index 0000000000..44baf79b63 --- /dev/null +++ b/apps/openmw/mwgui/postprocessorhud.hpp @@ -0,0 +1,107 @@ +#ifndef MYGUI_POSTPROCESSOR_HUD_H +#define MYGUI_POSTPROCESSOR_HUD_H + +#include "windowbase.hpp" + +#include +#include + +#include + +namespace MyGUI +{ + class ScrollView; + class EditBox; + class TabItem; +} +namespace Gui +{ + class AutoSizedButton; + class AutoSizedEditBox; +} + +namespace MWGui +{ + class PostProcessorHud : public WindowBase + { + class ListWrapper final : public MyGUI::ListBox + { + MYGUI_RTTI_DERIVED(ListWrapper) + protected: + void onKeyButtonPressed(MyGUI::KeyCode key, MyGUI::Char ch) override; + }; + + public: + PostProcessorHud(); + + void onOpen() override; + + void onClose() override; + + void updateTechniques(); + + void toggleMode(Settings::ShaderManager::Mode mode); + + static void registerMyGUIComponents(); + + private: + + void notifyWindowResize(MyGUI::Window* sender); + + void notifyFilterChanged(MyGUI::EditBox* sender); + + void updateConfigView(const std::string& name); + + void notifyModeToggle(MyGUI::Widget* sender); + + void notifyResetButtonClicked(MyGUI::Widget* sender); + + void notifyListChangePosition(MyGUI::ListBox* sender, size_t index); + + void notifyKeyButtonPressed(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char ch); + + void notifyActivatePressed(MyGUI::Widget* sender); + + void notifyDeactivatePressed(MyGUI::Widget* sender); + + void notifyShaderUpPressed(MyGUI::Widget* sender); + + void notifyShaderDownPressed(MyGUI::Widget* sender); + + enum class Direction + { + Up, + Down + }; + + void moveShader(Direction direction); + + void toggleTechnique(bool enabled); + + void select(ListWrapper* list, size_t index); + + void layout(); + + MyGUI::TabItem* mTabConfiguration; + + ListWrapper* mActiveList; + ListWrapper* mInactiveList; + + Gui::AutoSizedButton* mButtonActivate; + Gui::AutoSizedButton* mButtonDeactivate; + Gui::AutoSizedButton* mButtonDown; + Gui::AutoSizedButton* mButtonUp; + + MyGUI::ScrollView* mConfigLayout; + + MyGUI::Widget* mConfigArea; + + MyGUI::EditBox* mFilter; + Gui::AutoSizedButton* mModeToggle; + Gui::AutoSizedEditBox* mShaderInfo; + + std::string mOverrideHint; + }; +} + +#endif diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 80fb1e546b..3b79b21a7c 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -71,6 +71,7 @@ #include "../mwmechanics/actorutil.hpp" #include "../mwrender/localmap.hpp" +#include "../mwrender/postprocessor.hpp" #include "console.hpp" #include "journalwindow.hpp" @@ -113,6 +114,7 @@ #include "itemwidget.hpp" #include "screenfader.hpp" #include "debugwindow.hpp" +#include "postprocessorhud.hpp" #include "spellview.hpp" #include "draganddrop.hpp" #include "container.hpp" @@ -164,6 +166,7 @@ namespace MWGui , mHitFader(nullptr) , mScreenFader(nullptr) , mDebugWindow(nullptr) + , mPostProcessorHud(nullptr) , mJailScreen(nullptr) , mContainerWindow(nullptr) , mTranslationDataStorage (translationDataStorage) @@ -217,7 +220,8 @@ namespace MWGui MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Layer"); MyGUI::FactoryManager::getInstance().registerFactory("Layer"); - BookPage::registerMyGUIComponents (); + BookPage::registerMyGUIComponents(); + PostProcessorHud::registerMyGUIComponents(); ItemView::registerComponents(); ItemChargeView::registerComponents(); ItemWidget::registerComponents(); @@ -469,6 +473,10 @@ namespace MWGui mDebugWindow = new DebugWindow(); mWindows.push_back(mDebugWindow); + mPostProcessorHud = new PostProcessorHud(); + mWindows.push_back(mPostProcessorHud); + trackWindow(mPostProcessorHud, "postprocessor"); + mInputBlocker = MyGUI::Gui::getInstance().createWidget("",0,0,w,h,MyGUI::Align::Stretch,"InputBlocker"); mHud->setVisible(true); @@ -897,6 +905,8 @@ namespace MWGui mDebugWindow->onFrame(frameDuration); + mPostProcessorHud->onFrame(frameDuration); + if (mCharGen) mCharGen->onFrame(frameDuration); @@ -1400,6 +1410,7 @@ namespace MWGui MWGui::CountDialog* WindowManager::getCountDialog() { return mCountDialog; } MWGui::ConfirmationDialog* WindowManager::getConfirmationDialog() { return mConfirmationDialog; } MWGui::TradeWindow* WindowManager::getTradeWindow() { return mTradeWindow; } + MWGui::PostProcessorHud* WindowManager::getPostProcessorHud() { return mPostProcessorHud; } void WindowManager::useItem(const MWWorld::Ptr &item, bool bypassBeastRestrictions) { @@ -1488,6 +1499,7 @@ namespace MWGui return !mGuiModes.empty() || isConsoleMode() || + (mPostProcessorHud && mPostProcessorHud->isVisible()) || (mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox()); } @@ -2054,6 +2066,24 @@ namespace MWGui #endif } + void WindowManager::togglePostProcessorHud() + { + if (!MWBase::Environment::get().getWorld()->getPostProcessor()->isEnabled()) + return; + + bool visible = mPostProcessorHud->isVisible(); + + if (!visible && !mGuiModes.empty()) + mKeyboardNavigation->saveFocus(mGuiModes.back()); + + mPostProcessorHud->setVisible(!visible); + + if (visible && !mGuiModes.empty()) + mKeyboardNavigation->restoreFocus(mGuiModes.back()); + + updateVisible(); + } + void WindowManager::cycleSpell(bool next) { if (!isGuiMode()) diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 9a4de2b33f..58400ec76b 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -123,6 +123,7 @@ namespace MWGui class WindowModal; class ScreenFader; class DebugWindow; + class PostProcessorHud; class JailScreen; class KeyboardNavigation; @@ -188,6 +189,7 @@ namespace MWGui MWGui::ConfirmationDialog* getConfirmationDialog() override; MWGui::TradeWindow* getTradeWindow() override; const std::vector getActiveMessageBoxes() override; + MWGui::PostProcessorHud* getPostProcessorHud() override; /// Make the player use an item, while updating GUI state accordingly void useItem(const MWWorld::Ptr& item, bool bypassBeastRestrictions=false) override; @@ -366,6 +368,7 @@ namespace MWGui void toggleConsole() override; void toggleDebugWindow() override; + void togglePostProcessorHud() override; /// Cycle to next or previous spell void cycleSpell(bool next) override; @@ -452,6 +455,7 @@ namespace MWGui ScreenFader* mHitFader; ScreenFader* mScreenFader; DebugWindow* mDebugWindow; + PostProcessorHud* mPostProcessorHud; JailScreen* mJailScreen; ContainerWindow* mContainerWindow; diff --git a/apps/openmw/mwinput/actionmanager.cpp b/apps/openmw/mwinput/actionmanager.cpp index eae6996acd..6b5a100722 100644 --- a/apps/openmw/mwinput/actionmanager.cpp +++ b/apps/openmw/mwinput/actionmanager.cpp @@ -241,6 +241,9 @@ namespace MWInput case A_ToggleDebug: windowManager->toggleDebugWindow(); break; + case A_TogglePostProcessorHUD: + windowManager->togglePostProcessorHud(); + break; case A_QuickSave: quickSave(); break; diff --git a/apps/openmw/mwinput/actions.hpp b/apps/openmw/mwinput/actions.hpp index a1c1607126..c7bdbf28d3 100644 --- a/apps/openmw/mwinput/actions.hpp +++ b/apps/openmw/mwinput/actions.hpp @@ -73,6 +73,8 @@ namespace MWInput A_ZoomIn, A_ZoomOut, + A_TogglePostProcessorHUD, + A_Last // Marker for the last item }; } diff --git a/apps/openmw/mwinput/bindingsmanager.cpp b/apps/openmw/mwinput/bindingsmanager.cpp index 68be849dbd..82a469890c 100644 --- a/apps/openmw/mwinput/bindingsmanager.cpp +++ b/apps/openmw/mwinput/bindingsmanager.cpp @@ -286,6 +286,7 @@ namespace MWInput defaultKeyBindings[A_AlwaysRun] = SDL_SCANCODE_CAPSLOCK; defaultKeyBindings[A_QuickSave] = SDL_SCANCODE_F5; defaultKeyBindings[A_QuickLoad] = SDL_SCANCODE_F9; + defaultKeyBindings[A_TogglePostProcessorHUD] = SDL_SCANCODE_F2; std::map defaultMouseButtonBindings; defaultMouseButtonBindings[A_Inventory] = SDL_BUTTON_RIGHT; @@ -502,6 +503,8 @@ namespace MWInput return "#{sQuickSaveCmd}"; case A_QuickLoad: return "#{sQuickLoadCmd}"; + case A_TogglePostProcessorHUD: + return "Toggle Post Processor HUD"; default: return std::string(); // not configurable } @@ -563,7 +566,8 @@ namespace MWInput A_CycleSpellLeft, A_CycleSpellRight, A_CycleWeaponLeft, A_CycleWeaponRight, A_AutoMove, A_Jump, A_Inventory, A_Journal, A_Rest, A_Console, A_QuickSave, A_QuickLoad, A_ToggleHUD, A_Screenshot, A_QuickKeysMenu, A_QuickKey1, A_QuickKey2, A_QuickKey3, - A_QuickKey4, A_QuickKey5, A_QuickKey6, A_QuickKey7, A_QuickKey8, A_QuickKey9, A_QuickKey10 + A_QuickKey4, A_QuickKey5, A_QuickKey6, A_QuickKey7, A_QuickKey8, A_QuickKey9, A_QuickKey10, + A_TogglePostProcessorHUD }; return actions; diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index 1be6e086fa..fc16f514fd 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -136,6 +136,7 @@ namespace MWLua {"ToggleHUD", MWInput::A_ToggleHUD}, {"ToggleDebug", MWInput::A_ToggleDebug}, + {"TogglePostProcessorHUD", MWInput::A_TogglePostProcessorHUD}, {"ZoomIn", MWInput::A_ZoomIn}, {"ZoomOut", MWInput::A_ZoomOut} diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index 41bc8b29ae..af41199ad9 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -21,6 +21,7 @@ namespace MWLua sol::table initCorePackage(const Context&); sol::table initWorldPackage(const Context&); + sol::table initPostprocessingPackage(const Context&); sol::table initGlobalStoragePackage(const Context&, LuaUtil::LuaStorage* globalStorage); sol::table initLocalStoragePackage(const Context&, LuaUtil::LuaStorage* globalStorage); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 602f32710f..b0e22db110 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -95,6 +95,7 @@ namespace MWLua mPlayerSettingsPackage = initPlayerSettingsPackage(localContext); mLocalStoragePackage = initLocalStoragePackage(localContext, &mGlobalStorage); mPlayerStoragePackage = initPlayerStoragePackage(localContext, &mGlobalStorage, &mPlayerStorage); + mPostprocessingPackage = initPostprocessingPackage(localContext); initConfiguration(); mInitialized = true; @@ -407,6 +408,7 @@ namespace MWLua scripts->addPackage("openmw.input", mInputPackage); scripts->addPackage("openmw.settings", mPlayerSettingsPackage); scripts->addPackage("openmw.storage", mPlayerStoragePackage); + scripts->addPackage("openmw.postprocessing", mPostprocessingPackage); } else { diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index a75cac2da9..0700895497 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -140,6 +140,7 @@ namespace MWLua sol::table mPlayerSettingsPackage; sol::table mLocalStoragePackage; sol::table mPlayerStoragePackage; + sol::table mPostprocessingPackage; GlobalScripts mGlobalScripts{&mLua}; std::set mActiveLocalScripts; diff --git a/apps/openmw/mwlua/postprocessingbindings.cpp b/apps/openmw/mwlua/postprocessingbindings.cpp new file mode 100644 index 0000000000..b016c7716a --- /dev/null +++ b/apps/openmw/mwlua/postprocessingbindings.cpp @@ -0,0 +1,140 @@ +#include "luabindings.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwrender/postprocessor.hpp" + +#include "luamanagerimp.hpp" + +namespace +{ + template + class SetUniformShaderAction final : public MWLua::LuaManager::Action + { + public: + SetUniformShaderAction(LuaUtil::LuaState* state, std::shared_ptr shader, const std::string& name, const T& value) + : MWLua::LuaManager::Action(state), mShader(std::move(shader)), mName(name), mValue(value) {} + + void apply(MWLua::WorldView&) const override + { + MWBase::Environment::get().getWorld()->getPostProcessor()->setUniform(mShader, mName, mValue); + } + + std::string toString() const override + { + return std::string("SetUniformShaderAction shader=") + (mShader ? mShader->getName() : "nil") + + std::string("uniform=") + (mShader ? mName : "nil"); + } + + private: + std::shared_ptr mShader; + std::string mName; + T mValue; + }; +} + +namespace MWLua +{ + struct Shader; +} + +namespace sol +{ + template <> + struct is_automagical : std::false_type {}; +} + +namespace MWLua +{ + struct Shader + { + std::shared_ptr mShader; + + Shader(std::shared_ptr shader) : mShader(std::move(shader)) {} + + std::string toString() const + { + if (!mShader) + return "Shader(nil)"; + + return Misc::StringUtils::format("Shader(%s, %s)", mShader->getName(), mShader->getFileName()); + } + + bool mQueuedAction = false; + }; + + sol::table initPostprocessingPackage(const Context& context) + { + sol::table api(context.mLua->sol(), sol::create); + + sol::usertype shader = context.mLua->sol().new_usertype("Shader"); + shader[sol::meta_function::to_string] = [](const Shader& shader) { return shader.toString(); }; + + shader["enable"] = [context](Shader& shader, sol::optional optPos) + { + std::optional pos = std::nullopt; + if (optPos) + pos = optPos.value(); + + if (shader.mShader && shader.mShader->isValid()) + shader.mQueuedAction = true; + + context.mLuaManager->addAction( + [&] { MWBase::Environment::get().getWorld()->getPostProcessor()->enableTechnique(shader.mShader, pos); }, + "Enable shader " + (shader.mShader ? shader.mShader->getName() : "nil") + ); + }; + + shader["disable"] = [context](Shader& shader) + { + shader.mQueuedAction = false; + + context.mLuaManager->addAction( + [&] { MWBase::Environment::get().getWorld()->getPostProcessor()->disableTechnique(shader.mShader); }, + "Disable shader " + (shader.mShader ? shader.mShader->getName() : "nil") + ); + }; + + shader["isEnabled"] = [](const Shader& shader) + { + return shader.mQueuedAction; + }; + + shader["setBool"] = [context](const Shader& shader, const std::string& name, bool value) + { + context.mLuaManager->addAction(std::make_unique>(context.mLua, shader.mShader, name, value)); + }; + + shader["setFloat"] = [context](const Shader& shader, const std::string& name, float value) + { + context.mLuaManager->addAction(std::make_unique>(context.mLua, shader.mShader, name, value)); + }; + + shader["setInt"] = [context](const Shader& shader, const std::string& name, int value) + { + context.mLuaManager->addAction(std::make_unique>(context.mLua, shader.mShader, name, value)); + }; + + shader["setVector2"] = [context](const Shader& shader, const std::string& name, const osg::Vec2f& value) + { + context.mLuaManager->addAction(std::make_unique>(context.mLua, shader.mShader, name, value)); + }; + + shader["setVector3"] = [context](const Shader& shader, const std::string& name, const osg::Vec3f& value) + { + context.mLuaManager->addAction(std::make_unique>(context.mLua, shader.mShader, name, value)); + }; + + shader["setVector4"] = [context](const Shader& shader, const std::string& name, const osg::Vec4f& value) + { + context.mLuaManager->addAction(std::make_unique>(context.mLua, shader.mShader, name, value)); + }; + + api["load"] = [](const std::string& name) + { + return Shader(MWBase::Environment::get().getWorld()->getPostProcessor()->loadTechnique(name, false)); + }; + + return LuaUtil::makeReadOnly(api); + } + +} diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index cb4c074586..0ec8d8885d 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -1563,7 +1564,8 @@ namespace MWRender // Morrowind has a white ambient light attached to the root VFX node of the scenegraph node->getOrCreateStateSet()->setAttributeAndModes(getVFXLightModelInstance(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); - + if (mResourceSystem->getSceneManager()->getSupportsNormalsRT()) + node->getOrCreateStateSet()->setAttribute(new osg::ColorMaski(1, false, false, false, false)); SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor; node->accept(findMaxLengthVisitor); diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index f2bbe10460..3a6d2df7ff 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -147,7 +147,7 @@ namespace MWRender class CharacterPreviewRTTNode : public SceneUtil::RTTNode { static constexpr float fovYDegrees = 12.3f; - static constexpr float znear = 0.1f; + static constexpr float znear = 4.0f; static constexpr float zfar = 10000.f; public: @@ -162,31 +162,23 @@ namespace MWRender mGroup->getOrCreateStateSet()->addUniform(new osg::Uniform("projectionMatrix", mPerspectiveMatrix)); mViewMatrix = osg::Matrixf::identity(); setColorBufferInternalFormat(GL_RGBA); + setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); } void setDefaults(osg::Camera* camera) override { - - // hints that the camera is not relative to the master camera - camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); - camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); - camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 0.f)); - camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - camera->setViewport(0, 0, width(), height()); - camera->setRenderOrder(osg::Camera::PRE_RENDER); camera->setName("CharacterPreview"); - camera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); - camera->setCullMask(~(Mask_UpdateVisitor)); - SceneUtil::setCameraClearDepth(camera); - - // hints that the camera is not relative to the master camera camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 0.f)); - camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); camera->setProjectionMatrixAsPerspective(fovYDegrees, mAspectRatio, znear, zfar); camera->setViewport(0, 0, width(), height()); camera->setRenderOrder(osg::Camera::PRE_RENDER); + camera->setCullMask(~(Mask_UpdateVisitor)); + camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); + SceneUtil::setCameraClearDepth(camera); + #ifdef OSG_HAS_MULTIVIEW if (shouldDoTextureArray()) { diff --git a/apps/openmw/mwrender/hdr.cpp b/apps/openmw/mwrender/hdr.cpp new file mode 100644 index 0000000000..95015da83a --- /dev/null +++ b/apps/openmw/mwrender/hdr.cpp @@ -0,0 +1,123 @@ +#include "hdr.hpp" + +#include +#include + +#include "pingpongcanvas.hpp" + +namespace MWRender +{ + HDRDriver::HDRDriver(Shader::ShaderManager& shaderManager) + : mCompiled(false) + , mEnabled(false) + , mWidth(1) + , mHeight(1) + { + const float hdrExposureTime = std::clamp(Settings::Manager::getFloat("hdr exposure time", "Post Processing"), 0.f, 1.f); + + constexpr float minLog = -9.0; + constexpr float maxLog = 4.0; + constexpr float logLumRange = (maxLog - minLog); + constexpr float invLogLumRange = 1.0 / logLumRange; + constexpr float epsilon = 0.004; + + Shader::ShaderManager::DefineMap defines = { + {"minLog", std::to_string(minLog)}, + {"maxLog", std::to_string(maxLog)}, + {"logLumRange", std::to_string(logLumRange)}, + {"invLogLumRange", std::to_string(invLogLumRange)}, + {"hdrExposureTime", std::to_string(hdrExposureTime)}, + {"epsilon", std::to_string(epsilon)}, + }; + + auto vertex = shaderManager.getShader("fullscreen_tri_vertex.glsl", {}, osg::Shader::VERTEX); + auto hdrLuminance = shaderManager.getShader("hdr_luminance_fragment.glsl", defines, osg::Shader::FRAGMENT); + auto hdr = shaderManager.getShader("hdr_fragment.glsl", defines, osg::Shader::FRAGMENT); + + mProgram = shaderManager.getProgram(vertex, hdr); + mLuminanceProgram = shaderManager.getProgram(vertex, hdrLuminance); + } + + void HDRDriver::compile() + { + int mipmapLevels = osg::Image::computeNumberOfMipmapLevels(mWidth, mHeight); + + for (auto& buffer : mBuffers) + { + buffer.texture = new osg::Texture2D; + buffer.texture->setInternalFormat(GL_R16F); + buffer.texture->setSourceFormat(GL_RED); + buffer.texture->setSourceType(GL_FLOAT); + buffer.texture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR_MIPMAP_NEAREST); + buffer.texture->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::LINEAR); + buffer.texture->setTextureSize(mWidth, mHeight); + buffer.texture->setNumMipmapLevels(mipmapLevels); + + buffer.finalTexture = new osg::Texture2D; + buffer.finalTexture->setInternalFormat(GL_R16F); + buffer.finalTexture->setSourceFormat(GL_RED); + buffer.finalTexture->setSourceType(GL_FLOAT); + buffer.finalTexture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST); + buffer.finalTexture->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST); + buffer.finalTexture->setTextureSize(1, 1); + + buffer.finalFbo = new osg::FrameBufferObject; + buffer.finalFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(buffer.finalTexture)); + + buffer.fullscreenFbo = new osg::FrameBufferObject; + buffer.fullscreenFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(buffer.texture)); + + buffer.mipmapFbo = new osg::FrameBufferObject; + buffer.mipmapFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(buffer.texture, mipmapLevels - 1)); + + buffer.fullscreenStateset = new osg::StateSet; + buffer.fullscreenStateset->setAttributeAndModes(mLuminanceProgram); + buffer.fullscreenStateset->addUniform(new osg::Uniform("sceneTex", 0)); + + buffer.mipmapStateset = new osg::StateSet; + buffer.mipmapStateset->setAttributeAndModes(mProgram); + buffer.mipmapStateset->setTextureAttributeAndModes(0, buffer.texture); + buffer.mipmapStateset->addUniform(new osg::Uniform("luminanceSceneTex", 0)); + buffer.mipmapStateset->addUniform(new osg::Uniform("prevLuminanceSceneTex", 1)); + } + + mBuffers[0].mipmapStateset->setTextureAttributeAndModes(1, mBuffers[1].finalTexture); + mBuffers[1].mipmapStateset->setTextureAttributeAndModes(1, mBuffers[0].finalTexture); + + mCompiled = true; + } + + void HDRDriver::draw(const PingPongCanvas& canvas, osg::RenderInfo& renderInfo, osg::State& state, osg::GLExtensions* ext, size_t frameId) + { + if (!mEnabled) + return; + + if (!mCompiled) + compile(); + + auto& hdrBuffer = mBuffers[frameId]; + hdrBuffer.fullscreenFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + hdrBuffer.fullscreenStateset->setTextureAttributeAndModes(0, canvas.getSceneTexture(frameId)); + + state.apply(hdrBuffer.fullscreenStateset); + canvas.drawGeometry(renderInfo); + + state.applyTextureAttribute(0, hdrBuffer.texture); + ext->glGenerateMipmap(GL_TEXTURE_2D); + + hdrBuffer.mipmapFbo->apply(state, osg::FrameBufferObject::READ_FRAMEBUFFER); + hdrBuffer.finalFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + + ext->glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_LINEAR); + + state.apply(hdrBuffer.mipmapStateset); + canvas.drawGeometry(renderInfo); + + ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, state.getGraphicsContext() ? state.getGraphicsContext()->getDefaultFboId() : 0); + } + + osg::ref_ptr HDRDriver::getLuminanceTexture(size_t frameId) const + { + return mBuffers[frameId].finalTexture; + } +} \ No newline at end of file diff --git a/apps/openmw/mwrender/hdr.hpp b/apps/openmw/mwrender/hdr.hpp new file mode 100644 index 0000000000..95bdc6aa0a --- /dev/null +++ b/apps/openmw/mwrender/hdr.hpp @@ -0,0 +1,71 @@ +#ifndef OPENMW_MWRENDER_HDR_H +#define OPENMW_MWRENDER_HDR_H + +#include + +#include +#include +#include + +namespace Shader +{ + class ShaderManager; +} + +namespace MWRender +{ + class PingPongCanvas; + + class HDRDriver + { + + public: + + HDRDriver() = default; + + HDRDriver(Shader::ShaderManager& shaderManager); + + void draw(const PingPongCanvas& canvas, osg::RenderInfo& renderInfo, osg::State& state, osg::GLExtensions* ext, size_t frameId); + + bool isEnabled() const { return mEnabled; } + + void enable() { mEnabled = true; } + void disable() { mEnabled = false; } + + void dirty(int w, int h) + { + mWidth = w; + mHeight = h; + mCompiled = false; + } + + osg::ref_ptr getLuminanceTexture(size_t frameId) const; + + private: + + void compile(); + + struct HDRContainer + { + osg::ref_ptr fullscreenFbo; + osg::ref_ptr mipmapFbo; + osg::ref_ptr finalFbo; + osg::ref_ptr texture; + osg::ref_ptr finalTexture; + osg::ref_ptr fullscreenStateset; + osg::ref_ptr mipmapStateset; + }; + + std::array mBuffers; + osg::ref_ptr mLuminanceProgram; + osg::ref_ptr mProgram; + + bool mCompiled; + bool mEnabled; + + int mWidth; + int mHeight; + }; +} + +#endif diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 2fd3c8b7f7..fda98a1589 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -670,6 +670,7 @@ LocalMapRenderToTexture::LocalMapRenderToTexture(osg::Node* sceneRoot, int res, mViewMatrix.makeLookAt(osg::Vec3d(x, y, zmax + 5), osg::Vec3d(x, y, zmin), upVector); setUpdateCallback(new CameraLocalUpdateCallback); + setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); } void LocalMapRenderToTexture::setDefaults(osg::Camera* camera) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 0d74b8d302..7ce5594f96 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -327,24 +327,31 @@ public: state->applyAttribute(mDepth); - if (postProcessor && postProcessor->getFirstPersonRBProxy()) - { - osg::GLExtensions* ext = state->get(); + unsigned int frameId = state->getFrameStamp()->getFrameNumber() % 2; - osg::FrameBufferAttachment(postProcessor->getFirstPersonRBProxy()).attach(*state, GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, ext); + if (postProcessor && postProcessor->getFbo(PostProcessor::FBO_FirstPerson, frameId)) + { + postProcessor->getFbo(PostProcessor::FBO_FirstPerson, frameId)->apply(*state); glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // color accumulation pass bin->drawImplementation(renderInfo, previous); - auto primaryFBO = postProcessor->getMsaaFbo() ? postProcessor->getMsaaFbo() : postProcessor->getFbo(); - primaryFBO->getAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER).attach(*state, GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, ext); + auto primaryFBO = postProcessor->getPrimaryFbo(frameId); + + if (postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)) + postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)->apply(*state); + else + primaryFBO->apply(*state); - state->pushStateSet(mStateSet); - state->apply(); // depth accumulation pass + osg::ref_ptr restore = bin->getStateSet(); + bin->setStateSet(mStateSet); bin->drawImplementation(renderInfo, previous); - state->popStateSet(); + bin->setStateSet(restore); + + if (postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)) + primaryFBO->apply(*state); } else { diff --git a/apps/openmw/mwrender/pingpongcanvas.cpp b/apps/openmw/mwrender/pingpongcanvas.cpp new file mode 100644 index 0000000000..9592a8bbba --- /dev/null +++ b/apps/openmw/mwrender/pingpongcanvas.cpp @@ -0,0 +1,260 @@ +#include "pingpongcanvas.hpp" + +#include +#include + +#include "postprocessor.hpp" + +namespace MWRender +{ + PingPongCanvas::PingPongCanvas(Shader::ShaderManager& shaderManager) + : mFallbackStateSet(new osg::StateSet) + , mQueuedDispatchArray(std::nullopt) + , mQueuedDispatchFrameId(0) + { + setUseDisplayList(false); + setUseVertexBufferObjects(true); + + osg::ref_ptr verts = new osg::Vec3Array; + verts->push_back(osg::Vec3f(-1, -1, 0)); + verts->push_back(osg::Vec3f(-1, 3, 0)); + verts->push_back(osg::Vec3f(3, -1, 0)); + + setVertexArray(verts); + + addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, 3)); + + mHDRDriver = HDRDriver(shaderManager); + mHDRDriver.disable(); + + auto fallbackVertex = shaderManager.getShader("fullscreen_tri_vertex.glsl", {}, osg::Shader::VERTEX); + auto fallbackFragment = shaderManager.getShader("fullscreen_tri_fragment.glsl", {}, osg::Shader::FRAGMENT); + mFallbackProgram = shaderManager.getProgram(fallbackVertex, fallbackFragment); + + mFallbackStateSet->setAttributeAndModes(mFallbackProgram); + mFallbackStateSet->addUniform(new osg::Uniform("omw_SamplerLastShader", 0)); + } + + void PingPongCanvas::setCurrentFrameData(size_t frameId, fx::DispatchArray&& data) + { + mQueuedDispatchArray = fx::DispatchArray(data); + mQueuedDispatchFrameId = !frameId; + + mBufferData[frameId].data = std::move(data); + } + + void PingPongCanvas::setMask(size_t frameId, bool underwater, bool exterior) + { + mBufferData[frameId].mask = 0; + + mBufferData[frameId].mask |= underwater ? fx::Technique::Flag_Disable_Underwater : fx::Technique::Flag_Disable_Abovewater; + mBufferData[frameId].mask |= exterior ? fx::Technique::Flag_Disable_Exteriors : fx::Technique::Flag_Disable_Interiors; + } + + void PingPongCanvas::drawGeometry(osg::RenderInfo& renderInfo) const + { + osg::Geometry::drawImplementation(renderInfo); + } + + void PingPongCanvas::drawImplementation(osg::RenderInfo& renderInfo) const + { + osg::State& state = *renderInfo.getState(); + osg::GLExtensions* ext = state.get(); + + size_t frameId = state.getFrameStamp()->getFrameNumber() % 2; + + auto& bufferData = mBufferData[frameId]; + + if (mQueuedDispatchArray && mQueuedDispatchFrameId == frameId) + { + mBufferData[frameId].data = std::move(mQueuedDispatchArray.value()); + mQueuedDispatchArray = std::nullopt; + } + + const auto& data = bufferData.data; + + std::vector filtered; + + filtered.reserve(data.size()); + + const fx::DispatchNode::SubPass* resolvePass = nullptr; + + for (size_t i = 0; i < data.size(); ++i) + { + const auto& node = data[i]; + + if (bufferData.mask & node.mFlags) + continue; + + for (auto it = node.mPasses.crbegin(); it != node.mPasses.crend(); ++it) + { + if (!(*it).mRenderTarget) + { + resolvePass = &(*it); + break; + } + } + + filtered.push_back(i); + } + + auto* viewport = state.getCurrentViewport(); + + if (filtered.empty() || !bufferData.postprocessing) + { + if (bufferData.postprocessing) + Log(Debug::Error) << "Critical error, postprocess shaders failed to compile. Using default shader."; + + mFallbackStateSet->setTextureAttributeAndModes(0, bufferData.sceneTex); + + state.pushStateSet(mFallbackStateSet); + state.apply(); + viewport->apply(state); + + drawGeometry(renderInfo); + state.popStateSet(); + return; + } + + const unsigned int handle = mFbos[0] ? mFbos[0]->getHandle(state.getContextID()) : 0; + + if (handle == 0 || bufferData.dirty) + { + for (auto& fbo : mFbos) + { + fbo = new osg::FrameBufferObject; + fbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(new osg::Texture2D(*bufferData.sceneTexLDR))); + fbo->apply(state); + glClearColor(0.5, 0.5, 0.5, 1); + glClear(GL_COLOR_BUFFER_BIT); + } + + mHDRDriver.dirty(bufferData.sceneTex->getTextureWidth(), bufferData.sceneTex->getTextureHeight()); + + bufferData.dirty = false; + } + + constexpr std::array, 3> buffers = {{ + {GL_COLOR_ATTACHMENT1_EXT, GL_COLOR_ATTACHMENT2_EXT}, + {GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT2_EXT}, + {GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT} + }}; + + (bufferData.hdr) ? mHDRDriver.enable() : mHDRDriver.disable(); + + // A histogram based approach is superior way to calculate scene luminance. Using mipmaps is more broadly supported, so that's what we use for now. + mHDRDriver.draw(*this, renderInfo, state, ext, frameId); + + auto buffer = buffers[0]; + + int lastDraw = 0; + int lastShader = 0; + + unsigned int lastApplied = handle; + + const unsigned int cid = state.getContextID(); + + const osg::ref_ptr& destinationFbo = bufferData.destination ? bufferData.destination : nullptr; + unsigned int destinationHandle = destinationFbo ? destinationFbo->getHandle(cid) : 0; + + auto bindDestinationFbo = [&]() { + if (destinationFbo) + { + destinationFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + lastApplied = destinationHandle; + } + else + { + ext->glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, 0); + + lastApplied = 0; + } + }; + + for (const size_t& index : filtered) + { + const auto& node = data[index]; + + node.mRootStateSet->setTextureAttribute(PostProcessor::Unit_Depth, bufferData.depthTex); + + if (bufferData.hdr) + node.mRootStateSet->setTextureAttribute(PostProcessor::TextureUnits::Unit_EyeAdaptation, mHDRDriver.getLuminanceTexture(frameId)); + + if (bufferData.normalsTex) + node.mRootStateSet->setTextureAttribute(PostProcessor::TextureUnits::Unit_Normals, bufferData.normalsTex); + + state.pushStateSet(node.mRootStateSet); + state.apply(); + + for (size_t passIndex = 0; passIndex < node.mPasses.size(); ++passIndex) + { + const auto& pass = node.mPasses[passIndex]; + + bool lastPass = passIndex == node.mPasses.size() - 1; + + if (lastShader == 0) + pass.mStateSet->setTextureAttribute(PostProcessor::Unit_LastShader, bufferData.sceneTex); + else + pass.mStateSet->setTextureAttribute(PostProcessor::Unit_LastShader, (osg::Texture2D*)mFbos[lastShader - GL_COLOR_ATTACHMENT0_EXT]->getAttachment(osg::Camera::COLOR_BUFFER0).getTexture()); + + if (lastDraw == 0) + pass.mStateSet->setTextureAttribute(PostProcessor::Unit_LastPass, bufferData.sceneTex); + else + pass.mStateSet->setTextureAttribute(PostProcessor::Unit_LastPass, (osg::Texture2D*)mFbos[lastDraw - GL_COLOR_ATTACHMENT0_EXT]->getAttachment(osg::Camera::COLOR_BUFFER0).getTexture()); + + if (pass.mRenderTarget) + { + pass.mRenderTarget->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + + if (pass.mRenderTexture->getNumMipmapLevels() > 0) + { + state.setActiveTextureUnit(0); + state.applyTextureAttribute(0, pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0).getTexture()); + ext->glGenerateMipmap(GL_TEXTURE_2D); + } + + lastApplied = pass.mRenderTarget->getHandle(state.getContextID());; + } + else if (&pass == resolvePass) + { + bindDestinationFbo(); + } + else if (lastPass) + { + lastDraw = buffer[0]; + lastShader = buffer[0]; + mFbos[buffer[0] - GL_COLOR_ATTACHMENT0_EXT]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + buffer = buffers[lastShader - GL_COLOR_ATTACHMENT0_EXT]; + + lastApplied = mFbos[buffer[0] - GL_COLOR_ATTACHMENT0_EXT]->getHandle(cid); + } + else + { + mFbos[buffer[0] - GL_COLOR_ATTACHMENT0_EXT]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + lastDraw = buffer[0]; + std::swap(buffer[0], buffer[1]); + + lastApplied = mFbos[buffer[0] - GL_COLOR_ATTACHMENT0_EXT]->getHandle(cid); + } + + state.pushStateSet(pass.mStateSet); + state.apply(); + + if (!state.getLastAppliedProgramObject()) + mFallbackProgram->apply(state); + + drawGeometry(renderInfo); + + state.popStateSet(); + state.apply(); + } + + state.popStateSet(); + } + + if (lastApplied != destinationHandle) + { + bindDestinationFbo(); + } + } +} diff --git a/apps/openmw/mwrender/pingpongcanvas.hpp b/apps/openmw/mwrender/pingpongcanvas.hpp new file mode 100644 index 0000000000..bb39f9bcf3 --- /dev/null +++ b/apps/openmw/mwrender/pingpongcanvas.hpp @@ -0,0 +1,88 @@ +#ifndef OPENMW_MWRENDER_PINGPONGCANVAS_H +#define OPENMW_MWRENDER_PINGPONGCANVAS_H + +#include +#include + +#include +#include +#include + +#include + +#include "postprocessor.hpp" +#include "hdr.hpp" + +namespace Shader +{ + class ShaderManager; +} + +namespace MWRender +{ + class PingPongCanvas : public osg::Geometry + { + public: + PingPongCanvas(Shader::ShaderManager& shaderManager); + + void drawImplementation(osg::RenderInfo& renderInfo) const override; + + void dirty(size_t frameId) { mBufferData[frameId].dirty = true; } + + const fx::DispatchArray& getCurrentFrameData(size_t frame) { return mBufferData[frame % 2].data; } + + // Sets current frame pass data and stores copy of dispatch array to apply to next frame data + void setCurrentFrameData(size_t frameId, fx::DispatchArray&& data); + + void setMask(size_t frameId, bool underwater, bool exterior); + + void setSceneTexture(size_t frameId, osg::ref_ptr tex) { mBufferData[frameId].sceneTex = tex; } + + void setLDRSceneTexture(size_t frameId, osg::ref_ptr tex) { mBufferData[frameId].sceneTexLDR = tex; } + + void setDepthTexture(size_t frameId, osg::ref_ptr tex) { mBufferData[frameId].depthTex = tex; } + + void setNormalsTexture(size_t frameId, osg::ref_ptr tex) { mBufferData[frameId].normalsTex = tex; } + + void setHDR(size_t frameId, bool hdr) { mBufferData[frameId].hdr = hdr; } + + void setPostProcessing(size_t frameId, bool postprocessing) { mBufferData[frameId].postprocessing = postprocessing; } + + const osg::ref_ptr& getSceneTexture(size_t frameId) const { return mBufferData[frameId].sceneTex; } + + void drawGeometry(osg::RenderInfo& renderInfo) const; + + private: + void copyNewFrameData(size_t frameId) const; + + mutable HDRDriver mHDRDriver; + + osg::ref_ptr mFallbackProgram; + osg::ref_ptr mFallbackStateSet; + + struct BufferData + { + bool dirty = false; + bool hdr = false; + bool postprocessing = true; + + fx::DispatchArray data; + fx::FlagsType mask; + + osg::ref_ptr destination; + + osg::ref_ptr sceneTex; + osg::ref_ptr depthTex; + osg::ref_ptr sceneTexLDR; + osg::ref_ptr normalsTex; + }; + + mutable std::array mBufferData; + mutable std::array, 3> mFbos; + + mutable std::optional mQueuedDispatchArray; + mutable size_t mQueuedDispatchFrameId; + }; +} + +#endif diff --git a/apps/openmw/mwrender/pingpongcull.cpp b/apps/openmw/mwrender/pingpongcull.cpp new file mode 100644 index 0000000000..2bec155bbb --- /dev/null +++ b/apps/openmw/mwrender/pingpongcull.cpp @@ -0,0 +1,47 @@ +#include "pingpongcull.hpp" + +#include +#include +#include + +#include "postprocessor.hpp" +#include "pingpongcanvas.hpp" + +namespace MWRender +{ + void PingPongCull::operator()(osg::Node* node, osgUtil::CullVisitor* cv) + { + osgUtil::RenderStage* renderStage = cv->getCurrentRenderStage(); + size_t frame = cv->getTraversalNumber(); + size_t frameId = frame % 2; + + MWRender::PostProcessor* postProcessor = dynamic_cast(cv->getCurrentCamera()->getUserData()); + + postProcessor->getStateUpdater()->setViewMatrix(cv->getCurrentCamera()->getViewMatrix()); + postProcessor->getStateUpdater()->setInvViewMatrix(cv->getCurrentCamera()->getInverseViewMatrix()); + postProcessor->getStateUpdater()->setPrevViewMatrix(mLastViewMatrix); + mLastViewMatrix = cv->getCurrentCamera()->getViewMatrix(); + postProcessor->getStateUpdater()->setEyePos(cv->getEyePoint()); + postProcessor->getStateUpdater()->setEyeVec(cv->getLookVectorLocal()); + + if (!postProcessor || !postProcessor->getFbo(PostProcessor::FBO_Primary, frameId)) + { + renderStage->setMultisampleResolveFramebufferObject(nullptr); + renderStage->setFrameBufferObject(nullptr); + traverse(node, cv); + return; + } + + if (!postProcessor->getFbo(PostProcessor::FBO_Multisample, frameId)) + { + renderStage->setFrameBufferObject(postProcessor->getFbo(PostProcessor::FBO_Primary, frameId)); + } + else + { + renderStage->setMultisampleResolveFramebufferObject(postProcessor->getFbo(PostProcessor::FBO_Primary, frameId)); + renderStage->setFrameBufferObject(postProcessor->getFbo(PostProcessor::FBO_Multisample, frameId)); + } + + traverse(node, cv); + } +} diff --git a/apps/openmw/mwrender/pingpongcull.hpp b/apps/openmw/mwrender/pingpongcull.hpp new file mode 100644 index 0000000000..d4514e20d3 --- /dev/null +++ b/apps/openmw/mwrender/pingpongcull.hpp @@ -0,0 +1,22 @@ +#ifndef OPENMW_MWRENDER_PINGPONGCULL_H +#define OPENMW_MWRENDER_PINGPONGCULL_H + +#include + +#include + +#include "postprocessor.hpp" + +namespace MWRender +{ + class PostProcessor; + class PingPongCull : public SceneUtil::NodeCallback + { + public: + void operator()(osg::Node* node, osgUtil::CullVisitor* nv); + private: + osg::Matrixf mLastViewMatrix; + }; +} + +#endif diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 9fbc5de528..6fcbb4d51f 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -2,354 +2,696 @@ #include -#include -#include -#include +#include #include +#include #include -#include - -#include #include #include #include #include -#include - +#include +#include +#include +#include +#include #include #include +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" + +#include "../mwgui/postprocessorhud.hpp" + +#include "transparentpass.hpp" +#include "pingpongcull.hpp" +#include "renderingmanager.hpp" #include "vismask.hpp" +#include "sky.hpp" namespace { - osg::ref_ptr createFullScreenTri() - { - osg::ref_ptr geom = new osg::Geometry; - - osg::ref_ptr verts = new osg::Vec3Array; - verts->push_back(osg::Vec3f(-1, -1, 0)); - verts->push_back(osg::Vec3f(-1, 3, 0)); - verts->push_back(osg::Vec3f(3, -1, 0)); - - geom->setVertexArray(verts); - - geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, 3)); - - return geom; - } - - class CullCallback : public SceneUtil::NodeCallback - { - public: - CullCallback(MWRender::PostProcessor* pp) - : mPostProcessor(pp) - { - } - - void operator()(osg::Node* node, osgUtil::CullVisitor* cv) - { - osgUtil::RenderStage* renderStage = cv->getCurrentRenderStage(); - - if (!mPostProcessor->getMsaaFbo()) - { - renderStage->setFrameBufferObject(mPostProcessor->getFbo()); - } - else - { - renderStage->setMultisampleResolveFramebufferObject(mPostProcessor->getFbo()); - renderStage->setFrameBufferObject(mPostProcessor->getMsaaFbo()); - } - - traverse(node, cv); - } - - private: - MWRender::PostProcessor* mPostProcessor; - }; - struct ResizedCallback : osg::GraphicsContext::ResizedCallback { ResizedCallback(MWRender::PostProcessor* postProcessor) : mPostProcessor(postProcessor) - { - } + { } void resizedImplementation(osg::GraphicsContext* gc, int x, int y, int width, int height) override { gc->resizedImplementation(x, y, width, height); - mPostProcessor->resize(width, height); + + mPostProcessor->setRenderTargetSize(width, height); + mPostProcessor->resize(); } MWRender::PostProcessor* mPostProcessor; }; - // Copies the currently bound depth attachment to a new texture so drawables in transparent renderbin can safely sample from depth. - class OpaqueDepthCopyCallback : public osgUtil::RenderBin::DrawCallback + class HUDCullCallback : public SceneUtil::NodeCallback { public: - OpaqueDepthCopyCallback(osg::ref_ptr opaqueDepthTex, osg::ref_ptr sourceFbo) - : mOpaqueDepthFbo(new osg::FrameBufferObject) - , mSourceFbo(sourceFbo) - , mOpaqueDepthTex(opaqueDepthTex) - , mColorAttached(false) + void operator()(osg::Camera* camera, osgUtil::CullVisitor* cv) { - mOpaqueDepthFbo->setAttachment(osg::FrameBufferObject::BufferComponent::DEPTH_BUFFER, osg::FrameBufferAttachment(opaqueDepthTex)); + osg::ref_ptr stateset = new osg::StateSet; + auto& sm = Stereo::Manager::instance(); + auto* fullViewport = camera->getViewport(); + if (sm.getEye(cv) == Stereo::Eye::Left) + stateset->setAttributeAndModes(new osg::Viewport(0, 0, fullViewport->width() / 2, fullViewport->height())); + if (sm.getEye(cv) == Stereo::Eye::Right) + stateset->setAttributeAndModes(new osg::Viewport(fullViewport->width() / 2, 0, fullViewport->width() / 2, fullViewport->height())); -#ifdef __APPLE__ - // Mac OS drivers complain that a FBO is incomplete if it has no color attachment - mOpaqueDepthFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER, osg::FrameBufferAttachment(new osg::RenderBuffer(mOpaqueDepthTex->getTextureWidth(), mOpaqueDepthTex->getTextureHeight(), GL_RGB))); - mColorAttached = true; -#endif + cv->pushStateSet(stateset); + traverse(camera, cv); + cv->popViewport(); } - - void drawImplementation(osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) override - { - if (bin->getStage()->getFrameBufferObject() == mSourceFbo) - { - osg::State& state = *renderInfo.getState(); - osg::GLExtensions* ext = state.get(); - - mSourceFbo->apply(state, osg::FrameBufferObject::READ_FRAMEBUFFER); - postBindOperation(state); - - mOpaqueDepthFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); - postBindOperation(state); - - ext->glBlitFramebuffer(0, 0, mOpaqueDepthTex->getTextureWidth(), mOpaqueDepthTex->getTextureHeight(), 0, 0, mOpaqueDepthTex->getTextureWidth(), mOpaqueDepthTex->getTextureHeight(), GL_DEPTH_BUFFER_BIT, GL_NEAREST); - - mSourceFbo->apply(state); - } - - bin->drawImplementation(renderInfo, previous); - } - private: - void postBindOperation(osg::State& state) - { - if (mColorAttached) - return; - #if !defined(OSG_GLES1_AVAILABLE) && !defined(OSG_GLES2_AVAILABLE) && !defined(OSG_GLES3_AVAILABLE) - state.glDrawBuffer(GL_NONE); - state.glReadBuffer(GL_NONE); - #endif - } - - osg::ref_ptr mOpaqueDepthFbo; - osg::ref_ptr mSourceFbo; - osg::ref_ptr mOpaqueDepthTex; - bool mColorAttached; }; } namespace MWRender { - PostProcessor::PostProcessor(osgViewer::Viewer* viewer, osg::Group* rootNode) - : mViewer(viewer) - , mRootNode(new osg::Group) + PostProcessor::PostProcessor(RenderingManager& rendering, osgViewer::Viewer* viewer, osg::Group* rootNode, const VFS::Manager* vfs) + : osg::Group() + , mRootNode(rootNode) + , mSamples(Settings::Manager::getInt("antialiasing", "Video")) + , mDirty(false) + , mDirtyFrameId(0) + , mRendering(rendering) + , mViewer(viewer) + , mVFS(vfs) + , mReload(false) + , mEnabled(false) + , mUsePostProcessing(false) + , mSoftParticles(false) + , mDisableDepthPasses(false) + , mLastFrameNumber(0) + , mLastSimulationTime(0.f) + , mExteriorFlag(false) + , mUnderwater(false) + , mHDR(false) + , mNormals(false) + , mPrevNormals(false) + , mNormalsSupported(false) + , mMainTemplate(new osg::Texture2D) { - bool softParticles = Settings::Manager::getBool("soft particles", "Shaders"); - - if (!SceneUtil::AutoDepth::isReversed() && !softParticles && !Stereo::getStereo()) - return; + mSoftParticles = Settings::Manager::getBool("soft particles", "Shaders") && !Stereo::getStereo() && !Stereo::getMultiview(); + mUsePostProcessing = Settings::Manager::getBool("enabled", "Post Processing"); osg::GraphicsContext* gc = viewer->getCamera()->getGraphicsContext(); osg::GLExtensions* ext = gc->getState()->get(); - constexpr char errPreamble[] = "Postprocessing and floating point depth buffers disabled: "; + mWidth = gc->getTraits()->width; + mHeight = gc->getTraits()->height; - if (!ext->isFrameBufferObjectSupported) - { - Log(Debug::Warning) << errPreamble << "FrameBufferObject unsupported."; + if (!ext->glDisablei && ext->glDisableIndexedEXT) + ext->glDisablei = ext->glDisableIndexedEXT; + + if (ext->glDisablei) + mNormalsSupported = true; + else + Log(Debug::Error) << "'glDisablei' unsupported, pass normals will not be available to shaders."; + + if (mSoftParticles) + for (int i = 0; i < 2; ++i) + mTextures[i][Tex_OpaqueDepth] = new osg::Texture2D; + + mGLSLVersion = ext->glslLanguageVersion * 100; + mUBO = ext && ext->isUniformBufferObjectSupported && mGLSLVersion >= 330; + mStateUpdater = new fx::StateUpdater(mUBO); + + if (!SceneUtil::AutoDepth::isReversed() && !mSoftParticles && !mUsePostProcessing && !Stereo::getStereo() && !Stereo::getMultiview()) return; - } - if (Settings::Manager::getInt("antialiasing", "Video") > 1 && !ext->isRenderbufferMultisampleSupported()) - { - Log(Debug::Warning) << errPreamble << "RenderBufferMultiSample unsupported. Disabling antialiasing will resolve this issue."; - return; - } + enable(mUsePostProcessing); + } - if (SceneUtil::AutoDepth::isReversed()) + PostProcessor::~PostProcessor() + { + if (auto* bin = osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")) + bin->setDrawCallback(nullptr); + } + + void PostProcessor::resize() + { + for (auto& technique : mTechniques) { - if(SceneUtil::AutoDepth::depthSourceType() != GL_FLOAT_32_UNSIGNED_INT_24_8_REV) + for (auto& [name, rt] : technique->getRenderTargetsMap()) { - // TODO: Once we have post-processing implemented we want to skip this return and continue with setup. - // Rendering to a FBO to fullscreen geometry has overhead (especially when MSAA is enabled) and there are no - // benefits if no floating point depth formats are supported. - if (!softParticles && !Stereo::getStereo()) - return; + const auto [w, h] = rt.mSize.get(mWidth, mHeight); + rt.mTarget->setTextureSize(w, h); + rt.mTarget->dirtyTextureObject(); } } - auto* traits = gc->getTraits(); - int width = traits->width; - int height = traits->height; + size_t frameId = frame() % 2; - createTexturesAndCamera(width, height); - resize(width, height); + createTexturesAndCamera(frameId); + createObjectsForFrame(frameId); - mRootNode->addChild(mHUDCamera); - mRootNode->addChild(rootNode); - mViewer->setSceneData(mRootNode); + mHUDCamera->resize(mWidth, mHeight); + mViewer->getCamera()->resize(mWidth, mHeight); + mRendering.updateProjectionMatrix(); + mRendering.setScreenRes(mWidth, mHeight); - if (!Stereo::getStereo()) - { - // We need to manually set the FBO and resolve FBO during the cull callback. If we were using a separate - // RTT camera this would not be needed. - mViewer->getCamera()->addCullCallback(new CullCallback(this)); - mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); - mViewer->getCamera()->attach(osg::Camera::COLOR_BUFFER0, mSceneTex); - mViewer->getCamera()->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, mDepthTex); - } + dirtyTechniques(); - mViewer->getCamera()->getGraphicsContext()->setResizedCallback(new ResizedCallback(this)); - } + mPingPongCanvas->dirty(frameId); - void PostProcessor::resize(int width, int height) - { - mDepthTex->setTextureSize(width, height); - mSceneTex->setTextureSize(width, height); - mDepthTex->dirtyTextureObject(); - mSceneTex->dirtyTextureObject(); - - if (mOpaqueDepthTex) - { - mOpaqueDepthTex->setTextureSize(width, height); - mOpaqueDepthTex->dirtyTextureObject(); - } - - int samples = Settings::Manager::getInt("antialiasing", "Video"); - - mFbo = new osg::FrameBufferObject; - mFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(mSceneTex)); - mFbo->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mDepthTex)); - - // When MSAA is enabled we must first render to a render buffer, then - // blit the result to the FBO which is either passed to the main frame - // buffer for display or used as the entry point for a post process chain. - if (samples > 1) - { - mMsaaFbo = new osg::FrameBufferObject; - osg::ref_ptr colorRB = new osg::RenderBuffer(width, height, mSceneTex->getInternalFormat(), samples); - osg::ref_ptr depthRB = new osg::RenderBuffer(width, height, mDepthTex->getInternalFormat(), samples); - mMsaaFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(colorRB)); - mMsaaFbo->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(depthRB)); - } - - if (const auto depthProxy = std::getenv("OPENMW_ENABLE_DEPTH_CLEAR_PROXY")) - mFirstPersonDepthRBProxy = new osg::RenderBuffer(width, height, mDepthTex->getInternalFormat(), samples); - - if (Settings::Manager::getBool("soft particles", "Shaders")) - osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(new OpaqueDepthCopyCallback(mOpaqueDepthTex, mMsaaFbo ? mMsaaFbo : mFbo)); - - mViewer->getCamera()->resize(width, height); - mHUDCamera->resize(width, height); + mDirty = true; + mDirtyFrameId = !frameId; if (Stereo::getStereo()) Stereo::Manager::instance().screenResolutionChanged(); } - class HUDCameraStatesetUpdater final : public SceneUtil::StateSetUpdater + void PostProcessor::enable(bool usePostProcessing) { - public: - public: - HUDCameraStatesetUpdater(osg::ref_ptr HUDCamera, osg::ref_ptr program, osg::ref_ptr sceneTex) - : mHUDCamera(HUDCamera) - , mProgram(program) - , mSceneTex(sceneTex) + mReload = true; + mEnabled = true; + mUsePostProcessing = usePostProcessing && !Stereo::getStereo() && !Stereo::getMultiview(); + + if (!mDisableDepthPasses && !Stereo::getStereo() && !Stereo::getMultiview()) { + mTransparentDepthPostPass = new TransparentDepthBinCallback(mRendering.getResourceSystem()->getSceneManager()->getShaderManager(), Settings::Manager::getBool("transparent postpass", "Post Processing")); + osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(mTransparentDepthPostPass); } - void setDefaults(osg::StateSet* stateset) override + if (mUsePostProcessing && mTechniqueFileMap.empty()) { - stateset->setTextureAttributeAndModes(0, mSceneTex, osg::StateAttribute::ON); - stateset->setAttributeAndModes(mProgram, osg::StateAttribute::ON); - stateset->addUniform(new osg::Uniform("sceneTex", 0)); - stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); - stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); - - if (osg::DisplaySettings::instance()->getStereo()) + for (const auto& name : mVFS->getRecursiveDirectoryIterator(fx::Technique::sSubdir)) { - stateset->setAttribute(new osg::Viewport); - stateset->addUniform(new osg::Uniform("viewportIndex", 0)); + std::filesystem::path path = name; + std::string fileExt = Misc::StringUtils::lowerCase(path.extension().string()); + if (!path.parent_path().has_parent_path() && fileExt == fx::Technique::sExt) + { + auto absolutePath = std::filesystem::path(mVFS->getAbsoluteFileName(name)); + + mTechniqueFileMap[absolutePath.stem().string()] = absolutePath; + } } } - void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override + mMainTemplate->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + mMainTemplate->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + mMainTemplate->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mMainTemplate->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + mMainTemplate->setInternalFormat(GL_RGBA); + mMainTemplate->setSourceType(GL_UNSIGNED_BYTE); + mMainTemplate->setSourceFormat(GL_RGBA); + + createTexturesAndCamera(frame() % 2); + + removeChild(mHUDCamera); + removeChild(mRootNode); + + addChild(mHUDCamera); + addChild(mRootNode); + + mViewer->setSceneData(this); + mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + mViewer->getCamera()->setImplicitBufferAttachmentMask(0, 0); + mViewer->getCamera()->getGraphicsContext()->setResizedCallback(new ResizedCallback(this)); + mViewer->getCamera()->setUserData(this); + + setCullCallback(mStateUpdater); + mHUDCamera->setCullCallback(new HUDCullCallback); + + static bool init = false; + + if (init) { - if (Stereo::getMultiview()) + resize(); + init = true; + } + + init = true; + } + + void PostProcessor::disable() + { + if (!mSoftParticles) + osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(nullptr); + + if (!SceneUtil::AutoDepth::isReversed() && !mSoftParticles && !Stereo::getStereo() && !Stereo::getMultiview()) + { + removeChild(mHUDCamera); + setCullCallback(nullptr); + + mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER); + mViewer->getCamera()->getGraphicsContext()->setResizedCallback(nullptr); + mViewer->getCamera()->setUserData(nullptr); + + mEnabled = false; + } + + mUsePostProcessing = false; + mRendering.getSkyManager()->setSunglare(true); + } + + void PostProcessor::traverse(osg::NodeVisitor& nv) + { + if (!mEnabled) + { + osg::Group::traverse(nv); + return; + } + + size_t frameId = nv.getTraversalNumber() % 2; + + if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) + cull(frameId, static_cast(&nv)); + else if (nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR) + update(frameId); + + osg::Group::traverse(nv); + } + + void PostProcessor::cull(size_t frameId, osgUtil::CullVisitor* cv) + { + const auto& fbo = getFbo(FBO_Intercept, frameId); + if (fbo) + { + osgUtil::RenderStage* rs = cv->getRenderStage(); + if (rs && rs->getMultisampleResolveFramebufferObject()) + rs->setMultisampleResolveFramebufferObject(fbo); + } + + mPingPongCanvas->setPostProcessing(frameId, mUsePostProcessing); + mPingPongCanvas->setNormalsTexture(frameId, mNormals ? getTexture(Tex_Normal, frameId) : nullptr); + mPingPongCanvas->setMask(frameId, mUnderwater, mExteriorFlag); + mPingPongCanvas->setHDR(frameId, getHDR()); + + if (Stereo::getStereo()) + { + auto& sm = Stereo::Manager::instance(); + + int index = sm.getEye(cv) == Stereo::Eye::Left ? 0 : 1; + + mPingPongCanvas->setSceneTexture(frameId, sm.multiviewFramebuffer()->layerColorBuffer(index)); + mPingPongCanvas->setDepthTexture(frameId, sm.multiviewFramebuffer()->layerDepthBuffer(index)); + } + else if (Stereo::getMultiview()) + { + auto& sm = Stereo::Manager::instance(); + + mPingPongCanvas->setSceneTexture(frameId, sm.multiviewFramebuffer()->multiviewColorBuffer()); + mPingPongCanvas->setDepthTexture(frameId, sm.multiviewFramebuffer()->multiviewDepthBuffer()); + } + else + { + mPingPongCanvas->setSceneTexture(frameId, getTexture(Tex_Scene, frameId)); + if (mDisableDepthPasses) + mPingPongCanvas->setDepthTexture(frameId, getTexture(Tex_Depth, frameId)); + else + mPingPongCanvas->setDepthTexture(frameId, getTexture(Tex_OpaqueDepth, frameId)); + + mPingPongCanvas->setLDRSceneTexture(frameId, getTexture(Tex_Scene_LDR, frameId)); + + if (mTransparentDepthPostPass) { - auto& multiviewFbo = Stereo::Manager::instance().multiviewFramebuffer(); - stateset->setTextureAttributeAndModes(0, multiviewFbo->multiviewColorBuffer(), osg::StateAttribute::ON); + mTransparentDepthPostPass->mFbo[frameId] = mFbos[frameId][FBO_Primary]; + mTransparentDepthPostPass->mMsaaFbo[frameId] = mFbos[frameId][FBO_Multisample]; + mTransparentDepthPostPass->mOpaqueFbo[frameId] = mFbos[frameId][FBO_OpaqueDepth]; } } - void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* cv) override + size_t frame = cv->getTraversalNumber(); + + mStateUpdater->setResolution(osg::Vec2f(cv->getViewport()->width(), cv->getViewport()->height())); + + // per-frame data + if (frame != mLastFrameNumber) { - auto& multiviewFbo = Stereo::Manager::instance().multiviewFramebuffer(); - stateset->setTextureAttributeAndModes(0, multiviewFbo->layerColorBuffer(0), osg::StateAttribute::ON); + mLastFrameNumber = frame; - auto viewport = static_cast(stateset->getAttribute(osg::StateAttribute::VIEWPORT)); - auto fullViewport = mHUDCamera->getViewport(); - viewport->setViewport( - 0, - 0, - fullViewport->width() / 2, - fullViewport->height() - ); + auto stamp = cv->getFrameStamp(); + + mStateUpdater->setSimulationTime(static_cast(stamp->getSimulationTime())); + mStateUpdater->setDeltaSimulationTime(static_cast(stamp->getSimulationTime() - mLastSimulationTime)); + mLastSimulationTime = stamp->getSimulationTime(); + + for (const auto& dispatchNode : mPingPongCanvas->getCurrentFrameData(frame)) + { + for (auto& uniform : dispatchNode.mHandle->getUniformMap()) + { + if (uniform->getType().has_value() && !uniform->mSamplerType) + if (auto* u = dispatchNode.mRootStateSet->getUniform(uniform->mName)) + uniform->setUniform(u); + } + } } + } - void applyRight(osg::StateSet* stateset, osgUtil::CullVisitor* cv) override - { - auto& multiviewFbo = Stereo::Manager::instance().multiviewFramebuffer(); - stateset->setTextureAttributeAndModes(0, multiviewFbo->layerColorBuffer(1), osg::StateAttribute::ON); - - auto viewport = static_cast(stateset->getAttribute(osg::StateAttribute::VIEWPORT)); - auto fullViewport = mHUDCamera->getViewport(); - viewport->setViewport( - fullViewport->width() / 2, - 0, - fullViewport->width() / 2, - fullViewport->height() - ); - } - - private: - osg::ref_ptr mHUDCamera; - osg::ref_ptr mProgram; - osg::ref_ptr mSceneTex; - }; - - void PostProcessor::createTexturesAndCamera(int width, int height) + void PostProcessor::update(size_t frameId) { - mDepthTex = new osg::Texture2D; - mDepthTex->setTextureSize(width, height); - mDepthTex->setSourceFormat(SceneUtil::AutoDepth::depthSourceFormat()); - mDepthTex->setSourceType(SceneUtil::AutoDepth::depthSourceType()); - mDepthTex->setInternalFormat(SceneUtil::AutoDepth::depthInternalFormat()); - mDepthTex->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST); - mDepthTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST); - mDepthTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - mDepthTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - mDepthTex->setResizeNonPowerOfTwoHint(false); + static const bool liveReload = Settings::Manager::getBool("live reload", "Post Processing"); - if (Settings::Manager::getBool("soft particles", "Shaders")) + if (liveReload) { - mOpaqueDepthTex = new osg::Texture2D(*mDepthTex); - mOpaqueDepthTex->setName("opaqueTexMap"); + for (auto& technique : mTechniques) + { + if (technique->getStatus() == fx::Technique::Status::File_Not_exists) + continue; + + technique->setLastModificationTime(std::filesystem::last_write_time(mTechniqueFileMap[technique->getName()])); + + if(technique->isValid() && !technique->isDirty()) + continue; + + if (technique->isDirty()) + { + technique->compile(); + + if (technique->isValid()) + Log(Debug::Info) << "Reloaded technique : " << mTechniqueFileMap[technique->getName()].string(); + + if (!mReload) + mReload = technique->isValid(); + } + } } - mSceneTex = new osg::Texture2D; - mSceneTex->setTextureSize(width, height); - mSceneTex->setSourceFormat(SceneUtil::Color::colorSourceFormat()); - mSceneTex->setSourceType(SceneUtil::Color::colorSourceType()); - mSceneTex->setInternalFormat(SceneUtil::Color::colorInternalFormat()); - mSceneTex->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST); - mSceneTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST); - mSceneTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - mSceneTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - mSceneTex->setResizeNonPowerOfTwoHint(false); + if (mReload) + { + mReload = false; + + if (!mTechniques.empty()) + reloadMainPass(*mTechniques[0]); + + reloadTechniques(); + + if (!mUsePostProcessing) + resize(); + } + + if (mDirty && mDirtyFrameId == frameId) + { + createTexturesAndCamera(frameId); + createObjectsForFrame(frameId); + mDirty = false; + } + + if (mNormalsSupported && mNormals != mPrevNormals) + { + mPrevNormals = mNormals; + + mViewer->stopThreading(); + + auto& shaderManager = MWBase::Environment::get().getResourceSystem()->getSceneManager()->getShaderManager(); + auto defines = shaderManager.getGlobalDefines(); + defines["disableNormals"] = mNormals ? "0" : "1"; + shaderManager.setGlobalDefines(defines); + + mViewer->startThreading(); + + createTexturesAndCamera(frameId); + createObjectsForFrame(frameId); + mDirty = true; + mDirtyFrameId = !frameId; + } + } + + void PostProcessor::createObjectsForFrame(size_t frameId) + { + if (Stereo::getStereo() || Stereo::getMultiview()) + return; + + auto& fbos = mFbos[frameId]; + auto& textures = mTextures[frameId]; + + for (auto& tex : textures) + { + if (!tex) + continue; + + tex->setTextureSize(mWidth, mHeight); + tex->dirtyTextureObject(); + } + + fbos[FBO_Primary] = new osg::FrameBufferObject; + fbos[FBO_Primary]->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(textures[Tex_Scene])); + if (mNormals && mNormalsSupported) + fbos[FBO_Primary]->setAttachment(osg::Camera::COLOR_BUFFER1, osg::FrameBufferAttachment(textures[Tex_Normal])); + fbos[FBO_Primary]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(textures[Tex_Depth])); + + fbos[FBO_FirstPerson] = new osg::FrameBufferObject; + osg::ref_ptr fpDepthRb = new osg::RenderBuffer(mWidth, mHeight, textures[Tex_Depth]->getInternalFormat(), mSamples > 1 ? mSamples : 0); + fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(fpDepthRb)); + + // When MSAA is enabled we must first render to a render buffer, then + // blit the result to the FBO which is either passed to the main frame + // buffer for display or used as the entry point for a post process chain. + if (mSamples > 1) + { + fbos[FBO_Multisample] = new osg::FrameBufferObject; + osg::ref_ptr colorRB = new osg::RenderBuffer(mWidth, mHeight, textures[Tex_Scene]->getInternalFormat(), mSamples); + if (mNormals && mNormalsSupported) + { + osg::ref_ptr normalRB = new osg::RenderBuffer(mWidth, mHeight, textures[Tex_Normal]->getInternalFormat(), mSamples); + fbos[FBO_Multisample]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, osg::FrameBufferAttachment(normalRB)); + fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, osg::FrameBufferAttachment(normalRB)); + } + osg::ref_ptr depthRB = new osg::RenderBuffer(mWidth, mHeight, textures[Tex_Depth]->getInternalFormat(), mSamples); + fbos[FBO_Multisample]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(colorRB)); + fbos[FBO_Multisample]->setAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(depthRB)); + fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(colorRB)); + + fbos[FBO_Intercept] = new osg::FrameBufferObject; + fbos[FBO_Intercept]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(textures[Tex_Scene])); + fbos[FBO_Intercept]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, osg::FrameBufferAttachment(textures[Tex_Normal])); + } + else + { + fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(textures[Tex_Scene])); + if (mNormals && mNormalsSupported) + fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, osg::FrameBufferAttachment(textures[Tex_Normal])); + } + + if (textures[Tex_OpaqueDepth]) + { + fbos[FBO_OpaqueDepth] = new osg::FrameBufferObject; + fbos[FBO_OpaqueDepth]->setAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(textures[Tex_OpaqueDepth])); + } + +#ifdef __APPLE__ + if (textures[Tex_OpaqueDepth]) + fbos[FBO_OpaqueDepth]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER, osg::FrameBufferAttachment(new osg::RenderBuffer(textures[Tex_OpaqueDepth]->getTextureWidth(), textures[Tex_OpaqueDepth]->getTextureHeight(), textures[Tex_Scene]->getInternalFormat()))); +#endif + } + + void PostProcessor::dirtyTechniques() + { + if (!isEnabled()) + return; + + fx::DispatchArray data; + + bool sunglare = true; + mHDR = false; + mNormals = false; + + for (const auto& technique : mTechniques) + { + if (!technique->isValid()) + continue; + + if (technique->getGLSLVersion() > mGLSLVersion) + { + Log(Debug::Warning) << "Technique " << technique->getName() << " requires GLSL version " << technique->getGLSLVersion() << " which is unsupported by your hardware."; + continue; + } + + fx::DispatchNode node; + + node.mFlags = technique->getFlags(); + + if (technique->getHDR()) + mHDR = true; + + if (technique->getNormals()) + mNormals = true; + + if (node.mFlags & fx::Technique::Flag_Disable_SunGlare) + sunglare = false; + + // required default samplers available to every shader pass + node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerLastShader", Unit_LastShader)); + node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerLastPass", Unit_LastPass)); + node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerDepth", Unit_Depth)); + + if (mNormals) + node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerNormals", Unit_Normals)); + + if (technique->getHDR()) + node.mRootStateSet->addUniform(new osg::Uniform("omw_EyeAdaptation", Unit_EyeAdaptation)); + + int texUnit = Unit_NextFree; + + // user-defined samplers + for (const osg::Texture* texture : technique->getTextures()) + { + if (const auto* tex1D = dynamic_cast(texture)) + node.mRootStateSet->setTextureAttribute(texUnit, new osg::Texture1D(*tex1D)); + else if (const auto* tex2D = dynamic_cast(texture)) + node.mRootStateSet->setTextureAttribute(texUnit, new osg::Texture2D(*tex2D)); + else if (const auto* tex3D = dynamic_cast(texture)) + node.mRootStateSet->setTextureAttribute(texUnit, new osg::Texture3D(*tex3D)); + + node.mRootStateSet->addUniform(new osg::Uniform(texture->getName().c_str(), texUnit++)); + } + + // user-defined uniforms + for (auto& uniform : technique->getUniformMap()) + { + if (uniform->mSamplerType) continue; + + if (auto type = uniform->getType()) + uniform->setUniform(node.mRootStateSet->getOrCreateUniform(uniform->mName, type.value())); + } + + int subTexUnit = texUnit; + + for (const auto& pass : technique->getPasses()) + { + fx::DispatchNode::SubPass subPass; + + pass->prepareStateSet(subPass.mStateSet, technique->getName()); + + node.mHandle = technique; + + if (!pass->getTarget().empty()) + { + const auto& rt = technique->getRenderTargetsMap()[pass->getTarget()]; + + const auto [w, h] = rt.mSize.get(mWidth, mHeight); + + subPass.mRenderTexture = new osg::Texture2D(*rt.mTarget); + subPass.mRenderTexture->setTextureSize(w, h); + subPass.mRenderTexture->setName(std::string(pass->getTarget())); + + if (rt.mMipMap) + subPass.mRenderTexture->setNumMipmapLevels(osg::Image::computeNumberOfMipmapLevels(w, h)); + + subPass.mRenderTarget = new osg::FrameBufferObject; + subPass.mRenderTarget->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(subPass.mRenderTexture)); + subPass.mStateSet->setAttributeAndModes(new osg::Viewport(0, 0, w, h)); + + node.mRootStateSet->setTextureAttributeAndModes(subTexUnit, subPass.mRenderTexture); + node.mRootStateSet->addUniform(new osg::Uniform(subPass.mRenderTexture->getName().c_str(), subTexUnit++)); + } + node.mPasses.emplace_back(std::move(subPass)); + } + + data.emplace_back(std::move(node)); + } + + size_t frameId = frame() % 2; + + mPingPongCanvas->setCurrentFrameData(frameId, std::move(data)); + + if (auto hud = MWBase::Environment::get().getWindowManager()->getPostProcessorHud()) + hud->updateTechniques(); + + mRendering.getSkyManager()->setSunglare(sunglare); + } + + bool PostProcessor::enableTechnique(std::shared_ptr technique, std::optional location) + { + if (!technique || technique->getName() == "main" || (location.has_value() && location.value() <= 0)) + return false; + + disableTechnique(technique, false); + + int pos = std::min(location.value_or(mTechniques.size()), mTechniques.size()); + + mTechniques.insert(mTechniques.begin() + pos, technique); + dirtyTechniques(); + + return true; + } + + bool PostProcessor::disableTechnique(std::shared_ptr technique, bool dirty) + { + for (size_t i = 1; i < mTechniques.size(); ++i) + { + if (technique.get() == mTechniques[i].get()) + { + mTechniques.erase(mTechniques.begin() + i); + if (dirty) + dirtyTechniques(); + return true; + } + } + + return false; + } + + bool PostProcessor::isTechniqueEnabled(const std::shared_ptr& technique) const + { + for (const auto& t : mTechniques) + { + if (technique.get() == t.get()) + return technique->isValid(); + } + + return false; + } + + void PostProcessor::createTexturesAndCamera(size_t frameId) + { + auto& textures = mTextures[frameId]; + + for (auto& texture : textures) + { + if (!texture) + texture = new osg::Texture2D; + texture->setTextureSize(mWidth, mHeight); + texture->setSourceFormat(GL_RGBA); + texture->setSourceType(GL_UNSIGNED_BYTE); + texture->setInternalFormat(GL_RGBA); + texture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture::LINEAR); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + texture->setResizeNonPowerOfTwoHint(false); + } + + textures[Tex_Normal]->setSourceFormat(GL_RGB); + textures[Tex_Normal]->setInternalFormat(GL_RGB); + + if (mMainTemplate) + { + textures[Tex_Scene]->setSourceFormat(mMainTemplate->getSourceFormat()); + textures[Tex_Scene]->setSourceType(mMainTemplate->getSourceType()); + textures[Tex_Scene]->setInternalFormat(mMainTemplate->getInternalFormat()); + textures[Tex_Scene]->setFilter(osg::Texture2D::MIN_FILTER, mMainTemplate->getFilter(osg::Texture2D::MIN_FILTER)); + textures[Tex_Scene]->setFilter(osg::Texture2D::MAG_FILTER, mMainTemplate->getFilter(osg::Texture2D::MAG_FILTER)); + textures[Tex_Scene]->setWrap(osg::Texture::WRAP_S, mMainTemplate->getWrap(osg::Texture2D::WRAP_S)); + textures[Tex_Scene]->setWrap(osg::Texture::WRAP_T, mMainTemplate->getWrap(osg::Texture2D::WRAP_T)); + } + + auto setupDepth = [] (osg::Texture2D* tex) { + tex->setSourceFormat(GL_DEPTH_STENCIL_EXT); + tex->setSourceType(SceneUtil::AutoDepth::depthSourceType()); + tex->setInternalFormat(SceneUtil::AutoDepth::depthInternalFormat()); + }; + + setupDepth(textures[Tex_Depth]); + + if (mDisableDepthPasses) + { + textures[Tex_OpaqueDepth] = nullptr; + } + else + { + setupDepth(textures[Tex_OpaqueDepth]); + textures[Tex_OpaqueDepth]->setName("opaqueTexMap"); + } + + if (mHUDCamera) + return; mHUDCamera = new osg::Camera; mHUDCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); @@ -358,64 +700,106 @@ namespace MWRender mHUDCamera->setClearMask(0); mHUDCamera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1, 0, 1)); mHUDCamera->setAllowEventFocus(false); - mHUDCamera->setViewport(0, 0, width, height); + mHUDCamera->setViewport(0, 0, mWidth, mHeight); - // Shaders calculate correct UV coordinates for our fullscreen triangle - constexpr char vertSrc[] = R"GLSL( - #version 120 + mViewer->getCamera()->removeCullCallback(mPingPongCull); + mPingPongCull = new PingPongCull; + mViewer->getCamera()->addCullCallback(mPingPongCull); - varying vec2 uv; + mPingPongCanvas = new PingPongCanvas(mRendering.getResourceSystem()->getSceneManager()->getShaderManager()); - void main() - { - gl_Position = vec4(gl_Vertex.xy, 0.0, 1.0); - uv = gl_Position.xy * 0.5 + 0.5; - } - )GLSL"; - - constexpr char fragSrc[] = R"GLSL( - #version 120 - - varying vec2 uv; - uniform sampler2D sceneTex; - - void main() - { - gl_FragData[0] = texture2D(sceneTex, uv); - } - )GLSL"; - - constexpr char fragSrcMultiview[] = R"GLSL( - #version 330 compatibility - - #extension GL_EXT_texture_array : require - - varying vec2 uv; - uniform sampler2DArray sceneTex; - - void main() - { - vec3 array_uv = vec3(uv.x * 2, uv.y, 0); - if(array_uv.x >= 1.0) - { - array_uv.x -= 1.0; - array_uv.z = 1; - } - gl_FragData[0] = texture2DArray(sceneTex, array_uv); - } - )GLSL"; - - osg::ref_ptr vertShader = new osg::Shader(osg::Shader::VERTEX, vertSrc); - osg::ref_ptr fragShader = new osg::Shader(osg::Shader::FRAGMENT, Stereo::getMultiview() ? fragSrcMultiview : fragSrc); - - osg::ref_ptr program = new osg::Program; - program->addShader(vertShader); - program->addShader(fragShader); - - mHUDCamera->addChild(createFullScreenTri()); + mHUDCamera->addChild(mPingPongCanvas); mHUDCamera->setNodeMask(Mask_RenderToTexture); - mHUDCamera->setCullCallback(new HUDCameraStatesetUpdater(mHUDCamera, program, mSceneTex)); + + mHUDCamera->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + mHUDCamera->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); } + std::shared_ptr PostProcessor::loadTechnique(const std::string& name, bool insert) + { + if (!isEnabled()) + return nullptr; + + for (size_t i = 0; i < mTemplates.size(); ++i) + if (name == mTemplates[i]->getName()) + return mTemplates[i]; + + auto technique = std::make_shared(*mVFS, *mRendering.getResourceSystem()->getImageManager(), name, mWidth, mHeight, mUBO, mNormalsSupported); + + technique->compile(); + + if (technique->getStatus() != fx::Technique::Status::File_Not_exists) + technique->setLastModificationTime(std::filesystem::last_write_time(mTechniqueFileMap[technique->getName()]), false); + + if (!insert) + return technique; + + reloadMainPass(*technique); + + mTemplates.push_back(std::move(technique)); + + return mTemplates.back(); + } + + void PostProcessor::addTemplate(std::shared_ptr technique) + { + if (!isEnabled()) + return; + + for (size_t i = 0; i < mTemplates.size(); ++i) + if (technique.get() == mTemplates[i].get()) + return; + + mTemplates.push_back(technique); + } + + void PostProcessor::reloadTechniques() + { + if (!isEnabled()) + return; + + mTechniques.clear(); + + std::vector techniqueStrings; + Misc::StringUtils::split(Settings::Manager::getString("chain", "Post Processing"), techniqueStrings, ","); + + techniqueStrings.insert(techniqueStrings.begin(), "main"); + + for (auto& techniqueName : techniqueStrings) + { + Misc::StringUtils::trim(techniqueName); + + if (techniqueName.empty()) + continue; + + if ((&techniqueName != &techniqueStrings.front()) && Misc::StringUtils::ciEqual(techniqueName, "main")) + { + Log(Debug::Warning) << "main.omwfx techniqued specified in chain, this is not allowed. technique file will be ignored if it exists."; + continue; + } + + mTechniques.push_back(loadTechnique(techniqueName)); + } + + dirtyTechniques(); + } + + void PostProcessor::reloadMainPass(fx::Technique& technique) + { + if (!technique.getMainTemplate()) + return; + + mMainTemplate = technique.getMainTemplate(); + + resize(); + } + + void PostProcessor::toggleMode() + { + for (auto& technique : mTemplates) + technique->compile(); + + dirtyTechniques(); + } } diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index b1217b011b..8560b76eaa 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -1,11 +1,26 @@ #ifndef OPENMW_MWRENDER_POSTPROCESSOR_H #define OPENMW_MWRENDER_POSTPROCESSOR_H +#include +#include +#include +#include + +#include + #include #include #include #include -#include + +#include + +#include +#include +#include + +#include "pingpongcanvas.hpp" +#include "transparentpass.hpp" #include @@ -19,38 +34,197 @@ namespace Stereo class MultiviewFramebuffer; } +namespace VFS +{ + class Manager; +} + +namespace Shader +{ + class ShaderManager; +} + namespace MWRender { - class PostProcessor : public osg::Referenced + class RenderingManager; + class PingPongCull; + class PingPongCanvas; + class TransparentDepthBinCallback; + + class PostProcessor : public osg::Group { public: - PostProcessor(osgViewer::Viewer* viewer, osg::Group* rootNode); + using FBOArray = std::array, 5>; + using TextureArray = std::array, 5>; + using TechniqueList = std::vector>; - auto getMsaaFbo() { return mMsaaFbo; } - auto getFbo() { return mFbo; } - auto getFirstPersonRBProxy() { return mFirstPersonDepthRBProxy; } + enum TextureIndex + { + Tex_Scene, + Tex_Scene_LDR, + Tex_Depth, + Tex_OpaqueDepth, + Tex_Normal + }; - osg::ref_ptr getOpaqueDepthTex() { return mOpaqueDepthTex; } + enum FBOIndex + { + FBO_Primary, + FBO_Multisample, + FBO_FirstPerson, + FBO_OpaqueDepth, + FBO_Intercept + }; - void resize(int width, int height); + enum TextureUnits + { + Unit_LastShader = 0, + Unit_LastPass, + Unit_Depth, + Unit_EyeAdaptation, + Unit_Normals, + Unit_NextFree + }; + + PostProcessor(RenderingManager& rendering, osgViewer::Viewer* viewer, osg::Group* rootNode, const VFS::Manager* vfs); + + ~PostProcessor(); + + void traverse(osg::NodeVisitor& nv) override; + + osg::ref_ptr getFbo(FBOIndex index, unsigned int frameId) { return mFbos[frameId][index]; } + + osg::ref_ptr getTexture(TextureIndex index, unsigned int frameId) { return mTextures[frameId][index]; } + + osg::ref_ptr getPrimaryFbo(unsigned int frameId) { return mFbos[frameId][FBO_Multisample] ? mFbos[frameId][FBO_Multisample] : mFbos[frameId][FBO_Primary]; } + + osg::ref_ptr getStateUpdater() { return mStateUpdater; } + + const TechniqueList& getTechniques() { return mTechniques; } + + const TechniqueList& getTemplates() const { return mTemplates; } + + osg::ref_ptr getCanvas() { return mPingPongCanvas; } + + const auto& getTechniqueMap() const { return mTechniqueFileMap; } + + void resize(); + + bool enableTechnique(std::shared_ptr technique, std::optional location = std::nullopt); + + bool disableTechnique(std::shared_ptr technique, bool dirty = true); + + bool getSupportsNormalsRT() const { return mNormalsSupported; } + + template + void setUniform(std::shared_ptr technique, const std::string& name, const T& value) + { + if (!isEnabled()) + return; + + auto it = technique->findUniform(name); + + if (it == technique->getUniformMap().end()) + return; + + if ((*it)->mStatic) + { + Log(Debug::Warning) << "Attempting to set a configration variable [" << name << "] as a uniform"; + return; + } + + (*it)->setValue(value); + } + + bool isTechniqueEnabled(const std::shared_ptr& technique) const; + + void setExteriorFlag(bool exterior) { mExteriorFlag = exterior; } + + void setUnderwaterFlag(bool underwater) { mUnderwater = underwater; } + + void toggleMode(); + + std::shared_ptr loadTechnique(const std::string& name, bool insert=true); + + void addTemplate(std::shared_ptr technique); + + bool isEnabled() const { return mUsePostProcessing && mEnabled; } + + bool softParticlesEnabled() const {return mSoftParticles; } + + bool getHDR() const { return mHDR; } + + void disable(); + + void enable(bool usePostProcessing = true); + + void setRenderTargetSize(int width, int height) { mWidth = width; mHeight = height; } private: - void createTexturesAndCamera(int width, int height); + size_t frame() const { return mViewer->getFrameStamp()->getFrameNumber(); } + + void createObjectsForFrame(size_t frameId); + + void createTexturesAndCamera(size_t frameId); + + void reloadTechniques(); + + void reloadMainPass(fx::Technique& technique); + + void dirtyTechniques(); + + void update(size_t frameId); + + void cull(size_t frameId, osgUtil::CullVisitor* cv); - osgViewer::Viewer* mViewer; osg::ref_ptr mRootNode; osg::ref_ptr mHUDCamera; - std::shared_ptr mMultiviewFbo; - osg::ref_ptr mMsaaFbo; - osg::ref_ptr mFbo; - osg::ref_ptr mFirstPersonDepthRBProxy; + std::array mTextures; + std::array mFbos; - osg::ref_ptr mSceneTex; - osg::ref_ptr mDepthTex; - osg::ref_ptr mOpaqueDepthTex; + TechniqueList mTechniques; + TechniqueList mTemplates; + + std::unordered_map mTechniqueFileMap; + + int mSamples; + + bool mDirty; + size_t mDirtyFrameId; + + RenderingManager& mRendering; + osgViewer::Viewer* mViewer; + const VFS::Manager* mVFS; + + bool mReload; + bool mEnabled; + bool mUsePostProcessing; + bool mSoftParticles; + bool mDisableDepthPasses; + + size_t mLastFrameNumber; + float mLastSimulationTime; + + bool mExteriorFlag; + bool mUnderwater; + bool mHDR; + bool mNormals; + bool mPrevNormals; + bool mNormalsSupported; + bool mUBO; + int mGLSLVersion; + + osg::ref_ptr mMainTemplate; + + osg::ref_ptr mStateUpdater; + osg::ref_ptr mPingPongCull; + osg::ref_ptr mPingPongCanvas; + osg::ref_ptr mTransparentDepthPostPass; + + int mWidth; + int mHeight; }; } #endif - diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 24e37a93ea..359b2e0f9d 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -54,7 +54,9 @@ #include "../mwworld/class.hpp" #include "../mwworld/groundcoverstore.hpp" #include "../mwgui/loadingscreen.hpp" +#include "../mwgui/postprocessorhud.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwbase/windowmanager.hpp" #include "sky.hpp" #include "effectmanager.hpp" @@ -114,7 +116,7 @@ namespace MWRender mProjectionMatrix = projectionMatrix; } - const osg::Matrixf& projectionMatrix() const + const osg::Matrixf& getProjectionMatrix() const { return mProjectionMatrix; } @@ -208,7 +210,6 @@ namespace MWRender mPlayerPos = playerPos; } - private: float mLinearFac; float mNear; @@ -411,9 +412,11 @@ 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["refraction_enabled"] = "0"; globalDefines["useGPUShader4"] = "0"; globalDefines["useOVR_multiview"] = "0"; globalDefines["numViews"] = "1"; + globalDefines["disableNormals"] = "1"; for (auto itr = lightDefines.begin(); itr != lightDefines.end(); itr++) globalDefines[itr->first] = itr->second; @@ -502,12 +505,9 @@ namespace MWRender mPerViewUniformStateUpdater = new PerViewUniformStateUpdater(); rootNode->addCullCallback(mPerViewUniformStateUpdater); - mPostProcessor = new PostProcessor(viewer, mRootNode); - resourceSystem->getSceneManager()->setDepthFormat(SceneUtil::AutoDepth::depthInternalFormat()); - resourceSystem->getSceneManager()->setOpaqueDepthTex(mPostProcessor->getOpaqueDepthTex()); - - if (reverseZ && !SceneUtil::isFloatingPointDepthFormat(SceneUtil::AutoDepth::depthInternalFormat())) - Log(Debug::Warning) << "Floating point depth format not in use but reverse-z buffer is enabled, consider disabling it."; + mPostProcessor = new PostProcessor(*this, viewer, mRootNode, resourceSystem->getVFS()); + resourceSystem->getSceneManager()->setOpaqueDepthTex(mPostProcessor->getTexture(PostProcessor::Tex_OpaqueDepth, 0), mPostProcessor->getTexture(PostProcessor::Tex_OpaqueDepth, 1)); + resourceSystem->getSceneManager()->setSupportsNormalsRT(mPostProcessor->getSupportsNormalsRT()); // water goes after terrain for correct waterculling order mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath)); @@ -692,9 +692,10 @@ namespace MWRender void RenderingManager::configureAmbient(const ESM::Cell *cell) { + bool isInterior = !cell->isExterior() && !(cell->mData.mFlags & ESM::Cell::QuasiEx); bool needsAdjusting = false; if (mResourceSystem->getSceneManager()->getLightingMethod() != SceneUtil::LightingMethod::FFP) - needsAdjusting = !cell->isExterior() && !(cell->mData.mFlags & ESM::Cell::QuasiEx); + needsAdjusting = isInterior; auto ambient = SceneUtil::colourFromRGB(cell->mAmbi.mAmbient); @@ -724,11 +725,14 @@ namespace MWRender mSunLight->setPosition(osg::Vec4f(-0.15f, 0.15f, 1.f, 0.f)); } - void RenderingManager::setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular) + void RenderingManager::setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular, float sunVis) { // need to wrap this in a StateUpdater? mSunLight->setDiffuse(diffuse); mSunLight->setSpecular(specular); + + mPostProcessor->getStateUpdater()->setSunColor(diffuse); + mPostProcessor->getStateUpdater()->setSunVis(sunVis); } void RenderingManager::setSunDirection(const osg::Vec3f &direction) @@ -738,6 +742,8 @@ namespace MWRender mSunLight->setPosition(osg::Vec4(position.x(), position.y(), position.z(), 0)); mSky->setSunDirection(position); + + mPostProcessor->getStateUpdater()->setSunPos(mSunLight->getPosition(), mNight); } void RenderingManager::addCell(const MWWorld::CellStore *store) @@ -779,6 +785,7 @@ namespace MWRender mShadowManager->enableOutdoorMode(); else mShadowManager->enableIndoorMode(); + mPostProcessor->getStateUpdater()->setIsInterior(!enabled); } bool RenderingManager::toggleBorders() @@ -877,9 +884,28 @@ namespace MWRender mCamera->update(dt, paused); bool isUnderwater = mWater->isUnderwater(mCamera->getPosition()); - mStateUpdater->setFogStart(mFog->getFogStart(isUnderwater)); - mStateUpdater->setFogEnd(mFog->getFogEnd(isUnderwater)); - setFogColor(mFog->getFogColor(isUnderwater)); + + float fogStart = mFog->getFogStart(isUnderwater); + float fogEnd = mFog->getFogEnd(isUnderwater); + osg::Vec4f fogColor = mFog->getFogColor(isUnderwater); + + mStateUpdater->setFogStart(fogStart); + mStateUpdater->setFogEnd(fogEnd); + setFogColor(fogColor); + + auto world = MWBase::Environment::get().getWorld(); + const auto& stateUpdater = mPostProcessor->getStateUpdater(); + + stateUpdater->setFogRange(fogStart, fogEnd); + stateUpdater->setNearFar(mNearClip, mViewDistance); + stateUpdater->setIsUnderwater(isUnderwater); + stateUpdater->setFogColor(fogColor); + stateUpdater->setGameHour(world->getTimeStamp().getHour()); + stateUpdater->setWeatherId(world->getCurrentWeather()); + stateUpdater->setNextWeatherId(world->getNextWeather()); + stateUpdater->setWeatherTransition(world->getWeatherTransition()); + stateUpdater->setWindSpeed(world->getWindSpeed()); + mPostProcessor->setUnderwaterFlag(isUnderwater); } void RenderingManager::updatePlayerPtr(const MWWorld::Ptr &ptr) @@ -939,6 +965,8 @@ namespace MWRender mWater->setCullCallback(mTerrain->getHeightCullCallback(height, Mask_Water)); mWater->setHeight(height); mSky->setWaterHeight(height); + + mPostProcessor->getStateUpdater()->setWaterHeight(height); } void RenderingManager::screenshot(osg::Image* image, int w, int h) @@ -1131,6 +1159,11 @@ namespace MWRender return mObjects->getAnimation(ptr); } + PostProcessor* RenderingManager::getPostProcessor() + { + return mPostProcessor; + } + void RenderingManager::setupPlayer(const MWWorld::Ptr &player) { if (!mPlayerNode) @@ -1218,9 +1251,9 @@ namespace MWRender { auto res = Stereo::Manager::instance().eyeResolution(); mSharedUniformStateUpdater->setScreenRes(res.x(), res.y()); - Stereo::Manager::instance().setMasterProjectionMatrix(mPerViewUniformStateUpdater->projectionMatrix()); + Stereo::Manager::instance().setMasterProjectionMatrix(mPerViewUniformStateUpdater->getProjectionMatrix()); } - else + else if (!mPostProcessor->isEnabled()) { mSharedUniformStateUpdater->setScreenRes(width, height); } @@ -1229,6 +1262,17 @@ namespace MWRender // Limit FOV here just for sure, otherwise viewing distance can be too high. float distanceMult = std::cos(osg::DegreesToRadians(std::min(fov, 140.f))/2.f); mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f/distanceMult : 1.f)); + + if (mPostProcessor) + { + mPostProcessor->getStateUpdater()->setProjectionMatrix(mPerViewUniformStateUpdater->getProjectionMatrix()); + mPostProcessor->getStateUpdater()->setFov(fov); + } + } + + void RenderingManager::setScreenRes(int width, int height) + { + mSharedUniformStateUpdater->setScreenRes(width, height); } void RenderingManager::updateTextureFiltering() @@ -1335,6 +1379,17 @@ namespace MWRender mViewer->startThreading(); } } + else if (it->first == "Post Processing" && it->second == "enabled") + { + if (Settings::Manager::getBool("enabled", "Post Processing")) + mPostProcessor->enable(); + else + { + mPostProcessor->disable(); + if (auto* hud = MWBase::Environment::get().getWindowManager()->getPostProcessorHud()) + hud->setVisible(false); + } + } } if (updateProjection) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 5b5c28b198..850b1c5a39 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -128,7 +128,8 @@ namespace MWRender void skySetMoonColour(bool red); void setSunDirection(const osg::Vec3f& direction); - void setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular); + void setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular, float sunVis); + void setNight(bool isNight) { mNight = isNight; } void configureAmbient(const ESM::Cell* cell); void configureFog(const ESM::Cell* cell); @@ -192,6 +193,8 @@ namespace MWRender Animation* getAnimation(const MWWorld::Ptr& ptr); const Animation* getAnimation(const MWWorld::ConstPtr& ptr) const; + PostProcessor* getPostProcessor(); + void addWaterRippleEmitter(const MWWorld::Ptr& ptr); void removeWaterRippleEmitter(const MWWorld::Ptr& ptr); void emitWaterRipple(const osg::Vec3f& pos); @@ -247,6 +250,8 @@ namespace MWRender void updateProjectionMatrix(); + void setScreenRes(int width, int height); + private: void updateTextureFiltering(); void updateAmbient(); @@ -310,6 +315,7 @@ namespace MWRender float mFieldOfView; float mFirstPersonFieldOfView; bool mUpdateProjectionMatrix = false; + bool mNight = false; void operator = (const RenderingManager&); RenderingManager(const RenderingManager&); diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp index 65b6a1bdbc..64961bf037 100644 --- a/apps/openmw/mwrender/screenshotmanager.cpp +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -106,17 +106,16 @@ namespace MWRender if (ext) { + size_t frameId = renderInfo.getState()->getFrameStamp()->getFrameNumber() % 2; osg::FrameBufferObject* fbo = nullptr; + if (Stereo::getStereo()) fbo = Stereo::Manager::instance().multiviewFramebuffer()->layerFbo(0); - else if (postProcessor) - fbo = postProcessor->getFbo(); + else if (postProcessor && postProcessor->getFbo(PostProcessor::FBO_Primary, frameId)) + fbo = postProcessor->getFbo(PostProcessor::FBO_Primary, frameId); if (fbo) - { - ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, fbo->getHandle(renderInfo.getContextID())); - renderInfo.getState()->glReadBuffer(GL_COLOR_ATTACHMENT0_EXT); - } + fbo->apply(*renderInfo.getState(), osg::FrameBufferObject::READ_FRAMEBUFFER); } mImage->readPixels(leftPadding, topPadding, width, height, GL_RGB, GL_UNSIGNED_BYTE); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index f0a591477a..d8add9a685 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -248,6 +248,7 @@ namespace MWRender , mBaseWindSpeed(0.f) , mEnabled(true) , mSunEnabled(true) + , mSunglareEnabled(true) , mPrecipitationAlpha(0.f) , mDirtyParticlesEffect(false) { @@ -303,6 +304,7 @@ namespace MWRender atmosphereNight->addUpdateCallback(mAtmosphereNightUpdater); mSun.reset(new Sun(mEarlyRenderBinRoot, *mSceneManager)); + mSun->setSunglare(mSunglareEnabled); mMasser.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager, Fallback::Map::getFloat("Moons_Masser_Size")/125, Moon::Type_Masser)); mSecunda.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager, Fallback::Map::getFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda)); @@ -776,6 +778,14 @@ namespace MWRender return mBaseWindSpeed; } + void SkyManager::setSunglare(bool enabled) + { + mSunglareEnabled = enabled; + + if (mSun) + mSun->setSunglare(mSunglareEnabled); + } + void SkyManager::sunEnable() { if (!mCreated) return; diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index e2ceae45f4..1fdf476bd5 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -96,6 +96,8 @@ namespace MWRender float getBaseWindSpeed() const; + void setSunglare(bool enabled); + private: void create(); ///< no need to call this, automatically done on first enable() @@ -184,6 +186,7 @@ namespace MWRender bool mEnabled; bool mSunEnabled; + bool mSunglareEnabled; float mPrecipitationAlpha; bool mDirtyParticlesEffect; diff --git a/apps/openmw/mwrender/skyutil.cpp b/apps/openmw/mwrender/skyutil.cpp index ec9e6e7635..843582064b 100644 --- a/apps/openmw/mwrender/skyutil.cpp +++ b/apps/openmw/mwrender/skyutil.cpp @@ -812,6 +812,12 @@ namespace MWRender mSunGlareCallback->setTimeOfDayFade(val); } + void Sun::setSunglare(bool enabled) + { + mSunGlareNode->setNodeMask(enabled ? ~0u : 0); + mSunFlashNode->setNodeMask(enabled ? ~0u : 0); + } + osg::ref_ptr Sun::createOcclusionQueryNode(osg::Group* parent, bool queryVisible) { osg::ref_ptr oqn = new osg::OcclusionQueryNode; diff --git a/apps/openmw/mwrender/skyutil.hpp b/apps/openmw/mwrender/skyutil.hpp index a0d9ed72b4..604e5909e8 100644 --- a/apps/openmw/mwrender/skyutil.hpp +++ b/apps/openmw/mwrender/skyutil.hpp @@ -248,6 +248,7 @@ namespace MWRender void setDirection(const osg::Vec3f& direction); void setGlareTimeOfDayFade(float val); + void setSunglare(bool enabled); private: /// @param queryVisible If true, queries the amount of visible pixels. If false, queries the total amount of pixels. diff --git a/apps/openmw/mwrender/transparentpass.cpp b/apps/openmw/mwrender/transparentpass.cpp new file mode 100644 index 0000000000..8bb5713230 --- /dev/null +++ b/apps/openmw/mwrender/transparentpass.cpp @@ -0,0 +1,89 @@ +#include "transparentpass.hpp" + +#include +#include + +#include + +#include + +namespace MWRender +{ + TransparentDepthBinCallback::TransparentDepthBinCallback(Shader::ShaderManager& shaderManager, bool postPass) + : mStateSet(new osg::StateSet) + , mPostPass(postPass) + { + osg::ref_ptr image = new osg::Image; + image->allocateImage(1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE); + image->setColor(osg::Vec4(1,1,1,1), 0, 0); + + osg::ref_ptr dummyTexture = new osg::Texture2D(image); + + constexpr osg::StateAttribute::OverrideValue modeOff = osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE; + constexpr osg::StateAttribute::OverrideValue modeOn = osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE; + + mStateSet->setTextureAttributeAndModes(0, dummyTexture); + + osg::ref_ptr vertex = shaderManager.getShader("blended_depth_postpass_vertex.glsl", {}, osg::Shader::VERTEX); + osg::ref_ptr fragment = shaderManager.getShader("blended_depth_postpass_fragment.glsl", {}, osg::Shader::FRAGMENT); + + mStateSet->setAttributeAndModes(new osg::BlendFunc, modeOff); + mStateSet->setAttributeAndModes(shaderManager.getProgram(vertex, fragment), modeOn); + + for (unsigned int unit = 1; unit < 8; ++unit) + mStateSet->setTextureMode(unit, GL_TEXTURE_2D, modeOff); + } + + void TransparentDepthBinCallback::drawImplementation(osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) + { + osg::State& state = *renderInfo.getState(); + osg::GLExtensions* ext = state.get(); + + bool validFbo = false; + unsigned int frameId = state.getFrameStamp()->getFrameNumber() % 2; + + const auto& fbo = mFbo[frameId]; + const auto& msaaFbo = mMsaaFbo[frameId]; + const auto& opaqueFbo = mOpaqueFbo[frameId]; + + if (bin->getStage()->getMultisampleResolveFramebufferObject() && bin->getStage()->getMultisampleResolveFramebufferObject() == fbo) + validFbo = true; + else if (bin->getStage()->getFrameBufferObject() && (bin->getStage()->getFrameBufferObject() == fbo || bin->getStage()->getFrameBufferObject() == msaaFbo)) + validFbo = true; + + if (!validFbo) + { + bin->drawImplementation(renderInfo, previous); + return; + } + + const osg::Texture* tex = opaqueFbo->getAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER).getTexture(); + + opaqueFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + + ext->glBlitFramebuffer(0, 0, tex->getTextureWidth(), tex->getTextureHeight(), 0, 0, tex->getTextureWidth(), tex->getTextureHeight(), GL_DEPTH_BUFFER_BIT, GL_NEAREST); + + if (msaaFbo) + msaaFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + else + fbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + + // draws scene into primary attachments + bin->drawImplementation(renderInfo, previous); + + if (!mPostPass) + return; + + opaqueFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + + osg::ref_ptr restore = bin->getStateSet(); + bin->setStateSet(mStateSet); + // draws transparent post-pass to populate a postprocess friendly depth texture with alpha-clipped geometry + bin->drawImplementation(renderInfo, previous); + bin->setStateSet(restore); + + if (!msaaFbo) + fbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + } + +} \ No newline at end of file diff --git a/apps/openmw/mwrender/transparentpass.hpp b/apps/openmw/mwrender/transparentpass.hpp new file mode 100644 index 0000000000..a933dde989 --- /dev/null +++ b/apps/openmw/mwrender/transparentpass.hpp @@ -0,0 +1,38 @@ +#ifndef OPENMW_MWRENDER_TRANSPARENTPASS_H +#define OPENMW_MWRENDER_TRANSPARENTPASS_H + +#include + +#include +#include + +#include + +#include "postprocessor.hpp" + +namespace Shader +{ + class ShaderManager; +} + +namespace MWRender +{ + class TransparentDepthBinCallback : public osgUtil::RenderBin::DrawCallback + { + public: + TransparentDepthBinCallback(Shader::ShaderManager& shaderManager, bool postPass); + + void drawImplementation(osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) override; + + std::array, 2> mFbo; + std::array, 2> mMsaaFbo; + std::array, 2> mOpaqueFbo; + + private: + osg::ref_ptr mStateSet; + bool mPostPass; + }; + +} + +#endif \ No newline at end of file diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index c67a206416..9896893da2 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -267,6 +267,7 @@ public: : RTTNode(rttSize, rttSize, 0, false, 1, StereoAwareness::Aware) , mNodeMask(Refraction::sDefaultCullMask) { + setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); mClipCullNode = new ClipCullNode; } @@ -342,6 +343,7 @@ public: : RTTNode(rttSize, rttSize, 0, false, 0, StereoAwareness::Aware) { setInterior(isInterior); + setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); mClipCullNode = new ClipCullNode; } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 0da47a9a74..db1b9e592f 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -27,6 +27,7 @@ #include "../mwrender/renderingmanager.hpp" #include "../mwrender/landmanager.hpp" +#include "../mwrender/postprocessor.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwphysics/actor.hpp" @@ -860,6 +861,8 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); mNavigator.wait(*loadingListener, DetourNavigator::WaitConditionType::requiredTilesPresent); + + MWBase::Environment::get().getWorld()->getPostProcessor()->setExteriorFlag(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx); } void Scene::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) @@ -879,6 +882,8 @@ namespace MWWorld if (changeEvent) MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); + + MWBase::Environment::get().getWorld()->getPostProcessor()->setExteriorFlag(true); } CellStore* Scene::getCurrentCell () diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 16a0bef131..eb0226d506 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -786,6 +786,7 @@ namespace MWWorld -0.268f, // approx tan( -15 degrees ) static_cast(sin(theta))); mRendering.setSunDirection( final * -1 ); + mRendering.setNight(is_night); } float underwaterFog = mUnderwaterFog.getValue(time.getHour(), mTimeSettings, "Fog"); @@ -807,7 +808,7 @@ namespace MWWorld mRendering.configureFog(mResult.mFogDepth, underwaterFog, mResult.mDLFogFactor, mResult.mDLFogOffset/100.0f, mResult.mFogColor); mRendering.setAmbientColour(mResult.mAmbientColor); - mRendering.setSunColour(mResult.mSunColor, mResult.mSunColor * mResult.mGlareView * glareFade); + mRendering.setSunColour(mResult.mSunColor, mResult.mSunColor * mResult.mGlareView * glareFade, mResult.mGlareView * glareFade); mRendering.getSkyManager()->setWeather(mResult); @@ -857,11 +858,6 @@ namespace MWWorld mFastForward = !incremental ? true : mFastForward; } - unsigned int WeatherManager::getWeatherID() const - { - return mCurrentWeather; - } - NightDayMode WeatherManager::getNightDayMode() const { return mNightDayMode; diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index b29ad9e994..21b690f7b6 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -307,7 +307,11 @@ namespace MWWorld void advanceTime(double hours, bool incremental); - unsigned int getWeatherID() const; + int getWeatherID() const { return mCurrentWeather; } + + int getNextWeatherID() const { return mNextWeather; } + + float getTransitionFactor() const { return mTransitionFactor; } bool useTorches(float hour) const; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 851ba2daf4..78eeab79ff 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -55,6 +55,7 @@ #include "../mwrender/renderingmanager.hpp" #include "../mwrender/camera.hpp" #include "../mwrender/vismask.hpp" +#include "../mwrender/postprocessor.hpp" #include "../mwscript/globalscripts.hpp" @@ -2045,6 +2046,16 @@ namespace MWWorld return mWeatherManager->getWeatherID(); } + int World::getNextWeather() const + { + return mWeatherManager->getNextWeatherID(); + } + + float World::getWeatherTransition() const + { + return mWeatherManager->getTransitionFactor(); + } + unsigned int World::getNightDayMode() const { return mWeatherManager->getNightDayMode(); @@ -3986,4 +3997,8 @@ namespace MWWorld return mPrng; } + MWRender::PostProcessor* World::getPostProcessor() + { + return mRendering->getPostProcessor(); + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 8098c6ec20..088c4097aa 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -54,6 +54,7 @@ namespace MWRender class SkyManager; class Animation; class Camera; + class PostProcessor; } namespace ToUTF8 @@ -329,6 +330,10 @@ namespace MWWorld int getCurrentWeather() const override; + int getNextWeather() const override; + + float getWeatherTransition() const override; + unsigned int getNightDayMode() const override; int getMasserPhase() const override; @@ -747,6 +752,8 @@ namespace MWWorld Misc::Rng::Generator& getPrng() override; MWRender::RenderingManager* getRenderingManager() override { return mRendering.get(); } + + MWRender::PostProcessor* getPostProcessor() override; }; } diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 7e42a49f66..b17e49207d 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -52,6 +52,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) serialization/integration.cpp settings/parser.cpp + settings/shadermanager.cpp shader/parsedefines.cpp shader/parsefors.cpp @@ -75,6 +76,9 @@ if (GTEST_FOUND AND GMOCK_FOUND) toutf8/toutf8.cpp esm4/includes.cpp + + fx/lexer.cpp + fx/technique.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/fx/lexer.cpp b/apps/openmw_test_suite/fx/lexer.cpp new file mode 100644 index 0000000000..5024622a71 --- /dev/null +++ b/apps/openmw_test_suite/fx/lexer.cpp @@ -0,0 +1,216 @@ +#include + +#include + +namespace +{ + using namespace testing; + using namespace fx::Lexer; + + struct LexerTest : Test {}; + + struct LexerSingleTokenTest : Test + { + template + void test() + { + const std::string content = std::string(Token::repr); + Lexer lexer(content); + + EXPECT_TRUE(std::holds_alternative(lexer.next())); + } + }; + + TEST_F(LexerSingleTokenTest, single_token_shared) { test(); } + TEST_F(LexerSingleTokenTest, single_token_technique) { test(); } + TEST_F(LexerSingleTokenTest, single_token_main_pass) { test(); } + TEST_F(LexerSingleTokenTest, single_token_render_target) { test(); } + TEST_F(LexerSingleTokenTest, single_token_vertex) { test(); } + TEST_F(LexerSingleTokenTest, single_token_fragment) { test(); } + TEST_F(LexerSingleTokenTest, single_token_compute) { test(); } + TEST_F(LexerSingleTokenTest, single_token_sampler_1d) { test(); } + TEST_F(LexerSingleTokenTest, single_token_sampler_2d) { test(); } + TEST_F(LexerSingleTokenTest, single_token_sampler_3d) { test(); } + TEST_F(LexerSingleTokenTest, single_token_true) { test(); } + TEST_F(LexerSingleTokenTest, single_token_false) { test(); } + TEST_F(LexerSingleTokenTest, single_token_vec2) { test(); } + TEST_F(LexerSingleTokenTest, single_token_vec3) { test(); } + TEST_F(LexerSingleTokenTest, single_token_vec4) { test(); } + + TEST(LexerTest, peek_whitespace_only_content_should_be_eof) + { + Lexer lexer(R"( + + )"); + + EXPECT_TRUE(std::holds_alternative(lexer.peek())); + } + + TEST(LexerTest, float_with_no_prefixed_digits) + { + Lexer lexer(R"( + 0.123; + )"); + + auto token = lexer.next(); + EXPECT_TRUE(std::holds_alternative(token)); + EXPECT_FLOAT_EQ(std::get(token).value, 0.123f); + } + + TEST(LexerTest, float_with_alpha_prefix) + { + Lexer lexer(R"( + abc.123; + )"); + + EXPECT_TRUE(std::holds_alternative(lexer.next())); + + auto token = lexer.next(); + EXPECT_TRUE(std::holds_alternative(token)); + EXPECT_FLOAT_EQ(std::get(token).value, 0.123f); + } + + TEST(LexerTest, float_with_numeric_prefix) + { + Lexer lexer(R"( + 123.123; + )"); + + auto token = lexer.next(); + EXPECT_TRUE(std::holds_alternative(token)); + EXPECT_FLOAT_EQ(std::get(token).value, 123.123f); + } + + TEST(LexerTest, int_should_not_be_float) + { + Lexer lexer(R"( + 123 + )"); + + auto token = lexer.next(); + EXPECT_TRUE(std::holds_alternative(token)); + EXPECT_EQ(std::get(token).value, 123); + } + + TEST(LexerTest, simple_string) + { + Lexer lexer(R"( + "test string" + )"); + + auto token = lexer.next(); + EXPECT_TRUE(std::holds_alternative(token)); + + std::string parsed = std::string(std::get(token).value); + EXPECT_EQ("test string", parsed); + } + + TEST(LexerTest, fail_on_unterminated_double_quotes) + { + Lexer lexer(R"( + "unterminated string' + )"); + + EXPECT_THROW(lexer.next(), LexerException); + } + + TEST(LexerTest, multiline_strings_with_single_quotes) + { + Lexer lexer(R"( + "string that is + on multiple with 'single quotes' + and correctly terminated!" + )"); + + auto token = lexer.next(); + EXPECT_TRUE(std::holds_alternative(token)); + } + + TEST(LexerTest, fail_on_unterminated_double_quotes_with_multiline_strings) + { + Lexer lexer(R"( + "string that is + on multiple with 'single quotes' + and but is unterminated :( + )"); + + EXPECT_THROW(lexer.next(), LexerException); + } + + TEST(LexerTest, jump_with_single_nested_bracket) + { + const std::string content = R"( + #version 120 + + void main() + { + return 0; + }})"; + + const std::string expected = content.substr(0, content.size() - 1); + + Lexer lexer(content); + + auto block = lexer.jump(); + + EXPECT_NE(block, std::nullopt); + EXPECT_EQ(expected, std::string(block.value())); + } + + TEST(LexerTest, jump_with_single_line_comments_and_mismatching_brackets) + { + const std::string content = R"( + #version 120 + + void main() + { + // } + return 0; + }})"; + + const std::string expected = content.substr(0, content.size() - 1); + + Lexer lexer(content); + + auto block = lexer.jump(); + + EXPECT_NE(block, std::nullopt); + EXPECT_EQ(expected, std::string(block.value())); + } + + TEST(LexerTest, jump_with_multi_line_comments_and_mismatching_brackets) + { + const std::string content = R"( + #version 120 + + void main() + { + /* + } + */ + return 0; + }})"; + + const std::string expected = content.substr(0, content.size() - 1); + + Lexer lexer(content); + + auto block = lexer.jump(); + + EXPECT_NE(block, std::nullopt); + EXPECT_EQ(expected, std::string(block.value())); + } + + TEST(LexerTest, immediate_closed_blocks) + { + Lexer lexer(R"(block{})"); + + EXPECT_TRUE(std::holds_alternative(lexer.next())); + EXPECT_TRUE(std::holds_alternative(lexer.next())); + auto block = lexer.jump(); + EXPECT_TRUE(block.has_value()); + EXPECT_TRUE(block.value().empty()); + EXPECT_TRUE(std::holds_alternative(lexer.next())); + } + +} diff --git a/apps/openmw_test_suite/fx/technique.cpp b/apps/openmw_test_suite/fx/technique.cpp new file mode 100644 index 0000000000..d10e1f1b37 --- /dev/null +++ b/apps/openmw_test_suite/fx/technique.cpp @@ -0,0 +1,204 @@ +#include "gmock/gmock.h" +#include + +#include +#include +#include +#include + +#include "../lua/testing_util.hpp" + +namespace +{ + +TestFile technique_properties(R"( + fragment main {} + vertex main {} + technique { + passes = main; + version = "0.1a"; + description = "description"; + author = "author"; + glsl_version = 330; + glsl_profile = "compatability"; + glsl_extensions = GL_EXT_gpu_shader4, GL_ARB_uniform_buffer_object; + flags = disable_sunglare; + hdr = true; + } +)"); + +TestFile rendertarget_properties{R"( + render_target rendertarget { + width_ratio = 0.5; + height_ratio = 0.5; + internal_format = r16f; + source_type = float; + source_format = red; + mipmaps = true; + wrap_s = clamp_to_edge; + wrap_t = repeat; + min_filter = linear; + mag_filter = nearest; + } + fragment downsample2x(target=rendertarget) { + + omw_In vec2 omw_TexCoord; + + void main() + { + omw_FragColor.r = omw_GetLastShader(omw_TexCoord).r; + } + } + fragment main { } + technique { passes = downsample2x, main; } +)"}; + + +TestFile uniform_properties{R"( + uniform_vec4 uVec4 { + default = vec4(0,0,0,0); + min = vec4(0,1,0,0); + max = vec4(0,0,1,0); + step = 0.5; + header = "header"; + static = true; + description = "description"; + } + fragment main { } + technique { passes = main; } +)"}; + + +TestFile missing_sampler_source{R"( + sampler_1d mysampler1d { } + fragment main { } + technique { passes = main; } +)"}; + +TestFile repeated_shared_block{R"( + shared { + float myfloat = 1.0; + } + shared {} + fragment main { } + technique { passes = main; } +)"}; + + + using namespace testing; + using namespace fx; + + struct TechniqueTest : Test + { + std::unique_ptr mVFS; + Resource::ImageManager mImageManager; + std::unique_ptr mTechnique; + + TechniqueTest() + : mVFS(createTestVFS({ + {"shaders/technique_properties.omwfx", &technique_properties}, + {"shaders/rendertarget_properties.omwfx", &rendertarget_properties}, + {"shaders/uniform_properties.omwfx", &uniform_properties}, + {"shaders/missing_sampler_source.omwfx", &missing_sampler_source}, + {"shaders/repeated_shared_block.omwfx", &repeated_shared_block}, + })) + , mImageManager(mVFS.get()) + { + Settings::Manager::setBool("radial fog", "Shaders", true); + Settings::Manager::setBool("stereo enabled", "Stereo", false); + } + + void compile(const std::string& name) + { + mTechnique = std::make_unique(*mVFS.get(), mImageManager, name, 1, 1, true, true); + mTechnique->compile(); + } + }; + + TEST_F(TechniqueTest, technique_properties) + { + std::unordered_set targetExtensions = { + "GL_EXT_gpu_shader4", + "GL_ARB_uniform_buffer_object" + }; + + compile("technique_properties"); + + EXPECT_EQ(mTechnique->getVersion(), "0.1a"); + EXPECT_EQ(mTechnique->getDescription(), "description"); + EXPECT_EQ(mTechnique->getAuthor(), "author"); + EXPECT_EQ(mTechnique->getGLSLVersion(), 330); + EXPECT_EQ(mTechnique->getGLSLProfile(), "compatability"); + EXPECT_EQ(mTechnique->getGLSLExtensions(), targetExtensions); + EXPECT_EQ(mTechnique->getFlags(), Technique::Flag_Disable_SunGlare); + EXPECT_EQ(mTechnique->getHDR(), true); + EXPECT_EQ(mTechnique->getPasses().size(), 1); + EXPECT_EQ(mTechnique->getPasses().front()->getName(), "main"); + } + + TEST_F(TechniqueTest, rendertarget_properties) + { + compile("rendertarget_properties"); + + EXPECT_EQ(mTechnique->getRenderTargetsMap().size(), 1); + + const std::string_view name = mTechnique->getRenderTargetsMap().begin()->first; + auto& rt = mTechnique->getRenderTargetsMap().begin()->second; + auto& texture = rt.mTarget; + + EXPECT_EQ(name, "rendertarget"); + EXPECT_EQ(rt.mMipMap, true); + EXPECT_EQ(rt.mSize.mWidthRatio, 0.5f); + EXPECT_EQ(rt.mSize.mHeightRatio, 0.5f); + EXPECT_EQ(texture->getWrap(osg::Texture::WRAP_S), osg::Texture::CLAMP_TO_EDGE); + EXPECT_EQ(texture->getWrap(osg::Texture::WRAP_T), osg::Texture::REPEAT); + EXPECT_EQ(texture->getFilter(osg::Texture::MIN_FILTER), osg::Texture::LINEAR); + EXPECT_EQ(texture->getFilter(osg::Texture::MAG_FILTER), osg::Texture::NEAREST); + EXPECT_EQ(texture->getSourceType(), static_cast(GL_FLOAT)); + EXPECT_EQ(texture->getSourceFormat(), static_cast(GL_RED)); + EXPECT_EQ(texture->getInternalFormat(), static_cast(GL_R16F)); + + EXPECT_EQ(mTechnique->getPasses().size(), 2); + EXPECT_EQ(mTechnique->getPasses()[0]->getTarget(), "rendertarget"); + } + + TEST_F(TechniqueTest, uniform_properties) + { + compile("uniform_properties"); + + EXPECT_EQ(mTechnique->getUniformMap().size(), 1); + + const auto& uniform = mTechnique->getUniformMap().front(); + + EXPECT_TRUE(uniform->mStatic); + EXPECT_FLOAT_EQ(uniform->mStep, 0.5f); + EXPECT_EQ(uniform->getDefault(), osg::Vec4f(0,0,0,0)); + EXPECT_EQ(uniform->getMin(), osg::Vec4f(0,1,0,0)); + EXPECT_EQ(uniform->getMax(), osg::Vec4f(0,0,1,0)); + EXPECT_EQ(uniform->mHeader, "header"); + EXPECT_EQ(uniform->mDescription, "description"); + EXPECT_EQ(uniform->mName, "uVec4"); + } + + TEST_F(TechniqueTest, fail_with_missing_source_for_sampler) + { + internal::CaptureStdout(); + + compile("missing_sampler_source"); + + std::string output = internal::GetCapturedStdout(); + Log(Debug::Error) << output; + EXPECT_THAT(output, HasSubstr("sampler_1d 'mysampler1d' requires a filename")); + } + + TEST_F(TechniqueTest, fail_with_repeated_shared_block) + { + internal::CaptureStdout(); + + compile("repeated_shared_block"); + + std::string output = internal::GetCapturedStdout(); + Log(Debug::Error) << output; + EXPECT_THAT(output, HasSubstr("repeated 'shared' block")); + } +} \ No newline at end of file diff --git a/apps/openmw_test_suite/lua/testing_util.hpp b/apps/openmw_test_suite/lua/testing_util.hpp index a40314bd0d..217c8ae5d5 100644 --- a/apps/openmw_test_suite/lua/testing_util.hpp +++ b/apps/openmw_test_suite/lua/testing_util.hpp @@ -26,6 +26,11 @@ namespace return std::make_unique(mContent, std::ios_base::in); } + std::string getPath() override + { + return "TestFile"; + } + private: const std::string mContent; }; diff --git a/apps/openmw_test_suite/settings/shadermanager.cpp b/apps/openmw_test_suite/settings/shadermanager.cpp new file mode 100644 index 0000000000..8f8e09e134 --- /dev/null +++ b/apps/openmw_test_suite/settings/shadermanager.cpp @@ -0,0 +1,66 @@ +#include + +#include + +#include + +namespace +{ + using namespace testing; + using namespace Settings; + + struct ShaderSettingsTest : Test + { + template + void withSettingsFile( const std::string& content, F&& f) + { + const auto path = std::string(UnitTest::GetInstance()->current_test_info()->name()) + ".yaml"; + + { + std::ofstream stream; + stream.open(path); + stream << content; + stream.close(); + } + + f(path); + } + }; + + TEST_F(ShaderSettingsTest, fail_to_fetch_then_set_and_succeed) + { + const std::string content = +R"YAML( +config: + shader: + vec3_uniform: [1.0, 2.0] +)YAML"; + + withSettingsFile(content, [this] (const auto& path) { + EXPECT_TRUE(ShaderManager::get().load(path)); + EXPECT_FALSE(ShaderManager::get().getValue("shader", "vec3_uniform").has_value()); + EXPECT_TRUE(ShaderManager::get().setValue("shader", "vec3_uniform", osg::Vec3f(1, 2, 3))); + EXPECT_TRUE(ShaderManager::get().getValue("shader", "vec3_uniform").has_value()); + EXPECT_EQ(ShaderManager::get().getValue("shader", "vec3_uniform").value(), osg::Vec3f(1, 2, 3)); + EXPECT_TRUE(ShaderManager::get().save()); + }); + } + + TEST_F(ShaderSettingsTest, fail_to_load_file_then_fail_to_set_and_get) + { + const std::string content = +R"YAML( +config: + shader: + uniform: 12.0 + >Defeated by a sideways carrot +)YAML"; + + withSettingsFile(content, [this] (const auto& path) { + EXPECT_FALSE(ShaderManager::get().load(path)); + EXPECT_FALSE(ShaderManager::get().setValue("shader", "uniform", 12.0)); + EXPECT_FALSE(ShaderManager::get().getValue("shader", "uniform").has_value()); + EXPECT_FALSE(ShaderManager::get().save()); + }); + } +} \ No newline at end of file diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 5ad88523b8..8e7f1bbfa5 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -82,6 +82,10 @@ add_component_dir (to_utf8 add_component_dir(esm attr common defs esmcommon reader records util luascripts format) +add_component_dir(fx pass technique lexer widgets stateupdater) + +add_component_dir(std140 ubo) + add_component_dir (esm3 esmreader esmwriter loadacti loadalch loadappa loadarmo loadbody loadbook loadbsgn loadcell loadclas loadclot loadcont loadcrea loaddial loaddoor loadench loadfact loadglob loadgmst diff --git a/components/fx/lexer.cpp b/components/fx/lexer.cpp new file mode 100644 index 0000000000..d416dd692d --- /dev/null +++ b/components/fx/lexer.cpp @@ -0,0 +1,301 @@ +#include "lexer.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "types.hpp" + +namespace fx +{ + namespace Lexer + { + Lexer::Lexer(std::string_view buffer) + : mHead(buffer.data()) + , mTail(mHead + buffer.length()) + , mAbsolutePos(0) + , mColumn(0) + , mLine(0) + , mBuffer(buffer) + , mLastToken(Eof{}) + { } + + Token Lexer::next() + { + if (mLookahead) + { + auto token = *mLookahead; + drop(); + return token; + } + + mLastToken = scanToken(); + + return mLastToken; + } + + Token Lexer::peek() + { + if (!mLookahead) + mLookahead = scanToken(); + + return *mLookahead; + } + + void Lexer::drop() + { + mLookahead = std::nullopt; + } + + std::optional Lexer::jump() + { + bool multi = false; + bool single = false; + auto start = mHead; + std::size_t level = 1; + + mLastJumpBlock.line = mLine; + + if (head() == '}') + { + mLastJumpBlock.content = {}; + return mLastJumpBlock.content; + } + + for (; mHead != mTail; advance()) + { + if (head() == '\n') + { + mLine++; + mColumn = 0; + if (single) + { + single = false; + continue; + } + } + else if (multi && head() == '*' && peekChar('/')) + { + multi = false; + advance(); + continue; + } + else if (multi || single) + { + continue; + } + else if (head() == '/' && peekChar('/')) + { + single = true; + advance(); + continue; + } + else if (head() == '/' && peekChar('*')) + { + multi = true; + advance(); + continue; + } + + if (head() == '{') + level++; + else if (head() == '}') + level--; + + if (level == 0) + { + mHead--; + auto sv = std::string_view{start, static_cast(mHead + 1 - start)}; + mLastJumpBlock.content = sv; + return sv; + } + } + + mLastJumpBlock = {}; + return std::nullopt; + } + + Lexer::Block Lexer::getLastJumpBlock() const + { + return mLastJumpBlock; + } + + [[noreturn]] void Lexer::error(const std::string& msg) + { + throw LexerException(Misc::StringUtils::format("Line %zu Col %zu. %s", mLine + 1, mColumn, msg)); + } + + void Lexer::advance() + { + mAbsolutePos++; + mHead++; + mColumn++; + } + + char Lexer::head() + { + return *mHead; + } + + bool Lexer::peekChar(char c) + { + if (mHead == mTail) + return false; + return *(mHead + 1) == c; + } + + Token Lexer::scanToken() + { + while (true) + { + if (mHead == mTail) + return {Eof{}}; + + if (head() == '\n') + { + mLine++; + mColumn = 0; + } + + if (!std::isspace(head())) + break; + + advance(); + } + + if (head() == '\"') + return scanStringLiteral(); + + if (std::isalpha(head())) + return scanLiteral(); + + if (std::isdigit(head()) || head() == '.' || head() == '-') + return scanNumber(); + + switch(head()) + { + case '=': + advance(); + return {Equal{}}; + case '{': + advance(); + return {Open_bracket{}}; + case '}': + advance(); + return {Close_bracket{}}; + case '(': + advance(); + return {Open_Parenthesis{}}; + case ')': + advance(); + return {Close_Parenthesis{}}; + case '\"': + advance(); + return {Quote{}}; + case ':': + advance(); + return {Colon{}}; + case ';': + advance(); + return {SemiColon{}}; + case '|': + advance(); + return {VBar{}}; + case ',': + advance(); + return {Comma{}}; + default: + error(Misc::StringUtils::format("unexpected token <%c>", head())); + } + } + + Token Lexer::scanLiteral() + { + auto start = mHead; + advance(); + + while (mHead != mTail && (std::isalnum(head()) || head() == '_')) + advance(); + + std::string_view value{start, static_cast(mHead - start)}; + + if (value == "shared") return Shared{}; + if (value == "technique") return Technique{}; + if (value == "main_pass") return Main_Pass{}; + if (value == "render_target") return Render_Target{}; + if (value == "vertex") return Vertex{}; + if (value == "fragment") return Fragment{}; + if (value == "compute") return Compute{}; + if (value == "sampler_1d") return Sampler_1D{}; + if (value == "sampler_2d") return Sampler_2D{}; + if (value == "sampler_3d") return Sampler_3D{}; + if (value == "uniform_bool") return Uniform_Bool{}; + if (value == "uniform_float") return Uniform_Float{}; + if (value == "uniform_int") return Uniform_Int{}; + if (value == "uniform_vec2") return Uniform_Vec2{}; + if (value == "uniform_vec3") return Uniform_Vec3{}; + if (value == "uniform_vec4") return Uniform_Vec4{}; + if (value == "true") return True{}; + if (value == "false") return False{}; + if (value == "vec2") return Vec2{}; + if (value == "vec3") return Vec3{}; + if (value == "vec4") return Vec4{}; + + return Literal{value}; + } + + Token Lexer::scanStringLiteral() + { + advance(); // consume quote + auto start = mHead; + + bool terminated = false; + + for (; mHead != mTail; advance()) + { + if (head() == '\"') + { + terminated = true; + advance(); + break; + } + } + + if (!terminated) + error("unterminated string"); + + return String{{start, static_cast(mHead - start - 1)}}; + } + + Token Lexer::scanNumber() + { + double buffer; + + char* endPtr; + buffer = std::strtod(mHead, &endPtr); + + if (endPtr == nullptr) + error("critical error while parsing number"); + + const char* tmp = mHead; + mHead = endPtr; + + for (; tmp != endPtr; ++tmp) + { + if ((*tmp == '.')) + return Float{static_cast(buffer)}; + } + + return Integer{static_cast(buffer)}; + } + } +} diff --git a/components/fx/lexer.hpp b/components/fx/lexer.hpp new file mode 100644 index 0000000000..e24239399c --- /dev/null +++ b/components/fx/lexer.hpp @@ -0,0 +1,75 @@ +#ifndef OPENMW_COMPONENTS_FX_LEXER_H +#define OPENMW_COMPONENTS_FX_LEXER_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "lexer_types.hpp" + +namespace fx +{ + namespace Lexer + { + struct LexerException : std::runtime_error + { + LexerException(const std::string& message) : std::runtime_error(message) {} + LexerException(const char* message) : std::runtime_error(message) {} + }; + + class Lexer + { + public: + struct Block + { + int line; + std::string_view content; + }; + + Lexer(std::string_view buffer); + Lexer() = delete; + + Token next(); + Token peek(); + + // Jump ahead to next uncommented closing bracket at level zero. Assumes the head is at an opening bracket. + // Returns the contents of the block excluding the brackets and places cursor at closing bracket. + std::optional jump(); + + Block getLastJumpBlock() const; + + [[noreturn]] void error(const std::string& msg); + + private: + void drop(); + void advance(); + char head(); + bool peekChar(char c); + + Token scanToken(); + Token scanLiteral(); + Token scanStringLiteral(); + Token scanNumber(); + + const char* mHead; + const char* mTail; + std::size_t mAbsolutePos; + std::size_t mColumn; + std::size_t mLine; + std::string_view mBuffer; + Token mLastToken; + std::optional mLookahead; + + Block mLastJumpBlock; + }; + } +} + +#endif diff --git a/components/fx/lexer_types.hpp b/components/fx/lexer_types.hpp new file mode 100644 index 0000000000..0d81c483b7 --- /dev/null +++ b/components/fx/lexer_types.hpp @@ -0,0 +1,56 @@ +#ifndef OPENMW_COMPONENTS_FX_LEXER_TYPES_H +#define OPENMW_COMPONENTS_FX_LEXER_TYPES_H + +#include +#include + +namespace fx +{ + namespace Lexer + { + struct Float { inline static constexpr std::string_view repr = "float"; float value = 0.0;}; + struct Integer { inline static constexpr std::string_view repr = "integer"; int value = 0;}; + struct Boolean { inline static constexpr std::string_view repr = "boolean"; bool value = false;}; + struct Literal { inline static constexpr std::string_view repr = "literal"; std::string_view value;}; + struct String { inline static constexpr std::string_view repr = "string"; std::string_view value;}; + struct Shared { inline static constexpr std::string_view repr = "shared"; }; + struct Vertex { inline static constexpr std::string_view repr = "vertex"; }; + struct Fragment { inline static constexpr std::string_view repr = "fragment"; }; + struct Compute { inline static constexpr std::string_view repr = "compute"; }; + struct Technique { inline static constexpr std::string_view repr = "technique"; }; + struct Main_Pass { inline static constexpr std::string_view repr = "main_pass"; }; + struct Render_Target { inline static constexpr std::string_view repr = "render_target"; }; + struct Sampler_1D { inline static constexpr std::string_view repr = "sampler_1d"; }; + struct Sampler_2D { inline static constexpr std::string_view repr = "sampler_2d"; }; + struct Sampler_3D { inline static constexpr std::string_view repr = "sampler_3d"; }; + struct Uniform_Bool { inline static constexpr std::string_view repr = "uniform_bool"; }; + struct Uniform_Float { inline static constexpr std::string_view repr = "uniform_float"; }; + struct Uniform_Int { inline static constexpr std::string_view repr = "uniform_int"; }; + struct Uniform_Vec2 { inline static constexpr std::string_view repr = "uniform_vec2"; }; + struct Uniform_Vec3 { inline static constexpr std::string_view repr = "uniform_vec3"; }; + struct Uniform_Vec4 { inline static constexpr std::string_view repr = "uniform_vec4"; }; + struct Eof { inline static constexpr std::string_view repr = "eof"; }; + struct Equal { inline static constexpr std::string_view repr = "equal"; }; + struct Open_bracket { inline static constexpr std::string_view repr = "open_bracket"; }; + struct Close_bracket { inline static constexpr std::string_view repr = "close_bracket"; }; + struct Open_Parenthesis { inline static constexpr std::string_view repr = "open_parenthesis"; }; + struct Close_Parenthesis{ inline static constexpr std::string_view repr = "close_parenthesis"; }; + struct Quote { inline static constexpr std::string_view repr = "quote"; }; + struct SemiColon { inline static constexpr std::string_view repr = "semicolon"; }; + struct Comma { inline static constexpr std::string_view repr = "comma"; }; + struct VBar { inline static constexpr std::string_view repr = "vbar"; }; + struct Colon { inline static constexpr std::string_view repr = "colon"; }; + struct True { inline static constexpr std::string_view repr = "true"; }; + struct False { inline static constexpr std::string_view repr = "false"; }; + struct Vec2 { inline static constexpr std::string_view repr = "vec2"; }; + struct Vec3 { inline static constexpr std::string_view repr = "vec3"; }; + struct Vec4 { inline static constexpr std::string_view repr = "vec4"; }; + + using Token = std::variant; + } +} + +#endif \ No newline at end of file diff --git a/components/fx/parse_constants.hpp b/components/fx/parse_constants.hpp new file mode 100644 index 0000000000..18d32ee53a --- /dev/null +++ b/components/fx/parse_constants.hpp @@ -0,0 +1,133 @@ +#ifndef OPENMW_COMPONENTS_FX_PARSE_CONSTANTS_H +#define OPENMW_COMPONENTS_FX_PARSE_CONSTANTS_H + +#include +#include + +#include +#include +#include +#include + +#include + +#include "technique.hpp" + +namespace fx +{ + namespace constants + { + constexpr std::array, 6> TechniqueFlag = {{ + {"disable_interiors" , Technique::Flag_Disable_Interiors}, + {"disable_exteriors" , Technique::Flag_Disable_Exteriors}, + {"disable_underwater" , Technique::Flag_Disable_Underwater}, + {"disable_abovewater" , Technique::Flag_Disable_Abovewater}, + {"disable_sunglare" , Technique::Flag_Disable_SunGlare}, + {"hidden" , Technique::Flag_Hidden} + }}; + + constexpr std::array, 6> SourceFormat = {{ + {"red" , GL_RED}, + {"rg" , GL_RG}, + {"rgb" , GL_RGB}, + {"bgr" , GL_BGR}, + {"rgba", GL_RGBA}, + {"bgra", GL_BGRA}, + }}; + + constexpr std::array, 9> SourceType = {{ + {"byte" , GL_BYTE}, + {"unsigned_byte" , GL_UNSIGNED_BYTE}, + {"short" , GL_SHORT}, + {"unsigned_short" , GL_UNSIGNED_SHORT}, + {"int" , GL_INT}, + {"unsigned_int" , GL_UNSIGNED_INT}, + {"unsigned_int_24_8", GL_UNSIGNED_INT_24_8}, + {"float" , GL_FLOAT}, + {"double" , GL_DOUBLE}, + }}; + + constexpr std::array, 16> InternalFormat = {{ + {"red" , GL_RED}, + {"r16f" , GL_R16F}, + {"r32f" , GL_R32F}, + {"rg" , GL_RG}, + {"rg16f" , GL_RG16F}, + {"rg32f" , GL_RG32F}, + {"rgb" , GL_RGB}, + {"rgb16f" , GL_RGB16F}, + {"rgb32f" , GL_RGB32F}, + {"rgba" , GL_RGBA}, + {"rgba16f" , GL_RGBA16F}, + {"rgba32f" , GL_RGBA32F}, + {"depth_component16" , GL_DEPTH_COMPONENT16}, + {"depth_component24" , GL_DEPTH_COMPONENT24}, + {"depth_component32" , GL_DEPTH_COMPONENT32}, + {"depth_component32f", GL_DEPTH_COMPONENT32F} + }}; + + constexpr std::array, 13> Compression = {{ + {"auto" , osg::Texture::USE_USER_DEFINED_FORMAT}, + {"arb" , osg::Texture::USE_ARB_COMPRESSION}, + {"s3tc_dxt1" , osg::Texture::USE_S3TC_DXT1_COMPRESSION}, + {"s3tc_dxt3" , osg::Texture::USE_S3TC_DXT3_COMPRESSION}, + {"s3tc_dxt5" , osg::Texture::USE_S3TC_DXT5_COMPRESSION}, + {"pvrtc_2bpp" , osg::Texture::USE_PVRTC_2BPP_COMPRESSION}, + {"pvrtc_4bpp" , osg::Texture::USE_PVRTC_4BPP_COMPRESSION}, + {"etc" , osg::Texture::USE_ETC_COMPRESSION}, + {"etc2" , osg::Texture::USE_ETC2_COMPRESSION}, + {"rgtc1" , osg::Texture::USE_RGTC1_COMPRESSION}, + {"rgtc2" , osg::Texture::USE_RGTC2_COMPRESSION}, + {"s3tc_dxt1c" , osg::Texture::USE_S3TC_DXT1c_COMPRESSION}, + {"s3tc_dxt1a" , osg::Texture::USE_S3TC_DXT1a_COMPRESSION} + }}; + + constexpr std::array, 6> WrapMode = {{ + {"clamp" , osg::Texture::CLAMP}, + {"clamp_to_edge" , osg::Texture::CLAMP_TO_EDGE}, + {"clamp_to_border", osg::Texture::CLAMP_TO_BORDER}, + {"repeat" , osg::Texture::REPEAT}, + {"mirror" , osg::Texture::MIRROR} + }}; + + constexpr std::array, 6> FilterMode = {{ + {"linear" , osg::Texture::LINEAR}, + {"linear_mipmap_linear" , osg::Texture::LINEAR_MIPMAP_LINEAR}, + {"linear_mipmap_nearest" , osg::Texture::LINEAR_MIPMAP_NEAREST}, + {"nearest" , osg::Texture::NEAREST}, + {"nearest_mipmap_linear" , osg::Texture::NEAREST_MIPMAP_LINEAR}, + {"nearest_mipmap_nearest", osg::Texture::NEAREST_MIPMAP_NEAREST} + }}; + + constexpr std::array, 15> BlendFunc = {{ + {"dst_alpha" , osg::BlendFunc::DST_ALPHA}, + {"dst_color" , osg::BlendFunc::DST_COLOR}, + {"one" , osg::BlendFunc::ONE}, + {"one_minus_dst_alpha" , osg::BlendFunc::ONE_MINUS_DST_ALPHA}, + {"one_minus_dst_color" , osg::BlendFunc::ONE_MINUS_DST_COLOR}, + {"one_minus_src_alpha" , osg::BlendFunc::ONE_MINUS_SRC_ALPHA}, + {"one_minus_src_color" , osg::BlendFunc::ONE_MINUS_SRC_COLOR}, + {"src_alpha" , osg::BlendFunc::SRC_ALPHA}, + {"src_alpha_saturate" , osg::BlendFunc::SRC_ALPHA_SATURATE}, + {"src_color" , osg::BlendFunc::SRC_COLOR}, + {"constant_color" , osg::BlendFunc::CONSTANT_COLOR}, + {"one_minus_constant_color" , osg::BlendFunc::ONE_MINUS_CONSTANT_COLOR}, + {"constant_alpha" , osg::BlendFunc::CONSTANT_ALPHA}, + {"one_minus_constant_alpha" , osg::BlendFunc::ONE_MINUS_CONSTANT_ALPHA}, + {"zero" , osg::BlendFunc::ZERO} + }}; + + constexpr std::array, 8> BlendEquation = {{ + {"rgba_min" , osg::BlendEquation::RGBA_MIN}, + {"rgba_max" , osg::BlendEquation::RGBA_MAX}, + {"alpha_min" , osg::BlendEquation::ALPHA_MIN}, + {"alpha_max" , osg::BlendEquation::ALPHA_MAX}, + {"logic_op" , osg::BlendEquation::LOGIC_OP}, + {"add" , osg::BlendEquation::FUNC_ADD}, + {"subtract" , osg::BlendEquation::FUNC_SUBTRACT}, + {"reverse_subtract" , osg::BlendEquation::FUNC_REVERSE_SUBTRACT} + }}; + } +} + +#endif \ No newline at end of file diff --git a/components/fx/pass.cpp b/components/fx/pass.cpp new file mode 100644 index 0000000000..0d86bdcf50 --- /dev/null +++ b/components/fx/pass.cpp @@ -0,0 +1,253 @@ +#include "pass.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "technique.hpp" +#include "stateupdater.hpp" + +namespace +{ + constexpr char s_DefaultVertex[] = R"GLSL( +#if OMW_USE_BINDINGS + omw_In vec2 omw_Vertex; +#endif +omw_Out vec2 omw_TexCoord; + +void main() +{ + omw_Position = vec4(omw_Vertex.xy, 0.0, 1.0); + omw_TexCoord = omw_Position.xy * 0.5 + 0.5; +})GLSL"; + +} + +namespace fx +{ + Pass::Pass(Pass::Type type, Pass::Order order, bool ubo) + : mCompiled(false) + , mType(type) + , mOrder(order) + , mLegacyGLSL(true) + , mUBO(ubo) + { + } + + std::string Pass::getPassHeader(Technique& technique, std::string_view preamble, bool fragOut) + { + std::string header = R"GLSL( +#version @version @profile +@extensions + +@uboStruct + +#define OMW_REVERSE_Z @reverseZ +#define OMW_RADIAL_FOG @radialFog +#define OMW_HDR @hdr +#define OMW_NORMALS @normals +#define OMW_USE_BINDINGS @useBindings +#define OMW_MULTIVIEW @multiview +#define omw_In @in +#define omw_Out @out +#define omw_Position @position +#define omw_Texture1D @texture1D +#define omw_Texture2D @texture2D +#define omw_Texture3D @texture3D +#define omw_Vertex @vertex +#define omw_FragColor @fragColor + +@fragBinding + +uniform @builtinSampler omw_SamplerLastShader; +uniform @builtinSampler omw_SamplerLastPass; +uniform @builtinSampler omw_SamplerDepth; +uniform @builtinSampler omw_SamplerNormals; + +#if @ubo + layout(std140) uniform _data { _omw_data omw; }; +#else + uniform _omw_data omw; +#endif + + float omw_GetDepth(vec2 uv) + { +#if OMW_MULTIVIEW + float depth = omw_Texture2D(omw_SamplerDepth, vec3(uv, gl_ViewID_OVR)).r; +#else + float depth = omw_Texture2D(omw_SamplerDepth, uv).r; +#endif +#if OMW_REVERSE_Z + return 1.0 - depth; +#else + return depth; +#endif + } + + vec4 omw_GetLastShader(vec2 uv) + { +#if OMW_MULTIVIEW + return omw_Texture2D(omw_SamplerLastShader, vec3(uv, gl_ViewID_OVR)); +#else + return omw_Texture2D(omw_SamplerLastShader, uv); +#endif + } + + vec4 omw_GetLastPass(vec2 uv) + { +#if OMW_MULTIVIEW + return omw_Texture2D(omw_SamplerLastPass, vec3(uv, gl_ViewID_OVR)); +#else + return omw_Texture2D(omw_SamplerLastPass, uv); +#endif + } + + vec3 omw_GetNormals(vec2 uv) + { +#if OMW_MULTIVIEW + return omw_Texture2D(omw_SamplerNormals, vec3(uv, gl_ViewID_OVR)).rgb * 2.0 - 1.0; +#else + return omw_Texture2D(omw_SamplerNormals, uv).rgb * 2.0 - 1.0; +#endif + } + +#if OMW_HDR + uniform sampler2D omw_EyeAdaptation; +#endif + + float omw_GetEyeAdaptation() + { +#if OMW_HDR + return omw_Texture2D(omw_EyeAdaptation, vec2(0.5, 0.5)).r; +#else + return 1.0; +#endif + } +)GLSL"; + + std::stringstream extBlock; + for (const auto& extension : technique.getGLSLExtensions()) + extBlock << "#ifdef " << extension << '\n' << "\t#extension " << extension << ": enable" << '\n' << "#endif" << '\n'; + + const std::vector> defines = { + {"@version", std::to_string(technique.getGLSLVersion())}, + {"@multiview", Stereo::getMultiview() ? "1" : "0"}, + {"@builtinSampler", Stereo::getMultiview() ? "sampler2DArray" : "sampler2D"}, + {"@profile", technique.getGLSLProfile()}, + {"@extensions", extBlock.str()}, + {"@uboStruct", StateUpdater::getStructDefinition()}, + {"@ubo", mUBO ? "1" : "0"}, + {"@normals", technique.getNormals() ? "1" : "0"}, + {"@reverseZ", SceneUtil::AutoDepth::isReversed() ? "1" : "0"}, + {"@radialFog", Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"}, + {"@hdr", technique.getHDR() ? "1" : "0"}, + {"@in", mLegacyGLSL ? "varying" : "in"}, + {"@out", mLegacyGLSL ? "varying" : "out"}, + {"@position", "gl_Position"}, + {"@texture1D", mLegacyGLSL ? "texture1D" : "texture"}, + {"@texture2D", mLegacyGLSL ? "texture2D" : "texture"}, + {"@texture3D", mLegacyGLSL ? "texture3D" : "texture"}, + {"@vertex", mLegacyGLSL ? "gl_Vertex" : "_omw_Vertex"}, + {"@fragColor", mLegacyGLSL ? "gl_FragColor" : "_omw_FragColor"}, + {"@useBindings", mLegacyGLSL ? "0" : "1"}, + {"@fragBinding", mLegacyGLSL ? "" : "out vec4 omw_FragColor;"} + }; + + for (const auto& [define, value]: defines) + for (size_t pos = header.find(define); pos != std::string::npos; pos = header.find(define)) + header.replace(pos, define.size(), value); + + for (auto& uniform : technique.getUniformMap()) + if (auto glsl = uniform->getGLSL()) + header.append(glsl.value()); + + header.append(preamble); + + return header; + } + + void Pass::prepareStateSet(osg::StateSet* stateSet, const std::string& name) const + { + osg::ref_ptr program = new osg::Program; + if (mType == Type::Pixel) + { + program->addShader(new osg::Shader(*mVertex)); + program->addShader(new osg::Shader(*mFragment)); + } + else if (mType == Type::Compute) + { + program->addShader(new osg::Shader(*mCompute)); + } + + if (mUBO) + program->addBindUniformBlock("_data", static_cast(Resource::SceneManager::UBOBinding::PostProcessor)); + + program->setName(name); + + if (!mLegacyGLSL) + { + program->addBindFragDataLocation("_omw_FragColor", 0); + program->addBindAttribLocation("_omw_Vertex", 0); + } + + stateSet->setAttribute(program); + + if (mBlendSource && mBlendDest) + stateSet->setAttribute(new osg::BlendFunc(mBlendSource.value(), mBlendDest.value())); + + if (mBlendEq) + stateSet->setAttribute(new osg::BlendEquation(mBlendEq.value())); + + if (mClearColor) + stateSet->setAttribute(new SceneUtil::ClearColor(mClearColor.value(), GL_COLOR_BUFFER_BIT)); + } + + void Pass::dirty() + { + mVertex = nullptr; + mFragment = nullptr; + mCompute = nullptr; + mCompiled = false; + } + + void Pass::compile(Technique& technique, std::string_view preamble) + { + if (mCompiled) + return; + + mLegacyGLSL = technique.getGLSLVersion() != 330; + + if (mType == Type::Pixel) + { + if (!mVertex) + mVertex = new osg::Shader(osg::Shader::VERTEX, s_DefaultVertex); + + mVertex->setShaderSource(getPassHeader(technique, preamble).append(mVertex->getShaderSource())); + mFragment->setShaderSource(getPassHeader(technique, preamble, true).append(mFragment->getShaderSource())); + + mVertex->setName(mName); + mFragment->setName(mName); + } + else if (mType == Type::Compute) + { + mCompute->setShaderSource(getPassHeader(technique, preamble).append(mCompute->getShaderSource())); + mCompute->setName(mName); + } + + mCompiled = true; + } + +} diff --git a/components/fx/pass.hpp b/components/fx/pass.hpp new file mode 100644 index 0000000000..bd13c3f99b --- /dev/null +++ b/components/fx/pass.hpp @@ -0,0 +1,79 @@ +#ifndef OPENMW_COMPONENTS_FX_PASS_H +#define OPENMW_COMPONENTS_FX_PASS_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace fx +{ + class Technique; + + class Pass + { + public: + + enum class Order + { + Forward, + Post + }; + + enum class Type + { + None, + Pixel, + Compute + }; + + friend class Technique; + + Pass(Type type=Type::Pixel, Order order=Order::Post, bool ubo = false); + + void compile(Technique& technique, std::string_view preamble); + + std::string_view getTarget() const { return mTarget; } + + void prepareStateSet(osg::StateSet* stateSet, const std::string& name) const; + + std::string getName() const { return mName; } + + void dirty(); + + private: + std::string getPassHeader(Technique& technique, std::string_view preamble, bool fragOut = false); + + bool mCompiled; + + osg::ref_ptr mVertex; + osg::ref_ptr mFragment; + osg::ref_ptr mCompute; + + Type mType; + Order mOrder; + std::string mName; + bool mLegacyGLSL; + bool mUBO; + bool mSupportsNormals; + + std::string_view mTarget; + std::optional mClearColor; + + std::optional mBlendSource; + std::optional mBlendDest; + std::optional mBlendEq; + }; +} + +#endif diff --git a/components/fx/stateupdater.cpp b/components/fx/stateupdater.cpp new file mode 100644 index 0000000000..c362c16f54 --- /dev/null +++ b/components/fx/stateupdater.cpp @@ -0,0 +1,60 @@ +#include "stateupdater.hpp" + +#include +#include + +#include +#include + +namespace fx +{ + StateUpdater::StateUpdater(bool useUBO) : mUseUBO(useUBO) {} + + void StateUpdater::setDefaults(osg::StateSet* stateset) + { + if (mUseUBO) + { + osg::ref_ptr ubo = new osg::UniformBufferObject; + + osg::ref_ptr> data = new osg::BufferTemplate(); + data->setBufferObject(ubo); + + osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Resource::SceneManager::UBOBinding::PostProcessor), data, 0, mData.getGPUSize()); + + stateset->setAttributeAndModes(ubb, osg::StateAttribute::ON); + } + else + { + const auto createUniform = [&] (const auto& v) { + using T = std::decay_t; + std::string name = "omw." + std::string(T::sName); + stateset->addUniform(new osg::Uniform(name.c_str(), mData.get())); + }; + + std::apply([&] (const auto& ... v) { (createUniform(v) , ...); }, mData.getData()); + } + } + + void StateUpdater::apply(osg::StateSet* stateset, osg::NodeVisitor* nv) + { + if (mUseUBO) + { + osg::UniformBufferBinding* ubb = dynamic_cast(stateset->getAttribute(osg::StateAttribute::UNIFORMBUFFERBINDING, static_cast(Resource::SceneManager::UBOBinding::PostProcessor))); + + auto& dest = static_cast*>(ubb->getBufferData())->getData(); + mData.copyTo(dest); + + ubb->getBufferData()->dirty(); + } + else + { + const auto setUniform = [&] (const auto& v) { + using T = std::decay_t; + std::string name = "omw." + std::string(T::sName); + stateset->getUniform(name)->set(mData.get()); + }; + + std::apply([&] (const auto& ... v) { (setUniform(v) , ...); }, mData.getData()); + } + } +} \ No newline at end of file diff --git a/components/fx/stateupdater.hpp b/components/fx/stateupdater.hpp new file mode 100644 index 0000000000..25bb17fffa --- /dev/null +++ b/components/fx/stateupdater.hpp @@ -0,0 +1,192 @@ +#ifndef OPENMW_COMPONENTS_FX_STATEUPDATER_H +#define OPENMW_COMPONENTS_FX_STATEUPDATER_H + +#include + +#include +#include + +namespace fx +{ + class StateUpdater : public SceneUtil::StateSetUpdater + { + public: + StateUpdater(bool useUBO); + + void setProjectionMatrix(const osg::Matrixf& matrix) + { + mData.get() = matrix; + mData.get() = osg::Matrixf::inverse(matrix); + } + + void setViewMatrix(const osg::Matrixf& matrix) { mData.get() = matrix; } + + void setInvViewMatrix(const osg::Matrixf& matrix) { mData.get() = matrix; } + + void setPrevViewMatrix(const osg::Matrixf& matrix) { mData.get() = matrix;} + + void setEyePos(const osg::Vec3f& pos) { mData.get() = osg::Vec4f(pos, 0.f); } + + void setEyeVec(const osg::Vec3f& vec) { mData.get() = osg::Vec4f(vec, 0.f); } + + void setFogColor(const osg::Vec4f& color) { mData.get() = color; } + + void setSunColor(const osg::Vec4f& color) { mData.get() = color; } + + void setSunPos(const osg::Vec4f& pos, bool night) + { + mData.get() = pos; + + if (night) + mData.get().z() *= -1.f; + } + + void setResolution(const osg::Vec2f& size) + { + mData.get() = size; + mData.get() = {1.f / size.x(), 1.f / size.y()}; + } + + void setSunVis(float vis) + { + mData.get() = vis; + } + + void setFogRange(float near, float far) + { + mData.get() = near; + mData.get() = far; + } + + void setNearFar(float near, float far) + { + mData.get() = near; + mData.get() = far; + } + + void setIsUnderwater(bool underwater) { mData.get() = underwater; } + + void setIsInterior(bool interior) { mData.get() = interior; } + + void setFov(float fov) { mData.get() = fov; } + + void setGameHour(float hour) { mData.get() = hour; } + + void setWeatherId(int id) { mData.get() = id; } + + void setNextWeatherId(int id) { mData.get() = id; } + + void setWaterHeight(float height) { mData.get() = height; } + + void setSimulationTime(float time) { mData.get() = time; } + + void setDeltaSimulationTime(float time) { mData.get() = time; } + + void setWindSpeed(float speed) { mData.get() = speed; } + + void setWeatherTransition(float transition) { mData.get() = transition; } + + static std::string getStructDefinition() + { + static std::string definition = UniformData::getDefinition("_omw_data"); + return definition; + } + + private: + struct ProjectionMatrix : std140::Mat4 { static constexpr std::string_view sName = "projectionMatrix"; }; + + struct InvProjectionMatrix : std140::Mat4 { static constexpr std::string_view sName = "invProjectionMatrix"; }; + + struct ViewMatrix : std140::Mat4 { static constexpr std::string_view sName = "viewMatrix"; }; + + struct PrevViewMatrix : std140::Mat4 { static constexpr std::string_view sName = "prevViewMatrix"; }; + + struct InvViewMatrix : std140::Mat4 { static constexpr std::string_view sName = "invViewMatrix"; }; + + struct EyePos : std140::Vec4 { static constexpr std::string_view sName = "eyePos"; }; + + struct EyeVec : std140::Vec4 { static constexpr std::string_view sName = "eyeVec"; }; + + struct FogColor : std140::Vec4 { static constexpr std::string_view sName = "fogColor"; }; + + struct SunColor : std140::Vec4 { static constexpr std::string_view sName = "sunColor"; }; + + struct SunPos : std140::Vec4 { static constexpr std::string_view sName = "sunPos"; }; + + struct Resolution : std140::Vec2 { static constexpr std::string_view sName = "resolution"; }; + + struct RcpResolution : std140::Vec2 { static constexpr std::string_view sName = "rcpResolution"; }; + + struct FogNear : std140::Float { static constexpr std::string_view sName = "fogNear"; }; + + struct FogFar : std140::Float { static constexpr std::string_view sName = "fogFar"; }; + + struct Near : std140::Float { static constexpr std::string_view sName = "near"; }; + + struct Far : std140::Float { static constexpr std::string_view sName = "far"; }; + + struct Fov : std140::Float { static constexpr std::string_view sName = "fov"; }; + + struct GameHour : std140::Float { static constexpr std::string_view sName = "gameHour"; }; + + struct SunVis : std140::Float { static constexpr std::string_view sName = "sunVis"; }; + + struct WaterHeight : std140::Float { static constexpr std::string_view sName = "waterHeight"; }; + + struct SimulationTime : std140::Float { static constexpr std::string_view sName = "simulationTime"; }; + + struct DeltaSimulationTime : std140::Float { static constexpr std::string_view sName = "deltaSimulationTime"; }; + + struct WindSpeed : std140::Float { static constexpr std::string_view sName = "windSpeed"; }; + + struct WeatherTransition : std140::Float { static constexpr std::string_view sName = "weatherTransition"; }; + + struct WeatherID : std140::Int { static constexpr std::string_view sName = "weatherID"; }; + + struct NextWeatherID : std140::Int { static constexpr std::string_view sName = "nextWeatherID"; }; + + struct IsUnderwater : std140::Bool { static constexpr std::string_view sName = "isUnderwater"; }; + + struct IsInterior : std140::Bool { static constexpr std::string_view sName = "isInterior"; }; + + using UniformData = std140::UBO< + ProjectionMatrix, + InvProjectionMatrix, + ViewMatrix, + PrevViewMatrix, + InvViewMatrix, + EyePos, + EyeVec, + FogColor, + SunColor, + SunPos, + Resolution, + RcpResolution, + FogNear, + FogFar, + Near, + Far, + Fov, + GameHour, + SunVis, + WaterHeight, + SimulationTime, + DeltaSimulationTime, + WindSpeed, + WeatherTransition, + WeatherID, + NextWeatherID, + IsUnderwater, + IsInterior + >; + + private: + void setDefaults(osg::StateSet* stateset) override; + void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override; + + UniformData mData; + bool mUseUBO; + }; +} + +#endif \ No newline at end of file diff --git a/components/fx/technique.cpp b/components/fx/technique.cpp new file mode 100644 index 0000000000..86a6189fd7 --- /dev/null +++ b/components/fx/technique.cpp @@ -0,0 +1,1049 @@ +#include "technique.hpp" + +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "parse_constants.hpp" + +namespace +{ + struct ProxyTextureData + { + osg::Texture::WrapMode wrap_s = osg::Texture::CLAMP_TO_EDGE; + osg::Texture::WrapMode wrap_t = osg::Texture::CLAMP_TO_EDGE; + osg::Texture::WrapMode wrap_r = osg::Texture::CLAMP_TO_EDGE; + osg::Texture::FilterMode min_filter = osg::Texture::LINEAR_MIPMAP_LINEAR; + osg::Texture::FilterMode mag_filter =osg::Texture::LINEAR; + osg::Texture::InternalFormatMode compression = osg::Texture::USE_IMAGE_DATA_FORMAT; + std::optional source_format; + std::optional source_type; + std::optional internal_format; + }; +} + +namespace fx +{ + Technique::Technique(const VFS::Manager& vfs, Resource::ImageManager& imageManager, const std::string& name, int width, int height, bool ubo, bool supportsNormals) + : mName(name) + , mFileName((std::filesystem::path(Technique::sSubdir) / (mName + Technique::sExt)).string()) + , mLastModificationTime(std::filesystem::file_time_type()) + , mWidth(width) + , mHeight(height) + , mVFS(vfs) + , mImageManager(imageManager) + , mUBO(ubo) + , mSupportsNormals(supportsNormals) + { + clear(); + } + + void Technique::clear() + { + mTextures.clear(); + mStatus = Status::Uncompiled; + mDirty = false; + mValid = false; + mHDR = false; + mNormals = false; + mEnabled = true; + mPassMap.clear(); + mPasses.clear(); + mPassKeys.clear(); + mDefinedUniforms.clear(); + mRenderTargets.clear(); + mMainTemplate = nullptr; + mLastAppliedType = Pass::Type::None; + mFlags = 0; + mShared.clear(); + mAuthor = {}; + mDescription = {}; + mVersion = {}; + mGLSLExtensions.clear(); + mGLSLVersion = mUBO ? 330 : 120; + mGLSLProfile.clear(); + } + + std::string Technique::getBlockWithLineDirective() + { + auto block = mLexer->getLastJumpBlock(); + std::string content = std::string(block.content); + + content = "\n#line " + std::to_string(block.line + 1) + "\n" + std::string(block.content) + "\n"; + return content; + } + + Technique::UniformMap::iterator Technique::findUniform(const std::string& name) + { + return std::find_if(mDefinedUniforms.begin(), mDefinedUniforms.end(), [&name](const auto& uniform) + { + return uniform->mName == name; + }); + } + + bool Technique::compile() + { + clear(); + + if (!mVFS.exists(mFileName)) + { + Log(Debug::Error) << "Could not load technique, file does not exist '" << mFileName << "'"; + + mStatus = Status::File_Not_exists; + return false; + } + + try + { + std::string source(std::istreambuf_iterator(*mVFS.get(getFileName())), {}); + + parse(std::move(source)); + + if (mPassKeys.empty()) + error("no pass list found, ensure you define one in a 'technique' block"); + + int swaps = 0; + + for (auto name : mPassKeys) + { + auto it = mPassMap.find(name); + + if (it == mPassMap.end()) + error(Misc::StringUtils::format("pass '%s' was found in the pass list, but there was no matching 'fragment', 'vertex' or 'compute' block", std::string(name))); + + if (mLastAppliedType != Pass::Type::None && mLastAppliedType != it->second->mType) + { + swaps++; + if (swaps == 2) + Log(Debug::Warning) << "compute and pixel shaders are being swapped multiple times in shader chain, this can lead to serious performance drain."; + } + else + mLastAppliedType = it->second->mType; + + if (Stereo::getMultiview()) + { + mGLSLExtensions.insert("GL_OVR_multiview"); + mGLSLExtensions.insert("GL_OVR_multiview2"); + mGLSLExtensions.insert("GL_EXT_texture_array"); + } + + it->second->compile(*this, mShared); + + if (!it->second->mTarget.empty()) + { + auto rtIt = mRenderTargets.find(it->second->mTarget); + if (rtIt == mRenderTargets.end()) + error(Misc::StringUtils::format("target '%s' not defined", std::string(it->second->mTarget))); + } + + mPasses.emplace_back(it->second); + } + + if (mPasses.empty()) + error("invalid pass list, no passes defined for technique"); + + mValid = true; + } + catch(const std::runtime_error& e) + { + clear(); + mStatus = Status::Parse_Error; + + mLastError = "Failed parsing technique '" + getName() + "' " + e.what();; + Log(Debug::Error) << mLastError; + } + + return mValid; + } + + std::string Technique::getName() const + { + return mName; + } + + std::string Technique::getFileName() const + { + return mFileName; + } + + void Technique::setLastModificationTime(std::filesystem::file_time_type timeStamp, bool dirty) + { + if (dirty && mLastModificationTime != timeStamp) + mDirty = true; + + mLastModificationTime = timeStamp; + } + + [[noreturn]] void Technique::error(const std::string& msg) + { + mLexer->error(msg); + } + + template<> + void Technique::parseBlockImp() + { + if (!mLexer->jump()) + error(Misc::StringUtils::format("unterminated 'shared' block, expected closing brackets")); + + if (!mShared.empty()) + error("repeated 'shared' block, only one allowed per technique file"); + + mShared = getBlockWithLineDirective(); + } + + template<> + void Technique::parseBlockImp() + { + if (!mPassKeys.empty()) + error("exactly one 'technique' block can appear per file"); + + while (!isNext() && !isNext()) + { + expect(); + + auto key = std::get(mToken).value; + + expect(); + + if (key == "passes") + mPassKeys = parseLiteralList(); + else if (key == "version") + mVersion = parseString(); + else if (key == "description") + mDescription = parseString(); + else if (key == "author") + mAuthor = parseString(); + else if (key == "glsl_version") + { + int version = parseInteger(); + if (mUBO && version > 330) + mGLSLVersion = version; + } + else if (key == "flags") + mFlags = parseFlags(); + else if (key == "hdr") + mHDR = parseBool(); + else if (key == "pass_normals") + mNormals = parseBool() && mSupportsNormals; + else if (key == "glsl_profile") + { + expect(); + mGLSLProfile = std::string(std::get(mToken).value); + } + else if (key == "glsl_extensions") + { + for (const auto& ext : parseLiteralList()) + mGLSLExtensions.emplace(ext); + } + else + error(Misc::StringUtils::format("unexpected key '%s'", std::string{key})); + + expect(); + } + + if (mPassKeys.empty()) + error("pass list in 'technique' block cannot be empty."); + } + + template<> + void Technique::parseBlockImp() + { + if (mMainTemplate) + error("duplicate 'main_pass' block"); + + if (mName != "main") + error("'main_pass' block can only be defined in the 'main.omwfx' technique file"); + + mMainTemplate = new osg::Texture2D; + + mMainTemplate->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + mMainTemplate->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + + while (!isNext() && !isNext()) + { + expect(); + + auto key = std::get(mToken).value; + + expect(); + + if (key == "wrap_s") + mMainTemplate->setWrap(osg::Texture::WRAP_S, parseWrapMode()); + else if (key == "wrap_t") + mMainTemplate->setWrap(osg::Texture::WRAP_T, parseWrapMode()); + // Skip depth attachments for main scene, as some engine settings rely on specific depth formats. + // Allowing this to be overriden will cause confusion. + else if (key == "internal_format") + mMainTemplate->setInternalFormat(parseInternalFormat()); + else if (key == "source_type") + mMainTemplate->setSourceType(parseSourceType()); + else if (key == "source_format") + mMainTemplate->setSourceFormat(parseSourceFormat()); + else + error(Misc::StringUtils::format("unexpected key '%s'", std::string(key))); + + expect(); + } + } + + template<> + void Technique::parseBlockImp() + { + if (mRenderTargets.count(mBlockName)) + error(Misc::StringUtils::format("redeclaration of render target '%s'", std::string(mBlockName))); + + fx::Types::RenderTarget rt; + rt.mTarget->setTextureSize(mWidth, mHeight); + rt.mTarget->setSourceFormat(GL_RGB); + rt.mTarget->setInternalFormat(GL_RGB); + rt.mTarget->setSourceType(GL_UNSIGNED_BYTE); + + while (!isNext() && !isNext()) + { + expect(); + + auto key = std::get(mToken).value; + + expect(); + + if (key == "min_filter") + rt.mTarget->setFilter(osg::Texture2D::MIN_FILTER, parseFilterMode()); + else if (key == "mag_filter") + rt.mTarget->setFilter(osg::Texture2D::MAG_FILTER, parseFilterMode()); + else if (key == "wrap_s") + rt.mTarget->setWrap(osg::Texture2D::WRAP_S, parseWrapMode()); + else if (key == "wrap_t") + rt.mTarget->setWrap(osg::Texture2D::WRAP_T, parseWrapMode()); + else if (key == "width_ratio") + rt.mSize.mWidthRatio = parseFloat(); + else if (key == "height_ratio") + rt.mSize.mHeightRatio = parseFloat(); + else if (key == "width") + rt.mSize.mWidth = parseInteger(); + else if (key == "height") + rt.mSize.mHeight = parseInteger(); + else if (key == "internal_format") + rt.mTarget->setInternalFormat(parseInternalFormat()); + else if (key == "source_type") + rt.mTarget->setSourceType(parseSourceType()); + else if (key == "source_format") + rt.mTarget->setSourceFormat(parseSourceFormat()); + else if (key == "mipmaps") + rt.mMipMap = parseBool(); + else + error(Misc::StringUtils::format("unexpected key '%s'", std::string(key))); + + expect(); + } + + mRenderTargets.emplace(mBlockName, std::move(rt)); + } + + template<> + void Technique::parseBlockImp() + { + if (!mLexer->jump()) + error(Misc::StringUtils::format("unterminated 'vertex' block, expected closing brackets")); + + auto& pass = mPassMap[mBlockName]; + + if (!pass) + pass = std::make_shared(); + + pass->mName = mBlockName; + + if (pass->mCompute) + error(Misc::StringUtils::format("'compute' block already defined. Usage is ambiguous.")); + else if (!pass->mVertex) + pass->mVertex = new osg::Shader(osg::Shader::VERTEX, getBlockWithLineDirective()); + else + error(Misc::StringUtils::format("duplicate vertex shader for block '%s'", std::string(mBlockName))); + + pass->mType = Pass::Type::Pixel; + } + + template<> + void Technique::parseBlockImp() + { + if (!mLexer->jump()) + error(Misc::StringUtils::format("unterminated 'fragment' block, expected closing brackets")); + + auto& pass = mPassMap[mBlockName]; + + if (!pass) + pass = std::make_shared(); + + pass->mUBO = mUBO; + pass->mName = mBlockName; + + if (pass->mCompute) + error(Misc::StringUtils::format("'compute' block already defined. Usage is ambiguous.")); + else if (!pass->mFragment) + pass->mFragment = new osg::Shader(osg::Shader::FRAGMENT, getBlockWithLineDirective()); + else + error(Misc::StringUtils::format("duplicate vertex shader for block '%s'", std::string(mBlockName))); + + pass->mType = Pass::Type::Pixel; + } + + template<> + void Technique::parseBlockImp() + { + if (!mLexer->jump()) + error(Misc::StringUtils::format("unterminated 'compute' block, expected closing brackets")); + + auto& pass = mPassMap[mBlockName]; + + if (!pass) + pass = std::make_shared(); + + pass->mName = mBlockName; + + if (pass->mFragment) + error(Misc::StringUtils::format("'fragment' block already defined. Usage is ambiguous.")); + else if (pass->mVertex) + error(Misc::StringUtils::format("'vertex' block already defined. Usage is ambiguous.")); + else if (!pass->mFragment) + pass->mCompute = new osg::Shader(osg::Shader::COMPUTE, getBlockWithLineDirective()); + else + error(Misc::StringUtils::format("duplicate vertex shader for block '%s'", std::string(mBlockName))); + + pass->mType = Pass::Type::Compute; + } + + template + void Technique::parseSampler() + { + if (findUniform(std::string(mBlockName)) != mDefinedUniforms.end()) + error(Misc::StringUtils::format("redeclaration of uniform '%s'", std::string(mBlockName))); + + ProxyTextureData proxy; + osg::ref_ptr sampler; + + constexpr bool is1D = std::is_same_v; + constexpr bool is3D = std::is_same_v; + + Types::SamplerType type; + + while (!isNext() && !isNext()) + { + expect(); + + auto key = asLiteral(); + + expect(); + + if (!is1D && key == "min_filter") + proxy.min_filter = parseFilterMode(); + else if (!is1D && key == "mag_filter") + proxy.mag_filter = parseFilterMode(); + else if (key == "wrap_s") + proxy.wrap_s = parseWrapMode(); + else if (key == "wrap_t") + proxy.wrap_t = parseWrapMode(); + else if (is3D && key == "wrap_r") + proxy.wrap_r = parseWrapMode(); + else if (key == "compression") + proxy.compression = parseCompression(); + else if (key == "source_type") + proxy.source_type = parseSourceType(); + else if (key == "source_format") + proxy.source_format = parseSourceFormat(); + else if (key == "internal_format") + proxy.internal_format = parseInternalFormat(); + else if (key == "source") + { + expect(); + auto image = mImageManager.getImage(std::string{std::get(mToken).value}); + if constexpr (is1D) + { + type = Types::SamplerType::Texture_1D; + sampler = new osg::Texture1D(image); + } + else if constexpr (is3D) + { + type = Types::SamplerType::Texture_3D; + sampler = new osg::Texture3D(image); + } + else + { + type = Types::SamplerType::Texture_2D; + sampler = new osg::Texture2D(image); + } + } + else + error(Misc::StringUtils::format("unexpected key '%s'", std::string{key})); + + expect(); + } + if (!sampler) + error(Misc::StringUtils::format("%s '%s' requires a filename", std::string(T::repr), std::string{mBlockName})); + + if (!is1D) + { + sampler->setFilter(osg::Texture::MIN_FILTER, proxy.min_filter); + sampler->setFilter(osg::Texture::MAG_FILTER, proxy.mag_filter); + } + if (is3D) + sampler->setWrap(osg::Texture::WRAP_R, proxy.wrap_r); + sampler->setWrap(osg::Texture::WRAP_S, proxy.wrap_s); + sampler->setWrap(osg::Texture::WRAP_T, proxy.wrap_t); + sampler->setInternalFormatMode(proxy.compression); + if (proxy.internal_format.has_value()) + sampler->setInternalFormat(proxy.internal_format.value()); + if (proxy.source_type.has_value()) + sampler->setSourceType(proxy.source_type.value()); + if (proxy.internal_format.has_value()) + sampler->setSourceFormat(proxy.internal_format.value()); + sampler->setName(std::string{mBlockName}); + + mTextures.emplace_back(sampler); + + std::shared_ptr uniform = std::make_shared(); + uniform->mSamplerType = type; + uniform->mName = std::string(mBlockName); + mDefinedUniforms.emplace_back(std::move(uniform)); + } + + template + void Technique::parseUniform() + { + if (findUniform(std::string(mBlockName)) != mDefinedUniforms.end()) + error(Misc::StringUtils::format("redeclaration of uniform '%s'", std::string(mBlockName))); + + std::shared_ptr uniform = std::make_shared(); + Types::Uniform data; + + while (!isNext() && !isNext()) + { + expect(); + + auto key = asLiteral(); + + expect("error parsing config for uniform block"); + + constexpr bool isVec = std::is_same_v || std::is_same_v || std::is_same_v; + constexpr bool isFloat = std::is_same_v; + constexpr bool isInt = std::is_same_v; + + std::optional step; + + if constexpr (isInt) + step = 1.0; + + if (key == "default") + { + if constexpr (isVec) + data.mDefault = parseVec(); + else if constexpr (isFloat) + data.mDefault = parseFloat(); + else if constexpr (isInt) + data.mDefault = parseInteger(); + else + data.mDefault = parseBool(); + } + else if (key == "min") + { + if constexpr (isVec) + data.mMin = parseVec(); + else if constexpr (isFloat) + data.mMin = parseFloat(); + else if constexpr (isInt) + data.mMin = parseInteger(); + else + data.mMin = parseBool(); + } + else if (key == "max") + { + if constexpr (isVec) + data.mMax = parseVec(); + else if constexpr (isFloat) + data.mMax = parseFloat(); + else if constexpr (isInt) + data.mMax = parseInteger(); + else + data.mMax = parseBool(); + } + else if (key == "step") + step = parseFloat(); + else if (key == "static") + uniform->mStatic = parseBool(); + else if (key == "description") + { + expect(); + uniform->mDescription = std::get(mToken).value; + } + else if (key == "header") + { + expect(); + uniform->mHeader = std::get(mToken).value; + } + else + error(Misc::StringUtils::format("unexpected key '%s'", std::string{key})); + + if (step) + uniform->mStep = step.value(); + + expect(); + } + + uniform->mName = std::string(mBlockName); + uniform->mData = data; + uniform->mTechniqueName = mName; + + if (auto cached = Settings::ShaderManager::get().getValue(mName, uniform->mName)) + uniform->setValue(cached.value()); + + mDefinedUniforms.emplace_back(std::move(uniform)); + } + + template<> + void Technique::parseBlockImp() + { + parseSampler(); + } + + template<> + void Technique::parseBlockImp() + { + parseSampler(); + } + + template<> + void Technique::parseBlockImp() + { + parseSampler(); + } + + template<> + void Technique::parseBlockImp() + { + parseUniform(); + } + + template<> + void Technique::parseBlockImp() + { + parseUniform(); + } + + template<> + void Technique::parseBlockImp() + { + parseUniform(); + } + + template<> + void Technique::parseBlockImp() + { + parseUniform(); + } + + template<> + void Technique::parseBlockImp() + { + parseUniform(); + } + + template<> + void Technique::parseBlockImp() + { + parseUniform(); + } + + template + void Technique::expect(const std::string& err) + { + mToken = mLexer->next(); + if (!std::holds_alternative(mToken)) + { + if (err.empty()) + error(Misc::StringUtils::format("Expected %s", std::string(T::repr))); + else + error(Misc::StringUtils::format("%s. Expected %s", err, std::string(T::repr))); + } + } + + template + void Technique::expect(const std::string& err) + { + mToken = mLexer->next(); + if (!std::holds_alternative(mToken) && !std::holds_alternative(mToken)) + { + if (err.empty()) + error(Misc::StringUtils::format("%s. Expected %s or %s", err, std::string(T::repr), std::string(T2::repr))); + else + error(Misc::StringUtils::format("Expected %s or %s", std::string(T::repr), std::string(T2::repr))); + } + } + + template + bool Technique::isNext() + { + return std::holds_alternative(mLexer->peek()); + } + + void Technique::parse(std::string&& buffer) + { + mBuffer = std::move(buffer); + Misc::StringUtils::replaceAll(mBuffer, "\r\n", "\n"); + mLexer = std::make_unique(mBuffer); + + for (auto t = mLexer->next(); !std::holds_alternative(t); t = mLexer->next()) + { + std::visit([=](auto&& arg) { + using T = std::decay_t; + + if constexpr (std::is_same_v) + parseBlock(false); + else if constexpr (std::is_same_v) + parseBlock(false); + else if constexpr (std::is_same_v) + parseBlock(false); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else + error("invalid top level block"); + } + , t); + } + } + + template + void Technique::parseBlock(bool named) + { + mBlockName = T::repr; + + if (named) + { + expect("name is required for preceeding block decleration"); + + mBlockName = std::get(mToken).value; + + if (isNext()) + parseBlockHeader(); + } + + expect(); + + parseBlockImp(); + + expect(); + } + + template + std::vector Technique::parseLiteralList() + { + std::vector data; + + while (!isNext()) + { + expect(); + + data.emplace_back(std::get(mToken).value); + + if (!isNext()) + break; + + mLexer->next(); + } + + return data; + } + + void Technique::parseBlockHeader() + { + expect(); + + if (isNext()) + { + mLexer->next(); + return; + } + + auto& pass = mPassMap[mBlockName]; + + if (!pass) + pass = std::make_shared(); + + bool clear = true; + osg::Vec4f clearColor = {1,1,1,1}; + + while (!isNext()) + { + expect("invalid key in block header"); + + std::string_view key = std::get(mToken).value; + + expect(); + + if (key == "target") + { + expect(); + pass->mTarget = std::get(mToken).value; + } + else if (key == "blend") + { + expect(); + osg::BlendEquation::Equation blendEq = parseBlendEquation(); + expect(); + osg::BlendFunc::BlendFuncMode blendSrc = parseBlendFuncMode(); + expect(); + osg::BlendFunc::BlendFuncMode blendDest = parseBlendFuncMode(); + expect(); + + pass->mBlendSource = blendSrc; + pass->mBlendDest = blendDest; + if (blendEq != osg::BlendEquation::FUNC_ADD) + pass->mBlendEq = blendEq; + } + else if (key == "clear") + clear = parseBool(); + else if (key == "clear_color") + clearColor = parseVec(); + else + error(Misc::StringUtils::format("unrecognized key '%s' in block header", std::string(key))); + + mToken = mLexer->next(); + + if (std::holds_alternative(mToken)) + { + if (std::holds_alternative(mLexer->peek())) + error(Misc::StringUtils::format("leading comma in '%s' is not allowed", std::string(mBlockName))); + else + continue; + } + + if (std::holds_alternative(mToken)) + return; + } + + if (clear) + pass->mClearColor = clearColor; + + error("malformed block header"); + } + + std::string_view Technique::asLiteral() const + { + return std::get(mToken).value; + } + + FlagsType Technique::parseFlags() + { + auto parseBit = [this] (std::string_view term) { + for (const auto& [identifer, bit]: constants::TechniqueFlag) + { + if (Misc::StringUtils::ciEqual(term, identifer)) + return bit; + } + error(Misc::StringUtils::format("unrecognized flag '%s'", std::string(term))); + }; + + FlagsType flag = 0; + for (const auto& bit : parseLiteralList()) + flag |= parseBit(bit); + + return flag; + } + + osg::Texture::FilterMode Technique::parseFilterMode() + { + expect(); + + for (const auto& [identifer, mode]: constants::FilterMode) + { + if (asLiteral() == identifer) + return mode; + } + + error(Misc::StringUtils::format("unrecognized filter mode '%s'", std::string{asLiteral()})); + } + + osg::Texture::WrapMode Technique::parseWrapMode() + { + expect(); + + for (const auto& [identifer, mode]: constants::WrapMode) + { + if (asLiteral() == identifer) + return mode; + } + + error(Misc::StringUtils::format("unrecognized wrap mode '%s'", std::string{asLiteral()})); + } + + osg::Texture::InternalFormatMode Technique::parseCompression() + { + expect(); + + for (const auto& [identifer, mode]: constants::Compression) + { + if (asLiteral() == identifer) + return mode; + } + + error(Misc::StringUtils::format("unrecognized compression '%s'", std::string{asLiteral()})); + } + + int Technique::parseInternalFormat() + { + expect(); + + for (const auto& [identifer, mode]: constants::InternalFormat) + { + if (asLiteral() == identifer) + return mode; + } + + error(Misc::StringUtils::format("unrecognized internal format '%s'", std::string{asLiteral()})); + } + + int Technique::parseSourceType() + { + expect(); + + for (const auto& [identifer, mode]: constants::SourceType) + { + if (asLiteral() == identifer) + return mode; + } + + error(Misc::StringUtils::format("unrecognized source type '%s'", std::string{asLiteral()})); + } + + int Technique::parseSourceFormat() + { + expect(); + + for (const auto& [identifer, mode]: constants::SourceFormat) + { + if (asLiteral() == identifer) + return mode; + } + + error(Misc::StringUtils::format("unrecognized source format '%s'", std::string{asLiteral()})); + } + + osg::BlendEquation::Equation Technique::parseBlendEquation() + { + expect(); + + for (const auto& [identifer, mode]: constants::BlendEquation) + { + if (asLiteral() == identifer) + return mode; + } + + error(Misc::StringUtils::format("unrecognized blend equation '%s'", std::string{asLiteral()})); + } + + osg::BlendFunc::BlendFuncMode Technique::parseBlendFuncMode() + { + expect(); + + for (const auto& [identifer, mode]: constants::BlendFunc) + { + if (asLiteral() == identifer) + return mode; + } + + error(Misc::StringUtils::format("unrecognized blend function '%s'", std::string{asLiteral()})); + } + + bool Technique::parseBool() + { + mToken = mLexer->next(); + + if (std::holds_alternative(mToken)) + return true; + if (std::holds_alternative(mToken)) + return false; + + error("expected 'true' or 'false' as boolean value"); + } + + std::string_view Technique::parseString() + { + expect(); + + return std::get(mToken).value; + } + + float Technique::parseFloat() + { + mToken = mLexer->next(); + + if (std::holds_alternative(mToken)) + return std::get(mToken).value; + if (std::holds_alternative(mToken)) + return static_cast(std::get(mToken).value); + + error("expected float value"); + } + + int Technique::parseInteger() + { + expect(); + + return std::get(mToken).value; + } + + template + OSGVec Technique::parseVec() + { + expect(); + expect(); + + OSGVec value; + + for (int i = 0; i < OSGVec::num_components; ++i) + { + value[i] = parseFloat(); + + if (i < OSGVec::num_components - 1) + expect(); + } + + expect("check definition of the vector"); + + return value; + } +} diff --git a/components/fx/technique.hpp b/components/fx/technique.hpp new file mode 100644 index 0000000000..ca8ef96a52 --- /dev/null +++ b/components/fx/technique.hpp @@ -0,0 +1,300 @@ +#ifndef OPENMW_COMPONENTS_FX_TECHNIQUE_H +#define OPENMW_COMPONENTS_FX_TECHNIQUE_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pass.hpp" +#include "lexer.hpp" +#include "types.hpp" + +namespace Resource +{ + class ImageManager; +} + +namespace VFS +{ + class Manager; +} + +namespace fx +{ + using FlagsType = size_t; + + struct DispatchNode + { + DispatchNode() = default; + + DispatchNode(const DispatchNode& other, const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY) + : mHandle(other.mHandle) + , mFlags(other.mFlags) + , mRootStateSet(other.mRootStateSet) + { + mPasses.reserve(other.mPasses.size()); + + for (const auto& subpass : other.mPasses) + mPasses.emplace_back(subpass, copyOp); + } + + struct SubPass { + SubPass() = default; + + osg::ref_ptr mStateSet = new osg::StateSet; + osg::ref_ptr mRenderTarget; + osg::ref_ptr mRenderTexture; + + SubPass(const SubPass& other, const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY) + : mStateSet(new osg::StateSet(*other.mStateSet, copyOp)) + { + if (other.mRenderTarget) + mRenderTarget = new osg::FrameBufferObject(*other.mRenderTarget, copyOp); + if (other.mRenderTexture) + mRenderTexture = new osg::Texture2D(*other.mRenderTexture, copyOp); + } + }; + + // not safe to read/write in draw thread + std::shared_ptr mHandle = nullptr; + + FlagsType mFlags = 0; + + std::vector mPasses; + + osg::ref_ptr mRootStateSet = new osg::StateSet; + }; + + using DispatchArray = std::vector; + + class Technique + { + public: + using PassList = std::vector>; + using TexList = std::vector>; + + using UniformMap = std::vector>; + using RenderTargetMap = std::unordered_map; + + inline static std::string sExt = ".omwfx"; + inline static std::string sSubdir = "shaders"; + + enum class Status + { + Success, + Uncompiled, + File_Not_exists, + Parse_Error + }; + + static constexpr FlagsType Flag_Disable_Interiors = (1 << 0); + static constexpr FlagsType Flag_Disable_Exteriors = (1 << 1); + static constexpr FlagsType Flag_Disable_Underwater = (1 << 2); + static constexpr FlagsType Flag_Disable_Abovewater = (1 << 3); + static constexpr FlagsType Flag_Disable_SunGlare = (1 << 4); + static constexpr FlagsType Flag_Hidden = (1 << 5); + + Technique(const VFS::Manager& vfs, Resource::ImageManager& imageManager, const std::string& name, int width, int height, bool ubo, bool supportsNormals); + + bool compile(); + + std::string getName() const; + + std::string getFileName() const; + + void setLastModificationTime(std::filesystem::file_time_type timeStamp, bool dirty = true); + + bool isDirty() const { return mDirty; } + + void setDirty(bool dirty) { mDirty = dirty; } + + bool isValid() const { return mValid; } + + bool getHDR() const { return mHDR; } + + bool getNormals() const { return mNormals && mSupportsNormals; } + + const PassList& getPasses() { return mPasses; } + + const TexList& getTextures() const { return mTextures; } + + Status getStatus() const { return mStatus; } + + std::string_view getAuthor() const { return mAuthor; } + + std::string_view getDescription() const { return mDescription; } + + std::string_view getVersion() const { return mVersion; } + + int getGLSLVersion() const { return mGLSLVersion; } + + std::string getGLSLProfile() const { return mGLSLProfile; } + + const std::unordered_set& getGLSLExtensions() const { return mGLSLExtensions; } + + osg::ref_ptr getMainTemplate() const { return mMainTemplate; } + + FlagsType getFlags() const { return mFlags; } + + bool getHidden() const { return mFlags & Flag_Hidden; } + + UniformMap& getUniformMap() { return mDefinedUniforms; } + + RenderTargetMap& getRenderTargetsMap() { return mRenderTargets; } + + std::string getLastError() const { return mLastError; } + + UniformMap::iterator findUniform(const std::string& name); + + private: + [[noreturn]] void error(const std::string& msg); + + void clear(); + + std::string_view asLiteral() const; + + template + void expect(const std::string& err=""); + + template + void expect(const std::string& err=""); + + template + bool isNext(); + + void parse(std::string&& buffer); + + template + void parseUniform(); + + template + void parseSampler(); + + template + void parseBlock(bool named=true); + + template + void parseBlockImp() {} + + void parseBlockHeader(); + + bool parseBool(); + + std::string_view parseString(); + + float parseFloat(); + + int parseInteger(); + + int parseInternalFormat(); + + int parseSourceType(); + + int parseSourceFormat(); + + osg::BlendEquation::Equation parseBlendEquation(); + + osg::BlendFunc::BlendFuncMode parseBlendFuncMode(); + + osg::Texture::WrapMode parseWrapMode(); + + osg::Texture::InternalFormatMode parseCompression(); + + FlagsType parseFlags(); + + osg::Texture::FilterMode parseFilterMode(); + + template + std::vector parseLiteralList(); + + template + OSGVec parseVec(); + + std::string getBlockWithLineDirective(); + + std::unique_ptr mLexer; + Lexer::Token mToken; + + std::string mShared; + std::string mName; + std::string mFileName; + std::string_view mBlockName; + std::string_view mAuthor; + std::string_view mDescription; + std::string_view mVersion; + + std::unordered_set mGLSLExtensions; + int mGLSLVersion; + std::string mGLSLProfile; + + FlagsType mFlags; + + Status mStatus; + + bool mEnabled; + + std::filesystem::file_time_type mLastModificationTime; + bool mDirty; + bool mValid; + bool mHDR; + bool mNormals; + int mWidth; + int mHeight; + + osg::ref_ptr mMainTemplate; + RenderTargetMap mRenderTargets; + + TexList mTextures; + PassList mPasses; + + std::unordered_map> mPassMap; + std::vector mPassKeys; + + Pass::Type mLastAppliedType; + + UniformMap mDefinedUniforms; + + const VFS::Manager& mVFS; + Resource::ImageManager& mImageManager; + bool mUBO; + bool mSupportsNormals; + + std::string mBuffer; + + std::string mLastError; + }; + + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); +} + +#endif diff --git a/components/fx/types.hpp b/components/fx/types.hpp new file mode 100644 index 0000000000..f12810197f --- /dev/null +++ b/components/fx/types.hpp @@ -0,0 +1,259 @@ +#ifndef OPENMW_COMPONENTS_FX_TYPES_H +#define OPENMW_COMPONENTS_FX_TYPES_H + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "pass.hpp" + +namespace fx +{ + namespace Types + { + struct SizeProxy + { + std::optional mWidthRatio; + std::optional mHeightRatio; + std::optional mWidth; + std::optional mHeight; + + std::tuple get(int width, int height) const + { + int scaledWidth = width; + int scaledHeight = height; + + if (mWidthRatio) + scaledWidth = width * mWidthRatio.value(); + else if (mWidth) + scaledWidth = mWidth.value(); + + if (mHeightRatio > 0.f) + scaledHeight = height * mHeightRatio.value(); + else if (mHeight) + scaledHeight = mHeight.value(); + + return std::make_tuple(scaledWidth, scaledHeight); + } + }; + + struct RenderTarget + { + osg::ref_ptr mTarget = new osg::Texture2D; + SizeProxy mSize; + bool mMipMap = false; + }; + + template + struct Uniform + { + std::optional mValue; + T mDefault; + T mMin = std::numeric_limits::lowest(); + T mMax = std::numeric_limits::max(); + + using value_type = T; + + T getValue() const + { + return mValue.value_or(mDefault); + } + }; + + using Uniform_t = std::variant< + Uniform, + Uniform, + Uniform, + Uniform, + Uniform, + Uniform + >; + + enum SamplerType + { + Texture_1D, + Texture_2D, + Texture_3D + }; + + struct UniformBase + { + std::string mName; + std::string mHeader; + std::string mTechniqueName; + std::string mDescription; + + bool mStatic = true; + std::optional mSamplerType = std::nullopt; + double mStep; + + Uniform_t mData; + + template + T getValue() const + { + auto value = Settings::ShaderManager::get().getValue(mTechniqueName, mName); + + return value.value_or(std::get>(mData).getValue()); + } + + template + T getMin() const + { + return std::get>(mData).mMin; + } + + template + T getMax() const + { + return std::get>(mData).mMax; + } + + template + T getDefault() const + { + return std::get>(mData).mDefault; + } + + template + void setValue(const T& value) + { + std::visit([&, value](auto&& arg){ + using U = typename std::decay_t::value_type; + + if constexpr (std::is_same_v) + { + arg.mValue = value; + + if (mStatic) + Settings::ShaderManager::get().setValue(mTechniqueName, mName, value); + } + else + { + Log(Debug::Warning) << "Attempting to set uniform '" << mName << "' with wrong type"; + } + }, mData); + } + + void setUniform(osg::Uniform* uniform) + { + auto type = getType(); + if (!type || type.value() != uniform->getType()) + return; + + std::visit([&](auto&& arg) + { + const auto value = arg.getValue(); + uniform->set(value); + }, mData); + } + + std::optional getType() const + { + return std::visit([](auto&& arg) -> std::optional { + using T = typename std::decay_t::value_type; + + if constexpr (std::is_same_v) + return osg::Uniform::FLOAT_VEC2; + else if constexpr (std::is_same_v) + return osg::Uniform::FLOAT_VEC3; + else if constexpr (std::is_same_v) + return osg::Uniform::FLOAT_VEC4; + else if constexpr (std::is_same_v) + return osg::Uniform::FLOAT; + else if constexpr (std::is_same_v) + return osg::Uniform::INT; + else if constexpr (std::is_same_v) + return osg::Uniform::BOOL; + + return std::nullopt; + }, mData); + } + + std::optional getGLSL() + { + if (mSamplerType) + { + switch (mSamplerType.value()) + { + case Texture_1D: + return Misc::StringUtils::format("uniform sampler1D %s;", mName); + case Texture_2D: + return Misc::StringUtils::format("uniform sampler2D %s;", mName); + case Texture_3D: + return Misc::StringUtils::format("uniform sampler3D %s;", mName); + } + } + + bool useUniform = (Settings::ShaderManager::get().getMode() == Settings::ShaderManager::Mode::Debug || mStatic == false); + + return std::visit([&](auto&& arg) -> std::optional { + using T = typename std::decay_t::value_type; + + auto value = arg.getValue(); + + if constexpr (std::is_same_v) + { + if (useUniform) + return Misc::StringUtils::format("uniform vec2 %s;", mName); + + return Misc::StringUtils::format("const vec2 %s=vec2(%f,%f);", mName, value[0], value[1]); + } + else if constexpr (std::is_same_v) + { + if (useUniform) + return Misc::StringUtils::format("uniform vec3 %s;", mName); + + return Misc::StringUtils::format("const vec3 %s=vec3(%f,%f,%f);", mName, value[0], value[1], value[2]); + } + else if constexpr (std::is_same_v) + { + if (useUniform) + return Misc::StringUtils::format("uniform vec4 %s;", mName); + + return Misc::StringUtils::format("const vec4 %s=vec4(%f,%f,%f,%f);", mName, value[0], value[1], value[2], value[3]); + } + else if constexpr (std::is_same_v) + { + if (useUniform) + return Misc::StringUtils::format("uniform float %s;", mName); + + return Misc::StringUtils::format("const float %s=%f;", mName, value); + } + else if constexpr (std::is_same_v) + { + if (useUniform) + return Misc::StringUtils::format("uniform int %s;", mName); + + return Misc::StringUtils::format("const int %s=%i;", mName, value); + } + else if constexpr (std::is_same_v) + { + if (useUniform) + return Misc::StringUtils::format("uniform bool %s;", mName); + + return Misc::StringUtils::format("const bool %s=%s;", mName, value ? "true" : "false"); + } + + return std::nullopt; + + }, mData); + } + + }; + } +} + +#endif diff --git a/components/fx/widgets.cpp b/components/fx/widgets.cpp new file mode 100644 index 0000000000..2206fd8c7f --- /dev/null +++ b/components/fx/widgets.cpp @@ -0,0 +1,164 @@ +#include "widgets.hpp" + +#include + +namespace +{ + template + void createVectorWidget(const std::shared_ptr& uniform, MyGUI::Widget* client, fx::Widgets::UniformBase* base) + { + int height = client->getHeight(); + base->setSize(base->getSize().width, (base->getSize().height - height) + (height * T::num_components)); + client->setSize(client->getSize().width, height * T::num_components); + + for (int i = 0; i < T::num_components; ++i) + { + auto* widget = client->createWidget("MW_ValueEditNumber", {0, height * i, client->getWidth(), height}, MyGUI::Align::Default); + widget->setData(uniform, static_cast(i)); + base->addItem(widget); + } + } +} + +namespace fx +{ + namespace Widgets + { + void EditBool::setValue(bool value) + { + auto uniform = mUniform.lock(); + + if (!uniform) + return; + + mCheckbutton->setCaptionWithReplacing(value ? "#{sOn}" : "#{sOff}"); + + uniform->setValue(value); + } + + void EditBool::setValueFromUniform() + { + auto uniform = mUniform.lock(); + + if (!uniform) + return; + + setValue(uniform->template getValue()); + } + + void EditBool::toDefault() + { + auto uniform = mUniform.lock(); + + if (!uniform) + return; + + setValue(uniform->getDefault()); + } + + void EditBool::initialiseOverride() + { + Base::initialiseOverride(); + + assignWidget(mCheckbutton, "Checkbutton"); + + mCheckbutton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditBool::notifyMouseButtonClick); + } + + + void EditBool::notifyMouseButtonClick(MyGUI::Widget* sender) + { + auto uniform = mUniform.lock(); + + if (!uniform) + return; + + setValue(!uniform->getValue()); + } + + void UniformBase::init(const std::shared_ptr& uniform) + { + mLabel->setCaption(uniform->mName); + + if (uniform->mDescription.empty()) + { + mLabel->setUserString("ToolTipType", ""); + } + else + { + mLabel->setUserString("ToolTipType", "Layout"); + mLabel->setUserString("ToolTipLayout", "TextToolTip"); + mLabel->setUserString("Caption_Text", uniform->mDescription); + } + + std::visit([this, &uniform](auto&& arg) { + using T = typename std::decay_t::value_type; + + if constexpr (std::is_same_v) + { + createVectorWidget(uniform, mClient, this); + } + else if constexpr (std::is_same_v) + { + createVectorWidget(uniform, mClient, this); + } + else if constexpr (std::is_same_v) + { + createVectorWidget(uniform, mClient, this); + } + else if constexpr (std::is_same_v) + { + auto* widget = mClient->createWidget("MW_ValueEditNumber", {0, 0, mClient->getWidth(), mClient->getHeight()}, MyGUI::Align::Stretch); + widget->setData(uniform); + mBases.emplace_back(widget); + } + else if constexpr (std::is_same_v) + { + auto* widget = mClient->createWidget("MW_ValueEditNumber", {0, 0, mClient->getWidth(), mClient->getHeight()}, MyGUI::Align::Stretch); + widget->setData(uniform); + mBases.emplace_back(widget); + } + else if constexpr (std::is_same_v) + { + auto* widget = mClient->createWidget("MW_ValueEditBool", {0, 0, mClient->getWidth(), mClient->getHeight()}, MyGUI::Align::Stretch); + widget->setData(uniform); + mBases.emplace_back(widget); + } + + mReset->eventMouseButtonClick += MyGUI::newDelegate(this, &UniformBase::notifyResetClicked); + + for (EditBase* base : mBases) + base->setValueFromUniform(); + + }, uniform->mData); + } + + void UniformBase::addItem(EditBase* item) + { + mBases.emplace_back(item); + } + + void UniformBase::toDefault() + { + for (EditBase* base : mBases) + { + if (base) + base->toDefault(); + } + } + + void UniformBase::notifyResetClicked(MyGUI::Widget* sender) + { + toDefault(); + } + + void UniformBase::initialiseOverride() + { + Base::initialiseOverride(); + + assignWidget(mReset, "Reset"); + assignWidget(mLabel, "Label"); + assignWidget(mClient, "Client"); + } + } +} \ No newline at end of file diff --git a/components/fx/widgets.hpp b/components/fx/widgets.hpp new file mode 100644 index 0000000000..f15e676fb6 --- /dev/null +++ b/components/fx/widgets.hpp @@ -0,0 +1,266 @@ +#ifndef OPENMW_COMPONENTS_FX_WIDGETS_H +#define OPENMW_COMPONENTS_FX_WIDGETS_H + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "technique.hpp" +#include "types.hpp" + +namespace Gui +{ + class AutoSizedTextBox; + class AutoSizedButton; +} + +namespace fx +{ + namespace Widgets + { + enum Index + { + None = -1, + Zero = 0, + One = 1, + Two = 2, + Three = 3 + }; + + class EditBase + { + public: + virtual ~EditBase() = default; + + void setData(const std::shared_ptr& uniform, Index index = None) + { + mUniform = uniform; + mIndex = index; + } + + virtual void setValueFromUniform() = 0; + + virtual void toDefault() = 0; + + protected: + std::weak_ptr mUniform; + Index mIndex; + }; + + class EditBool : public EditBase, public MyGUI::Widget + { + MYGUI_RTTI_DERIVED(EditBool) + + public: + void setValue(bool value); + void setValueFromUniform() override; + void toDefault() override; + + private: + void initialiseOverride() override; + void notifyMouseButtonClick(MyGUI::Widget* sender); + + MyGUI::Button* mCheckbutton; + }; + + template + class EditNumber : public EditBase, public MyGUI::Widget + { + MYGUI_RTTI_DERIVED(EditNumber) + + public: + EditNumber() : mLastPointerX(0) {} + + void setValue(T value) + { + mValue = value; + if constexpr (std::is_floating_point_v) + mValueLabel->setCaption(Misc::StringUtils::format("%.3f", mValue)); + else + mValueLabel->setCaption(std::to_string(mValue)); + + if (auto uniform = mUniform.lock()) + { + if constexpr (std::is_fundamental_v) + uniform->template setValue(mValue); + else + { + UType uvalue = uniform->template getValue(); + uvalue[mIndex] = mValue; + uniform->template setValue(uvalue); + } + } + } + + void setValueFromUniform() override + { + if (auto uniform = mUniform.lock()) + { + T value; + + if constexpr (std::is_fundamental_v) + value = uniform->template getValue(); + else + value = uniform->template getValue()[mIndex]; + + setValue(value); + } + } + + void toDefault() override + { + if (auto uniform = mUniform.lock()) + { + if constexpr (std::is_fundamental_v) + setValue(uniform->template getDefault()); + else + setValue(uniform->template getDefault()[mIndex]); + } + } + + private: + + void initialiseOverride() override + { + Base::initialiseOverride(); + + assignWidget(mDragger, "Dragger"); + assignWidget(mValueLabel, "Value"); + assignWidget(mButtonIncrease, "ButtonIncrease"); + assignWidget(mButtonDecrease, "ButtonDecrease"); + + mButtonIncrease->eventMouseButtonClick += MyGUI::newDelegate(this, &EditNumber::notifyButtonClicked); + mButtonDecrease->eventMouseButtonClick += MyGUI::newDelegate(this, &EditNumber::notifyButtonClicked); + + mDragger->eventMouseButtonPressed += MyGUI::newDelegate(this, &EditNumber::notifyMouseButtonPressed); + mDragger->eventMouseDrag += MyGUI::newDelegate(this, &EditNumber::notifyMouseButtonDragged); + mDragger->eventMouseWheel += MyGUI::newDelegate(this, &EditNumber::notifyMouseWheel); + } + + void notifyMouseWheel(MyGUI::Widget* sender, int rel) + { + auto uniform = mUniform.lock(); + + if (!uniform) + return; + + if (rel > 0) + increment(uniform->mStep); + else + increment(-uniform->mStep); + } + + void notifyMouseButtonDragged(MyGUI::Widget* sender, int left, int top, MyGUI::MouseButton id) + { + if (id != MyGUI::MouseButton::Left) + return; + + auto uniform = mUniform.lock(); + + if (!uniform) + return; + + int delta = left - mLastPointerX; + + // allow finer tuning when shift is pressed + constexpr double scaling = 20.0; + T step = MyGUI::InputManager::getInstance().isShiftPressed() ? uniform->mStep / scaling : uniform->mStep; + + if (step == 0) + { + if constexpr (std::is_integral_v) + step = 1; + else + step = uniform->mStep; + } + + if (delta > 0) + increment(step); + else if (delta < 0) + increment(-step); + + mLastPointerX = left; + } + + void notifyMouseButtonPressed(MyGUI::Widget* sender, int left, int top, MyGUI::MouseButton id) + { + if (id != MyGUI::MouseButton::Left) + return; + + mLastPointerX = left; + } + + void increment(T step) + { + auto uniform = mUniform.lock(); + + if (!uniform) + return; + + if constexpr (std::is_fundamental_v) + setValue(std::clamp(uniform->template getValue() + step, uniform->template getMin(), uniform->template getMax())); + else + setValue(std::clamp(uniform->template getValue()[mIndex] + step, uniform->template getMin()[mIndex], uniform->template getMax()[mIndex])); + } + + void notifyButtonClicked(MyGUI::Widget* sender) + { + auto uniform = mUniform.lock(); + + if (!uniform) + return; + + if (sender == mButtonDecrease) + increment(-uniform->mStep); + else if (sender == mButtonIncrease) + increment(uniform->mStep); + } + + MyGUI::Button* mButtonDecrease; + MyGUI::Button* mButtonIncrease; + MyGUI::Widget* mDragger; + MyGUI::TextBox* mValueLabel; + T mValue; + + int mLastPointerX; + }; + + class EditNumberFloat4 : public EditNumber { MYGUI_RTTI_DERIVED(EditNumberFloat4) }; + class EditNumberFloat3 : public EditNumber { MYGUI_RTTI_DERIVED(EditNumberFloat3) }; + class EditNumberFloat2 : public EditNumber { MYGUI_RTTI_DERIVED(EditNumberFloat2) }; + class EditNumberFloat : public EditNumber { MYGUI_RTTI_DERIVED(EditNumberFloat) }; + class EditNumberInt : public EditNumber { MYGUI_RTTI_DERIVED(EditNumberInt) }; + + class UniformBase final : public MyGUI::Widget + { + MYGUI_RTTI_DERIVED(UniformBase) + + public: + void init(const std::shared_ptr& uniform); + + void toDefault(); + + void addItem(EditBase* item); + + private: + + void notifyResetClicked(MyGUI::Widget* sender); + + void initialiseOverride() override; + + Gui::AutoSizedButton* mReset; + Gui::AutoSizedTextBox* mLabel; + MyGUI::Widget* mClient; + std::vector mBases; + }; + } +} + +#endif diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 8ddc3fd102..a2779109ac 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -318,7 +318,7 @@ namespace Resource , mApplyLightingToEnvMaps(false) , mLightingMethod(SceneUtil::LightingMethod::FFP) , mConvertAlphaTestToAlphaToCoverage(false) - , mDepthFormat(0) + , mSupportsNormalsRT(false) , mSharedStateManager(new SharedStateManager) , mImageManager(imageManager) , mNifFileManager(nifFileManager) @@ -348,7 +348,7 @@ namespace Resource if (forceShadersForNode) shaderVisitor->setForceShaders(true); if (disableSoftParticles) - shaderVisitor->setOpaqueDepthTex(nullptr); + shaderVisitor->setOpaqueDepthTex(nullptr, nullptr); node->accept(*shaderVisitor); } @@ -368,16 +368,6 @@ namespace Resource return mClampLighting; } - void SceneManager::setDepthFormat(GLenum format) - { - mDepthFormat = format; - } - - GLenum SceneManager::getDepthFormat() const - { - return mDepthFormat; - } - void SceneManager::setAutoUseNormalMaps(bool use) { mAutoUseNormalMaps = use; @@ -440,9 +430,9 @@ namespace Resource mConvertAlphaTestToAlphaToCoverage = convert; } - void SceneManager::setOpaqueDepthTex(osg::ref_ptr texture) + void SceneManager::setOpaqueDepthTex(osg::ref_ptr texturePing, osg::ref_ptr texturePong) { - mOpaqueDepthTex = texture; + mOpaqueDepthTex = { texturePing, texturePong }; } SceneManager::~SceneManager() @@ -930,7 +920,8 @@ namespace Resource shaderVisitor->setSpecularMapPattern(mSpecularMapPattern); shaderVisitor->setApplyLightingToEnvMaps(mApplyLightingToEnvMaps); shaderVisitor->setConvertAlphaTestToAlphaToCoverage(mConvertAlphaTestToAlphaToCoverage); - shaderVisitor->setOpaqueDepthTex(mOpaqueDepthTex); + shaderVisitor->setOpaqueDepthTex(mOpaqueDepthTex[0], mOpaqueDepthTex[1]); + shaderVisitor->setSupportsNormalsRT(mSupportsNormalsRT); return shaderVisitor; } } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index b3a42bac45..ba5c75f7ea 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -91,9 +91,6 @@ namespace Resource void setClampLighting(bool clamp); bool getClampLighting() const; - void setDepthFormat(GLenum format); - GLenum getDepthFormat() const; - /// @see ShaderVisitor::setAutoUseNormalMaps void setAutoUseNormalMaps(bool use); @@ -112,12 +109,13 @@ namespace Resource void setSupportedLightingMethods(const SceneUtil::LightManager::SupportedMethods& supported); bool isSupportedLightingMethod(SceneUtil::LightingMethod method) const; - void setOpaqueDepthTex(osg::ref_ptr texture); + void setOpaqueDepthTex(osg::ref_ptr texturePing, osg::ref_ptr texturePong); enum class UBOBinding { // If we add more UBO's, we should probably assign their bindings dynamically according to the current count of UBO's in the programTemplate - LightBuffer + LightBuffer, + PostProcessor }; void setLightingMethod(SceneUtil::LightingMethod method); SceneUtil::LightingMethod getLightingMethod() const; @@ -195,6 +193,9 @@ namespace Resource void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; + void setSupportsNormalsRT(bool supports) { mSupportsNormalsRT = supports; } + bool getSupportsNormalsRT() const { return mSupportsNormalsRT; } + private: Shader::ShaderVisitor* createShaderVisitor(const std::string& shaderPrefix = "objects"); @@ -211,8 +212,8 @@ namespace Resource SceneUtil::LightingMethod mLightingMethod; SceneUtil::LightManager::SupportedMethods mSupportedLightingMethods; bool mConvertAlphaTestToAlphaToCoverage; - GLenum mDepthFormat; - osg::ref_ptr mOpaqueDepthTex; + bool mSupportsNormalsRT; + std::array, 2> mOpaqueDepthTex; osg::ref_ptr mSharedStateManager; mutable std::mutex mSharedStateMutex; diff --git a/components/sceneutil/clearcolor.hpp b/components/sceneutil/clearcolor.hpp new file mode 100755 index 0000000000..e6e6468ecc --- /dev/null +++ b/components/sceneutil/clearcolor.hpp @@ -0,0 +1,42 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_CLEARCOLOR_H +#define OPENMW_COMPONENTS_SCENEUTIL_CLEARCOLOR_H + +#include +#include + +namespace SceneUtil +{ + class ClearColor : public osg::StateAttribute + { + public: + ClearColor() : mMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) {} + ClearColor(const osg::Vec4f& color, GLbitfield mask) : mColor(color), mMask(mask) {} + + ClearColor(const ClearColor& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) + : osg::StateAttribute(copy,copyop), mColor(copy.mColor), mMask(copy.mMask) {} + + META_StateAttribute(fx, ClearColor, static_cast(100)) + + int compare(const StateAttribute& sa) const override + { + COMPARE_StateAttribute_Types(ClearColor, sa); + + COMPARE_StateAttribute_Parameter(mColor); + COMPARE_StateAttribute_Parameter(mMask); + + return 0; + } + + void apply(osg::State& state) const override + { + glClearColor(mColor[0], mColor[1], mColor[2], mColor[3]); + glClear(mMask); + } + + private: + osg::Vec4f mColor; + GLbitfield mMask; + }; +} + +#endif diff --git a/components/sceneutil/depth.cpp b/components/sceneutil/depth.cpp index c52bdf2ed2..f51c973389 100644 --- a/components/sceneutil/depth.cpp +++ b/components/sceneutil/depth.cpp @@ -44,18 +44,6 @@ namespace SceneUtil ); } - bool isFloatingPointDepthFormat(GLenum format) - { - constexpr std::array formats = { - GL_DEPTH_COMPONENT32F, - GL_DEPTH_COMPONENT32F_NV, - GL_DEPTH32F_STENCIL8, - GL_DEPTH32F_STENCIL8_NV, - }; - - return std::find(formats.cbegin(), formats.cend(), format) != formats.cend(); - } - bool isDepthFormat(GLenum format) { constexpr std::array formats = { diff --git a/components/sceneutil/depth.hpp b/components/sceneutil/depth.hpp index 67cf5bdb45..f7b1875206 100644 --- a/components/sceneutil/depth.hpp +++ b/components/sceneutil/depth.hpp @@ -45,9 +45,6 @@ namespace SceneUtil // Returns an orthographic projection matrix for use with a reversed z-buffer. osg::Matrix getReversedZProjectionMatrixAsOrtho(double left, double right, double bottom, double top, double near, double far); - // Returns true if the GL format is a floating point depth format. - bool isFloatingPointDepthFormat(GLenum format); - // Returns true if the GL format is a depth format bool isDepthFormat(GLenum format); diff --git a/components/sceneutil/rtt.cpp b/components/sceneutil/rtt.cpp index 099425330a..4ab1b4c92a 100644 --- a/components/sceneutil/rtt.cpp +++ b/components/sceneutil/rtt.cpp @@ -33,7 +33,7 @@ namespace SceneUtil , mSamples(samples) , mGenerateMipmaps(generateMipmaps) , mColorBufferInternalFormat(Color::colorInternalFormat()) - , mDepthBufferInternalFormat(AutoDepth::depthInternalFormat()) + , mDepthBufferInternalFormat(SceneUtil::AutoDepth::depthInternalFormat()) , mRenderOrderNum(renderOrderNum) , mStereoAwareness(stereoAwareness) { diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index b70a0e2481..52b7be1798 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -152,42 +152,6 @@ void GlowUpdater::setDuration(float duration) mDuration = duration; } -// Allows camera to render to a color and floating point depth texture with a multisampled framebuffer. -class AttachMultisampledDepthColorCallback : public SceneUtil::NodeCallback -{ -public: - AttachMultisampledDepthColorCallback(osg::Texture2D* colorTex, osg::Texture2D* depthTex, int samples, int colorSamples) - { - int width = colorTex->getTextureWidth(); - int height = colorTex->getTextureHeight(); - - osg::ref_ptr rbColor = new osg::RenderBuffer(width, height, colorTex->getInternalFormat(), samples, colorSamples); - osg::ref_ptr rbDepth = new osg::RenderBuffer(width, height, depthTex->getInternalFormat(), samples, colorSamples); - - mMsaaFbo = new osg::FrameBufferObject; - mMsaaFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(rbColor)); - mMsaaFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(rbDepth)); - - mFbo = new osg::FrameBufferObject; - mFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(colorTex)); - mFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(depthTex)); - } - - void operator()(osg::Node* node, osgUtil::CullVisitor* cv) - { - osgUtil::RenderStage* renderStage = cv->getCurrentRenderStage(); - - renderStage->setMultisampleResolveFramebufferObject(mFbo); - renderStage->setFrameBufferObject(mMsaaFbo); - - traverse(node, cv); - } - -private: - osg::ref_ptr mFbo; - osg::ref_ptr mMsaaFbo; -}; - osg::Vec4f colourFromRGB(unsigned int clr) { osg::Vec4f colour(((clr >> 0) & 0xFF) / 255.0f, diff --git a/components/serialization/osgyaml.hpp b/components/serialization/osgyaml.hpp new file mode 100644 index 0000000000..866d1db2fe --- /dev/null +++ b/components/serialization/osgyaml.hpp @@ -0,0 +1,64 @@ +#ifndef OPENMW_COMPONENTS_SERIALIZATION_OSGYAML_H +#define OPENMW_COMPONENTS_SERIALIZATION_OSGYAML_H + +#include + +#include +#include +#include + +namespace Serialization +{ + template + YAML::Node encodeOSGVec(const OSGVec& rhs) + { + YAML::Node node; + for (int i = 0; i < OSGVec::num_components; ++i) + node.push_back(rhs[i]); + + return node; + } + + template + bool decodeOSGVec(const YAML::Node& node, OSGVec& rhs) + { + if (!node.IsSequence() || node.size() != OSGVec::num_components) + return false; + + for (int i = 0; i < OSGVec::num_components; ++i) + rhs[i] = node[i].as(); + + return true; + } +} + +namespace YAML +{ + + template<> + struct convert + { + static Node encode(const osg::Vec2f& rhs) { return Serialization::encodeOSGVec(rhs); } + + static bool decode(const Node& node, osg::Vec2f& rhs) { return Serialization::decodeOSGVec(node, rhs); } + }; + + template<> + struct convert + { + static Node encode(const osg::Vec3f& rhs) { return Serialization::encodeOSGVec(rhs); } + + static bool decode(const Node& node, osg::Vec3f& rhs) { return Serialization::decodeOSGVec(node, rhs); } + }; + + template<> + struct convert + { + static Node encode(const osg::Vec4f& rhs) { return Serialization::encodeOSGVec(rhs); } + + static bool decode(const Node& node, osg::Vec4f& rhs) { return Serialization::decodeOSGVec(node, rhs); } + }; + +} + +#endif \ No newline at end of file diff --git a/components/settings/shadermanager.hpp b/components/settings/shadermanager.hpp new file mode 100644 index 0000000000..322d6169b3 --- /dev/null +++ b/components/settings/shadermanager.hpp @@ -0,0 +1,174 @@ +#ifndef OPENMW_COMPONENTS_SETTINGS_SHADERMANAGER_H +#define OPENMW_COMPONENTS_SETTINGS_SHADERMANAGER_H + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +namespace Settings +{ + /* + * Manages the shader.yaml file which is auto-generated and lives next to settings.cfg. + * This YAML file is simply a mapping of technique name to a list of uniforms and their values. + * Currently only vec2f, vec3f, vec4f, int, and float uniforms are supported. + * + * config: + * TECHNIQUE: + * MY_FLOAT: 10.34 + * MY_VEC2: [0.23, 0.34] + * TECHNIQUE2: + * MY_VEC3: [0.22, 0.33, 0.20] + */ + class ShaderManager + { + public: + + enum class Mode + { + Normal, + Debug + }; + + ShaderManager() = default; + ShaderManager(ShaderManager const&) = delete; + void operator=(ShaderManager const&) = delete; + + static ShaderManager& get() + { + static ShaderManager instance; + return instance; + } + + Mode getMode() + { + return mMode; + } + + void setMode(Mode mode) + { + mMode = mode; + } + + const YAML::Node& getRoot() + { + return mData; + } + + template + bool setValue(const std::string& tname, const std::string& uname, const T& value) + { + if (mData.IsNull()) + { + Log(Debug::Warning) << "Failed setting " << tname << ", " << uname << " : shader settings failed to load"; + return false; + } + + mData["config"][tname][uname] = value; + return true; + } + + template + std::optional getValue(const std::string& tname, const std::string& uname) + { + if (mData.IsNull()) + { + Log(Debug::Warning) << "Failed getting " << tname << ", " << uname << " : shader settings failed to load"; + return std::nullopt; + } + + try + { + auto value = mData["config"][tname][uname]; + + if (!value) + return std::nullopt; + + return value.as(); + } + catch(const YAML::BadConversion& e) + { + Log(Debug::Warning) << "Failed retrieving " << tname << ", " << uname << " : mismatched types in config file."; + } + + return std::nullopt; + } + + bool load(const std::string& path) + { + mData = YAML::Null; + mPath = std::filesystem::path(path); + + Log(Debug::Info) << "Loading shader settings file: " << mPath; + + if (!std::filesystem::exists(mPath)) + { + std::ofstream fout(mPath); + if (!fout) + { + Log(Debug::Error) << "Failed creating shader settings file: " << mPath; + return false; + } + } + + try + { + mData = YAML::LoadFile(mPath.string()); + mData.SetStyle(YAML::EmitterStyle::Block); + + if (!mData["config"]) + mData["config"] = YAML::Node(); + + return true; + } + catch(const YAML::Exception& e) + { + Log(Debug::Error) << "Shader settings failed to load, " << e.msg; + } + + return false; + } + + bool save() + { + if (mData.IsNull()) + { + Log(Debug::Error) << "Shader settings failed to load, settings will not be saved: " << mPath; + return false; + } + + Log(Debug::Info) << "Saving shader settings file: " << mPath; + + YAML::Emitter out; + out.SetMapFormat(YAML::Block); + out << mData; + + std::ofstream fout(mPath.string()); + fout << out.c_str(); + + if (!fout) + { + Log(Debug::Error) << "Failed saving shader settings file: " << mPath; + return false; + } + + return true; + } + + private: + std::filesystem::path mPath; + YAML::Node mData; + Mode mMode = Mode::Normal; + }; +} + +#endif diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 8923fec666..4ef0d7a0ea 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include @@ -28,6 +29,50 @@ #include "removedalphafunc.hpp" #include "shadermanager.hpp" +namespace +{ + class OpaqueDepthAttribute : public osg::StateAttribute + { + public: + OpaqueDepthAttribute() = default; + + OpaqueDepthAttribute(const OpaqueDepthAttribute& copy, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) + : osg::StateAttribute(copy, copyop), mTextures(copy.mTextures), mUnit(copy.mUnit) {} + + void setTexturesAndUnit(const std::array, 2>& textures, int unit) + { + mTextures = textures; + mUnit = unit; + } + + META_StateAttribute(Shader, OpaqueDepthAttribute, osg::StateAttribute::TEXTURE) + + int compare(const StateAttribute& sa) const override + { + COMPARE_StateAttribute_Types(OpaqueDepthAttribute, sa); + + COMPARE_StateAttribute_Parameter(mTextures); + + return 0; + } + + void apply(osg::State& state) const override + { + auto index = state.getFrameStamp()->getFrameNumber() % 2; + + if (!mTextures[index]) + return; + + state.setActiveTextureUnit(mUnit); + state.applyTextureAttribute(mUnit, mTextures[index]); + } + + private: + mutable std::array, 2> mTextures; + int mUnit; + }; +} + namespace Shader { /** @@ -165,6 +210,7 @@ namespace Shader , mAutoUseSpecularMaps(false) , mApplyLightingToEnvMaps(false) , mConvertAlphaTestToAlphaToCoverage(false) + , mSupportsNormalsRT(false) , mShaderManager(shaderManager) , mImageManager(imageManager) , mDefaultShaderPrefix(defaultShaderPrefix) @@ -611,6 +657,14 @@ namespace Shader defineMap["endLight"] = "0"; } + if (reqs.mAlphaBlend && mSupportsNormalsRT) + { + if (reqs.mSoftParticles) + defineMap["disableNormals"] = "1"; + else + writableStateSet->setAttribute(new osg::Disablei(GL_BLEND, 1)); + } + if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT && !previousAddedState->hasMode(GL_ALPHA_TEST)) removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST)); // This disables the deprecated fixed-function alpha test @@ -629,7 +683,7 @@ namespace Shader updateRemovedState(*writableUserData, removedState); } - if (reqs.mSoftParticles) + if (reqs.mSoftParticles && mOpaqueDepthTex.front()) { osg::ref_ptr depth = new SceneUtil::AutoDepth; depth->setWriteMask(false); @@ -639,14 +693,18 @@ namespace Shader writableStateSet->addUniform(new osg::Uniform("particleSize", reqs.mSoftParticleSize)); addedState->addUniform("particleSize"); - writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", 2)); + constexpr int unit = 2; + + writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", unit)); addedState->addUniform("opaqueDepthTex"); - writableStateSet->setTextureAttributeAndModes(2, mOpaqueDepthTex, osg::StateAttribute::ON); - addedState->setTextureAttributeAndModes(2, mOpaqueDepthTex); + osg::ref_ptr opaqueDepthAttr = new OpaqueDepthAttribute; + opaqueDepthAttr->setTexturesAndUnit(mOpaqueDepthTex, unit); + writableStateSet->setAttributeAndModes(opaqueDepthAttr, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + addedState->setAttributeAndModes(opaqueDepthAttr); } - defineMap["softParticles"] = reqs.mSoftParticles ? "1" : "0"; + defineMap["softParticles"] = reqs.mSoftParticles && mOpaqueDepthTex.front() ? "1" : "0"; Stereo::Manager::instance().shaderStereoDefines(defineMap); @@ -840,7 +898,7 @@ namespace Shader { pushRequirements(drawable); - if (partsys && mOpaqueDepthTex) + if (partsys) { mRequirements.back().mSoftParticles = true; mRequirements.back().mSoftParticleSize = partsys->getDefaultParticleTemplate().getSizeRange().maximum; @@ -915,9 +973,9 @@ namespace Shader mConvertAlphaTestToAlphaToCoverage = convert; } - void ShaderVisitor::setOpaqueDepthTex(osg::ref_ptr texture) + void ShaderVisitor::setOpaqueDepthTex(osg::ref_ptr texturePing, osg::ref_ptr texturePong) { - mOpaqueDepthTex = texture; + mOpaqueDepthTex = { texturePing, texturePong }; } ReinstateRemovedStateVisitor::ReinstateRemovedStateVisitor(bool allowedToModifyStateSets) diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 72dec05b5e..cd1ca421d9 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_COMPONENTS_SHADERVISITOR_H #define OPENMW_COMPONENTS_SHADERVISITOR_H +#include + #include #include #include @@ -46,7 +48,9 @@ namespace Shader void setConvertAlphaTestToAlphaToCoverage(bool convert); - void setOpaqueDepthTex(osg::ref_ptr texture); + void setOpaqueDepthTex(osg::ref_ptr texturePing, osg::ref_ptr texturePong); + + void setSupportsNormalsRT(bool supports) { mSupportsNormalsRT = supports; } void apply(osg::Node& node) override; @@ -73,6 +77,8 @@ namespace Shader bool mConvertAlphaTestToAlphaToCoverage; + bool mSupportsNormalsRT; + ShaderManager& mShaderManager; Resource::ImageManager& mImageManager; @@ -87,7 +93,7 @@ namespace Shader bool mShaderRequired; int mColorMode; - + bool mMaterialOverridden; bool mAlphaTestOverridden; bool mAlphaBlendOverridden; @@ -116,7 +122,7 @@ namespace Shader bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs); osg::ref_ptr mProgramTemplate; - osg::ref_ptr mOpaqueDepthTex; + std::array, 2> mOpaqueDepthTex; }; class ReinstateRemovedStateVisitor : public osg::NodeVisitor diff --git a/components/std140/ubo.hpp b/components/std140/ubo.hpp new file mode 100644 index 0000000000..6154cd32ac --- /dev/null +++ b/components/std140/ubo.hpp @@ -0,0 +1,162 @@ +#ifndef COMPONENTS_STD140_UBO_H +#define COMPONENTS_STD140_UBO_H + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace std140 +{ + struct Mat4 + { + using Value = osg::Matrixf; + Value mValue; + static constexpr size_t sAlign = sizeof(Value); + static constexpr std::string_view sTypeName = "mat4"; + }; + + struct Vec4 + { + using Value = osg::Vec4f; + Value mValue; + static constexpr size_t sAlign = sizeof(Value); + static constexpr std::string_view sTypeName = "vec4"; + }; + + struct Vec3 + { + using Value = osg::Vec3f; + Value mValue; + static constexpr std::size_t sAlign = 4 * sizeof(osg::Vec3f::value_type); + static constexpr std::string_view sTypeName = "vec3"; + }; + + struct Vec2 + { + using Value = osg::Vec2f; + Value mValue; + static constexpr std::size_t sAlign = sizeof(Value); + static constexpr std::string_view sTypeName = "vec2"; + }; + + struct Float + { + using Value = float; + Value mValue; + static constexpr std::size_t sAlign = sizeof(Value); + static constexpr std::string_view sTypeName = "float"; + }; + + struct Int + { + using Value = std::int32_t; + Value mValue; + static constexpr std::size_t sAlign = sizeof(Value); + static constexpr std::string_view sTypeName = "int"; + }; + + struct UInt + { + using Value = std::uint32_t; + Value mValue; + static constexpr std::size_t sAlign = sizeof(Value); + static constexpr std::string_view sTypeName = "uint"; + }; + + struct Bool + { + using Value = std::int32_t; + Value mValue; + static constexpr std::size_t sAlign = sizeof(Value); + static constexpr std::string_view sTypeName = "bool"; + }; + + template + class UBO + { + private: + + template + struct contains : std::bool_constant<(std::is_base_of_v || ...)> { }; + + static_assert((contains() && ...)); + + static constexpr size_t roundUpRemainder(size_t x, size_t multiple) + { + size_t remainder = x % multiple; + if (remainder == 0) + return 0; + return multiple - remainder; + } + + template + static constexpr std::size_t getOffset() + { + bool found = false; + std::size_t size = 0; + (( + found = found || std::is_same_v, + size += (found ? 0 : sizeof(typename CArgs::Value) + roundUpRemainder(size, CArgs::sAlign)) + ) , ...); + return size + roundUpRemainder(size, T::sAlign); + } + + public: + + static constexpr size_t getGPUSize() + { + std::size_t size = 0; + ((size += (sizeof(typename CArgs::Value) + roundUpRemainder(size, CArgs::sAlign))), ...); + return size; + } + + static std::string getDefinition(const std::string& name) + { + std::string structDefinition = "struct " + name + " {\n"; + ((structDefinition += (" " + std::string(CArgs::sTypeName) + " " + std::string(CArgs::sName) + ";\n")), ...); + return structDefinition + "};"; + } + + using BufferType = std::array; + using TupleType = std::tuple; + + template + typename T::Value& get() + { + return std::get(mData).mValue; + } + + template + const typename T::Value& get() const + { + return std::get(mData).mValue; + } + + void copyTo(BufferType& buffer) const + { + const auto copy = [&] (const auto& v) { + static_assert(std::is_standard_layout_v>); + constexpr std::size_t offset = getOffset>(); + std::memcpy(buffer.data() + offset, &v.mValue, sizeof(v.mValue)); + }; + + std::apply([&] (const auto& ... v) { (copy(v) , ...); }, mData); + } + + const auto& getData() const + { + return mData; + } + + private: + std::tuple mData; + }; +} + +#endif diff --git a/components/stereo/multiview.cpp b/components/stereo/multiview.cpp index 56c1ada349..de48bf6d17 100644 --- a/components/stereo/multiview.cpp +++ b/components/stereo/multiview.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -322,10 +321,7 @@ namespace Stereo for (unsigned i = 0; i < 2; i++) { if (mSamples > 1) - { - mMsaaColorTexture[i] = createTextureMsaa(sourceFormat, sourceType, internalFormat); - mLayerMsaaFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mMsaaColorTexture[i])); - } + mLayerMsaaFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(new osg::RenderBuffer(mWidth, mHeight, internalFormat, mSamples))); mColorTexture[i] = createTexture(sourceFormat, sourceType, internalFormat); mLayerFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mColorTexture[i])); } @@ -351,10 +347,7 @@ namespace Stereo for (unsigned i = 0; i < 2; i++) { if (mSamples > 1) - { - mMsaaDepthTexture[i] = createTextureMsaa(sourceFormat, sourceType, internalFormat); - mLayerMsaaFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mMsaaDepthTexture[i])); - } + mLayerMsaaFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(new osg::RenderBuffer(mWidth, mHeight, internalFormat, mSamples))); mDepthTexture[i] = createTexture(sourceFormat, sourceType, internalFormat); mLayerFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mDepthTexture[i])); } @@ -381,6 +374,11 @@ namespace Stereo return mMultiviewColorTexture; } + osg::Texture2DArray* MultiviewFramebuffer::multiviewDepthBuffer() + { + return mMultiviewDepthTexture; + } + osg::Texture2D* MultiviewFramebuffer::layerColorBuffer(int i) { return mColorTexture[i]; @@ -451,22 +449,6 @@ namespace Stereo return texture; } - osg::Texture2DMultisample* MultiviewFramebuffer::createTextureMsaa(GLint sourceFormat, GLint sourceType, GLint internalFormat) - { - osg::Texture2DMultisample* texture = new osg::Texture2DMultisample; - texture->setTextureSize(mWidth, mHeight); - texture->setNumSamples(mSamples); - texture->setSourceFormat(sourceFormat); - texture->setSourceType(sourceType); - texture->setInternalFormat(internalFormat); - texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - texture->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE); - return texture; - } - osg::Texture2DArray* MultiviewFramebuffer::createTextureArray(GLint sourceFormat, GLint sourceType, GLint internalFormat) { osg::Texture2DArray* textureArray = new osg::Texture2DArray; diff --git a/components/stereo/multiview.hpp b/components/stereo/multiview.hpp index 298d042ffc..cfdc721d1a 100644 --- a/components/stereo/multiview.hpp +++ b/components/stereo/multiview.hpp @@ -13,7 +13,6 @@ namespace osg class FrameBufferObject; class Texture; class Texture2D; - class Texture2DMultisample; class Texture2DArray; } @@ -50,6 +49,7 @@ namespace Stereo osg::FrameBufferObject* layerFbo(int i); osg::FrameBufferObject* layerMsaaFbo(int i); osg::Texture2DArray* multiviewColorBuffer(); + osg::Texture2DArray* multiviewDepthBuffer(); osg::Texture2D* layerColorBuffer(int i); osg::Texture2D* layerDepthBuffer(int i); @@ -62,7 +62,6 @@ namespace Stereo private: osg::Texture2D* createTexture(GLint sourceFormat, GLint sourceType, GLint internalFormat); - osg::Texture2DMultisample* createTextureMsaa(GLint sourceFormat, GLint sourceType, GLint internalFormat); osg::Texture2DArray* createTextureArray(GLint sourceFormat, GLint sourceType, GLint internalFormat); int mWidth; @@ -76,9 +75,7 @@ namespace Stereo osg::ref_ptr mMultiviewColorTexture; osg::ref_ptr mMultiviewDepthTexture; std::array, 2> mColorTexture; - std::array, 2> mMsaaColorTexture; std::array, 2> mDepthTexture; - std::array, 2> mMsaaDepthTexture; }; } diff --git a/components/stereo/stereomanager.hpp b/components/stereo/stereomanager.hpp index 49ae685e6b..41c05b8785 100644 --- a/components/stereo/stereomanager.hpp +++ b/components/stereo/stereomanager.hpp @@ -17,7 +17,6 @@ namespace osg { class FrameBufferObject; class Texture2D; - class Texture2DMultisample; class Texture2DArray; } diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index d57b73768f..badca42977 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -180,7 +180,7 @@ std::vector > ChunkManager::createPasses(float chunk float blendmapScale = mStorage->getBlendmapScale(chunkSize); - return ::Terrain::createPasses(useShaders, &mSceneManager->getShaderManager(), layers, blendmapTextures, blendmapScale, blendmapScale); + return ::Terrain::createPasses(useShaders, mSceneManager, layers, blendmapTextures, blendmapScale, blendmapScale); } osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, unsigned char lod, unsigned int lodFlags, bool compile, TerrainDrawable* templateGeometry) @@ -268,7 +268,7 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve layer.mDiffuseMap = compositeMap->mTexture; layer.mParallax = false; layer.mSpecular = false; - geometry->setPasses(::Terrain::createPasses(mSceneManager->getForceShaders() || !mSceneManager->getClampLighting(), &mSceneManager->getShaderManager(), std::vector(1, layer), std::vector >(), 1.f, 1.f)); + geometry->setPasses(::Terrain::createPasses(mSceneManager->getForceShaders() || !mSceneManager->getClampLighting(), mSceneManager, std::vector(1, layer), std::vector >(), 1.f, 1.f)); } else { diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 8e892a08c8..1c6770e6bc 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -6,8 +6,10 @@ #include #include #include +#include #include +#include #include #include @@ -194,9 +196,10 @@ namespace namespace Terrain { - std::vector > createPasses(bool useShaders, Shader::ShaderManager* shaderManager, const std::vector &layers, + std::vector > createPasses(bool useShaders, Resource::SceneManager* sceneManager, const std::vector &layers, const std::vector > &blendmaps, int blendmapScale, float layerTileSize) { + auto& shaderManager = sceneManager->getShaderManager(); std::vector > passes; unsigned int blendmapIndex = 0; @@ -209,6 +212,8 @@ namespace Terrain if (!blendmaps.empty()) { stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + if (sceneManager->getSupportsNormalsRT()) + stateset->setAttribute(new osg::Disablei(GL_BLEND, 1)); stateset->setRenderBinDetails(firstLayer ? 0 : 1, "RenderBin"); if (!firstLayer) { @@ -251,18 +256,18 @@ namespace Terrain defineMap["blendMap"] = (!blendmaps.empty()) ? "1" : "0"; defineMap["specularMap"] = it->mSpecular ? "1" : "0"; defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0"; - + defineMap["writeNormals"] = (it == layers.end() - 1) ? "1" : "0"; Stereo::Manager::instance().shaderStereoDefines(defineMap); - osg::ref_ptr vertexShader = shaderManager->getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX); - osg::ref_ptr fragmentShader = shaderManager->getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT); + osg::ref_ptr vertexShader = shaderManager.getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX); + osg::ref_ptr fragmentShader = shaderManager.getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT); if (!vertexShader || !fragmentShader) { // Try again without shader. Error already logged by above - return createPasses(false, shaderManager, layers, blendmaps, blendmapScale, layerTileSize); + return createPasses(false, sceneManager, layers, blendmaps, blendmapScale, layerTileSize); } - stateset->setAttributeAndModes(shaderManager->getProgram(vertexShader, fragmentShader)); + stateset->setAttributeAndModes(shaderManager.getProgram(vertexShader, fragmentShader)); stateset->addUniform(UniformCollection::value().mColorMode); } else diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index 5f78af6a06..d5ef40a29e 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -10,9 +10,9 @@ namespace osg class Texture2D; } -namespace Shader +namespace Resource { - class ShaderManager; + class SceneManager; } namespace Terrain @@ -26,7 +26,7 @@ namespace Terrain bool mSpecular; }; - std::vector > createPasses(bool useShaders, Shader::ShaderManager* shaderManager, + std::vector > createPasses(bool useShaders, Resource::SceneManager* sceneManager, const std::vector& layers, const std::vector >& blendmaps, int blendmapScale, float layerTileSize); diff --git a/components/vfs/archive.hpp b/components/vfs/archive.hpp index 971ac15b39..faefe30dfe 100644 --- a/components/vfs/archive.hpp +++ b/components/vfs/archive.hpp @@ -14,6 +14,8 @@ namespace VFS virtual ~File() {} virtual Files::IStreamPtr open() = 0; + + virtual std::string getPath() = 0; }; class Archive diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 1bf9cf4381..a52104efd7 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -15,6 +15,8 @@ namespace VFS Files::IStreamPtr open() override; + std::string getPath() override { return mInfo->name(); } + const Bsa::BSAFile::FileStruct* mInfo; Bsa::BSAFile* mFile; }; @@ -26,6 +28,8 @@ namespace VFS Files::IStreamPtr open() override; + std::string getPath() override { return mInfo->name(); } + const Bsa::BSAFile::FileStruct* mInfo; Bsa::CompressedBSAFile* mCompressedFile; }; diff --git a/components/vfs/filesystemarchive.hpp b/components/vfs/filesystemarchive.hpp index 70463d32f2..fa9b50edc5 100644 --- a/components/vfs/filesystemarchive.hpp +++ b/components/vfs/filesystemarchive.hpp @@ -13,6 +13,8 @@ namespace VFS Files::IStreamPtr open() override; + std::string getPath() override { return mPath; } + private: std::string mPath; diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index faebc782aa..8a43eda5ee 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -105,6 +105,17 @@ namespace VFS return {}; } + std::string Manager::getAbsoluteFileName(const std::string& name) const + { + std::string normalized = name; + normalize_path(normalized, mStrict); + + std::map::const_iterator found = mIndex.find(normalized); + if (found == mIndex.end()) + throw std::runtime_error("Resource '" + normalized + "' not found"); + return found->second->getPath(); + } + namespace { bool startsWith(std::string_view text, std::string_view start) diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index 8568e8e784..98acfb7955 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -90,6 +90,11 @@ namespace VFS /// @note May be called from any thread once the index has been built. RecursiveDirectoryRange getRecursiveDirectoryIterator(const std::string& path) const; + /// Retrieve the absolute path to the file + /// @note Throws an exception if the file can not be found. + /// @note May be called from any thread once the index has been built. + std::string getAbsoluteFileName(const std::string& name) const; + private: bool mStrict; diff --git a/docs/source/reference/index.rst b/docs/source/reference/index.rst index aa6ff1d96f..9aa409f784 100644 --- a/docs/source/reference/index.rst +++ b/docs/source/reference/index.rst @@ -7,4 +7,5 @@ Reference Material modding/index lua-scripting/index + postprocessing/index documentationHowTo diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 0c850d3676..c3b77a15ee 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -20,6 +20,7 @@ Lua API reference openmw_input openmw_ui openmw_camera + openmw_postprocessing openmw_aux_calendar openmw_aux_util openmw_aux_time @@ -46,35 +47,37 @@ It can not be overloaded even if there is a lua file with the same name. The list of available packages is different for global and for local scripts. Player scripts are local scripts that are attached to a player. -+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -| Package | Can be used | Description | -+=========================================================+====================+===============================================================+ -|:ref:`openmw.interfaces