moddable post-processing pipeline

pull/3227/head
cody glassman 3 years ago
parent e7fb8b6fd8
commit 04843fed6d

@ -128,6 +128,7 @@
Feature #2858: Add a tab to the launcher for handling datafolders Feature #2858: Add a tab to the launcher for handling datafolders
Feature #3245: Grid and angle snapping for the OpenMW-CS Feature #3245: Grid and angle snapping for the OpenMW-CS
Feature #3616: Allow Zoom levels on the World Map Feature #3616: Allow Zoom levels on the World Map
Feature #4067: Post Processing
Feature #4297: Implement APPLIED_ONCE flag for magic effects Feature #4297: Implement APPLIED_ONCE flag for magic effects
Feature #4414: Handle duration of EXTRA SPELL magic effect Feature #4414: Handle duration of EXTRA SPELL magic effect
Feature #4595: Unique object identifier Feature #4595: Unique object identifier

@ -145,6 +145,12 @@ bool Launcher::AdvancedPage::loadSettings()
objectPagingMinSizeComboBox->setValue(Settings::Manager::getDouble("object paging min size", "Terrain")); objectPagingMinSizeComboBox->setValue(Settings::Manager::getDouble("object paging min size", "Terrain"));
loadSettingBool(nightDaySwitchesCheckBox, "day night switches", "Game"); 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 // Audio
@ -302,6 +308,11 @@ void Launcher::AdvancedPage::saveSettings()
Settings::Manager::setDouble("object paging min size", "Terrain", objectPagingMinSize); Settings::Manager::setDouble("object paging min size", "Terrain", objectPagingMinSize);
saveSettingBool(nightDaySwitchesCheckBox, "day night switches", "Game"); 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 // Audio
@ -464,3 +475,11 @@ void Launcher::AdvancedPage::slotViewOverShoulderToggled(bool checked)
{ {
viewOverShoulderVerticalLayout->setEnabled(viewOverShoulderCheckBox->checkState()); viewOverShoulderVerticalLayout->setEnabled(viewOverShoulderCheckBox->checkState());
} }
void Launcher::AdvancedPage::slotPostProcessToggled(bool checked)
{
postprocessLiveReloadCheckBox->setEnabled(checked);
postprocessTransparentPostpassCheckBox->setEnabled(checked);
postprocessHDRTimeComboBox->setEnabled(checked);
postprocessHDRTimeLabel->setEnabled(checked);
}

@ -30,6 +30,7 @@ namespace Launcher
void on_runScriptAfterStartupBrowseButton_clicked(); void on_runScriptAfterStartupBrowseButton_clicked();
void slotAnimSourcesToggled(bool checked); void slotAnimSourcesToggled(bool checked);
void slotViewOverShoulderToggled(bool checked); void slotViewOverShoulderToggled(bool checked);
void slotPostProcessToggled(bool checked);
private: private:
Config::GameSettings &mGameSettings; Config::GameSettings &mGameSettings;

@ -84,6 +84,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat
defines["radialFog"] = "0"; defines["radialFog"] = "0";
defines["lightingModel"] = "0"; defines["lightingModel"] = "0";
defines["reverseZ"] = "0"; defines["reverseZ"] = "0";
defines["refraction_enabled"] = "0";
for (const auto& define : shadowDefines) for (const auto& define : shadowDefines)
defines[define.first] = define.second; defines[define.first] = define.second;
mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines); mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines);

@ -22,7 +22,8 @@ add_openmw_dir (mwrender
actors objects renderingmanager animation rotatecontroller sky skyutil npcanimation vismask actors objects renderingmanager animation rotatecontroller sky skyutil npcanimation vismask
creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager
bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation 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 add_openmw_dir (mwinput
@ -43,6 +44,7 @@ add_openmw_dir (mwgui
tradeitemmodel companionitemmodel pickpocketitemmodel controllers savegamedialog tradeitemmodel companionitemmodel pickpocketitemmodel controllers savegamedialog
recharge mode videowidget backgroundimage itemwidget screenfader debugwindow spellmodel spellview recharge mode videowidget backgroundimage itemwidget screenfader debugwindow spellmodel spellview
draganddrop timeadvancer jailscreen itemchargeview keyboardnavigation textcolours statswatcher draganddrop timeadvancer jailscreen itemchargeview keyboardnavigation textcolours statswatcher
postprocessorhud
) )
add_openmw_dir (mwdialogue add_openmw_dir (mwdialogue
@ -59,7 +61,7 @@ add_openmw_dir (mwscript
add_openmw_dir (mwlua add_openmw_dir (mwlua
luamanagerimp object worldview userdataserializer eventqueue luamanagerimp object worldview userdataserializer eventqueue
luabindings localscripts playerscripts objectbindings cellbindings asyncbindings settingsbindings 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 types/types types/door types/actor types/container types/weapon types/npc types/creature
) )

@ -46,6 +46,8 @@
#include <components/sceneutil/color.hpp> #include <components/sceneutil/color.hpp>
#include <components/sceneutil/util.hpp> #include <components/sceneutil/util.hpp>
#include <components/settings/shadermanager.hpp>
#include "mwinput/inputmanagerimp.hpp" #include "mwinput/inputmanagerimp.hpp"
#include "mwgui/windowmanagerimp.hpp" #include "mwgui/windowmanagerimp.hpp"
@ -987,6 +989,8 @@ void OMW::Engine::go()
Settings::Manager settings; Settings::Manager settings;
std::string settingspath = settings.load(mCfgMgr); std::string settingspath = settings.load(mCfgMgr);
Settings::ShaderManager::get().load((mCfgMgr.getUserConfigPath() / "shaders.yaml").string());
MWClass::registerClasses(); MWClass::registerClasses();
// Create encoder // Create encoder
@ -1110,6 +1114,7 @@ void OMW::Engine::go()
// Save user settings // Save user settings
settings.saveUser(settingspath); settings.saveUser(settingspath);
Settings::ShaderManager::get().save();
mLuaManager->savePermanentStorage(mCfgMgr.getUserConfigPath().string()); mLuaManager->savePermanentStorage(mCfgMgr.getUserConfigPath().string());
Log(Debug::Info) << "Quitting peacefully."; Log(Debug::Info) << "Quitting peacefully.";

@ -70,6 +70,7 @@ namespace MWGui
class WindowModal; class WindowModal;
class JailScreen; class JailScreen;
class MessageBox; class MessageBox;
class PostProcessorHud;
enum ShowInDialogueMode { enum ShowInDialogueMode {
ShowInDialogueMode_IfPossible, ShowInDialogueMode_IfPossible,
@ -147,6 +148,7 @@ namespace MWBase
virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0; virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0;
virtual MWGui::TradeWindow* getTradeWindow() = 0; virtual MWGui::TradeWindow* getTradeWindow() = 0;
virtual const std::vector<MWGui::MessageBox*> getActiveMessageBoxes() = 0; virtual const std::vector<MWGui::MessageBox*> getActiveMessageBoxes() = 0;
virtual MWGui::PostProcessorHud* getPostProcessorHud() = 0;
/// Make the player use an item, while updating GUI state accordingly /// Make the player use an item, while updating GUI state accordingly
virtual void useItem(const MWWorld::Ptr& item, bool force=false) = 0; virtual void useItem(const MWWorld::Ptr& item, bool force=false) = 0;
@ -326,6 +328,7 @@ namespace MWBase
virtual void toggleConsole() = 0; virtual void toggleConsole() = 0;
virtual void toggleDebugWindow() = 0; virtual void toggleDebugWindow() = 0;
virtual void togglePostProcessorHud() = 0;
/// Cycle to next or previous spell /// Cycle to next or previous spell
virtual void cycleSpell(bool next) = 0; virtual void cycleSpell(bool next) = 0;

@ -67,6 +67,7 @@ namespace MWRender
class Animation; class Animation;
class Camera; class Camera;
class RenderingManager; class RenderingManager;
class PostProcessor;
} }
namespace MWMechanics namespace MWMechanics
@ -238,6 +239,10 @@ namespace MWBase
virtual int getCurrentWeather() const = 0; virtual int getCurrentWeather() const = 0;
virtual int getNextWeather() const = 0;
virtual float getWeatherTransition() const = 0;
virtual unsigned int getNightDayMode() const = 0; virtual unsigned int getNightDayMode() const = 0;
virtual int getMasserPhase() const = 0; virtual int getMasserPhase() const = 0;
@ -664,6 +669,8 @@ namespace MWBase
virtual Misc::Rng::Generator& getPrng() = 0; virtual Misc::Rng::Generator& getPrng() = 0;
virtual MWRender::RenderingManager* getRenderingManager() = 0; virtual MWRender::RenderingManager* getRenderingManager() = 0;
virtual MWRender::PostProcessor* getPostProcessor() = 0;
}; };
} }

@ -0,0 +1,454 @@
#include "postprocessorhud.hpp"
#include <MyGUI_Window.h>
#include <MyGUI_Button.h>
#include <MyGUI_TabItem.h>
#include <MyGUI_TabControl.h>
#include <MyGUI_RenderManager.h>
#include <MyGUI_FactoryManager.h>
#include <components/fx/widgets.hpp>
#include <components/fx/technique.hpp>
#include <components/widgets/box.hpp>
#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<MyGUI::Window>()->eventWindowChangeCoord += MyGUI::newDelegate(this, &PostProcessorHud::notifyWindowResize);
mShaderInfo = mConfigLayout->createWidget<Gui::AutoSizedEditBox>("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::Widget>("", {}, 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<fx::Widgets::UniformBase*>(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<std::shared_ptr<fx::Technique>>(selected));
else
processor->disableTechnique(*list->getItemDataAt<std::shared_ptr<fx::Technique>>(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<int>(selected) - 1 : selected + 1;
index = std::clamp<int>(index, 0, mActiveList->getItemCount() - 1);
if (static_cast<size_t>(index) != selected)
{
if (processor->enableTechnique(*mActiveList->getItemDataAt<std::shared_ptr<fx::Technique>>(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<MyGUI::ListBox*>(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<MyGUI::Button>("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<Gui::AutoSizedTextBox>("MW_UniformGroup", {0,0,0,34}, MyGUI::Align::Default)->setCaption(uniform->mHeader);
fx::Widgets::UniformBase* uwidget = mConfigArea->createWidget<fx::Widgets::UniformBase>("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<fx::Widgets::UniformBase>("Widget");
factory.registerFactory<fx::Widgets::EditNumberFloat4>("Widget");
factory.registerFactory<fx::Widgets::EditNumberFloat3>("Widget");
factory.registerFactory<fx::Widgets::EditNumberFloat2>("Widget");
factory.registerFactory<fx::Widgets::EditNumberFloat>("Widget");
factory.registerFactory<fx::Widgets::EditNumberInt>("Widget");
factory.registerFactory<fx::Widgets::EditBool>("Widget");
factory.registerFactory<ListWrapper>("Widget");
}
}

@ -0,0 +1,107 @@
#ifndef MYGUI_POSTPROCESSOR_HUD_H
#define MYGUI_POSTPROCESSOR_HUD_H
#include "windowbase.hpp"
#include <MyGUI_Gui.h>
#include <MyGUI_ListBox.h>
#include <components/settings/shadermanager.hpp>
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

@ -71,6 +71,7 @@
#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/actorutil.hpp"
#include "../mwrender/localmap.hpp" #include "../mwrender/localmap.hpp"
#include "../mwrender/postprocessor.hpp"
#include "console.hpp" #include "console.hpp"
#include "journalwindow.hpp" #include "journalwindow.hpp"
@ -113,6 +114,7 @@
#include "itemwidget.hpp" #include "itemwidget.hpp"
#include "screenfader.hpp" #include "screenfader.hpp"
#include "debugwindow.hpp" #include "debugwindow.hpp"
#include "postprocessorhud.hpp"
#include "spellview.hpp" #include "spellview.hpp"
#include "draganddrop.hpp" #include "draganddrop.hpp"
#include "container.hpp" #include "container.hpp"
@ -164,6 +166,7 @@ namespace MWGui
, mHitFader(nullptr) , mHitFader(nullptr)
, mScreenFader(nullptr) , mScreenFader(nullptr)
, mDebugWindow(nullptr) , mDebugWindow(nullptr)
, mPostProcessorHud(nullptr)
, mJailScreen(nullptr) , mJailScreen(nullptr)
, mContainerWindow(nullptr) , mContainerWindow(nullptr)
, mTranslationDataStorage (translationDataStorage) , mTranslationDataStorage (translationDataStorage)
@ -217,7 +220,8 @@ namespace MWGui
MyGUI::FactoryManager::getInstance().registerFactory<BackgroundImage>("Widget"); MyGUI::FactoryManager::getInstance().registerFactory<BackgroundImage>("Widget");
MyGUI::FactoryManager::getInstance().registerFactory<osgMyGUI::AdditiveLayer>("Layer"); MyGUI::FactoryManager::getInstance().registerFactory<osgMyGUI::AdditiveLayer>("Layer");
MyGUI::FactoryManager::getInstance().registerFactory<osgMyGUI::ScalingLayer>("Layer"); MyGUI::FactoryManager::getInstance().registerFactory<osgMyGUI::ScalingLayer>("Layer");
BookPage::registerMyGUIComponents (); BookPage::registerMyGUIComponents();
PostProcessorHud::registerMyGUIComponents();
ItemView::registerComponents(); ItemView::registerComponents();
ItemChargeView::registerComponents(); ItemChargeView::registerComponents();
ItemWidget::registerComponents(); ItemWidget::registerComponents();
@ -469,6 +473,10 @@ namespace MWGui
mDebugWindow = new DebugWindow(); mDebugWindow = new DebugWindow();
mWindows.push_back(mDebugWindow); mWindows.push_back(mDebugWindow);
mPostProcessorHud = new PostProcessorHud();
mWindows.push_back(mPostProcessorHud);
trackWindow(mPostProcessorHud, "postprocessor");
mInputBlocker = MyGUI::Gui::getInstance().createWidget<MyGUI::Widget>("",0,0,w,h,MyGUI::Align::Stretch,"InputBlocker"); mInputBlocker = MyGUI::Gui::getInstance().createWidget<MyGUI::Widget>("",0,0,w,h,MyGUI::Align::Stretch,"InputBlocker");
mHud->setVisible(true); mHud->setVisible(true);
@ -897,6 +905,8 @@ namespace MWGui
mDebugWindow->onFrame(frameDuration); mDebugWindow->onFrame(frameDuration);
mPostProcessorHud->onFrame(frameDuration);
if (mCharGen) if (mCharGen)
mCharGen->onFrame(frameDuration); mCharGen->onFrame(frameDuration);
@ -1400,6 +1410,7 @@ namespace MWGui
MWGui::CountDialog* WindowManager::getCountDialog() { return mCountDialog; } MWGui::CountDialog* WindowManager::getCountDialog() { return mCountDialog; }
MWGui::ConfirmationDialog* WindowManager::getConfirmationDialog() { return mConfirmationDialog; } MWGui::ConfirmationDialog* WindowManager::getConfirmationDialog() { return mConfirmationDialog; }
MWGui::TradeWindow* WindowManager::getTradeWindow() { return mTradeWindow; } MWGui::TradeWindow* WindowManager::getTradeWindow() { return mTradeWindow; }
MWGui::PostProcessorHud* WindowManager::getPostProcessorHud() { return mPostProcessorHud; }
void WindowManager::useItem(const MWWorld::Ptr &item, bool bypassBeastRestrictions) void WindowManager::useItem(const MWWorld::Ptr &item, bool bypassBeastRestrictions)
{ {
@ -1488,6 +1499,7 @@ namespace MWGui
return return
!mGuiModes.empty() || !mGuiModes.empty() ||
isConsoleMode() || isConsoleMode() ||
(mPostProcessorHud && mPostProcessorHud->isVisible()) ||
(mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox()); (mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox());
} }
@ -2054,6 +2066,24 @@ namespace MWGui
#endif #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) void WindowManager::cycleSpell(bool next)
{ {
if (!isGuiMode()) if (!isGuiMode())

@ -123,6 +123,7 @@ namespace MWGui
class WindowModal; class WindowModal;
class ScreenFader; class ScreenFader;
class DebugWindow; class DebugWindow;
class PostProcessorHud;
class JailScreen; class JailScreen;
class KeyboardNavigation; class KeyboardNavigation;
@ -188,6 +189,7 @@ namespace MWGui
MWGui::ConfirmationDialog* getConfirmationDialog() override; MWGui::ConfirmationDialog* getConfirmationDialog() override;
MWGui::TradeWindow* getTradeWindow() override; MWGui::TradeWindow* getTradeWindow() override;
const std::vector<MWGui::MessageBox*> getActiveMessageBoxes() override; const std::vector<MWGui::MessageBox*> getActiveMessageBoxes() override;
MWGui::PostProcessorHud* getPostProcessorHud() override;
/// Make the player use an item, while updating GUI state accordingly /// Make the player use an item, while updating GUI state accordingly
void useItem(const MWWorld::Ptr& item, bool bypassBeastRestrictions=false) override; void useItem(const MWWorld::Ptr& item, bool bypassBeastRestrictions=false) override;
@ -366,6 +368,7 @@ namespace MWGui
void toggleConsole() override; void toggleConsole() override;
void toggleDebugWindow() override; void toggleDebugWindow() override;
void togglePostProcessorHud() override;
/// Cycle to next or previous spell /// Cycle to next or previous spell
void cycleSpell(bool next) override; void cycleSpell(bool next) override;
@ -452,6 +455,7 @@ namespace MWGui
ScreenFader* mHitFader; ScreenFader* mHitFader;
ScreenFader* mScreenFader; ScreenFader* mScreenFader;
DebugWindow* mDebugWindow; DebugWindow* mDebugWindow;
PostProcessorHud* mPostProcessorHud;
JailScreen* mJailScreen; JailScreen* mJailScreen;
ContainerWindow* mContainerWindow; ContainerWindow* mContainerWindow;

@ -241,6 +241,9 @@ namespace MWInput
case A_ToggleDebug: case A_ToggleDebug:
windowManager->toggleDebugWindow(); windowManager->toggleDebugWindow();
break; break;
case A_TogglePostProcessorHUD:
windowManager->togglePostProcessorHud();
break;
case A_QuickSave: case A_QuickSave:
quickSave(); quickSave();
break; break;

@ -73,6 +73,8 @@ namespace MWInput
A_ZoomIn, A_ZoomIn,
A_ZoomOut, A_ZoomOut,
A_TogglePostProcessorHUD,
A_Last // Marker for the last item A_Last // Marker for the last item
}; };
} }

@ -286,6 +286,7 @@ namespace MWInput
defaultKeyBindings[A_AlwaysRun] = SDL_SCANCODE_CAPSLOCK; defaultKeyBindings[A_AlwaysRun] = SDL_SCANCODE_CAPSLOCK;
defaultKeyBindings[A_QuickSave] = SDL_SCANCODE_F5; defaultKeyBindings[A_QuickSave] = SDL_SCANCODE_F5;
defaultKeyBindings[A_QuickLoad] = SDL_SCANCODE_F9; defaultKeyBindings[A_QuickLoad] = SDL_SCANCODE_F9;
defaultKeyBindings[A_TogglePostProcessorHUD] = SDL_SCANCODE_F2;
std::map<int, int> defaultMouseButtonBindings; std::map<int, int> defaultMouseButtonBindings;
defaultMouseButtonBindings[A_Inventory] = SDL_BUTTON_RIGHT; defaultMouseButtonBindings[A_Inventory] = SDL_BUTTON_RIGHT;
@ -502,6 +503,8 @@ namespace MWInput
return "#{sQuickSaveCmd}"; return "#{sQuickSaveCmd}";
case A_QuickLoad: case A_QuickLoad:
return "#{sQuickLoadCmd}"; return "#{sQuickLoadCmd}";
case A_TogglePostProcessorHUD:
return "Toggle Post Processor HUD";
default: default:
return std::string(); // not configurable return std::string(); // not configurable
} }
@ -563,7 +566,8 @@ namespace MWInput
A_CycleSpellLeft, A_CycleSpellRight, A_CycleWeaponLeft, A_CycleWeaponRight, A_AutoMove, 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_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_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; return actions;

@ -136,6 +136,7 @@ namespace MWLua
{"ToggleHUD", MWInput::A_ToggleHUD}, {"ToggleHUD", MWInput::A_ToggleHUD},
{"ToggleDebug", MWInput::A_ToggleDebug}, {"ToggleDebug", MWInput::A_ToggleDebug},
{"TogglePostProcessorHUD", MWInput::A_TogglePostProcessorHUD},
{"ZoomIn", MWInput::A_ZoomIn}, {"ZoomIn", MWInput::A_ZoomIn},
{"ZoomOut", MWInput::A_ZoomOut} {"ZoomOut", MWInput::A_ZoomOut}

@ -21,6 +21,7 @@ namespace MWLua
sol::table initCorePackage(const Context&); sol::table initCorePackage(const Context&);
sol::table initWorldPackage(const Context&); sol::table initWorldPackage(const Context&);
sol::table initPostprocessingPackage(const Context&);
sol::table initGlobalStoragePackage(const Context&, LuaUtil::LuaStorage* globalStorage); sol::table initGlobalStoragePackage(const Context&, LuaUtil::LuaStorage* globalStorage);
sol::table initLocalStoragePackage(const Context&, LuaUtil::LuaStorage* globalStorage); sol::table initLocalStoragePackage(const Context&, LuaUtil::LuaStorage* globalStorage);

@ -95,6 +95,7 @@ namespace MWLua
mPlayerSettingsPackage = initPlayerSettingsPackage(localContext); mPlayerSettingsPackage = initPlayerSettingsPackage(localContext);
mLocalStoragePackage = initLocalStoragePackage(localContext, &mGlobalStorage); mLocalStoragePackage = initLocalStoragePackage(localContext, &mGlobalStorage);
mPlayerStoragePackage = initPlayerStoragePackage(localContext, &mGlobalStorage, &mPlayerStorage); mPlayerStoragePackage = initPlayerStoragePackage(localContext, &mGlobalStorage, &mPlayerStorage);
mPostprocessingPackage = initPostprocessingPackage(localContext);
initConfiguration(); initConfiguration();
mInitialized = true; mInitialized = true;
@ -407,6 +408,7 @@ namespace MWLua
scripts->addPackage("openmw.input", mInputPackage); scripts->addPackage("openmw.input", mInputPackage);
scripts->addPackage("openmw.settings", mPlayerSettingsPackage); scripts->addPackage("openmw.settings", mPlayerSettingsPackage);
scripts->addPackage("openmw.storage", mPlayerStoragePackage); scripts->addPackage("openmw.storage", mPlayerStoragePackage);
scripts->addPackage("openmw.postprocessing", mPostprocessingPackage);
} }
else else
{ {

@ -140,6 +140,7 @@ namespace MWLua
sol::table mPlayerSettingsPackage; sol::table mPlayerSettingsPackage;
sol::table mLocalStoragePackage; sol::table mLocalStoragePackage;
sol::table mPlayerStoragePackage; sol::table mPlayerStoragePackage;
sol::table mPostprocessingPackage;
GlobalScripts mGlobalScripts{&mLua}; GlobalScripts mGlobalScripts{&mLua};
std::set<LocalScripts*> mActiveLocalScripts; std::set<LocalScripts*> mActiveLocalScripts;

@ -0,0 +1,140 @@
#include "luabindings.hpp"
#include "../mwbase/environment.hpp"
#include "../mwrender/postprocessor.hpp"
#include "luamanagerimp.hpp"
namespace
{
template <class T>
class SetUniformShaderAction final : public MWLua::LuaManager::Action
{
public:
SetUniformShaderAction(LuaUtil::LuaState* state, std::shared_ptr<fx::Technique> 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<fx::Technique> mShader;
std::string mName;
T mValue;
};
}
namespace MWLua
{
struct Shader;
}
namespace sol
{
template <>
struct is_automagical<MWLua::Shader> : std::false_type {};
}
namespace MWLua
{
struct Shader
{
std::shared_ptr<fx::Technique> mShader;
Shader(std::shared_ptr<fx::Technique> 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> shader = context.mLua->sol().new_usertype<Shader>("Shader");
shader[sol::meta_function::to_string] = [](const Shader& shader) { return shader.toString(); };
shader["enable"] = [context](Shader& shader, sol::optional<int> optPos)
{
std::optional<int> 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<SetUniformShaderAction<bool>>(context.mLua, shader.mShader, name, value));
};
shader["setFloat"] = [context](const Shader& shader, const std::string& name, float value)
{
context.mLuaManager->addAction(std::make_unique<SetUniformShaderAction<float>>(context.mLua, shader.mShader, name, value));
};
shader["setInt"] = [context](const Shader& shader, const std::string& name, int value)
{
context.mLuaManager->addAction(std::make_unique<SetUniformShaderAction<int>>(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<SetUniformShaderAction<osg::Vec2f>>(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<SetUniformShaderAction<osg::Vec3f>>(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<SetUniformShaderAction<osg::Vec4f>>(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);
}
}

@ -8,6 +8,7 @@
#include <osg/Material> #include <osg/Material>
#include <osg/Switch> #include <osg/Switch>
#include <osg/LightModel> #include <osg/LightModel>
#include <osg/ColorMaski>
#include <osgParticle/ParticleSystem> #include <osgParticle/ParticleSystem>
#include <osgParticle/ParticleProcessor> #include <osgParticle/ParticleProcessor>
@ -1563,7 +1564,8 @@ namespace MWRender
// Morrowind has a white ambient light attached to the root VFX node of the scenegraph // 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); 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; SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor;
node->accept(findMaxLengthVisitor); node->accept(findMaxLengthVisitor);

@ -147,7 +147,7 @@ namespace MWRender
class CharacterPreviewRTTNode : public SceneUtil::RTTNode class CharacterPreviewRTTNode : public SceneUtil::RTTNode
{ {
static constexpr float fovYDegrees = 12.3f; static constexpr float fovYDegrees = 12.3f;
static constexpr float znear = 0.1f; static constexpr float znear = 4.0f;
static constexpr float zfar = 10000.f; static constexpr float zfar = 10000.f;
public: public:
@ -162,31 +162,23 @@ namespace MWRender
mGroup->getOrCreateStateSet()->addUniform(new osg::Uniform("projectionMatrix", mPerspectiveMatrix)); mGroup->getOrCreateStateSet()->addUniform(new osg::Uniform("projectionMatrix", mPerspectiveMatrix));
mViewMatrix = osg::Matrixf::identity(); mViewMatrix = osg::Matrixf::identity();
setColorBufferInternalFormat(GL_RGBA); setColorBufferInternalFormat(GL_RGBA);
setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8);
} }
void setDefaults(osg::Camera* camera) override void setDefaults(osg::Camera* camera) override
{ {
camera->setName("CharacterPreview");
// hints that the camera is not relative to the master camera
camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF);
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); 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->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->setViewport(0, 0, width(), height());
camera->setRenderOrder(osg::Camera::PRE_RENDER); camera->setRenderOrder(osg::Camera::PRE_RENDER);
camera->setName("CharacterPreview");
camera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES);
camera->setCullMask(~(Mask_UpdateVisitor)); camera->setCullMask(~(Mask_UpdateVisitor));
camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR);
SceneUtil::setCameraClearDepth(camera); 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->setProjectionMatrixAsPerspective(fovYDegrees, mAspectRatio, znear, zfar);
camera->setViewport(0, 0, width(), height());
camera->setRenderOrder(osg::Camera::PRE_RENDER);
#ifdef OSG_HAS_MULTIVIEW #ifdef OSG_HAS_MULTIVIEW
if (shouldDoTextureArray()) if (shouldDoTextureArray())
{ {

@ -0,0 +1,123 @@
#include "hdr.hpp"
#include <components/settings/settings.hpp>
#include <components/shader/shadermanager.hpp>
#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<osg::Texture2D> HDRDriver::getLuminanceTexture(size_t frameId) const
{
return mBuffers[frameId].finalTexture;
}
}

@ -0,0 +1,71 @@
#ifndef OPENMW_MWRENDER_HDR_H
#define OPENMW_MWRENDER_HDR_H
#include <array>
#include <osg/FrameBufferObject>
#include <osg/Texture2D>
#include <osg/Program>
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<osg::Texture2D> getLuminanceTexture(size_t frameId) const;
private:
void compile();
struct HDRContainer
{
osg::ref_ptr<osg::FrameBufferObject> fullscreenFbo;
osg::ref_ptr<osg::FrameBufferObject> mipmapFbo;
osg::ref_ptr<osg::FrameBufferObject> finalFbo;
osg::ref_ptr<osg::Texture2D> texture;
osg::ref_ptr<osg::Texture2D> finalTexture;
osg::ref_ptr<osg::StateSet> fullscreenStateset;
osg::ref_ptr<osg::StateSet> mipmapStateset;
};
std::array<HDRContainer, 2> mBuffers;
osg::ref_ptr<osg::Program> mLuminanceProgram;
osg::ref_ptr<osg::Program> mProgram;
bool mCompiled;
bool mEnabled;
int mWidth;
int mHeight;
};
}
#endif

@ -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); mViewMatrix.makeLookAt(osg::Vec3d(x, y, zmax + 5), osg::Vec3d(x, y, zmin), upVector);
setUpdateCallback(new CameraLocalUpdateCallback); setUpdateCallback(new CameraLocalUpdateCallback);
setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8);
} }
void LocalMapRenderToTexture::setDefaults(osg::Camera* camera) void LocalMapRenderToTexture::setDefaults(osg::Camera* camera)

@ -327,24 +327,31 @@ public:
state->applyAttribute(mDepth); state->applyAttribute(mDepth);
if (postProcessor && postProcessor->getFirstPersonRBProxy()) unsigned int frameId = state->getFrameStamp()->getFrameNumber() % 2;
{
osg::GLExtensions* ext = state->get<osg::GLExtensions>();
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); glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// color accumulation pass // color accumulation pass
bin->drawImplementation(renderInfo, previous); bin->drawImplementation(renderInfo, previous);
auto primaryFBO = postProcessor->getMsaaFbo() ? postProcessor->getMsaaFbo() : postProcessor->getFbo(); auto primaryFBO = postProcessor->getPrimaryFbo(frameId);
primaryFBO->getAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER).attach(*state, GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, ext);
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 // depth accumulation pass
osg::ref_ptr<osg::StateSet> restore = bin->getStateSet();
bin->setStateSet(mStateSet);
bin->drawImplementation(renderInfo, previous); bin->drawImplementation(renderInfo, previous);
state->popStateSet(); bin->setStateSet(restore);
if (postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId))
primaryFBO->apply(*state);
} }
else else
{ {

@ -0,0 +1,260 @@
#include "pingpongcanvas.hpp"
#include <components/shader/shadermanager.hpp>
#include <components/debug/debuglog.hpp>
#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<osg::Vec3Array> 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<osg::GLExtensions>();
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<size_t> 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<std::array<int, 2>, 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<osg::FrameBufferObject>& 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();
}
}
}

@ -0,0 +1,88 @@
#ifndef OPENMW_MWRENDER_PINGPONGCANVAS_H
#define OPENMW_MWRENDER_PINGPONGCANVAS_H
#include <array>
#include <optional>
#include <osg/Geometry>
#include <osg/Texture2D>
#include <osg/FrameBufferObject>
#include <components/fx/technique.hpp>
#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<osg::Texture> tex) { mBufferData[frameId].sceneTex = tex; }
void setLDRSceneTexture(size_t frameId, osg::ref_ptr<osg::Texture2D> tex) { mBufferData[frameId].sceneTexLDR = tex; }
void setDepthTexture(size_t frameId, osg::ref_ptr<osg::Texture> tex) { mBufferData[frameId].depthTex = tex; }
void setNormalsTexture(size_t frameId, osg::ref_ptr<osg::Texture2D> 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<osg::Texture>& 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<osg::Program> mFallbackProgram;
osg::ref_ptr<osg::StateSet> mFallbackStateSet;
struct BufferData
{
bool dirty = false;
bool hdr = false;
bool postprocessing = true;
fx::DispatchArray data;
fx::FlagsType mask;
osg::ref_ptr<osg::FrameBufferObject> destination;
osg::ref_ptr<osg::Texture> sceneTex;
osg::ref_ptr<osg::Texture> depthTex;
osg::ref_ptr<osg::Texture2D> sceneTexLDR;
osg::ref_ptr<osg::Texture2D> normalsTex;
};
mutable std::array<BufferData, 2> mBufferData;
mutable std::array<osg::ref_ptr<osg::FrameBufferObject>, 3> mFbos;
mutable std::optional<fx::DispatchArray> mQueuedDispatchArray;
mutable size_t mQueuedDispatchFrameId;
};
}
#endif

@ -0,0 +1,47 @@
#include "pingpongcull.hpp"
#include <osg/Camera>
#include <osg/FrameBufferObject>
#include <osgUtil/CullVisitor>
#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<MWRender::PostProcessor*>(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);
}
}

@ -0,0 +1,22 @@
#ifndef OPENMW_MWRENDER_PINGPONGCULL_H
#define OPENMW_MWRENDER_PINGPONGCULL_H
#include <array>
#include <components/sceneutil/nodecallback.hpp>
#include "postprocessor.hpp"
namespace MWRender
{
class PostProcessor;
class PingPongCull : public SceneUtil::NodeCallback<PingPongCull, osg::Node*, osgUtil::CullVisitor*>
{
public:
void operator()(osg::Node* node, osgUtil::CullVisitor* nv);
private:
osg::Matrixf mLastViewMatrix;
};
}
#endif

File diff suppressed because it is too large Load Diff

@ -1,11 +1,26 @@
#ifndef OPENMW_MWRENDER_POSTPROCESSOR_H #ifndef OPENMW_MWRENDER_POSTPROCESSOR_H
#define OPENMW_MWRENDER_POSTPROCESSOR_H #define OPENMW_MWRENDER_POSTPROCESSOR_H
#include <array>
#include <vector>
#include <string>
#include <unordered_map>
#include <filesystem>
#include <osg/Texture2D> #include <osg/Texture2D>
#include <osg/Group> #include <osg/Group>
#include <osg/FrameBufferObject> #include <osg/FrameBufferObject>
#include <osg/Camera> #include <osg/Camera>
#include <osg/ref_ptr>
#include <osgViewer/Viewer>
#include <components/fx/stateupdater.hpp>
#include <components/fx/technique.hpp>
#include <components/debug/debuglog.hpp>
#include "pingpongcanvas.hpp"
#include "transparentpass.hpp"
#include <memory> #include <memory>
@ -19,38 +34,197 @@ namespace Stereo
class MultiviewFramebuffer; class MultiviewFramebuffer;
} }
namespace VFS
{
class Manager;
}
namespace Shader
{
class ShaderManager;
}
namespace MWRender namespace MWRender
{ {
class PostProcessor : public osg::Referenced class RenderingManager;
class PingPongCull;
class PingPongCanvas;
class TransparentDepthBinCallback;
class PostProcessor : public osg::Group
{ {
public: public:
PostProcessor(osgViewer::Viewer* viewer, osg::Group* rootNode); using FBOArray = std::array<osg::ref_ptr<osg::FrameBufferObject>, 5>;
using TextureArray = std::array<osg::ref_ptr<osg::Texture2D>, 5>;
using TechniqueList = std::vector<std::shared_ptr<fx::Technique>>;
enum TextureIndex
{
Tex_Scene,
Tex_Scene_LDR,
Tex_Depth,
Tex_OpaqueDepth,
Tex_Normal
};
enum FBOIndex
{
FBO_Primary,
FBO_Multisample,
FBO_FirstPerson,
FBO_OpaqueDepth,
FBO_Intercept
};
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<osg::FrameBufferObject> getFbo(FBOIndex index, unsigned int frameId) { return mFbos[frameId][index]; }
osg::ref_ptr<osg::Texture2D> getTexture(TextureIndex index, unsigned int frameId) { return mTextures[frameId][index]; }
osg::ref_ptr<osg::FrameBufferObject> getPrimaryFbo(unsigned int frameId) { return mFbos[frameId][FBO_Multisample] ? mFbos[frameId][FBO_Multisample] : mFbos[frameId][FBO_Primary]; }
osg::ref_ptr<fx::StateUpdater> getStateUpdater() { return mStateUpdater; }
const TechniqueList& getTechniques() { return mTechniques; }
const TechniqueList& getTemplates() const { return mTemplates; }
osg::ref_ptr<PingPongCanvas> getCanvas() { return mPingPongCanvas; }
const auto& getTechniqueMap() const { return mTechniqueFileMap; }
void resize();
bool enableTechnique(std::shared_ptr<fx::Technique> technique, std::optional<int> location = std::nullopt);
bool disableTechnique(std::shared_ptr<fx::Technique> technique, bool dirty = true);
bool getSupportsNormalsRT() const { return mNormalsSupported; }
template <class T>
void setUniform(std::shared_ptr<fx::Technique> technique, const std::string& name, const T& value)
{
if (!isEnabled())
return;
auto it = technique->findUniform(name);
auto getMsaaFbo() { return mMsaaFbo; } if (it == technique->getUniformMap().end())
auto getFbo() { return mFbo; } return;
auto getFirstPersonRBProxy() { return mFirstPersonDepthRBProxy; }
osg::ref_ptr<osg::Texture2D> getOpaqueDepthTex() { return mOpaqueDepthTex; } if ((*it)->mStatic)
{
Log(Debug::Warning) << "Attempting to set a configration variable [" << name << "] as a uniform";
return;
}
void resize(int width, int height); (*it)->setValue(value);
}
bool isTechniqueEnabled(const std::shared_ptr<fx::Technique>& technique) const;
void setExteriorFlag(bool exterior) { mExteriorFlag = exterior; }
void setUnderwaterFlag(bool underwater) { mUnderwater = underwater; }
void toggleMode();
std::shared_ptr<fx::Technique> loadTechnique(const std::string& name, bool insert=true);
void addTemplate(std::shared_ptr<fx::Technique> 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: 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<osg::Group> mRootNode; osg::ref_ptr<osg::Group> mRootNode;
osg::ref_ptr<osg::Camera> mHUDCamera; osg::ref_ptr<osg::Camera> mHUDCamera;
std::shared_ptr<Stereo::MultiviewFramebuffer> mMultiviewFbo; std::array<TextureArray, 2> mTextures;
osg::ref_ptr<osg::FrameBufferObject> mMsaaFbo; std::array<FBOArray, 2> mFbos;
osg::ref_ptr<osg::FrameBufferObject> mFbo;
osg::ref_ptr<osg::RenderBuffer> mFirstPersonDepthRBProxy; TechniqueList mTechniques;
TechniqueList mTemplates;
std::unordered_map<std::string, std::filesystem::path> mTechniqueFileMap;
int mSamples;
bool mDirty;
size_t mDirtyFrameId;
osg::ref_ptr<osg::Texture2D> mSceneTex; RenderingManager& mRendering;
osg::ref_ptr<osg::Texture2D> mDepthTex; osgViewer::Viewer* mViewer;
osg::ref_ptr<osg::Texture2D> mOpaqueDepthTex; 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<osg::Texture2D> mMainTemplate;
osg::ref_ptr<fx::StateUpdater> mStateUpdater;
osg::ref_ptr<PingPongCull> mPingPongCull;
osg::ref_ptr<PingPongCanvas> mPingPongCanvas;
osg::ref_ptr<TransparentDepthBinCallback> mTransparentDepthPostPass;
int mWidth;
int mHeight;
}; };
} }
#endif #endif

@ -54,7 +54,9 @@
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/groundcoverstore.hpp" #include "../mwworld/groundcoverstore.hpp"
#include "../mwgui/loadingscreen.hpp" #include "../mwgui/loadingscreen.hpp"
#include "../mwgui/postprocessorhud.hpp"
#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/actorutil.hpp"
#include "../mwbase/windowmanager.hpp"
#include "sky.hpp" #include "sky.hpp"
#include "effectmanager.hpp" #include "effectmanager.hpp"
@ -114,7 +116,7 @@ namespace MWRender
mProjectionMatrix = projectionMatrix; mProjectionMatrix = projectionMatrix;
} }
const osg::Matrixf& projectionMatrix() const const osg::Matrixf& getProjectionMatrix() const
{ {
return mProjectionMatrix; return mProjectionMatrix;
} }
@ -208,7 +210,6 @@ namespace MWRender
mPlayerPos = playerPos; mPlayerPos = playerPos;
} }
private: private:
float mLinearFac; float mLinearFac;
float mNear; float mNear;
@ -411,9 +412,11 @@ namespace MWRender
globalDefines["clamp"] = Settings::Manager::getBool("clamp lighting", "Shaders") ? "1" : "0"; globalDefines["clamp"] = Settings::Manager::getBool("clamp lighting", "Shaders") ? "1" : "0";
globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "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["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0";
globalDefines["refraction_enabled"] = "0";
globalDefines["useGPUShader4"] = "0"; globalDefines["useGPUShader4"] = "0";
globalDefines["useOVR_multiview"] = "0"; globalDefines["useOVR_multiview"] = "0";
globalDefines["numViews"] = "1"; globalDefines["numViews"] = "1";
globalDefines["disableNormals"] = "1";
for (auto itr = lightDefines.begin(); itr != lightDefines.end(); itr++) for (auto itr = lightDefines.begin(); itr != lightDefines.end(); itr++)
globalDefines[itr->first] = itr->second; globalDefines[itr->first] = itr->second;
@ -502,12 +505,9 @@ namespace MWRender
mPerViewUniformStateUpdater = new PerViewUniformStateUpdater(); mPerViewUniformStateUpdater = new PerViewUniformStateUpdater();
rootNode->addCullCallback(mPerViewUniformStateUpdater); rootNode->addCullCallback(mPerViewUniformStateUpdater);
mPostProcessor = new PostProcessor(viewer, mRootNode); mPostProcessor = new PostProcessor(*this, viewer, mRootNode, resourceSystem->getVFS());
resourceSystem->getSceneManager()->setDepthFormat(SceneUtil::AutoDepth::depthInternalFormat()); resourceSystem->getSceneManager()->setOpaqueDepthTex(mPostProcessor->getTexture(PostProcessor::Tex_OpaqueDepth, 0), mPostProcessor->getTexture(PostProcessor::Tex_OpaqueDepth, 1));
resourceSystem->getSceneManager()->setOpaqueDepthTex(mPostProcessor->getOpaqueDepthTex()); resourceSystem->getSceneManager()->setSupportsNormalsRT(mPostProcessor->getSupportsNormalsRT());
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.";
// water goes after terrain for correct waterculling order // water goes after terrain for correct waterculling order
mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath)); 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) void RenderingManager::configureAmbient(const ESM::Cell *cell)
{ {
bool isInterior = !cell->isExterior() && !(cell->mData.mFlags & ESM::Cell::QuasiEx);
bool needsAdjusting = false; bool needsAdjusting = false;
if (mResourceSystem->getSceneManager()->getLightingMethod() != SceneUtil::LightingMethod::FFP) 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); 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)); 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? // need to wrap this in a StateUpdater?
mSunLight->setDiffuse(diffuse); mSunLight->setDiffuse(diffuse);
mSunLight->setSpecular(specular); mSunLight->setSpecular(specular);
mPostProcessor->getStateUpdater()->setSunColor(diffuse);
mPostProcessor->getStateUpdater()->setSunVis(sunVis);
} }
void RenderingManager::setSunDirection(const osg::Vec3f &direction) void RenderingManager::setSunDirection(const osg::Vec3f &direction)
@ -738,6 +742,8 @@ namespace MWRender
mSunLight->setPosition(osg::Vec4(position.x(), position.y(), position.z(), 0)); mSunLight->setPosition(osg::Vec4(position.x(), position.y(), position.z(), 0));
mSky->setSunDirection(position); mSky->setSunDirection(position);
mPostProcessor->getStateUpdater()->setSunPos(mSunLight->getPosition(), mNight);
} }
void RenderingManager::addCell(const MWWorld::CellStore *store) void RenderingManager::addCell(const MWWorld::CellStore *store)
@ -779,6 +785,7 @@ namespace MWRender
mShadowManager->enableOutdoorMode(); mShadowManager->enableOutdoorMode();
else else
mShadowManager->enableIndoorMode(); mShadowManager->enableIndoorMode();
mPostProcessor->getStateUpdater()->setIsInterior(!enabled);
} }
bool RenderingManager::toggleBorders() bool RenderingManager::toggleBorders()
@ -877,9 +884,28 @@ namespace MWRender
mCamera->update(dt, paused); mCamera->update(dt, paused);
bool isUnderwater = mWater->isUnderwater(mCamera->getPosition()); bool isUnderwater = mWater->isUnderwater(mCamera->getPosition());
mStateUpdater->setFogStart(mFog->getFogStart(isUnderwater));
mStateUpdater->setFogEnd(mFog->getFogEnd(isUnderwater)); float fogStart = mFog->getFogStart(isUnderwater);
setFogColor(mFog->getFogColor(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) void RenderingManager::updatePlayerPtr(const MWWorld::Ptr &ptr)
@ -939,6 +965,8 @@ namespace MWRender
mWater->setCullCallback(mTerrain->getHeightCullCallback(height, Mask_Water)); mWater->setCullCallback(mTerrain->getHeightCullCallback(height, Mask_Water));
mWater->setHeight(height); mWater->setHeight(height);
mSky->setWaterHeight(height); mSky->setWaterHeight(height);
mPostProcessor->getStateUpdater()->setWaterHeight(height);
} }
void RenderingManager::screenshot(osg::Image* image, int w, int h) void RenderingManager::screenshot(osg::Image* image, int w, int h)
@ -1131,6 +1159,11 @@ namespace MWRender
return mObjects->getAnimation(ptr); return mObjects->getAnimation(ptr);
} }
PostProcessor* RenderingManager::getPostProcessor()
{
return mPostProcessor;
}
void RenderingManager::setupPlayer(const MWWorld::Ptr &player) void RenderingManager::setupPlayer(const MWWorld::Ptr &player)
{ {
if (!mPlayerNode) if (!mPlayerNode)
@ -1218,9 +1251,9 @@ namespace MWRender
{ {
auto res = Stereo::Manager::instance().eyeResolution(); auto res = Stereo::Manager::instance().eyeResolution();
mSharedUniformStateUpdater->setScreenRes(res.x(), res.y()); 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); mSharedUniformStateUpdater->setScreenRes(width, height);
} }
@ -1229,6 +1262,17 @@ namespace MWRender
// Limit FOV here just for sure, otherwise viewing distance can be too high. // 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); float distanceMult = std::cos(osg::DegreesToRadians(std::min(fov, 140.f))/2.f);
mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f/distanceMult : 1.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() void RenderingManager::updateTextureFiltering()
@ -1335,6 +1379,17 @@ namespace MWRender
mViewer->startThreading(); 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) if (updateProjection)

@ -128,7 +128,8 @@ namespace MWRender
void skySetMoonColour(bool red); void skySetMoonColour(bool red);
void setSunDirection(const osg::Vec3f& direction); 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 configureAmbient(const ESM::Cell* cell);
void configureFog(const ESM::Cell* cell); void configureFog(const ESM::Cell* cell);
@ -192,6 +193,8 @@ namespace MWRender
Animation* getAnimation(const MWWorld::Ptr& ptr); Animation* getAnimation(const MWWorld::Ptr& ptr);
const Animation* getAnimation(const MWWorld::ConstPtr& ptr) const; const Animation* getAnimation(const MWWorld::ConstPtr& ptr) const;
PostProcessor* getPostProcessor();
void addWaterRippleEmitter(const MWWorld::Ptr& ptr); void addWaterRippleEmitter(const MWWorld::Ptr& ptr);
void removeWaterRippleEmitter(const MWWorld::Ptr& ptr); void removeWaterRippleEmitter(const MWWorld::Ptr& ptr);
void emitWaterRipple(const osg::Vec3f& pos); void emitWaterRipple(const osg::Vec3f& pos);
@ -247,6 +250,8 @@ namespace MWRender
void updateProjectionMatrix(); void updateProjectionMatrix();
void setScreenRes(int width, int height);
private: private:
void updateTextureFiltering(); void updateTextureFiltering();
void updateAmbient(); void updateAmbient();
@ -310,6 +315,7 @@ namespace MWRender
float mFieldOfView; float mFieldOfView;
float mFirstPersonFieldOfView; float mFirstPersonFieldOfView;
bool mUpdateProjectionMatrix = false; bool mUpdateProjectionMatrix = false;
bool mNight = false;
void operator = (const RenderingManager&); void operator = (const RenderingManager&);
RenderingManager(const RenderingManager&); RenderingManager(const RenderingManager&);

@ -106,17 +106,16 @@ namespace MWRender
if (ext) if (ext)
{ {
size_t frameId = renderInfo.getState()->getFrameStamp()->getFrameNumber() % 2;
osg::FrameBufferObject* fbo = nullptr; osg::FrameBufferObject* fbo = nullptr;
if (Stereo::getStereo()) if (Stereo::getStereo())
fbo = Stereo::Manager::instance().multiviewFramebuffer()->layerFbo(0); fbo = Stereo::Manager::instance().multiviewFramebuffer()->layerFbo(0);
else if (postProcessor) else if (postProcessor && postProcessor->getFbo(PostProcessor::FBO_Primary, frameId))
fbo = postProcessor->getFbo(); fbo = postProcessor->getFbo(PostProcessor::FBO_Primary, frameId);
if (fbo) if (fbo)
{ fbo->apply(*renderInfo.getState(), osg::FrameBufferObject::READ_FRAMEBUFFER);
ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, fbo->getHandle(renderInfo.getContextID()));
renderInfo.getState()->glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
}
} }
mImage->readPixels(leftPadding, topPadding, width, height, GL_RGB, GL_UNSIGNED_BYTE); mImage->readPixels(leftPadding, topPadding, width, height, GL_RGB, GL_UNSIGNED_BYTE);

@ -248,6 +248,7 @@ namespace MWRender
, mBaseWindSpeed(0.f) , mBaseWindSpeed(0.f)
, mEnabled(true) , mEnabled(true)
, mSunEnabled(true) , mSunEnabled(true)
, mSunglareEnabled(true)
, mPrecipitationAlpha(0.f) , mPrecipitationAlpha(0.f)
, mDirtyParticlesEffect(false) , mDirtyParticlesEffect(false)
{ {
@ -303,6 +304,7 @@ namespace MWRender
atmosphereNight->addUpdateCallback(mAtmosphereNightUpdater); atmosphereNight->addUpdateCallback(mAtmosphereNightUpdater);
mSun.reset(new Sun(mEarlyRenderBinRoot, *mSceneManager)); 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)); 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)); mSecunda.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager, Fallback::Map::getFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda));
@ -776,6 +778,14 @@ namespace MWRender
return mBaseWindSpeed; return mBaseWindSpeed;
} }
void SkyManager::setSunglare(bool enabled)
{
mSunglareEnabled = enabled;
if (mSun)
mSun->setSunglare(mSunglareEnabled);
}
void SkyManager::sunEnable() void SkyManager::sunEnable()
{ {
if (!mCreated) return; if (!mCreated) return;

@ -96,6 +96,8 @@ namespace MWRender
float getBaseWindSpeed() const; float getBaseWindSpeed() const;
void setSunglare(bool enabled);
private: private:
void create(); void create();
///< no need to call this, automatically done on first enable() ///< no need to call this, automatically done on first enable()
@ -184,6 +186,7 @@ namespace MWRender
bool mEnabled; bool mEnabled;
bool mSunEnabled; bool mSunEnabled;
bool mSunglareEnabled;
float mPrecipitationAlpha; float mPrecipitationAlpha;
bool mDirtyParticlesEffect; bool mDirtyParticlesEffect;

@ -812,6 +812,12 @@ namespace MWRender
mSunGlareCallback->setTimeOfDayFade(val); mSunGlareCallback->setTimeOfDayFade(val);
} }
void Sun::setSunglare(bool enabled)
{
mSunGlareNode->setNodeMask(enabled ? ~0u : 0);
mSunFlashNode->setNodeMask(enabled ? ~0u : 0);
}
osg::ref_ptr<osg::OcclusionQueryNode> Sun::createOcclusionQueryNode(osg::Group* parent, bool queryVisible) osg::ref_ptr<osg::OcclusionQueryNode> Sun::createOcclusionQueryNode(osg::Group* parent, bool queryVisible)
{ {
osg::ref_ptr<osg::OcclusionQueryNode> oqn = new osg::OcclusionQueryNode; osg::ref_ptr<osg::OcclusionQueryNode> oqn = new osg::OcclusionQueryNode;

@ -248,6 +248,7 @@ namespace MWRender
void setDirection(const osg::Vec3f& direction); void setDirection(const osg::Vec3f& direction);
void setGlareTimeOfDayFade(float val); void setGlareTimeOfDayFade(float val);
void setSunglare(bool enabled);
private: private:
/// @param queryVisible If true, queries the amount of visible pixels. If false, queries the total amount of pixels. /// @param queryVisible If true, queries the amount of visible pixels. If false, queries the total amount of pixels.

@ -0,0 +1,89 @@
#include "transparentpass.hpp"
#include <osg/BlendFunc>
#include <osg/Texture2D>
#include <osgUtil/RenderStage>
#include <components/shader/shadermanager.hpp>
namespace MWRender
{
TransparentDepthBinCallback::TransparentDepthBinCallback(Shader::ShaderManager& shaderManager, bool postPass)
: mStateSet(new osg::StateSet)
, mPostPass(postPass)
{
osg::ref_ptr<osg::Image> 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<osg::Texture2D> 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<osg::Shader> vertex = shaderManager.getShader("blended_depth_postpass_vertex.glsl", {}, osg::Shader::VERTEX);
osg::ref_ptr<osg::Shader> 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<osg::GLExtensions>();
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<osg::StateSet> 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);
}
}

@ -0,0 +1,38 @@
#ifndef OPENMW_MWRENDER_TRANSPARENTPASS_H
#define OPENMW_MWRENDER_TRANSPARENTPASS_H
#include <array>
#include <osg/FrameBufferObject>
#include <osg/StateSet>
#include <osgUtil/RenderBin>
#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<osg::ref_ptr<osg::FrameBufferObject>, 2> mFbo;
std::array<osg::ref_ptr<osg::FrameBufferObject>, 2> mMsaaFbo;
std::array<osg::ref_ptr<osg::FrameBufferObject>, 2> mOpaqueFbo;
private:
osg::ref_ptr<osg::StateSet> mStateSet;
bool mPostPass;
};
}
#endif

@ -267,6 +267,7 @@ public:
: RTTNode(rttSize, rttSize, 0, false, 1, StereoAwareness::Aware) : RTTNode(rttSize, rttSize, 0, false, 1, StereoAwareness::Aware)
, mNodeMask(Refraction::sDefaultCullMask) , mNodeMask(Refraction::sDefaultCullMask)
{ {
setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8);
mClipCullNode = new ClipCullNode; mClipCullNode = new ClipCullNode;
} }
@ -342,6 +343,7 @@ public:
: RTTNode(rttSize, rttSize, 0, false, 0, StereoAwareness::Aware) : RTTNode(rttSize, rttSize, 0, false, 0, StereoAwareness::Aware)
{ {
setInterior(isInterior); setInterior(isInterior);
setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8);
mClipCullNode = new ClipCullNode; mClipCullNode = new ClipCullNode;
} }

@ -27,6 +27,7 @@
#include "../mwrender/renderingmanager.hpp" #include "../mwrender/renderingmanager.hpp"
#include "../mwrender/landmanager.hpp" #include "../mwrender/landmanager.hpp"
#include "../mwrender/postprocessor.hpp"
#include "../mwphysics/physicssystem.hpp" #include "../mwphysics/physicssystem.hpp"
#include "../mwphysics/actor.hpp" #include "../mwphysics/actor.hpp"
@ -860,6 +861,8 @@ namespace MWWorld
MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell);
mNavigator.wait(*loadingListener, DetourNavigator::WaitConditionType::requiredTilesPresent); 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) void Scene::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
@ -879,6 +882,8 @@ namespace MWWorld
if (changeEvent) if (changeEvent)
MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5);
MWBase::Environment::get().getWorld()->getPostProcessor()->setExteriorFlag(true);
} }
CellStore* Scene::getCurrentCell () CellStore* Scene::getCurrentCell ()

@ -786,6 +786,7 @@ namespace MWWorld
-0.268f, // approx tan( -15 degrees ) -0.268f, // approx tan( -15 degrees )
static_cast<float>(sin(theta))); static_cast<float>(sin(theta)));
mRendering.setSunDirection( final * -1 ); mRendering.setSunDirection( final * -1 );
mRendering.setNight(is_night);
} }
float underwaterFog = mUnderwaterFog.getValue(time.getHour(), mTimeSettings, "Fog"); float underwaterFog = mUnderwaterFog.getValue(time.getHour(), mTimeSettings, "Fog");
@ -807,7 +808,7 @@ namespace MWWorld
mRendering.configureFog(mResult.mFogDepth, underwaterFog, mResult.mDLFogFactor, mRendering.configureFog(mResult.mFogDepth, underwaterFog, mResult.mDLFogFactor,
mResult.mDLFogOffset/100.0f, mResult.mFogColor); mResult.mDLFogOffset/100.0f, mResult.mFogColor);
mRendering.setAmbientColour(mResult.mAmbientColor); 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); mRendering.getSkyManager()->setWeather(mResult);
@ -857,11 +858,6 @@ namespace MWWorld
mFastForward = !incremental ? true : mFastForward; mFastForward = !incremental ? true : mFastForward;
} }
unsigned int WeatherManager::getWeatherID() const
{
return mCurrentWeather;
}
NightDayMode WeatherManager::getNightDayMode() const NightDayMode WeatherManager::getNightDayMode() const
{ {
return mNightDayMode; return mNightDayMode;

@ -307,7 +307,11 @@ namespace MWWorld
void advanceTime(double hours, bool incremental); 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; bool useTorches(float hour) const;

@ -55,6 +55,7 @@
#include "../mwrender/renderingmanager.hpp" #include "../mwrender/renderingmanager.hpp"
#include "../mwrender/camera.hpp" #include "../mwrender/camera.hpp"
#include "../mwrender/vismask.hpp" #include "../mwrender/vismask.hpp"
#include "../mwrender/postprocessor.hpp"
#include "../mwscript/globalscripts.hpp" #include "../mwscript/globalscripts.hpp"
@ -2045,6 +2046,16 @@ namespace MWWorld
return mWeatherManager->getWeatherID(); return mWeatherManager->getWeatherID();
} }
int World::getNextWeather() const
{
return mWeatherManager->getNextWeatherID();
}
float World::getWeatherTransition() const
{
return mWeatherManager->getTransitionFactor();
}
unsigned int World::getNightDayMode() const unsigned int World::getNightDayMode() const
{ {
return mWeatherManager->getNightDayMode(); return mWeatherManager->getNightDayMode();
@ -3986,4 +3997,8 @@ namespace MWWorld
return mPrng; return mPrng;
} }
MWRender::PostProcessor* World::getPostProcessor()
{
return mRendering->getPostProcessor();
}
} }

@ -54,6 +54,7 @@ namespace MWRender
class SkyManager; class SkyManager;
class Animation; class Animation;
class Camera; class Camera;
class PostProcessor;
} }
namespace ToUTF8 namespace ToUTF8
@ -329,6 +330,10 @@ namespace MWWorld
int getCurrentWeather() const override; int getCurrentWeather() const override;
int getNextWeather() const override;
float getWeatherTransition() const override;
unsigned int getNightDayMode() const override; unsigned int getNightDayMode() const override;
int getMasserPhase() const override; int getMasserPhase() const override;
@ -747,6 +752,8 @@ namespace MWWorld
Misc::Rng::Generator& getPrng() override; Misc::Rng::Generator& getPrng() override;
MWRender::RenderingManager* getRenderingManager() override { return mRendering.get(); } MWRender::RenderingManager* getRenderingManager() override { return mRendering.get(); }
MWRender::PostProcessor* getPostProcessor() override;
}; };
} }

@ -52,6 +52,7 @@ if (GTEST_FOUND AND GMOCK_FOUND)
serialization/integration.cpp serialization/integration.cpp
settings/parser.cpp settings/parser.cpp
settings/shadermanager.cpp
shader/parsedefines.cpp shader/parsedefines.cpp
shader/parsefors.cpp shader/parsefors.cpp
@ -75,6 +76,9 @@ if (GTEST_FOUND AND GMOCK_FOUND)
toutf8/toutf8.cpp toutf8/toutf8.cpp
esm4/includes.cpp esm4/includes.cpp
fx/lexer.cpp
fx/technique.cpp
) )
source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES})

@ -0,0 +1,216 @@
#include <components/fx/lexer.hpp>
#include <gtest/gtest.h>
namespace
{
using namespace testing;
using namespace fx::Lexer;
struct LexerTest : Test {};
struct LexerSingleTokenTest : Test
{
template <class Token>
void test()
{
const std::string content = std::string(Token::repr);
Lexer lexer(content);
EXPECT_TRUE(std::holds_alternative<Token>(lexer.next()));
}
};
TEST_F(LexerSingleTokenTest, single_token_shared) { test<Shared>(); }
TEST_F(LexerSingleTokenTest, single_token_technique) { test<Technique>(); }
TEST_F(LexerSingleTokenTest, single_token_main_pass) { test<Main_Pass>(); }
TEST_F(LexerSingleTokenTest, single_token_render_target) { test<Render_Target>(); }
TEST_F(LexerSingleTokenTest, single_token_vertex) { test<Vertex>(); }
TEST_F(LexerSingleTokenTest, single_token_fragment) { test<Fragment>(); }
TEST_F(LexerSingleTokenTest, single_token_compute) { test<Compute>(); }
TEST_F(LexerSingleTokenTest, single_token_sampler_1d) { test<Sampler_1D>(); }
TEST_F(LexerSingleTokenTest, single_token_sampler_2d) { test<Sampler_2D>(); }
TEST_F(LexerSingleTokenTest, single_token_sampler_3d) { test<Sampler_3D>(); }
TEST_F(LexerSingleTokenTest, single_token_true) { test<True>(); }
TEST_F(LexerSingleTokenTest, single_token_false) { test<False>(); }
TEST_F(LexerSingleTokenTest, single_token_vec2) { test<Vec2>(); }
TEST_F(LexerSingleTokenTest, single_token_vec3) { test<Vec3>(); }
TEST_F(LexerSingleTokenTest, single_token_vec4) { test<Vec4>(); }
TEST(LexerTest, peek_whitespace_only_content_should_be_eof)
{
Lexer lexer(R"(
)");
EXPECT_TRUE(std::holds_alternative<Eof>(lexer.peek()));
}
TEST(LexerTest, float_with_no_prefixed_digits)
{
Lexer lexer(R"(
0.123;
)");
auto token = lexer.next();
EXPECT_TRUE(std::holds_alternative<Float>(token));
EXPECT_FLOAT_EQ(std::get<Float>(token).value, 0.123f);
}
TEST(LexerTest, float_with_alpha_prefix)
{
Lexer lexer(R"(
abc.123;
)");
EXPECT_TRUE(std::holds_alternative<Literal>(lexer.next()));
auto token = lexer.next();
EXPECT_TRUE(std::holds_alternative<Float>(token));
EXPECT_FLOAT_EQ(std::get<Float>(token).value, 0.123f);
}
TEST(LexerTest, float_with_numeric_prefix)
{
Lexer lexer(R"(
123.123;
)");
auto token = lexer.next();
EXPECT_TRUE(std::holds_alternative<Float>(token));
EXPECT_FLOAT_EQ(std::get<Float>(token).value, 123.123f);
}
TEST(LexerTest, int_should_not_be_float)
{
Lexer lexer(R"(
123
)");
auto token = lexer.next();
EXPECT_TRUE(std::holds_alternative<Integer>(token));
EXPECT_EQ(std::get<Integer>(token).value, 123);
}
TEST(LexerTest, simple_string)
{
Lexer lexer(R"(
"test string"
)");
auto token = lexer.next();
EXPECT_TRUE(std::holds_alternative<String>(token));
std::string parsed = std::string(std::get<String>(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<String>(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<Literal>(lexer.next()));
EXPECT_TRUE(std::holds_alternative<Open_bracket>(lexer.next()));
auto block = lexer.jump();
EXPECT_TRUE(block.has_value());
EXPECT_TRUE(block.value().empty());
EXPECT_TRUE(std::holds_alternative<Close_bracket>(lexer.next()));
}
}

@ -0,0 +1,204 @@
#include "gmock/gmock.h"
#include <gtest/gtest.h>
#include <components/settings/settings.hpp>
#include <components/fx/technique.hpp>
#include <components/resource/imagemanager.hpp>
#include <components/files/configurationmanager.hpp>
#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<VFS::Manager> mVFS;
Resource::ImageManager mImageManager;
std::unique_ptr<Technique> 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<Technique>(*mVFS.get(), mImageManager, name, 1, 1, true, true);
mTechnique->compile();
}
};
TEST_F(TechniqueTest, technique_properties)
{
std::unordered_set<std::string> 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<GLenum>(GL_FLOAT));
EXPECT_EQ(texture->getSourceFormat(), static_cast<GLenum>(GL_RED));
EXPECT_EQ(texture->getInternalFormat(), static_cast<GLint>(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>(), osg::Vec4f(0,0,0,0));
EXPECT_EQ(uniform->getMin<osg::Vec4f>(), osg::Vec4f(0,1,0,0));
EXPECT_EQ(uniform->getMax<osg::Vec4f>(), 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"));
}
}

@ -26,6 +26,11 @@ namespace
return std::make_unique<std::stringstream>(mContent, std::ios_base::in); return std::make_unique<std::stringstream>(mContent, std::ios_base::in);
} }
std::string getPath() override
{
return "TestFile";
}
private: private:
const std::string mContent; const std::string mContent;
}; };

@ -0,0 +1,66 @@
#include <components/settings/shadermanager.hpp>
#include <fstream>
#include <gtest/gtest.h>
namespace
{
using namespace testing;
using namespace Settings;
struct ShaderSettingsTest : Test
{
template <typename F>
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<osg::Vec3f>("shader", "vec3_uniform").has_value());
EXPECT_TRUE(ShaderManager::get().setValue<osg::Vec3f>("shader", "vec3_uniform", osg::Vec3f(1, 2, 3)));
EXPECT_TRUE(ShaderManager::get().getValue<osg::Vec3f>("shader", "vec3_uniform").has_value());
EXPECT_EQ(ShaderManager::get().getValue<osg::Vec3f>("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<float>("shader", "uniform").has_value());
EXPECT_FALSE(ShaderManager::get().save());
});
}
}

@ -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(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 add_component_dir (esm3
esmreader esmwriter loadacti loadalch loadappa loadarmo loadbody loadbook loadbsgn loadcell esmreader esmwriter loadacti loadalch loadappa loadarmo loadbody loadbook loadbsgn loadcell
loadclas loadclot loadcont loadcrea loaddial loaddoor loadench loadfact loadglob loadgmst loadclas loadclot loadcont loadcrea loaddial loaddoor loadench loadfact loadglob loadgmst

@ -0,0 +1,301 @@
#include "lexer.hpp"
#include <string_view>
#include <string>
#include <variant>
#include <optional>
#include <cstdint>
#include <array>
#include <cmath>
#include <exception>
#include <components/misc/stringops.hpp>
#include <components/debug/debuglog.hpp>
#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<std::string_view> 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<std::string_view::size_type>(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<std::string_view::size_type>(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<std::string_view::size_type>(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<float>(buffer)};
}
return Integer{static_cast<int>(buffer)};
}
}
}

@ -0,0 +1,75 @@
#ifndef OPENMW_COMPONENTS_FX_LEXER_H
#define OPENMW_COMPONENTS_FX_LEXER_H
#include <string_view>
#include <string>
#include <variant>
#include <optional>
#include <cstdint>
#include <stdexcept>
#include <osg/Vec2f>
#include <osg/Vec3f>
#include <osg/Vec4f>
#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<std::string_view> 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<Token> mLookahead;
Block mLastJumpBlock;
};
}
}
#endif

@ -0,0 +1,56 @@
#ifndef OPENMW_COMPONENTS_FX_LEXER_TYPES_H
#define OPENMW_COMPONENTS_FX_LEXER_TYPES_H
#include <variant>
#include <string_view>
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<Float, Integer, Boolean, String, Literal, Equal, Open_bracket, Close_bracket, Open_Parenthesis,
Close_Parenthesis, Quote, SemiColon, Comma, VBar, Colon, Shared, Technique, Render_Target, Vertex, Fragment,
Compute, Sampler_1D, Sampler_2D, Sampler_3D, Uniform_Bool, Uniform_Float, Uniform_Int, Uniform_Vec2, Uniform_Vec3, Uniform_Vec4,
True, False, Vec2, Vec3, Vec4, Main_Pass, Eof>;
}
}
#endif

@ -0,0 +1,133 @@
#ifndef OPENMW_COMPONENTS_FX_PARSE_CONSTANTS_H
#define OPENMW_COMPONENTS_FX_PARSE_CONSTANTS_H
#include <array>
#include <string_view>
#include <osg/Texture>
#include <osg/Image>
#include <osg/BlendFunc>
#include <osg/BlendEquation>
#include <components/sceneutil/color.hpp>
#include "technique.hpp"
namespace fx
{
namespace constants
{
constexpr std::array<std::pair<std::string_view, fx::FlagsType>, 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<std::pair<std::string_view, int>, 6> SourceFormat = {{
{"red" , GL_RED},
{"rg" , GL_RG},
{"rgb" , GL_RGB},
{"bgr" , GL_BGR},
{"rgba", GL_RGBA},
{"bgra", GL_BGRA},
}};
constexpr std::array<std::pair<std::string_view, int>, 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<std::pair<std::string_view, int>, 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<std::pair<std::string_view, osg::Texture::InternalFormatMode>, 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<std::pair<std::string_view, osg::Texture::WrapMode>, 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<std::pair<std::string_view, osg::Texture::FilterMode>, 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<std::pair<std::string_view, osg::BlendFunc::BlendFuncMode>, 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<std::pair<std::string_view, osg::BlendEquation::Equation>, 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

@ -0,0 +1,253 @@
#include "pass.hpp"
#include <unordered_set>
#include <string>
#include <sstream>
#include <osg/Program>
#include <osg/Shader>
#include <osg/State>
#include <osg/StateSet>
#include <osg/BindImageTexture>
#include <osg/FrameBufferObject>
#include <components/misc/stringops.hpp>
#include <components/sceneutil/util.hpp>
#include <components/sceneutil/clearcolor.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/stereo/multiview.hpp>
#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<std::pair<std::string,std::string>> 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<osg::Program> 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<int>(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;
}
}

@ -0,0 +1,79 @@
#ifndef OPENMW_COMPONENTS_FX_PASS_H
#define OPENMW_COMPONENTS_FX_PASS_H
#include <array>
#include <string>
#include <sstream>
#include <cstdint>
#include <unordered_set>
#include <optional>
#include <osg/Timer>
#include <osg/Program>
#include <osg/Shader>
#include <osg/State>
#include <osg/Texture2D>
#include <osg/BlendEquation>
#include <osg/BlendFunc>
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<osg::Shader> mVertex;
osg::ref_ptr<osg::Shader> mFragment;
osg::ref_ptr<osg::Shader> mCompute;
Type mType;
Order mOrder;
std::string mName;
bool mLegacyGLSL;
bool mUBO;
bool mSupportsNormals;
std::string_view mTarget;
std::optional<osg::Vec4f> mClearColor;
std::optional<osg::BlendFunc::BlendFuncMode> mBlendSource;
std::optional<osg::BlendFunc::BlendFuncMode> mBlendDest;
std::optional<osg::BlendEquation::Equation> mBlendEq;
};
}
#endif

@ -0,0 +1,60 @@
#include "stateupdater.hpp"
#include <osg/BufferObject>
#include <osg/BufferIndexBinding>
#include <components/resource/scenemanager.hpp>
#include <components/debug/debuglog.hpp>
namespace fx
{
StateUpdater::StateUpdater(bool useUBO) : mUseUBO(useUBO) {}
void StateUpdater::setDefaults(osg::StateSet* stateset)
{
if (mUseUBO)
{
osg::ref_ptr<osg::UniformBufferObject> ubo = new osg::UniformBufferObject;
osg::ref_ptr<osg::BufferTemplate<UniformData::BufferType>> data = new osg::BufferTemplate<UniformData::BufferType>();
data->setBufferObject(ubo);
osg::ref_ptr<osg::UniformBufferBinding> ubb = new osg::UniformBufferBinding(static_cast<int>(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<decltype(v)>;
std::string name = "omw." + std::string(T::sName);
stateset->addUniform(new osg::Uniform(name.c_str(), mData.get<T>()));
};
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<osg::UniformBufferBinding*>(stateset->getAttribute(osg::StateAttribute::UNIFORMBUFFERBINDING, static_cast<int>(Resource::SceneManager::UBOBinding::PostProcessor)));
auto& dest = static_cast<osg::BufferTemplate<UniformData::BufferType>*>(ubb->getBufferData())->getData();
mData.copyTo(dest);
ubb->getBufferData()->dirty();
}
else
{
const auto setUniform = [&] (const auto& v) {
using T = std::decay_t<decltype(v)>;
std::string name = "omw." + std::string(T::sName);
stateset->getUniform(name)->set(mData.get<T>());
};
std::apply([&] (const auto& ... v) { (setUniform(v) , ...); }, mData.getData());
}
}
}

@ -0,0 +1,192 @@
#ifndef OPENMW_COMPONENTS_FX_STATEUPDATER_H
#define OPENMW_COMPONENTS_FX_STATEUPDATER_H
#include <osg/BufferTemplate>
#include <components/sceneutil/statesetupdater.hpp>
#include <components/std140/ubo.hpp>
namespace fx
{
class StateUpdater : public SceneUtil::StateSetUpdater
{
public:
StateUpdater(bool useUBO);
void setProjectionMatrix(const osg::Matrixf& matrix)
{
mData.get<ProjectionMatrix>() = matrix;
mData.get<InvProjectionMatrix>() = osg::Matrixf::inverse(matrix);
}
void setViewMatrix(const osg::Matrixf& matrix) { mData.get<ViewMatrix>() = matrix; }
void setInvViewMatrix(const osg::Matrixf& matrix) { mData.get<InvViewMatrix>() = matrix; }
void setPrevViewMatrix(const osg::Matrixf& matrix) { mData.get<PrevViewMatrix>() = matrix;}
void setEyePos(const osg::Vec3f& pos) { mData.get<EyePos>() = osg::Vec4f(pos, 0.f); }
void setEyeVec(const osg::Vec3f& vec) { mData.get<EyeVec>() = osg::Vec4f(vec, 0.f); }
void setFogColor(const osg::Vec4f& color) { mData.get<FogColor>() = color; }
void setSunColor(const osg::Vec4f& color) { mData.get<SunColor>() = color; }
void setSunPos(const osg::Vec4f& pos, bool night)
{
mData.get<SunPos>() = pos;
if (night)
mData.get<SunPos>().z() *= -1.f;
}
void setResolution(const osg::Vec2f& size)
{
mData.get<Resolution>() = size;
mData.get<RcpResolution>() = {1.f / size.x(), 1.f / size.y()};
}
void setSunVis(float vis)
{
mData.get<SunVis>() = vis;
}
void setFogRange(float near, float far)
{
mData.get<FogNear>() = near;
mData.get<FogFar>() = far;
}
void setNearFar(float near, float far)
{
mData.get<Near>() = near;
mData.get<Far>() = far;
}
void setIsUnderwater(bool underwater) { mData.get<IsUnderwater>() = underwater; }
void setIsInterior(bool interior) { mData.get<IsInterior>() = interior; }
void setFov(float fov) { mData.get<Fov>() = fov; }
void setGameHour(float hour) { mData.get<GameHour>() = hour; }
void setWeatherId(int id) { mData.get<WeatherID>() = id; }
void setNextWeatherId(int id) { mData.get<NextWeatherID>() = id; }
void setWaterHeight(float height) { mData.get<WaterHeight>() = height; }
void setSimulationTime(float time) { mData.get<SimulationTime>() = time; }
void setDeltaSimulationTime(float time) { mData.get<DeltaSimulationTime>() = time; }
void setWindSpeed(float speed) { mData.get<WindSpeed>() = speed; }
void setWeatherTransition(float transition) { mData.get<WeatherTransition>() = 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

File diff suppressed because it is too large Load Diff

@ -0,0 +1,300 @@
#ifndef OPENMW_COMPONENTS_FX_TECHNIQUE_H
#define OPENMW_COMPONENTS_FX_TECHNIQUE_H
#include <vector>
#include <string>
#include <variant>
#include <memory>
#include <unordered_map>
#include <filesystem>
#include <osg/Node>
#include <osg/Program>
#include <osg/Shader>
#include <osg/Texture2D>
#include <osg/StateSet>
#include <osg/FrameBufferObject>
#include <osg/Vec2f>
#include <osg/Vec3f>
#include <osg/Vec4f>
#include <osg/BlendFunc>
#include <osg/BlendEquation>
#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<osg::StateSet> mStateSet = new osg::StateSet;
osg::ref_ptr<osg::FrameBufferObject> mRenderTarget;
osg::ref_ptr<osg::Texture2D> 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<fx::Technique> mHandle = nullptr;
FlagsType mFlags = 0;
std::vector<SubPass> mPasses;
osg::ref_ptr<osg::StateSet> mRootStateSet = new osg::StateSet;
};
using DispatchArray = std::vector<DispatchNode>;
class Technique
{
public:
using PassList = std::vector<std::shared_ptr<Pass>>;
using TexList = std::vector<osg::ref_ptr<osg::Texture>>;
using UniformMap = std::vector<std::shared_ptr<Types::UniformBase>>;
using RenderTargetMap = std::unordered_map<std::string_view, Types::RenderTarget>;
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<std::string>& getGLSLExtensions() const { return mGLSLExtensions; }
osg::ref_ptr<osg::Texture2D> 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<class T>
void expect(const std::string& err="");
template<class T, class T2>
void expect(const std::string& err="");
template <class T>
bool isNext();
void parse(std::string&& buffer);
template <class SrcT, class T>
void parseUniform();
template <class T>
void parseSampler();
template <class T>
void parseBlock(bool named=true);
template <class T>
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 <class TDelimeter>
std::vector<std::string_view> parseLiteralList();
template <class OSGVec, class T>
OSGVec parseVec();
std::string getBlockWithLineDirective();
std::unique_ptr<Lexer::Lexer> 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<std::string> 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<osg::Texture2D> mMainTemplate;
RenderTargetMap mRenderTargets;
TexList mTextures;
PassList mPasses;
std::unordered_map<std::string_view, std::shared_ptr<Pass>> mPassMap;
std::vector<std::string_view> 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<Lexer::Shared>();
template<> void Technique::parseBlockImp<Lexer::Technique>();
template<> void Technique::parseBlockImp<Lexer::Main_Pass>();
template<> void Technique::parseBlockImp<Lexer::Render_Target>();
template<> void Technique::parseBlockImp<Lexer::Vertex>();
template<> void Technique::parseBlockImp<Lexer::Fragment>();
template<> void Technique::parseBlockImp<Lexer::Compute>();
template<> void Technique::parseBlockImp<Lexer::Sampler_1D>();
template<> void Technique::parseBlockImp<Lexer::Sampler_2D>();
template<> void Technique::parseBlockImp<Lexer::Sampler_3D>();
template<> void Technique::parseBlockImp<Lexer::Uniform_Bool>();
template<> void Technique::parseBlockImp<Lexer::Uniform_Float>();
template<> void Technique::parseBlockImp<Lexer::Uniform_Int>();
template<> void Technique::parseBlockImp<Lexer::Uniform_Vec2>();
template<> void Technique::parseBlockImp<Lexer::Uniform_Vec3>();
template<> void Technique::parseBlockImp<Lexer::Uniform_Vec4>();
}
#endif

@ -0,0 +1,259 @@
#ifndef OPENMW_COMPONENTS_FX_TYPES_H
#define OPENMW_COMPONENTS_FX_TYPES_H
#include <optional>
#include <osg/Camera>
#include <osg/Uniform>
#include <osg/Texture2D>
#include <osg/FrameBufferObject>
#include <osg/BlendFunc>
#include <osg/BlendEquation>
#include <MyGUI_Widget.h>
#include <components/sceneutil/depth.hpp>
#include <components/settings/shadermanager.hpp>
#include <components/misc/stringops.hpp>
#include <components/debug/debuglog.hpp>
#include "pass.hpp"
namespace fx
{
namespace Types
{
struct SizeProxy
{
std::optional<float> mWidthRatio;
std::optional<float> mHeightRatio;
std::optional<int> mWidth;
std::optional<int> mHeight;
std::tuple<int,int> 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<osg::Texture2D> mTarget = new osg::Texture2D;
SizeProxy mSize;
bool mMipMap = false;
};
template <class T>
struct Uniform
{
std::optional<T> mValue;
T mDefault;
T mMin = std::numeric_limits<T>::lowest();
T mMax = std::numeric_limits<T>::max();
using value_type = T;
T getValue() const
{
return mValue.value_or(mDefault);
}
};
using Uniform_t = std::variant<
Uniform<osg::Vec2f>,
Uniform<osg::Vec3f>,
Uniform<osg::Vec4f>,
Uniform<bool>,
Uniform<float>,
Uniform<int>
>;
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<SamplerType> mSamplerType = std::nullopt;
double mStep;
Uniform_t mData;
template <class T>
T getValue() const
{
auto value = Settings::ShaderManager::get().getValue<T>(mTechniqueName, mName);
return value.value_or(std::get<Uniform<T>>(mData).getValue());
}
template <class T>
T getMin() const
{
return std::get<Uniform<T>>(mData).mMin;
}
template <class T>
T getMax() const
{
return std::get<Uniform<T>>(mData).mMax;
}
template <class T>
T getDefault() const
{
return std::get<Uniform<T>>(mData).mDefault;
}
template <class T>
void setValue(const T& value)
{
std::visit([&, value](auto&& arg){
using U = typename std::decay_t<decltype(arg)>::value_type;
if constexpr (std::is_same_v<T, U>)
{
arg.mValue = value;
if (mStatic)
Settings::ShaderManager::get().setValue<T>(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<osg::Uniform::Type> getType() const
{
return std::visit([](auto&& arg) -> std::optional<osg::Uniform::Type> {
using T = typename std::decay_t<decltype(arg)>::value_type;
if constexpr (std::is_same_v<T, osg::Vec2f>)
return osg::Uniform::FLOAT_VEC2;
else if constexpr (std::is_same_v<T, osg::Vec3f>)
return osg::Uniform::FLOAT_VEC3;
else if constexpr (std::is_same_v<T, osg::Vec4f>)
return osg::Uniform::FLOAT_VEC4;
else if constexpr (std::is_same_v<T, float>)
return osg::Uniform::FLOAT;
else if constexpr (std::is_same_v<T, int>)
return osg::Uniform::INT;
else if constexpr (std::is_same_v<T, bool>)
return osg::Uniform::BOOL;
return std::nullopt;
}, mData);
}
std::optional<std::string> 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<std::string> {
using T = typename std::decay_t<decltype(arg)>::value_type;
auto value = arg.getValue();
if constexpr (std::is_same_v<T, osg::Vec2f>)
{
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<T, osg::Vec3f>)
{
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<T, osg::Vec4f>)
{
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<T, float>)
{
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<T, int>)
{
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<T, bool>)
{
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

@ -0,0 +1,164 @@
#include "widgets.hpp"
#include <components/widgets/box.hpp>
namespace
{
template <class T, class WidgetT>
void createVectorWidget(const std::shared_ptr<fx::Types::UniformBase>& 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<WidgetT>("MW_ValueEditNumber", {0, height * i, client->getWidth(), height}, MyGUI::Align::Default);
widget->setData(uniform, static_cast<fx::Widgets::Index>(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<bool>(value);
}
void EditBool::setValueFromUniform()
{
auto uniform = mUniform.lock();
if (!uniform)
return;
setValue(uniform->template getValue<bool>());
}
void EditBool::toDefault()
{
auto uniform = mUniform.lock();
if (!uniform)
return;
setValue(uniform->getDefault<bool>());
}
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<bool>());
}
void UniformBase::init(const std::shared_ptr<fx::Types::UniformBase>& 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<decltype(arg)>::value_type;
if constexpr (std::is_same_v<osg::Vec4f, T>)
{
createVectorWidget<T, EditNumberFloat4>(uniform, mClient, this);
}
else if constexpr (std::is_same_v<osg::Vec3f, T>)
{
createVectorWidget<T, EditNumberFloat3>(uniform, mClient, this);
}
else if constexpr (std::is_same_v<osg::Vec2f, T>)
{
createVectorWidget<T, EditNumberFloat2>(uniform, mClient, this);
}
else if constexpr (std::is_same_v<T, float>)
{
auto* widget = mClient->createWidget<EditNumberFloat>("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<T, int>)
{
auto* widget = mClient->createWidget<EditNumberInt>("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<T, bool>)
{
auto* widget = mClient->createWidget<EditBool>("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");
}
}
}

@ -0,0 +1,266 @@
#ifndef OPENMW_COMPONENTS_FX_WIDGETS_H
#define OPENMW_COMPONENTS_FX_WIDGETS_H
#include <MyGUI_Gui.h>
#include <MyGUI_Button.h>
#include <MyGUI_ScrollView.h>
#include <MyGUI_InputManager.h>
#include <osg/Vec2f>
#include <osg/Vec3f>
#include <osg/Vec4f>
#include <components/misc/stringops.hpp>
#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<fx::Types::UniformBase>& uniform, Index index = None)
{
mUniform = uniform;
mIndex = index;
}
virtual void setValueFromUniform() = 0;
virtual void toDefault() = 0;
protected:
std::weak_ptr<fx::Types::UniformBase> 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 T, class UType>
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<T>)
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<UType>)
uniform->template setValue<UType>(mValue);
else
{
UType uvalue = uniform->template getValue<UType>();
uvalue[mIndex] = mValue;
uniform->template setValue<UType>(uvalue);
}
}
}
void setValueFromUniform() override
{
if (auto uniform = mUniform.lock())
{
T value;
if constexpr (std::is_fundamental_v<UType>)
value = uniform->template getValue<UType>();
else
value = uniform->template getValue<UType>()[mIndex];
setValue(value);
}
}
void toDefault() override
{
if (auto uniform = mUniform.lock())
{
if constexpr (std::is_fundamental_v<UType>)
setValue(uniform->template getDefault<UType>());
else
setValue(uniform->template getDefault<UType>()[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<T>)
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<UType>)
setValue(std::clamp<T>(uniform->template getValue<UType>() + step, uniform->template getMin<UType>(), uniform->template getMax<T>()));
else
setValue(std::clamp<T>(uniform->template getValue<UType>()[mIndex] + step, uniform->template getMin<UType>()[mIndex], uniform->template getMax<UType>()[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<float, osg::Vec4f> { MYGUI_RTTI_DERIVED(EditNumberFloat4) };
class EditNumberFloat3 : public EditNumber<float, osg::Vec3f> { MYGUI_RTTI_DERIVED(EditNumberFloat3) };
class EditNumberFloat2 : public EditNumber<float, osg::Vec2f> { MYGUI_RTTI_DERIVED(EditNumberFloat2) };
class EditNumberFloat : public EditNumber<float, float> { MYGUI_RTTI_DERIVED(EditNumberFloat) };
class EditNumberInt : public EditNumber<int, int> { MYGUI_RTTI_DERIVED(EditNumberInt) };
class UniformBase final : public MyGUI::Widget
{
MYGUI_RTTI_DERIVED(UniformBase)
public:
void init(const std::shared_ptr<fx::Types::UniformBase>& 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<EditBase*> mBases;
};
}
}
#endif

@ -318,7 +318,7 @@ namespace Resource
, mApplyLightingToEnvMaps(false) , mApplyLightingToEnvMaps(false)
, mLightingMethod(SceneUtil::LightingMethod::FFP) , mLightingMethod(SceneUtil::LightingMethod::FFP)
, mConvertAlphaTestToAlphaToCoverage(false) , mConvertAlphaTestToAlphaToCoverage(false)
, mDepthFormat(0) , mSupportsNormalsRT(false)
, mSharedStateManager(new SharedStateManager) , mSharedStateManager(new SharedStateManager)
, mImageManager(imageManager) , mImageManager(imageManager)
, mNifFileManager(nifFileManager) , mNifFileManager(nifFileManager)
@ -348,7 +348,7 @@ namespace Resource
if (forceShadersForNode) if (forceShadersForNode)
shaderVisitor->setForceShaders(true); shaderVisitor->setForceShaders(true);
if (disableSoftParticles) if (disableSoftParticles)
shaderVisitor->setOpaqueDepthTex(nullptr); shaderVisitor->setOpaqueDepthTex(nullptr, nullptr);
node->accept(*shaderVisitor); node->accept(*shaderVisitor);
} }
@ -368,16 +368,6 @@ namespace Resource
return mClampLighting; return mClampLighting;
} }
void SceneManager::setDepthFormat(GLenum format)
{
mDepthFormat = format;
}
GLenum SceneManager::getDepthFormat() const
{
return mDepthFormat;
}
void SceneManager::setAutoUseNormalMaps(bool use) void SceneManager::setAutoUseNormalMaps(bool use)
{ {
mAutoUseNormalMaps = use; mAutoUseNormalMaps = use;
@ -440,9 +430,9 @@ namespace Resource
mConvertAlphaTestToAlphaToCoverage = convert; mConvertAlphaTestToAlphaToCoverage = convert;
} }
void SceneManager::setOpaqueDepthTex(osg::ref_ptr<osg::Texture2D> texture) void SceneManager::setOpaqueDepthTex(osg::ref_ptr<osg::Texture2D> texturePing, osg::ref_ptr<osg::Texture2D> texturePong)
{ {
mOpaqueDepthTex = texture; mOpaqueDepthTex = { texturePing, texturePong };
} }
SceneManager::~SceneManager() SceneManager::~SceneManager()
@ -930,7 +920,8 @@ namespace Resource
shaderVisitor->setSpecularMapPattern(mSpecularMapPattern); shaderVisitor->setSpecularMapPattern(mSpecularMapPattern);
shaderVisitor->setApplyLightingToEnvMaps(mApplyLightingToEnvMaps); shaderVisitor->setApplyLightingToEnvMaps(mApplyLightingToEnvMaps);
shaderVisitor->setConvertAlphaTestToAlphaToCoverage(mConvertAlphaTestToAlphaToCoverage); shaderVisitor->setConvertAlphaTestToAlphaToCoverage(mConvertAlphaTestToAlphaToCoverage);
shaderVisitor->setOpaqueDepthTex(mOpaqueDepthTex); shaderVisitor->setOpaqueDepthTex(mOpaqueDepthTex[0], mOpaqueDepthTex[1]);
shaderVisitor->setSupportsNormalsRT(mSupportsNormalsRT);
return shaderVisitor; return shaderVisitor;
} }
} }

@ -91,9 +91,6 @@ namespace Resource
void setClampLighting(bool clamp); void setClampLighting(bool clamp);
bool getClampLighting() const; bool getClampLighting() const;
void setDepthFormat(GLenum format);
GLenum getDepthFormat() const;
/// @see ShaderVisitor::setAutoUseNormalMaps /// @see ShaderVisitor::setAutoUseNormalMaps
void setAutoUseNormalMaps(bool use); void setAutoUseNormalMaps(bool use);
@ -112,12 +109,13 @@ namespace Resource
void setSupportedLightingMethods(const SceneUtil::LightManager::SupportedMethods& supported); void setSupportedLightingMethods(const SceneUtil::LightManager::SupportedMethods& supported);
bool isSupportedLightingMethod(SceneUtil::LightingMethod method) const; bool isSupportedLightingMethod(SceneUtil::LightingMethod method) const;
void setOpaqueDepthTex(osg::ref_ptr<osg::Texture2D> texture); void setOpaqueDepthTex(osg::ref_ptr<osg::Texture2D> texturePing, osg::ref_ptr<osg::Texture2D> texturePong);
enum class UBOBinding 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 // 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); void setLightingMethod(SceneUtil::LightingMethod method);
SceneUtil::LightingMethod getLightingMethod() const; SceneUtil::LightingMethod getLightingMethod() const;
@ -195,6 +193,9 @@ namespace Resource
void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; void reportStats(unsigned int frameNumber, osg::Stats* stats) const override;
void setSupportsNormalsRT(bool supports) { mSupportsNormalsRT = supports; }
bool getSupportsNormalsRT() const { return mSupportsNormalsRT; }
private: private:
Shader::ShaderVisitor* createShaderVisitor(const std::string& shaderPrefix = "objects"); Shader::ShaderVisitor* createShaderVisitor(const std::string& shaderPrefix = "objects");
@ -211,8 +212,8 @@ namespace Resource
SceneUtil::LightingMethod mLightingMethod; SceneUtil::LightingMethod mLightingMethod;
SceneUtil::LightManager::SupportedMethods mSupportedLightingMethods; SceneUtil::LightManager::SupportedMethods mSupportedLightingMethods;
bool mConvertAlphaTestToAlphaToCoverage; bool mConvertAlphaTestToAlphaToCoverage;
GLenum mDepthFormat; bool mSupportsNormalsRT;
osg::ref_ptr<osg::Texture2D> mOpaqueDepthTex; std::array<osg::ref_ptr<osg::Texture2D>, 2> mOpaqueDepthTex;
osg::ref_ptr<Resource::SharedStateManager> mSharedStateManager; osg::ref_ptr<Resource::SharedStateManager> mSharedStateManager;
mutable std::mutex mSharedStateMutex; mutable std::mutex mSharedStateMutex;

@ -0,0 +1,42 @@
#ifndef OPENMW_COMPONENTS_SCENEUTIL_CLEARCOLOR_H
#define OPENMW_COMPONENTS_SCENEUTIL_CLEARCOLOR_H
#include <osg/StateAttribute>
#include <osg/Vec4f>
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<osg::StateAttribute::Type>(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

@ -44,18 +44,6 @@ namespace SceneUtil
); );
} }
bool isFloatingPointDepthFormat(GLenum format)
{
constexpr std::array<GLenum, 4> 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) bool isDepthFormat(GLenum format)
{ {
constexpr std::array<GLenum, 8> formats = { constexpr std::array<GLenum, 8> formats = {

@ -45,9 +45,6 @@ namespace SceneUtil
// Returns an orthographic projection matrix for use with a reversed z-buffer. // 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); 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 // Returns true if the GL format is a depth format
bool isDepthFormat(GLenum format); bool isDepthFormat(GLenum format);

@ -33,7 +33,7 @@ namespace SceneUtil
, mSamples(samples) , mSamples(samples)
, mGenerateMipmaps(generateMipmaps) , mGenerateMipmaps(generateMipmaps)
, mColorBufferInternalFormat(Color::colorInternalFormat()) , mColorBufferInternalFormat(Color::colorInternalFormat())
, mDepthBufferInternalFormat(AutoDepth::depthInternalFormat()) , mDepthBufferInternalFormat(SceneUtil::AutoDepth::depthInternalFormat())
, mRenderOrderNum(renderOrderNum) , mRenderOrderNum(renderOrderNum)
, mStereoAwareness(stereoAwareness) , mStereoAwareness(stereoAwareness)
{ {

@ -152,42 +152,6 @@ void GlowUpdater::setDuration(float duration)
mDuration = duration; mDuration = duration;
} }
// Allows camera to render to a color and floating point depth texture with a multisampled framebuffer.
class AttachMultisampledDepthColorCallback : public SceneUtil::NodeCallback<AttachMultisampledDepthColorCallback, osg::Node*, osgUtil::CullVisitor*>
{
public:
AttachMultisampledDepthColorCallback(osg::Texture2D* colorTex, osg::Texture2D* depthTex, int samples, int colorSamples)
{
int width = colorTex->getTextureWidth();
int height = colorTex->getTextureHeight();
osg::ref_ptr<osg::RenderBuffer> rbColor = new osg::RenderBuffer(width, height, colorTex->getInternalFormat(), samples, colorSamples);
osg::ref_ptr<osg::RenderBuffer> 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<osg::FrameBufferObject> mFbo;
osg::ref_ptr<osg::FrameBufferObject> mMsaaFbo;
};
osg::Vec4f colourFromRGB(unsigned int clr) osg::Vec4f colourFromRGB(unsigned int clr)
{ {
osg::Vec4f colour(((clr >> 0) & 0xFF) / 255.0f, osg::Vec4f colour(((clr >> 0) & 0xFF) / 255.0f,

@ -0,0 +1,64 @@
#ifndef OPENMW_COMPONENTS_SERIALIZATION_OSGYAML_H
#define OPENMW_COMPONENTS_SERIALIZATION_OSGYAML_H
#include <yaml-cpp/yaml.h>
#include <osg/Vec2f>
#include <osg/Vec3f>
#include <osg/Vec4f>
namespace Serialization
{
template <class OSGVec>
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 <class OSGVec>
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<typename OSGVec::value_type>();
return true;
}
}
namespace YAML
{
template<>
struct convert<osg::Vec2f>
{
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<osg::Vec3f>
{
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<osg::Vec4f>
{
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

@ -0,0 +1,174 @@
#ifndef OPENMW_COMPONENTS_SETTINGS_SHADERMANAGER_H
#define OPENMW_COMPONENTS_SETTINGS_SHADERMANAGER_H
#include <unordered_map>
#include <filesystem>
#include <optional>
#include <fstream>
#include <yaml-cpp/yaml.h>
#include <osg/Vec2f>
#include <osg/Vec3f>
#include <osg/Vec4f>
#include <components/serialization/osgyaml.hpp>
#include <components/debug/debuglog.hpp>
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 <class T>
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 <class T>
std::optional<T> 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<T>();
}
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

@ -11,6 +11,7 @@
#include <osg/Multisample> #include <osg/Multisample>
#include <osg/Texture> #include <osg/Texture>
#include <osg/ValueObject> #include <osg/ValueObject>
#include <osg/Capability>
#include <osgParticle/ParticleSystem> #include <osgParticle/ParticleSystem>
@ -28,6 +29,50 @@
#include "removedalphafunc.hpp" #include "removedalphafunc.hpp"
#include "shadermanager.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<osg::ref_ptr<osg::Texture2D>, 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<osg::ref_ptr<osg::Texture2D>, 2> mTextures;
int mUnit;
};
}
namespace Shader namespace Shader
{ {
/** /**
@ -165,6 +210,7 @@ namespace Shader
, mAutoUseSpecularMaps(false) , mAutoUseSpecularMaps(false)
, mApplyLightingToEnvMaps(false) , mApplyLightingToEnvMaps(false)
, mConvertAlphaTestToAlphaToCoverage(false) , mConvertAlphaTestToAlphaToCoverage(false)
, mSupportsNormalsRT(false)
, mShaderManager(shaderManager) , mShaderManager(shaderManager)
, mImageManager(imageManager) , mImageManager(imageManager)
, mDefaultShaderPrefix(defaultShaderPrefix) , mDefaultShaderPrefix(defaultShaderPrefix)
@ -611,6 +657,14 @@ namespace Shader
defineMap["endLight"] = "0"; 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)) if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT && !previousAddedState->hasMode(GL_ALPHA_TEST))
removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST)); removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST));
// This disables the deprecated fixed-function alpha test // This disables the deprecated fixed-function alpha test
@ -629,7 +683,7 @@ namespace Shader
updateRemovedState(*writableUserData, removedState); updateRemovedState(*writableUserData, removedState);
} }
if (reqs.mSoftParticles) if (reqs.mSoftParticles && mOpaqueDepthTex.front())
{ {
osg::ref_ptr<osg::Depth> depth = new SceneUtil::AutoDepth; osg::ref_ptr<osg::Depth> depth = new SceneUtil::AutoDepth;
depth->setWriteMask(false); depth->setWriteMask(false);
@ -639,14 +693,18 @@ namespace Shader
writableStateSet->addUniform(new osg::Uniform("particleSize", reqs.mSoftParticleSize)); writableStateSet->addUniform(new osg::Uniform("particleSize", reqs.mSoftParticleSize));
addedState->addUniform("particleSize"); addedState->addUniform("particleSize");
writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", 2)); constexpr int unit = 2;
writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", unit));
addedState->addUniform("opaqueDepthTex"); addedState->addUniform("opaqueDepthTex");
writableStateSet->setTextureAttributeAndModes(2, mOpaqueDepthTex, osg::StateAttribute::ON); osg::ref_ptr<OpaqueDepthAttribute> opaqueDepthAttr = new OpaqueDepthAttribute;
addedState->setTextureAttributeAndModes(2, mOpaqueDepthTex); 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); Stereo::Manager::instance().shaderStereoDefines(defineMap);
@ -840,7 +898,7 @@ namespace Shader
{ {
pushRequirements(drawable); pushRequirements(drawable);
if (partsys && mOpaqueDepthTex) if (partsys)
{ {
mRequirements.back().mSoftParticles = true; mRequirements.back().mSoftParticles = true;
mRequirements.back().mSoftParticleSize = partsys->getDefaultParticleTemplate().getSizeRange().maximum; mRequirements.back().mSoftParticleSize = partsys->getDefaultParticleTemplate().getSizeRange().maximum;
@ -915,9 +973,9 @@ namespace Shader
mConvertAlphaTestToAlphaToCoverage = convert; mConvertAlphaTestToAlphaToCoverage = convert;
} }
void ShaderVisitor::setOpaqueDepthTex(osg::ref_ptr<osg::Texture2D> texture) void ShaderVisitor::setOpaqueDepthTex(osg::ref_ptr<osg::Texture2D> texturePing, osg::ref_ptr<osg::Texture2D> texturePong)
{ {
mOpaqueDepthTex = texture; mOpaqueDepthTex = { texturePing, texturePong };
} }
ReinstateRemovedStateVisitor::ReinstateRemovedStateVisitor(bool allowedToModifyStateSets) ReinstateRemovedStateVisitor::ReinstateRemovedStateVisitor(bool allowedToModifyStateSets)

@ -1,6 +1,8 @@
#ifndef OPENMW_COMPONENTS_SHADERVISITOR_H #ifndef OPENMW_COMPONENTS_SHADERVISITOR_H
#define OPENMW_COMPONENTS_SHADERVISITOR_H #define OPENMW_COMPONENTS_SHADERVISITOR_H
#include <array>
#include <osg/NodeVisitor> #include <osg/NodeVisitor>
#include <osg/Program> #include <osg/Program>
#include <osg/Texture2D> #include <osg/Texture2D>
@ -46,7 +48,9 @@ namespace Shader
void setConvertAlphaTestToAlphaToCoverage(bool convert); void setConvertAlphaTestToAlphaToCoverage(bool convert);
void setOpaqueDepthTex(osg::ref_ptr<osg::Texture2D> texture); void setOpaqueDepthTex(osg::ref_ptr<osg::Texture2D> texturePing, osg::ref_ptr<osg::Texture2D> texturePong);
void setSupportsNormalsRT(bool supports) { mSupportsNormalsRT = supports; }
void apply(osg::Node& node) override; void apply(osg::Node& node) override;
@ -73,6 +77,8 @@ namespace Shader
bool mConvertAlphaTestToAlphaToCoverage; bool mConvertAlphaTestToAlphaToCoverage;
bool mSupportsNormalsRT;
ShaderManager& mShaderManager; ShaderManager& mShaderManager;
Resource::ImageManager& mImageManager; Resource::ImageManager& mImageManager;
@ -87,7 +93,7 @@ namespace Shader
bool mShaderRequired; bool mShaderRequired;
int mColorMode; int mColorMode;
bool mMaterialOverridden; bool mMaterialOverridden;
bool mAlphaTestOverridden; bool mAlphaTestOverridden;
bool mAlphaBlendOverridden; bool mAlphaBlendOverridden;
@ -116,7 +122,7 @@ namespace Shader
bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs); bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs);
osg::ref_ptr<const osg::Program> mProgramTemplate; osg::ref_ptr<const osg::Program> mProgramTemplate;
osg::ref_ptr<osg::Texture2D> mOpaqueDepthTex; std::array<osg::ref_ptr<osg::Texture2D>, 2> mOpaqueDepthTex;
}; };
class ReinstateRemovedStateVisitor : public osg::NodeVisitor class ReinstateRemovedStateVisitor : public osg::NodeVisitor

@ -0,0 +1,162 @@
#ifndef COMPONENTS_STD140_UBO_H
#define COMPONENTS_STD140_UBO_H
#include <osg/Vec2f>
#include <osg/Vec4f>
#include <osg/Matrixf>
#include <cstdint>
#include <tuple>
#include <cstring>
#include <string>
#include <string_view>
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... CArgs>
class UBO
{
private:
template<typename T, typename... Args>
struct contains : std::bool_constant<(std::is_base_of_v<Args, T> || ...)> { };
static_assert((contains<CArgs, Mat4, Vec4, Vec3, Vec2, Float, Int, UInt, Bool>() && ...));
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 <class T>
static constexpr std::size_t getOffset()
{
bool found = false;
std::size_t size = 0;
((
found = found || std::is_same_v<T, CArgs>,
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<char, getGPUSize()>;
using TupleType = std::tuple<CArgs...>;
template <class T>
typename T::Value& get()
{
return std::get<T>(mData).mValue;
}
template <class T>
const typename T::Value& get() const
{
return std::get<T>(mData).mValue;
}
void copyTo(BufferType& buffer) const
{
const auto copy = [&] (const auto& v) {
static_assert(std::is_standard_layout_v<std::decay_t<decltype(v.mValue)>>);
constexpr std::size_t offset = getOffset<std::decay_t<decltype(v)>>();
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<CArgs...> mData;
};
}
#endif

@ -3,7 +3,6 @@
#include <osg/FrameBufferObject> #include <osg/FrameBufferObject>
#include <osg/GLExtensions> #include <osg/GLExtensions>
#include <osg/Texture2D> #include <osg/Texture2D>
#include <osg/Texture2DMultisample>
#include <osg/Texture2DArray> #include <osg/Texture2DArray>
#include <osgUtil/RenderStage> #include <osgUtil/RenderStage>
#include <osgUtil/CullVisitor> #include <osgUtil/CullVisitor>
@ -322,10 +321,7 @@ namespace Stereo
for (unsigned i = 0; i < 2; i++) for (unsigned i = 0; i < 2; i++)
{ {
if (mSamples > 1) if (mSamples > 1)
{ mLayerMsaaFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(new osg::RenderBuffer(mWidth, mHeight, internalFormat, mSamples)));
mMsaaColorTexture[i] = createTextureMsaa(sourceFormat, sourceType, internalFormat);
mLayerMsaaFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mMsaaColorTexture[i]));
}
mColorTexture[i] = createTexture(sourceFormat, sourceType, internalFormat); mColorTexture[i] = createTexture(sourceFormat, sourceType, internalFormat);
mLayerFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mColorTexture[i])); mLayerFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mColorTexture[i]));
} }
@ -351,10 +347,7 @@ namespace Stereo
for (unsigned i = 0; i < 2; i++) for (unsigned i = 0; i < 2; i++)
{ {
if (mSamples > 1) if (mSamples > 1)
{ mLayerMsaaFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(new osg::RenderBuffer(mWidth, mHeight, internalFormat, mSamples)));
mMsaaDepthTexture[i] = createTextureMsaa(sourceFormat, sourceType, internalFormat);
mLayerMsaaFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mMsaaDepthTexture[i]));
}
mDepthTexture[i] = createTexture(sourceFormat, sourceType, internalFormat); mDepthTexture[i] = createTexture(sourceFormat, sourceType, internalFormat);
mLayerFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mDepthTexture[i])); mLayerFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mDepthTexture[i]));
} }
@ -381,6 +374,11 @@ namespace Stereo
return mMultiviewColorTexture; return mMultiviewColorTexture;
} }
osg::Texture2DArray* MultiviewFramebuffer::multiviewDepthBuffer()
{
return mMultiviewDepthTexture;
}
osg::Texture2D* MultiviewFramebuffer::layerColorBuffer(int i) osg::Texture2D* MultiviewFramebuffer::layerColorBuffer(int i)
{ {
return mColorTexture[i]; return mColorTexture[i];
@ -451,22 +449,6 @@ namespace Stereo
return texture; 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* MultiviewFramebuffer::createTextureArray(GLint sourceFormat, GLint sourceType, GLint internalFormat)
{ {
osg::Texture2DArray* textureArray = new osg::Texture2DArray; osg::Texture2DArray* textureArray = new osg::Texture2DArray;

@ -13,7 +13,6 @@ namespace osg
class FrameBufferObject; class FrameBufferObject;
class Texture; class Texture;
class Texture2D; class Texture2D;
class Texture2DMultisample;
class Texture2DArray; class Texture2DArray;
} }
@ -50,6 +49,7 @@ namespace Stereo
osg::FrameBufferObject* layerFbo(int i); osg::FrameBufferObject* layerFbo(int i);
osg::FrameBufferObject* layerMsaaFbo(int i); osg::FrameBufferObject* layerMsaaFbo(int i);
osg::Texture2DArray* multiviewColorBuffer(); osg::Texture2DArray* multiviewColorBuffer();
osg::Texture2DArray* multiviewDepthBuffer();
osg::Texture2D* layerColorBuffer(int i); osg::Texture2D* layerColorBuffer(int i);
osg::Texture2D* layerDepthBuffer(int i); osg::Texture2D* layerDepthBuffer(int i);
@ -62,7 +62,6 @@ namespace Stereo
private: private:
osg::Texture2D* createTexture(GLint sourceFormat, GLint sourceType, GLint internalFormat); 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); osg::Texture2DArray* createTextureArray(GLint sourceFormat, GLint sourceType, GLint internalFormat);
int mWidth; int mWidth;
@ -76,9 +75,7 @@ namespace Stereo
osg::ref_ptr<osg::Texture2DArray> mMultiviewColorTexture; osg::ref_ptr<osg::Texture2DArray> mMultiviewColorTexture;
osg::ref_ptr<osg::Texture2DArray> mMultiviewDepthTexture; osg::ref_ptr<osg::Texture2DArray> mMultiviewDepthTexture;
std::array<osg::ref_ptr<osg::Texture2D>, 2> mColorTexture; std::array<osg::ref_ptr<osg::Texture2D>, 2> mColorTexture;
std::array<osg::ref_ptr<osg::Texture2DMultisample>, 2> mMsaaColorTexture;
std::array<osg::ref_ptr<osg::Texture2D>, 2> mDepthTexture; std::array<osg::ref_ptr<osg::Texture2D>, 2> mDepthTexture;
std::array<osg::ref_ptr<osg::Texture2DMultisample>, 2> mMsaaDepthTexture;
}; };
} }

@ -17,7 +17,6 @@ namespace osg
{ {
class FrameBufferObject; class FrameBufferObject;
class Texture2D; class Texture2D;
class Texture2DMultisample;
class Texture2DArray; class Texture2DArray;
} }

@ -180,7 +180,7 @@ std::vector<osg::ref_ptr<osg::StateSet> > ChunkManager::createPasses(float chunk
float blendmapScale = mStorage->getBlendmapScale(chunkSize); 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<osg::Node> ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, unsigned char lod, unsigned int lodFlags, bool compile, TerrainDrawable* templateGeometry) osg::ref_ptr<osg::Node> 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<osg::Node> ChunkManager::createChunk(float chunkSize, const osg::Ve
layer.mDiffuseMap = compositeMap->mTexture; layer.mDiffuseMap = compositeMap->mTexture;
layer.mParallax = false; layer.mParallax = false;
layer.mSpecular = false; layer.mSpecular = false;
geometry->setPasses(::Terrain::createPasses(mSceneManager->getForceShaders() || !mSceneManager->getClampLighting(), &mSceneManager->getShaderManager(), std::vector<TextureLayer>(1, layer), std::vector<osg::ref_ptr<osg::Texture2D> >(), 1.f, 1.f)); geometry->setPasses(::Terrain::createPasses(mSceneManager->getForceShaders() || !mSceneManager->getClampLighting(), mSceneManager, std::vector<TextureLayer>(1, layer), std::vector<osg::ref_ptr<osg::Texture2D> >(), 1.f, 1.f));
} }
else else
{ {

@ -6,8 +6,10 @@
#include <osg/Texture2D> #include <osg/Texture2D>
#include <osg/TexMat> #include <osg/TexMat>
#include <osg/BlendFunc> #include <osg/BlendFunc>
#include <osg/Capability>
#include <components/stereo/stereomanager.hpp> #include <components/stereo/stereomanager.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/shader/shadermanager.hpp> #include <components/shader/shadermanager.hpp>
#include <components/sceneutil/depth.hpp> #include <components/sceneutil/depth.hpp>
@ -194,9 +196,10 @@ namespace
namespace Terrain namespace Terrain
{ {
std::vector<osg::ref_ptr<osg::StateSet> > createPasses(bool useShaders, Shader::ShaderManager* shaderManager, const std::vector<TextureLayer> &layers, std::vector<osg::ref_ptr<osg::StateSet> > createPasses(bool useShaders, Resource::SceneManager* sceneManager, const std::vector<TextureLayer> &layers,
const std::vector<osg::ref_ptr<osg::Texture2D> > &blendmaps, int blendmapScale, float layerTileSize) const std::vector<osg::ref_ptr<osg::Texture2D> > &blendmaps, int blendmapScale, float layerTileSize)
{ {
auto& shaderManager = sceneManager->getShaderManager();
std::vector<osg::ref_ptr<osg::StateSet> > passes; std::vector<osg::ref_ptr<osg::StateSet> > passes;
unsigned int blendmapIndex = 0; unsigned int blendmapIndex = 0;
@ -209,6 +212,8 @@ namespace Terrain
if (!blendmaps.empty()) if (!blendmaps.empty())
{ {
stateset->setMode(GL_BLEND, osg::StateAttribute::ON); stateset->setMode(GL_BLEND, osg::StateAttribute::ON);
if (sceneManager->getSupportsNormalsRT())
stateset->setAttribute(new osg::Disablei(GL_BLEND, 1));
stateset->setRenderBinDetails(firstLayer ? 0 : 1, "RenderBin"); stateset->setRenderBinDetails(firstLayer ? 0 : 1, "RenderBin");
if (!firstLayer) if (!firstLayer)
{ {
@ -251,18 +256,18 @@ namespace Terrain
defineMap["blendMap"] = (!blendmaps.empty()) ? "1" : "0"; defineMap["blendMap"] = (!blendmaps.empty()) ? "1" : "0";
defineMap["specularMap"] = it->mSpecular ? "1" : "0"; defineMap["specularMap"] = it->mSpecular ? "1" : "0";
defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0"; defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0";
defineMap["writeNormals"] = (it == layers.end() - 1) ? "1" : "0";
Stereo::Manager::instance().shaderStereoDefines(defineMap); Stereo::Manager::instance().shaderStereoDefines(defineMap);
osg::ref_ptr<osg::Shader> vertexShader = shaderManager->getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX); osg::ref_ptr<osg::Shader> vertexShader = shaderManager.getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX);
osg::ref_ptr<osg::Shader> fragmentShader = shaderManager->getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT); osg::ref_ptr<osg::Shader> fragmentShader = shaderManager.getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT);
if (!vertexShader || !fragmentShader) if (!vertexShader || !fragmentShader)
{ {
// Try again without shader. Error already logged by above // 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); stateset->addUniform(UniformCollection::value().mColorMode);
} }
else else

@ -10,9 +10,9 @@ namespace osg
class Texture2D; class Texture2D;
} }
namespace Shader namespace Resource
{ {
class ShaderManager; class SceneManager;
} }
namespace Terrain namespace Terrain
@ -26,7 +26,7 @@ namespace Terrain
bool mSpecular; bool mSpecular;
}; };
std::vector<osg::ref_ptr<osg::StateSet> > createPasses(bool useShaders, Shader::ShaderManager* shaderManager, std::vector<osg::ref_ptr<osg::StateSet> > createPasses(bool useShaders, Resource::SceneManager* sceneManager,
const std::vector<TextureLayer>& layers, const std::vector<TextureLayer>& layers,
const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps, int blendmapScale, float layerTileSize); const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps, int blendmapScale, float layerTileSize);

@ -14,6 +14,8 @@ namespace VFS
virtual ~File() {} virtual ~File() {}
virtual Files::IStreamPtr open() = 0; virtual Files::IStreamPtr open() = 0;
virtual std::string getPath() = 0;
}; };
class Archive class Archive

@ -15,6 +15,8 @@ namespace VFS
Files::IStreamPtr open() override; Files::IStreamPtr open() override;
std::string getPath() override { return mInfo->name(); }
const Bsa::BSAFile::FileStruct* mInfo; const Bsa::BSAFile::FileStruct* mInfo;
Bsa::BSAFile* mFile; Bsa::BSAFile* mFile;
}; };
@ -26,6 +28,8 @@ namespace VFS
Files::IStreamPtr open() override; Files::IStreamPtr open() override;
std::string getPath() override { return mInfo->name(); }
const Bsa::BSAFile::FileStruct* mInfo; const Bsa::BSAFile::FileStruct* mInfo;
Bsa::CompressedBSAFile* mCompressedFile; Bsa::CompressedBSAFile* mCompressedFile;
}; };

@ -13,6 +13,8 @@ namespace VFS
Files::IStreamPtr open() override; Files::IStreamPtr open() override;
std::string getPath() override { return mPath; }
private: private:
std::string mPath; std::string mPath;

@ -105,6 +105,17 @@ namespace VFS
return {}; return {};
} }
std::string Manager::getAbsoluteFileName(const std::string& name) const
{
std::string normalized = name;
normalize_path(normalized, mStrict);
std::map<std::string, File*>::const_iterator found = mIndex.find(normalized);
if (found == mIndex.end())
throw std::runtime_error("Resource '" + normalized + "' not found");
return found->second->getPath();
}
namespace namespace
{ {
bool startsWith(std::string_view text, std::string_view start) bool startsWith(std::string_view text, std::string_view start)

@ -90,6 +90,11 @@ namespace VFS
/// @note May be called from any thread once the index has been built. /// @note May be called from any thread once the index has been built.
RecursiveDirectoryRange getRecursiveDirectoryIterator(const std::string& path) const; 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: private:
bool mStrict; bool mStrict;

@ -7,4 +7,5 @@ Reference Material
modding/index modding/index
lua-scripting/index lua-scripting/index
postprocessing/index
documentationHowTo documentationHowTo

@ -20,6 +20,7 @@ Lua API reference
openmw_input openmw_input
openmw_ui openmw_ui
openmw_camera openmw_camera
openmw_postprocessing
openmw_aux_calendar openmw_aux_calendar
openmw_aux_util openmw_aux_util
openmw_aux_time 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. The list of available packages is different for global and for local scripts.
Player scripts are local scripts that are attached to a player. Player scripts are local scripts that are attached to a player.
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
| Package | Can be used | Description | | Package | Can be used | Description |
+=========================================================+====================+===============================================================+ +============================================================+====================+===============================================================+
|:ref:`openmw.interfaces <Script interfaces>` | everywhere | | Public interfaces of other scripts. | |:ref:`openmw.interfaces <Script interfaces>` | everywhere | | Public interfaces of other scripts. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.util <Package openmw.util>` | everywhere | | Defines utility functions and classes like 3D vectors, | |:ref:`openmw.util <Package openmw.util>` | everywhere | | Defines utility functions and classes like 3D vectors, |
| | | | that don't depend on the game world. | | | | | that don't depend on the game world. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.storage <Package openmw.storage>` | everywhere | | Storage API. In particular can be used to store data | |:ref:`openmw.storage <Package openmw.storage>` | everywhere | | Storage API. In particular can be used to store data |
| | | | between game sessions. | | | | | between game sessions. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.core <Package openmw.core>` | everywhere | | Functions that are common for both global and local scripts | |:ref:`openmw.core <Package openmw.core>` | everywhere | | Functions that are common for both global and local scripts |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.types <Package openmw.types>` | everywhere | | Functions for specific types of game objects. | |:ref:`openmw.types <Package openmw.types>` | everywhere | | Functions for specific types of game objects. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.async <Package openmw.async>` | everywhere | | Timers (implemented) and coroutine utils (not implemented) | |:ref:`openmw.async <Package openmw.async>` | everywhere | | Timers (implemented) and coroutine utils (not implemented) |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.world <Package openmw.world>` | by global scripts | | Read-write access to the game world. | |:ref:`openmw.world <Package openmw.world>` | by global scripts | | Read-write access to the game world. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.self <Package openmw.self>` | by local scripts | | Full access to the object the script is attached to. | |:ref:`openmw.self <Package openmw.self>` | by local scripts | | Full access to the object the script is attached to. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.nearby <Package openmw.nearby>` | by local scripts | | Read-only access to the nearest area of the game world. | |:ref:`openmw.nearby <Package openmw.nearby>` | by local scripts | | Read-only access to the nearest area of the game world. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.input <Package openmw.input>` | by player scripts | | User input. | |:ref:`openmw.input <Package openmw.input>` | by player scripts | | User input. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.ui <Package openmw.ui>` | by player scripts | | Controls :ref:`user interface <User interface reference>`. | |:ref:`openmw.ui <Package openmw.ui>` | by player scripts | | Controls :ref:`user interface <User interface reference>`. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.camera <Package openmw.camera>` | by player scripts | | Controls camera. | |:ref:`openmw.camera <Package openmw.camera>` | by player scripts | | Controls camera. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.postprocessing <Package openmw.postprocessing>`| by player scripts | | Controls post-process shaders. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
**openmw_aux** **openmw_aux**

@ -0,0 +1,5 @@
Package openmw.postprocessing
=============================
.. raw:: html
:file: generated_html/openmw_postprocessing.html

@ -340,35 +340,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. The list of available packages is different for global and for local scripts.
Player scripts are local scripts that are attached to a player. Player scripts are local scripts that are attached to a player.
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
| Package | Can be used | Description | | Package | Can be used | Description |
+=========================================================+====================+===============================================================+ +============================================================+====================+===============================================================+
|:ref:`openmw.interfaces <Script interfaces>` | everywhere | | Public interfaces of other scripts. | |:ref:`openmw.interfaces <Script interfaces>` | everywhere | | Public interfaces of other scripts. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.util <Package openmw.util>` | everywhere | | Defines utility functions and classes like 3D vectors, | |:ref:`openmw.util <Package openmw.util>` | everywhere | | Defines utility functions and classes like 3D vectors, |
| | | | that don't depend on the game world. | | | | | that don't depend on the game world. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.storage <Package openmw.storage>` | everywhere | | Storage API. In particular can be used to store data | |:ref:`openmw.storage <Package openmw.storage>` | everywhere | | Storage API. In particular can be used to store data |
| | | | between game sessions. | | | | | between game sessions. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.core <Package openmw.core>` | everywhere | | Functions that are common for both global and local scripts | |:ref:`openmw.core <Package openmw.core>` | everywhere | | Functions that are common for both global and local scripts |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.types <Package openmw.types>` | everywhere | | Functions for specific types of game objects. | |:ref:`openmw.types <Package openmw.types>` | everywhere | | Functions for specific types of game objects. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.async <Package openmw.async>` | everywhere | | Timers (implemented) and coroutine utils (not implemented) | |:ref:`openmw.async <Package openmw.async>` | everywhere | | Timers (implemented) and coroutine utils (not implemented) |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.world <Package openmw.world>` | by global scripts | | Read-write access to the game world. | |:ref:`openmw.world <Package openmw.world>` | by global scripts | | Read-write access to the game world. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.self <Package openmw.self>` | by local scripts | | Full access to the object the script is attached to. | |:ref:`openmw.self <Package openmw.self>` | by local scripts | | Full access to the object the script is attached to. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.nearby <Package openmw.nearby>` | by local scripts | | Read-only access to the nearest area of the game world. | |:ref:`openmw.nearby <Package openmw.nearby>` | by local scripts | | Read-only access to the nearest area of the game world. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.input <Package openmw.input>` | by player scripts | | User input | |:ref:`openmw.input <Package openmw.input>` | by player scripts | | User input |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.ui <Package openmw.ui>` | by player scripts | | Controls :ref:`user interface <User interface reference>` | |:ref:`openmw.ui <Package openmw.ui>` | by player scripts | | Controls :ref:`user interface <User interface reference>` |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.camera <Package openmw.camera>` | by player scripts | | Controls camera | |:ref:`openmw.camera <Package openmw.camera>` | by player scripts | | Controls camera |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.postprocessing <Package openmw.postprocessing>`| by player scripts | | Controls postprocess shaders |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
openmw_aux openmw_aux
---------- ----------

@ -71,3 +71,4 @@ The ranges included with each setting are the physically possible ranges, not re
navigator navigator
physics physics
models models
postprocessing

@ -0,0 +1,65 @@
Post Processing Settings
########################
enabled
-------
:Type: boolean
:Range: True/False
:Default: False
Enable or disable post processing.
This enables use of post processing shaders, which must be installed.
chain
-----
:Type: string list
Controls which post process effects are active and their order.
It is recommended to configure the settings and order of shaders through the in game HUD. By default this is available with the F2 key.
Note, an empty chain will not disable post processing.
This setting has no effect if :ref:`enabled` is set to false.
live reload
-----------
:Type: boolean
:Range: True/False
:Default: False
Automatically reloads a shader if the file has been changed. This is useful for debugging and writing shaders yourself.
.. warning::
This should be disabled for normal gameplay
hdr exposure time
-----------------
:Type: float
:Range: 0.0 to 1.0
:Default: 0.05
Use for eye adaptation to control speed at which average scene luminance can change from one frame to the next.
Average scene luminance is used in some shader effects for features such as dynamic eye adaptation.
Smaller values will cause slower changes in scene luminance. This is most impactful when the brightness
drastically changes quickly, like when entering a dark cave or exiting an interior and looking into a bright sun.
This settings has no effect when HDR is disabled or :ref:`enabled` is set to false.
transparent postpass
--------------------
:Type: boolean
:Range: True/False
:Default: True
Re-renders transparent objects with alpha-clipping forced with a fixed threshold. This is particularly important with vanilla content, where blended
objects usually have depth writes disabled and massive margins between the geometry and texture alpha.
.. warning::
This can be quite costly with vanilla assets. For best performance it is recommended to use a mod replacer which
uses alpha tested foliage and disable this setting. Morrowind Optimizaton Patch is a great option.
If you are not using any shaders which utilize the depth buffer this setting should be disabled.

@ -256,7 +256,21 @@ settings
w = 0.33 w = 0.33
h = 0.66 h = 0.66
The settings window. The settings window.
Activated by clicking Options in the main menu. Activated by clicking Options in the main menu.
postprocessor
-------------
:Default:
x = 0.01
y = 0.02
w = 0.44
h = 0.95
The postprocessor window used to configure shaders.
Activated by pressing the F2 key.

@ -0,0 +1,11 @@
######################
OpenMW Post Processing
######################
.. toctree::
:caption: Table of Contents
:includehidden:
:maxdepth: 2
overview
omwfx

@ -0,0 +1,862 @@
#########################
OMWFX Language Reference
#########################
Overview
########
Shaders are written in a OpenMW specific ``*.omwfx`` format. This is a light
wrapper around GLSL, so a basic understanding of GLSL should be acquired before
attempting to write any shaders. Every shader must be contained within a single
``*.omwfx`` file, ``#include`` directives are currently unsupported.
By default, all shaders only guarantee support of GLSL 120 features. To target a
newer GLSL version, you must specify it in the `technique`_ block properties. If
the specified version is not supported on the target machine, the shader will
not load.
Reserved Keywords
#################
GLSL doesn't support namespaces, instead reserved prefixes are used. Do not
attempt to name anything starting with ``_`` or ``omw``, this will cause
name clashes.
Builtin Samplers
################
+------------------+---------------------------+---------------------------------------------+
| GLSL Type | Name | Description |
+==================+===========================+=============================================+
| sampler2D[Array] |``omw_SamplerLastShader`` | Color output of the last shader |
+------------------+---------------------------+---------------------------------------------+
| sampler2D[Array] |``omw_SamplerLastPass`` | Color output of the last pass |
+------------------+---------------------------+---------------------------------------------+
| sampler2D[Array] |``omw_SamplerDepth`` | Non-linear normalized depth |
+------------------+---------------------------+---------------------------------------------+
| sampler2D[Array] |``omw_SamplerNormals`` | Normalized world-space normals [0, 1] |
+------------------+---------------------------+---------------------------------------------+
These are included in a common header in every pass, they do not need to be re-defined.
It is recommended to use the accessor functions to retrieve the sampler value.
OpenMW supports multiview rendering, so these samplers will either be a
``sampler2D`` or ``sampler2DArray``. If you want more control over how you
sample textures, use the ``OMW_MULTIVIEW`` macro to determine the appropriate functions to use.
Builtin Uniforms
################
+-------------+------------------------------+--------------------------------------------------+
| GLSL Type | Name | Description |
+=============+==============================+==================================================+
| mat4 | ``omw.projectionMatrix`` | The camera's projection matrix |
+-------------+------------------------------+--------------------------------------------------+
| mat4 | ``omw.invProjectionMatrix`` | The inverse of the camera's projection matrix |
+-------------+------------------------------+--------------------------------------------------+
| mat4 | ``omw.viewMatrix`` | The camera's view matrix |
+-------------+------------------------------+--------------------------------------------------+
| mat4 | ``omw.prevViewMatrix`` | The camera's previous frame view matrix |
+-------------+------------------------------+--------------------------------------------------+
| mat4 | ``omw.invViewMatrix`` | The inverse of the camera's view matrix |
+-------------+------------------------------+--------------------------------------------------+
| vec4 | ``omw.eyePos`` | The camera's eye position |
+-------------+------------------------------+--------------------------------------------------+
| vec4 | ``omw.eyeVec`` | The normalized camera's eye vector |
+-------------+------------------------------+--------------------------------------------------+
| vec4 | ``omw.fogColor`` | The RGBA color of fog |
+-------------+------------------------------+--------------------------------------------------+
| vec4 | ``omw.sunColor`` | The RGBA color of sun |
+-------------+------------------------------+--------------------------------------------------+
| vec4 | ``omw.sunPos`` | The normalized sun direction |
| | | |
| | | When the sun is set `omw.sunpos.z` is negated |
+-------------+------------------------------+--------------------------------------------------+
| vec2 | ``omw.resolution`` | The render target's resolution |
+-------------+------------------------------+--------------------------------------------------+
| vec2 | ``omw.rcpResolution`` | Reciprocal of the render target resolution |
+-------------+------------------------------+--------------------------------------------------+
| vec2 | ``omw.fogNear`` | The units at which the fog begins to render |
+-------------+------------------------------+--------------------------------------------------+
| float | ``omw.fogFar`` | The units at which the fog ends |
+-------------+------------------------------+--------------------------------------------------+
| float | ``omw.near`` | The camera's near clip |
+-------------+------------------------------+--------------------------------------------------+
| float | ``omw.far`` | The camera's far clip |
+-------------+------------------------------+--------------------------------------------------+
| float | ``omw.gameHour`` | The game hour in range [0,24) |
+-------------+------------------------------+--------------------------------------------------+
| float | ``omw.sunVis`` | The sun's visibility between [0, 1] |
| | | |
| | | Influenced by types of weather |
| | | |
| | | Closer to zero during overcast weathers |
+-------------+------------------------------+--------------------------------------------------+
| float | ``omw.waterHeight`` | The water height of current cell |
| | | |
| | | Exterior water level is always zero |
+-------------+------------------------------+--------------------------------------------------+
| float | ``omw.simulationTime`` | The time in milliseconds since simulation began |
+-------------+------------------------------+--------------------------------------------------+
| float | ``omw.deltaSimulationTime`` | The change in `omw.simulationTime` |
+-------------+------------------------------+--------------------------------------------------+
| float | ``omw.windSpeed`` | The current wind speed |
+-------------+------------------------------+--------------------------------------------------+
| float | ``omw.weatherTransition`` | The transition factor between weathers [0, 1] |
+-------------+------------------------------+--------------------------------------------------+
| int | ``omw.weatherID`` | The current weather ID |
+-------------+------------------------------+--------------------------------------------------+
| int | ``omw.nextWeatherID`` | The next weather ID |
+-------------+------------------------------+--------------------------------------------------+
| bool | ``omw.isUnderwater`` | True if player is submerged underwater |
+-------------+------------------------------+--------------------------------------------------+
| bool | ``omw.isInterior`` | True if player is in an interior |
| | | |
| | | False for interiors that behave like exteriors |
+-------------+------------------------------+--------------------------------------------------+
Builtin Macros
##############
+------------------+----------------+---------------------------------------------------------------------------+
| Macro | Definition | Description |
+==================+================+===========================================================================+
|``OMW_REVERSE_Z`` | ``0`` or ``1`` | Whether a reversed depth buffer is in use. |
| | | |
| | | ``0`` Depth sampler will be in range [1, 0] |
| | | |
| | | ``1`` Depth sampler will be in range [0, 1] |
+------------------+----------------+---------------------------------------------------------------------------+
|``OMW_RADIAL_FOG``| ``0`` or ``1`` | Whether radial fog is in use. |
| | | |
| | | ``0`` Fog is linear |
| | | |
| | | ``1`` Fog is radial |
+------------------+----------------+---------------------------------------------------------------------------+
| ``OMW_HDR`` | ``0`` or ``1`` | Whether average scene luminance is computed every frame. |
| | | |
| | | ``0`` Average scene luminance is not computed |
| | | |
| | | ``1`` Average scene luminance is computed |
+------------------+----------------+---------------------------------------------------------------------------+
| ``OMW_NORMALS`` | ``0`` or ``1`` | Whether normals are available as a sampler in the technique. |
| | | |
| | | ``0`` Normals are not available |
| | | |
| | | ``1`` Normals are available. |
+------------------+----------------+---------------------------------------------------------------------------+
| ``OMW_MULTIVIEW``| ``0`` or ``1`` | Whether multiview rendering is in use. |
| | | |
| | | ``0`` Multiview not in use |
| | | |
| | | ``1`` Multiview in use. |
+------------------+----------------+---------------------------------------------------------------------------+
Builtin Functions
#################
The following functions can be accessed in any fragment or vertex shader.
+----------------------------------------+-------------------------------------------------------------------------------+
| Function | Description |
+========================================+===============================================================================+
| ``float omw_GetDepth(vec2)`` | Returns the depth value from a sampler given a uv coordinate. |
| | |
| | Reverses sampled value when ``OMW_REVERSE_Z`` is set. |
+----------------------------------------+-------------------------------------------------------------------------------+
| ``float omw_GetEyeAdaptation()`` | Returns the average scene luminance in range [0, 1]. |
| | |
| | If HDR is not in use, this returns `1.0` |
| | |
| | Scene luminance is always calculated on original scene texture. |
+----------------------------------------+-------------------------------------------------------------------------------+
| ``vec4 omw_GetDepth(vec2 uv)`` | Returns non-linear normalized depth |
+----------------------------------------+-------------------------------------------------------------------------------+
| ``vec4 omw_GetLastShader(vec2 uv)`` | Returns RGBA color output of the last shader |
+----------------------------------------+-------------------------------------------------------------------------------+
| ``vec4 omw_GetLastPass(vec2 uv)`` | Returns RGBA color output of the last pass |
+----------------------------------------+-------------------------------------------------------------------------------+
| ``vec3 omw_GetNormals(vec2 uv)`` | Returns normalized worldspace normals [-1, 1] |
| | |
| | The values in sampler are in [0, 1] but are transformed to [-1, 1] |
+----------------------------------------+-----------------------+-------------------------------------------------------+
Special Attributes
##################
To maintain maximum compatability with future releases, OpenMW defines specific keywords, attributes, and functions for you to use. These should be used instead of their
GLSL equivalent. Refer to the table below to view these mappings.
+-------------------+---------------------------------------------------------+
| .omwfx | Description |
+===================+=========================================================+
| omw_In | use in place of ``in`` and ``varying`` |
+-------------------+---------------------------------------------------------+
| omw_Out | use in place of ``out`` and ```varying`` |
+-------------------+---------------------------------------------------------+
| omw_Position | use in place of ``gl_Position`` |
+-------------------+---------------------------------------------------------+
| omw_Vertex | use in place of ``gl_Vertex`` |
+-------------------+---------------------------------------------------------+
| omw_Fragment | use in place of ``gl_FragData[*]`` and ``gl_FragColor``|
+-------------------+---------------------------------------------------------+
| omw_Texture1D() | use in place of ``texture1D()`` or ``texture()`` |
+-------------------+---------------------------------------------------------+
| omw_Texture2D() | use in place of ``texture2D()`` or ``texture()`` |
+-------------------+---------------------------------------------------------+
| omw_Texture3D() | use in place of ``texture3D()`` or ``texture()`` |
+-------------------+---------------------------------------------------------+
Blocks
######
``fragment``
*************
Declare your passes with ``fragment`` followed by a unique name. We will define the order of these passes later on.
Each ``fragment`` block must contain valid GLSL. Below is a simple example of defining two passes.
.. code-block:: none
fragment pass {
void main()
{
omw_FragColor = vec4(1.0);
}
}
fragment otherPass {
omw_In vec2 omw_TexCoord;
void main()
{
omw_FragColor = omw_GetLastPass(omw_TexCoord);
}
}
``vertex``
***********
For every ``fragment`` block you declare, OpenMW generates a default vertex shader if you do not define one. This is used to draw the fullscreen triangle used in postprocessing.
This means you rarely need to use a custom vertex shader. Using a vertex shader can sometimes be useful when you need to do lots of complicated calculations that don't rely on pixel location.
The vertex shader only invocates on the `3` vertices of the fullscreen triangle.
Below is an example of passing a value through a custom vertex shader to the fragment shader.
.. code-block:: none
vertex pass {
#if OMW_USE_BINDINGS
omw_In vec2 omw_Vertex;
#endif
uniform sampler2D noiseSampler;
omw_Out vec2 omw_TexCoord;
// custom output from vertex shader
omw_Out float noise;
void main()
{
omw_Position = vec4(omw_Vertex.xy, 0.0, 1.0);
omw_TexCoord = omw_Position.xy * 0.5 + 0.5;
noise = sqrt(omw_Texture2D(noiseSampler, vec2(0.5, 0.5)).r);
}
}
fragment pass {
omw_Out vec2 omw_TexCoord;
// our custom output from the vertex shader is available
omw_Out float noise;
void main()
{
omw_FragColor = vec4(1.0);
}
}
``technique``
*************
Exactly one ``technique`` block is required for every shader file. In this we define important traits like author, description, requirements, and flags.
+------------------+--------------------+---------------------------------------------------+
| Property | Type | Description |
+==================+====================+===================================================+
| passes | literal list | ``,`` separated list of pass names |
+------------------+--------------------+---------------------------------------------------+
| version | string | Shader version that shows in HUD |
+------------------+--------------------+---------------------------------------------------+
| description | string | Shader description that shows in HUD |
+------------------+--------------------+---------------------------------------------------+
| author | string | Shader authors that shows in HUD |
+------------------+--------------------+---------------------------------------------------+
| glsl_Version | integer | GLSL version |
+------------------+--------------------+---------------------------------------------------+
| glsl_profile | string | GLSL profile, like ``compatability`` |
+------------------+--------------------+---------------------------------------------------+
| glsl_extensions | literal list | ``,`` separated list of required GLSL extensions |
+------------------+--------------------+---------------------------------------------------+
| hdr | boolean | Whether HDR eye adaptation is required. |
+------------------+--------------------+---------------------------------------------------+
| pass_normals | boolean | Pass normals from the forward passes. |
| | | |
| | | If unsupported, `OMW_NORMALS` will be set to `0` |
+------------------+--------------------+---------------------------------------------------+
| flags | `SHADER_FLAG`_ | ``,`` separated list of shader flags |
+------------------+--------------------+---------------------------------------------------+
In the code snippet below, a shader is defined that requires GLSL `330`, HDR capatiblities, and is only enabled underwater in exteriors.
.. code-block:: none
fragment dummy {
void main()
{
omw_FragColor = vec4(0.0);
}
}
technique {
passes = dummy;
glsl_version = 330;
hdr = true;
flags = disable_interiors | disable_abovewater;
}
``sampler_*``
*************
Any texture in the VFS can be loaded by a shader. All passes within the technique will have access to this texture as a sampler.
OpenMW currently supports ``1D``, ``2D``, and ``3D`` texture samplers, cubemaps can not yet be loaded.
+-------------+
| Block |
+=============+
| sampler_1d |
+-------------+
| sampler_2d |
+-------------+
| sampler_3d |
+-------------+
The properites for a ``sampler_*`` block are as following.
The only required property for a texture is its ``source``.
+-----------------------+-----------------------+
| Property | Type |
+=======================+=======================+
|``source`` | string |
+-----------------------+-----------------------+
|``min_filter`` | `FILTER_MODE`_ |
+-----------------------+-----------------------+
|``mag_filter`` | `FILTER_MODE`_ |
+-----------------------+-----------------------+
|``wrap_s`` | `WRAP_MODE`_ |
+-----------------------+-----------------------+
|``wrap_t`` | `WRAP_MODE`_ |
+-----------------------+-----------------------+
|``wrap_r`` | `WRAP_MODE`_ |
+-----------------------+-----------------------+
|``compression`` | `COMPRESSION_MODE`_ |
+-----------------------+-----------------------+
|``source_format`` | `SOURCE_FORMAT`_ |
+-----------------------+-----------------------+
|``source_type`` | `SOURCE_TYPE`_ |
+-----------------------+-----------------------+
|``internal_format`` | `INTERNAL_FORMAT`_ |
+-----------------------+-----------------------+
In the code snippet below, a simple noise texture is loaded with nearest filtering.
.. code-block:: none
sampler_2d noise {
source = "textures/noise.png";
mag_filter = nearest;
min_filter = nearest;
}
To use the sampler, define the appropriately named `sampler2D` in any of your passes.
.. code-block:: none
fragment pass {
omw_In vec2 omw_TexCoord;
uniform sampler2D noise;
void main()
{
// ...
vec4 noise = omw_Texture2D(noise, omw_TexCoord);
}
}
``uniform_*``
**************
It is possible to define settings for your shaders that can be adjusted by either users or a Lua script.
+-----------------+----------+----------+----------+---------+----------+--------------+---------+
| Block | default | min | max | static | step | description | header |
+=================+==========+==========+==========+=========+==========+==============+=========+
|``uniform_bool`` | boolean | x | x | boolean | x | string | string |
+-----------------+----------+----------+----------+---------+----------+--------------+---------+
|``uniform_float``| float | float | float | boolean | float | string | string |
+-----------------+----------+----------+----------+---------+----------+--------------+---------+
|``uniform_int`` | integer | integer | integer | boolean | integer | string | string |
+-----------------+----------+----------+----------+---------+----------+--------------+---------+
|``uniform_vec2`` | vec2 | vec2 | vec2 | boolean | vec2 | string | string |
+-----------------+----------+----------+----------+---------+----------+--------------+---------+
|``uniform_vec3`` | vec3 | vec3 | vec3 | boolean | vec3 | string | string |
+-----------------+----------+----------+----------+---------+----------+--------------+---------+
|``uniform_vec4`` | vec4 | vec4 | vec4 | boolean | vec4 | string | string |
+-----------------+----------+----------+----------+---------+----------+--------------+---------+
The ``description`` field is used to display a toolip when viewed in the in-game HUD. The ``header`` field
field can be used to organize uniforms into groups in the HUD.
If you would like a uniform to be adjustable with Lua API you `must` set ``static = false;``. Doing this
will also remove the uniform from the players HUD.
Below is an example of declaring a ``vec3`` uniform.
.. code-block:: none
uniform_vec3 uColor {
default = vec3(0,1,1);
min = vec3(0,0,0);
max = vec3(1,1,1);
step = vec3(0.1, 0.1, 0.1);
description = "Color uniform";
static = true;
header = "Colors";
}
To use the uniform you can reference it in any pass, it should **not** be declared with the ``uniform`` keyword.
.. code-block:: none
fragment pass {
void main()
{
// ...
vec3 color = uColor;
}
}
``render_target``
*****************
Normally when defining passes, OpenMW will take care of setting up all of the render targets. Sometimes, this behavior
is not wanted and you want a custom render target.
+------------------+---------------------+-----------------------------------------------------------------------------+
| Property | Type | Description |
+==================+=====================+=============================================================================+
| min_filter | `FILTER_MODE`_ | x |
+------------------+---------------------+-----------------------------------------------------------------------------+
| mag_filter | `FILTER_MODE`_ | x |
+------------------+---------------------+-----------------------------------------------------------------------------+
| wrap_s | `WRAP_MODE`_ | x |
+------------------+---------------------+-----------------------------------------------------------------------------+
| wrap_t | `WRAP_MODE`_ | x |
+------------------+---------------------+-----------------------------------------------------------------------------+
| internal_format | `INTERNAL_FORMAT`_ | x |
+------------------+---------------------+-----------------------------------------------------------------------------+
| source_type | `SOURCE_TYPE`_ | x |
+------------------+---------------------+-----------------------------------------------------------------------------+
| source_format | `SOURCE_FORMAT`_ | x |
+------------------+---------------------+-----------------------------------------------------------------------------+
| width_ratio | float | Automatic width as a percentage of screen width |
+------------------+---------------------+-----------------------------------------------------------------------------+
| height_ratio | float | Automatic width as a percentage of screen height |
+------------------+---------------------+-----------------------------------------------------------------------------+
| width | float | Width in pixels |
+------------------+---------------------+-----------------------------------------------------------------------------+
| height | float | Height in pixels |
+------------------+---------------------+-----------------------------------------------------------------------------+
| mipmaps | boolean | Whether mipmaps should be generated every frame |
+------------------+---------------------+-----------------------------------------------------------------------------+
To use the render target you must assign passes to it, along with any optional clear modes or custom blend modes.
In the code snippet below a rendertarget is used to draw the red cannel of a scene at half resolution.
.. code-block:: none
render_target RT_Downsample {
width_ratio = 0.5;
height_ratio = 0.5;
internal_format = r16f;
source_type = float;
source_format = red;
}
fragment downsample2x(target=RT_Downsample) {
omw_In vec2 omw_TexCoord;
void main()
{
omw_FragColor.r = omw_GetLastShader(omw_TexCoord).r;
}
}
Now, if we ever run the `downsample2x` pass it will write to the target buffer instead of the default
one assigned by the engine.
To use the uniform you can reference it in any pass, it should **not** be declared with the ``uniform`` keyword.
.. code-block:: none
fragment pass {
void main()
{
// ...
vec3 color = uColor;
}
}
Simple Example
##############
Let us go through a simple example in which we apply a simple desaturation
filter with a user-configurable factor.
Our first step is defining our user-configurable variable. In this case all we
want is a normalized value between 0 and 1 to influence the amount of
desaturation to apply to the scene. Here we setup a new variable of type
``float``, define a few basic properties, and give it a tooltip description.
.. code-block:: none
uniform_float uDesaturationFactor {
default = 0.5;
min = 0.0;
max = 1.0;
step = 0.05;
static = true;
description = "Desaturation factor. A value of 1.0 is full grayscale.";
}
Now, we can setup our first pass. Remember a pass is just a pixel shader invocation.
.. code-block:: none
fragment desaturate {
omw_In vec2 omw_TexCoord;
void main()
{
// fetch scene texture from last shader
vec4 scene = omw_GetLastShader(omw_TexCoord);
// desaturate RGB component
const vec3 luminance = vec3(0.299, 0.587, 0.144);
float gray = dot(luminance, scene.rgb);
omw_FragColor = vec4(mix(scene.rgb, vec3(gray), uDesaturationFactor), scene.a);
}
}
Next we can define our ``technique`` block, which is in charge of glueing
together passes, setting up metadata, and setting up various flags.
.. code-block:: none
technique {
description = "Desaturates scene";
passes = desaturate;
version = "1.0";
author = "Fargoth";
passes = desaturate;
}
Putting it all together we have this simple shader.
.. code-block:: none
uniform_float uDesaturationFactor {
default = 0.5;
min = 0.0;
max = 1.0;
step = 0.05;
description = "Desaturation factor. A value of 1.0 is full grayscale.";
}
fragment desaturate {
omw_In vec2 omw_TexCoord;
void main()
{
// fetch scene texture from last shader
vec4 scene = omw_GetLastShader(omw_TexCoord);
// desaturate RGB component
const vec3 luminance = vec3(0.299, 0.587, 0.144);
float gray = dot(luminance, scene.rgb);
omw_FragColor = vec4(mix(scene.rgb, vec3(gray), uDesaturationFactor), scene.a);
}
}
technique {
description = "Desaturates scene";
passes = desaturate;
version = "1.0";
author = "Fargoth";
passes = desaturate;
}
Types
#####
`SHADER_FLAG`
*************
+--------------------+--------------------------------------------------------------------------+
| Flag | Description |
+====================+==========================================================================+
| disable_interiors | Disable in interiors. |
+--------------------+--------------------------------------------------------------------------+
| disable_exteriors | Disable when in exteriors or interiors which behave like exteriors. |
+--------------------+--------------------------------------------------------------------------+
| disable_underwater | Disable when underwater. |
+--------------------+--------------------------------------------------------------------------+
| disable_abovewater | Disable when above water. |
+--------------------+--------------------------------------------------------------------------+
| disable_sunglare | Disables builtin sunglare. |
+--------------------+--------------------------------------------------------------------------+
| hidden | Shader does not show in the HUD. Useful for shaders driven by Lua API. |
+--------------------+--------------------------------------------------------------------------+
`BLEND_EQ`
**********
+-------------------+---------------------------+
| .omwfx | OpenGL |
+===================+===========================+
| rgba_min | GL_MIN |
+-------------------+---------------------------+
| rgba_max | GL_MAX |
+-------------------+---------------------------+
| alpha_min | GL_ALPHA_MIN_SGIX |
+-------------------+---------------------------+
| alpha_max | GL_ALPHA_MAX_SGIX |
+-------------------+---------------------------+
| logic_op | GL_LOGIC_OP |
+-------------------+---------------------------+
| add | GL_FUNC_ADD |
+-------------------+---------------------------+
| subtract | GL_FUNC_SUBTRACT |
+-------------------+---------------------------+
| reverse_subtract | GL_FUNC_REVERSE_SUBTRACT |
+-------------------+---------------------------+
`BLEND_FUNC`
************
+---------------------------+------------------------------+
| .omwfx | OpenGL |
+===========================+==============================+
| dst_alpha | GL_DST_ALPHA |
+---------------------------+------------------------------+
| dst_color | GL_DST_COLOR |
+---------------------------+------------------------------+
| one | GL_ONE |
+---------------------------+------------------------------+
| one_minus_dst_alpha | GL_ONE_MINUS_DST_ALPHA |
+---------------------------+------------------------------+
| one_minus_dst_color | GL_ONE_MINUS_DST_COLOR |
+---------------------------+------------------------------+
| one_minus_src_alpha | GL_ONE_MINUS_SRC_ALPHA |
+---------------------------+------------------------------+
| one_minus_src_color | GL_ONE_MINUS_SRC_COLOR |
+---------------------------+------------------------------+
| src_alpha | GL_SRC_ALPHA |
+---------------------------+------------------------------+
| src_alpha_saturate | GL_SRC_ALPHA_SATURATE |
+---------------------------+------------------------------+
| src_color | GL_SRC_COLOR |
+---------------------------+------------------------------+
| constant_color | GL_CONSTANT_COLOR |
+---------------------------+------------------------------+
| one_minus_constant_color | GL_ONE_MINUS_CONSTANT_COLOR |
+---------------------------+------------------------------+
| constant_alpha | GL_CONSTANT_ALPHA |
+---------------------------+------------------------------+
| one_minus_constant_alpha | GL_ONE_MINUS_CONSTANT_ALPHA |
+---------------------------+------------------------------+
| zero | GL_ZERO |
+---------------------------+------------------------------+
`INTERNAL_FORMAT`
*****************
+--------------------+-----------------------+
| .omwfx | OpenGL |
+====================+=======================+
| 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 |
+--------------------+-----------------------+
`SOURCE_TYPE`
*************
+--------------------+-----------------------+
| .omwfx | OpenGL |
+====================+=======================+
| 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 |
+--------------------+-----------------------+
`SOURCE_FORMAT`
***************
+---------+---------+
| .omwfx | OpenGL |
+=========+=========+
| red | GL_RED |
+---------+---------+
| rg | GL_RG |
+---------+---------+
| rgb | GL_RGB |
+---------+---------+
| bgr | GL_BGR |
+---------+---------+
| rgba | GL_RGBA |
+---------+---------+
| bgra | GL_BGRA |
+---------+---------+
`FILTER_MODE`
*************
+-------------------------+----------------------------+
| .omwfx | OpenGL |
+=========================+============================+
| linear | GL_LINEAR |
+-------------------------+----------------------------+
| linear_mipmap_linear | GL_LINEAR_MIPMAP_LINEAR |
+-------------------------+----------------------------+
| linear_mipmap_nearest | GL_LINEAR_MIPMAP_NEAREST |
+-------------------------+----------------------------+
| nearest | GL_NEAREST |
+-------------------------+----------------------------+
| nearest_mipmap_linear | GL_NEAREST_MIPMAP_LINEAR |
+-------------------------+----------------------------+
| nearest_mipmap_nearest | GL_NEAREST_MIPMAP_NEAREST |
+-------------------------+----------------------------+
`WRAP_MODE`
***********
+------------------+---------------------+
| .omwfx | OpenGL |
+==================+=====================+
| clamp | GL_CLAMP |
+------------------+---------------------+
| clamp_to_edge | GL_CLAMP_TO_EDGE |
+------------------+---------------------+
| clamp_to_border | GL_CLAMP_TO_BORDER |
+------------------+---------------------+
| repeat | GL_REPEAT |
+------------------+---------------------+
| mirror | GL_MIRRORED_REPEAT |
+------------------+---------------------+
`COMPRESSION_MODE`
******************
+-------------+
| .omwfx |
+=============+
| auto |
+-------------+
| arb |
+-------------+
| s3tc_dxt1 |
+-------------+
| s3tc_dxt3 |
+-------------+
| s3tc_dxt5 |
+-------------+
| pvrtc_2bpp |
+-------------+
| pvrtc_4bpp |
+-------------+
| etc |
+-------------+
| etc2 |
+-------------+
| rgtc1 |
+-------------+
| rgtc2 |
+-------------+
| s3tc_dxt1c |
+-------------+
| s3tc_dxt1a |
+-------------+

@ -0,0 +1,45 @@
#####################################
Overview of Post Processing Framework
#####################################
Overview
========
OpenMW supports a moddable post process framework for creating and
controlling screenspace effects. This is integrated into OpenMW's Lua API, see
`reference <../lua-scripting/openmw_shader.html>`_ for details.
Basic concepts
==============
Pass
Describes a single shader invocation pass. Currently only pixel (also known
as fragment) shaders are supported.
Technique/Shader
An ordered list of passes, techniques will encompass a single effect like
bloom or SSAO. Technique is interchangable with shader.
Installing and Activating
=========================
Shaders are managed through the virtual file system, simply install the associated
archive or folder as described in :ref:`mod-install<install>`. Shaders must be
in the `Shaders` directory to be discoverable. A shader can be activated in one
of two ways:
1. Adding the shaders filename (without its extension) to the end of the
:ref:`chain` list in ``settings.cfg``.
2. Using the in game post processor HUD, which can be activated with the ``F2``
key by default. This is the recommended method as manual editing can be error
prone.
Hot Reloading
==============
It is possible to modify a shader without restarting OpenMW, :ref:`live reload`
must be enabled in ``settings.cfg``. Whenever a file is modified and saved, the
shader will automatically reload in game. This allows shaders to be written in a
text editor you are comfortable with. The only restriction is that new shaders
cannot be added, as the VFS will not be rebuilt and OpenMW will not be aware of
the new file.

@ -13,6 +13,7 @@ set(LUA_API_FILES
openmw/util.lua openmw/util.lua
openmw/world.lua openmw/world.lua
openmw/types.lua openmw/types.lua
openmw/postprocessing.lua
) )
foreach (f ${LUA_API_FILES}) foreach (f ${LUA_API_FILES})

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save