Merge branch 'post_malone' into 'master'

Post Processing

See merge request OpenMW/openmw!1124
pull/3227/head
psi29a 3 years ago
commit f092d8da9a

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

@ -145,6 +145,12 @@ bool Launcher::AdvancedPage::loadSettings()
objectPagingMinSizeComboBox->setValue(Settings::Manager::getDouble("object paging min size", "Terrain"));
loadSettingBool(nightDaySwitchesCheckBox, "day night switches", "Game");
connect(postprocessEnabledCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotPostProcessToggled(bool)));
loadSettingBool(postprocessEnabledCheckBox, "enabled", "Post Processing");
loadSettingBool(postprocessLiveReloadCheckBox, "live reload", "Post Processing");
loadSettingBool(postprocessTransparentPostpassCheckBox, "transparent postpass", "Post Processing");
postprocessHDRTimeComboBox->setValue(Settings::Manager::getDouble("hdr exposure time", "Post Processing"));
}
// Audio
@ -302,6 +308,11 @@ void Launcher::AdvancedPage::saveSettings()
Settings::Manager::setDouble("object paging min size", "Terrain", objectPagingMinSize);
saveSettingBool(nightDaySwitchesCheckBox, "day night switches", "Game");
saveSettingBool(postprocessEnabledCheckBox, "enabled", "Post Processing");
saveSettingBool(postprocessLiveReloadCheckBox, "live reload", "Post Processing");
saveSettingBool(postprocessTransparentPostpassCheckBox, "transparent postpass", "Post Processing");
Settings::Manager::setDouble("hdr exposure time", "Post Processing", postprocessHDRTimeComboBox->value());
}
// Audio
@ -464,3 +475,11 @@ void Launcher::AdvancedPage::slotViewOverShoulderToggled(bool checked)
{
viewOverShoulderVerticalLayout->setEnabled(viewOverShoulderCheckBox->checkState());
}
void Launcher::AdvancedPage::slotPostProcessToggled(bool checked)
{
postprocessLiveReloadCheckBox->setEnabled(checked);
postprocessTransparentPostpassCheckBox->setEnabled(checked);
postprocessHDRTimeComboBox->setEnabled(checked);
postprocessHDRTimeLabel->setEnabled(checked);
}

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

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

@ -22,7 +22,8 @@ add_openmw_dir (mwrender
actors objects renderingmanager animation rotatecontroller sky skyutil npcanimation vismask
creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager
bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation
renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover postprocessor
renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover
postprocessor pingpongcull hdr pingpongcanvas transparentpass
)
add_openmw_dir (mwinput
@ -43,6 +44,7 @@ add_openmw_dir (mwgui
tradeitemmodel companionitemmodel pickpocketitemmodel controllers savegamedialog
recharge mode videowidget backgroundimage itemwidget screenfader debugwindow spellmodel spellview
draganddrop timeadvancer jailscreen itemchargeview keyboardnavigation textcolours statswatcher
postprocessorhud
)
add_openmw_dir (mwdialogue
@ -59,7 +61,7 @@ add_openmw_dir (mwscript
add_openmw_dir (mwlua
luamanagerimp object worldview userdataserializer eventqueue
luabindings localscripts playerscripts objectbindings cellbindings asyncbindings settingsbindings
camerabindings uibindings inputbindings nearbybindings stats
camerabindings uibindings inputbindings nearbybindings postprocessingbindings stats
types/types types/door types/actor types/container types/weapon types/npc types/creature
)

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

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

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

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

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

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

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

@ -286,6 +286,7 @@ namespace MWInput
defaultKeyBindings[A_AlwaysRun] = SDL_SCANCODE_CAPSLOCK;
defaultKeyBindings[A_QuickSave] = SDL_SCANCODE_F5;
defaultKeyBindings[A_QuickLoad] = SDL_SCANCODE_F9;
defaultKeyBindings[A_TogglePostProcessorHUD] = SDL_SCANCODE_F2;
std::map<int, int> defaultMouseButtonBindings;
defaultMouseButtonBindings[A_Inventory] = SDL_BUTTON_RIGHT;
@ -502,6 +503,8 @@ namespace MWInput
return "#{sQuickSaveCmd}";
case A_QuickLoad:
return "#{sQuickLoadCmd}";
case A_TogglePostProcessorHUD:
return "Toggle Post Processor HUD";
default:
return std::string(); // not configurable
}
@ -563,7 +566,8 @@ namespace MWInput
A_CycleSpellLeft, A_CycleSpellRight, A_CycleWeaponLeft, A_CycleWeaponRight, A_AutoMove,
A_Jump, A_Inventory, A_Journal, A_Rest, A_Console, A_QuickSave, A_QuickLoad,
A_ToggleHUD, A_Screenshot, A_QuickKeysMenu, A_QuickKey1, A_QuickKey2, A_QuickKey3,
A_QuickKey4, A_QuickKey5, A_QuickKey6, A_QuickKey7, A_QuickKey8, A_QuickKey9, A_QuickKey10
A_QuickKey4, A_QuickKey5, A_QuickKey6, A_QuickKey7, A_QuickKey8, A_QuickKey9, A_QuickKey10,
A_TogglePostProcessorHUD
};
return actions;

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

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

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

@ -140,6 +140,7 @@ namespace MWLua
sol::table mPlayerSettingsPackage;
sol::table mLocalStoragePackage;
sol::table mPlayerStoragePackage;
sol::table mPostprocessingPackage;
GlobalScripts mGlobalScripts{&mLua};
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/Switch>
#include <osg/LightModel>
#include <osg/ColorMaski>
#include <osgParticle/ParticleSystem>
#include <osgParticle/ParticleProcessor>
@ -1563,7 +1564,8 @@ namespace MWRender
// Morrowind has a white ambient light attached to the root VFX node of the scenegraph
node->getOrCreateStateSet()->setAttributeAndModes(getVFXLightModelInstance(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
if (mResourceSystem->getSceneManager()->getSupportsNormalsRT())
node->getOrCreateStateSet()->setAttribute(new osg::ColorMaski(1, false, false, false, false));
SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor;
node->accept(findMaxLengthVisitor);

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

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

@ -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
#define OPENMW_MWRENDER_POSTPROCESSOR_H
#include <array>
#include <vector>
#include <string>
#include <unordered_map>
#include <filesystem>
#include <osg/Texture2D>
#include <osg/Group>
#include <osg/FrameBufferObject>
#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>
@ -19,38 +34,199 @@ namespace Stereo
class MultiviewFramebuffer;
}
namespace VFS
{
class Manager;
}
namespace Shader
{
class ShaderManager;
}
namespace MWRender
{
class PostProcessor : public osg::Referenced
class RenderingManager;
class PingPongCull;
class PingPongCanvas;
class TransparentDepthBinCallback;
class PostProcessor : public osg::Group
{
public:
PostProcessor(osgViewer::Viewer* viewer, osg::Group* rootNode);
using FBOArray = std::array<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; }
auto getFbo() { return mFbo; }
auto getFirstPersonRBProxy() { return mFirstPersonDepthRBProxy; }
if (it == technique->getUniformMap().end())
return;
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:
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::Camera> mHUDCamera;
std::shared_ptr<Stereo::MultiviewFramebuffer> mMultiviewFbo;
osg::ref_ptr<osg::FrameBufferObject> mMsaaFbo;
osg::ref_ptr<osg::FrameBufferObject> mFbo;
osg::ref_ptr<osg::RenderBuffer> mFirstPersonDepthRBProxy;
std::array<TextureArray, 2> mTextures;
std::array<FBOArray, 2> mFbos;
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;
osg::ref_ptr<osg::Texture2D> mDepthTex;
osg::ref_ptr<osg::Texture2D> mOpaqueDepthTex;
RenderingManager& mRendering;
osgViewer::Viewer* mViewer;
const VFS::Manager* mVFS;
bool mReload;
bool mEnabled;
bool mUsePostProcessing;
bool mSoftParticles;
bool mDisableDepthPasses;
size_t mLastFrameNumber;
float mLastSimulationTime;
bool mExteriorFlag;
bool mUnderwater;
bool mHDR;
bool mNormals;
bool mPrevNormals;
bool mNormalsSupported;
bool mPassLights;
bool mPrevPassLights;
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

@ -42,6 +42,8 @@
#include <components/sceneutil/writescene.hpp>
#include <components/sceneutil/shadow.hpp>
#include <components/misc/constants.hpp>
#include <components/terrain/terraingrid.hpp>
#include <components/terrain/quadtreeworld.hpp>
@ -54,7 +56,9 @@
#include "../mwworld/class.hpp"
#include "../mwworld/groundcoverstore.hpp"
#include "../mwgui/loadingscreen.hpp"
#include "../mwgui/postprocessorhud.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwbase/windowmanager.hpp"
#include "sky.hpp"
#include "effectmanager.hpp"
@ -114,7 +118,7 @@ namespace MWRender
mProjectionMatrix = projectionMatrix;
}
const osg::Matrixf& projectionMatrix() const
const osg::Matrixf& getProjectionMatrix() const
{
return mProjectionMatrix;
}
@ -208,7 +212,6 @@ namespace MWRender
mPlayerPos = playerPos;
}
private:
float mLinearFac;
float mNear;
@ -411,9 +414,11 @@ namespace MWRender
globalDefines["clamp"] = Settings::Manager::getBool("clamp lighting", "Shaders") ? "1" : "0";
globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0";
globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0";
globalDefines["refraction_enabled"] = "0";
globalDefines["useGPUShader4"] = "0";
globalDefines["useOVR_multiview"] = "0";
globalDefines["numViews"] = "1";
globalDefines["disableNormals"] = "1";
for (auto itr = lightDefines.begin(); itr != lightDefines.end(); itr++)
globalDefines[itr->first] = itr->second;
@ -502,12 +507,9 @@ namespace MWRender
mPerViewUniformStateUpdater = new PerViewUniformStateUpdater();
rootNode->addCullCallback(mPerViewUniformStateUpdater);
mPostProcessor = new PostProcessor(viewer, mRootNode);
resourceSystem->getSceneManager()->setDepthFormat(SceneUtil::AutoDepth::depthInternalFormat());
resourceSystem->getSceneManager()->setOpaqueDepthTex(mPostProcessor->getOpaqueDepthTex());
if (reverseZ && !SceneUtil::isFloatingPointDepthFormat(SceneUtil::AutoDepth::depthInternalFormat()))
Log(Debug::Warning) << "Floating point depth format not in use but reverse-z buffer is enabled, consider disabling it.";
mPostProcessor = new PostProcessor(*this, viewer, mRootNode, resourceSystem->getVFS());
resourceSystem->getSceneManager()->setOpaqueDepthTex(mPostProcessor->getTexture(PostProcessor::Tex_OpaqueDepth, 0), mPostProcessor->getTexture(PostProcessor::Tex_OpaqueDepth, 1));
resourceSystem->getSceneManager()->setSupportsNormalsRT(mPostProcessor->getSupportsNormalsRT());
// water goes after terrain for correct waterculling order
mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath));
@ -558,10 +560,9 @@ namespace MWRender
cullingMode |= osg::CullStack::SMALL_FEATURE_CULLING;
}
mViewer->getCamera()->setCullingMode( cullingMode );
mViewer->getCamera()->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR);
mViewer->getCamera()->setCullingMode(cullingMode);
mViewer->getCamera()->setName(Constants::SceneCamera);
auto mask = ~(Mask_UpdateVisitor | Mask_SimpleWater);
MWBase::Environment::get().getWindowManager()->setCullMask(mask);
@ -650,7 +651,7 @@ namespace MWRender
return mViewer->getFrameStamp()->getReferenceTime();
}
osg::Group* RenderingManager::getLightRoot()
SceneUtil::LightManager* RenderingManager::getLightRoot()
{
return mSceneRoot.get();
}
@ -692,9 +693,10 @@ namespace MWRender
void RenderingManager::configureAmbient(const ESM::Cell *cell)
{
bool isInterior = !cell->isExterior() && !(cell->mData.mFlags & ESM::Cell::QuasiEx);
bool needsAdjusting = false;
if (mResourceSystem->getSceneManager()->getLightingMethod() != SceneUtil::LightingMethod::FFP)
needsAdjusting = !cell->isExterior() && !(cell->mData.mFlags & ESM::Cell::QuasiEx);
needsAdjusting = isInterior;
auto ambient = SceneUtil::colourFromRGB(cell->mAmbi.mAmbient);
@ -724,11 +726,14 @@ namespace MWRender
mSunLight->setPosition(osg::Vec4f(-0.15f, 0.15f, 1.f, 0.f));
}
void RenderingManager::setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular)
void RenderingManager::setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular, float sunVis)
{
// need to wrap this in a StateUpdater?
mSunLight->setDiffuse(diffuse);
mSunLight->setSpecular(specular);
mPostProcessor->getStateUpdater()->setSunColor(diffuse);
mPostProcessor->getStateUpdater()->setSunVis(sunVis);
}
void RenderingManager::setSunDirection(const osg::Vec3f &direction)
@ -738,6 +743,8 @@ namespace MWRender
mSunLight->setPosition(osg::Vec4(position.x(), position.y(), position.z(), 0));
mSky->setSunDirection(position);
mPostProcessor->getStateUpdater()->setSunPos(mSunLight->getPosition(), mNight);
}
void RenderingManager::addCell(const MWWorld::CellStore *store)
@ -779,6 +786,7 @@ namespace MWRender
mShadowManager->enableOutdoorMode();
else
mShadowManager->enableIndoorMode();
mPostProcessor->getStateUpdater()->setIsInterior(!enabled);
}
bool RenderingManager::toggleBorders()
@ -877,9 +885,28 @@ namespace MWRender
mCamera->update(dt, paused);
bool isUnderwater = mWater->isUnderwater(mCamera->getPosition());
mStateUpdater->setFogStart(mFog->getFogStart(isUnderwater));
mStateUpdater->setFogEnd(mFog->getFogEnd(isUnderwater));
setFogColor(mFog->getFogColor(isUnderwater));
float fogStart = mFog->getFogStart(isUnderwater);
float fogEnd = mFog->getFogEnd(isUnderwater);
osg::Vec4f fogColor = mFog->getFogColor(isUnderwater);
mStateUpdater->setFogStart(fogStart);
mStateUpdater->setFogEnd(fogEnd);
setFogColor(fogColor);
auto world = MWBase::Environment::get().getWorld();
const auto& stateUpdater = mPostProcessor->getStateUpdater();
stateUpdater->setFogRange(fogStart, fogEnd);
stateUpdater->setNearFar(mNearClip, mViewDistance);
stateUpdater->setIsUnderwater(isUnderwater);
stateUpdater->setFogColor(fogColor);
stateUpdater->setGameHour(world->getTimeStamp().getHour());
stateUpdater->setWeatherId(world->getCurrentWeather());
stateUpdater->setNextWeatherId(world->getNextWeather());
stateUpdater->setWeatherTransition(world->getWeatherTransition());
stateUpdater->setWindSpeed(world->getWindSpeed());
mPostProcessor->setUnderwaterFlag(isUnderwater);
}
void RenderingManager::updatePlayerPtr(const MWWorld::Ptr &ptr)
@ -939,6 +966,8 @@ namespace MWRender
mWater->setCullCallback(mTerrain->getHeightCullCallback(height, Mask_Water));
mWater->setHeight(height);
mSky->setWaterHeight(height);
mPostProcessor->getStateUpdater()->setWaterHeight(height);
}
void RenderingManager::screenshot(osg::Image* image, int w, int h)
@ -1131,6 +1160,11 @@ namespace MWRender
return mObjects->getAnimation(ptr);
}
PostProcessor* RenderingManager::getPostProcessor()
{
return mPostProcessor;
}
void RenderingManager::setupPlayer(const MWWorld::Ptr &player)
{
if (!mPlayerNode)
@ -1218,9 +1252,9 @@ namespace MWRender
{
auto res = Stereo::Manager::instance().eyeResolution();
mSharedUniformStateUpdater->setScreenRes(res.x(), res.y());
Stereo::Manager::instance().setMasterProjectionMatrix(mPerViewUniformStateUpdater->projectionMatrix());
Stereo::Manager::instance().setMasterProjectionMatrix(mPerViewUniformStateUpdater->getProjectionMatrix());
}
else
else if (!mPostProcessor->isEnabled())
{
mSharedUniformStateUpdater->setScreenRes(width, height);
}
@ -1229,6 +1263,17 @@ namespace MWRender
// Limit FOV here just for sure, otherwise viewing distance can be too high.
float distanceMult = std::cos(osg::DegreesToRadians(std::min(fov, 140.f))/2.f);
mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f/distanceMult : 1.f));
if (mPostProcessor)
{
mPostProcessor->getStateUpdater()->setProjectionMatrix(mPerViewUniformStateUpdater->getProjectionMatrix());
mPostProcessor->getStateUpdater()->setFov(fov);
}
}
void RenderingManager::setScreenRes(int width, int height)
{
mSharedUniformStateUpdater->setScreenRes(width, height);
}
void RenderingManager::updateTextureFiltering()
@ -1316,7 +1361,7 @@ namespace MWRender
it->second == "light fade start" ||
it->second == "max lights"))
{
auto* lightManager = static_cast<SceneUtil::LightManager*>(getLightRoot());
auto* lightManager = getLightRoot();
lightManager->processChangedSettings(changed);
if (it->second == "max lights" && !lightManager->usingFFP())
@ -1335,6 +1380,17 @@ namespace MWRender
mViewer->startThreading();
}
}
else if (it->first == "Post Processing" && it->second == "enabled")
{
if (Settings::Manager::getBool("enabled", "Post Processing"))
mPostProcessor->enable();
else
{
mPostProcessor->disable();
if (auto* hud = MWBase::Environment::get().getWindowManager()->getPostProcessorHud())
hud->setVisible(false);
}
}
}
if (updateProjection)

@ -59,6 +59,7 @@ namespace SceneUtil
{
class ShadowManager;
class WorkQueue;
class LightManager;
}
namespace DetourNavigator
@ -116,7 +117,7 @@ namespace MWRender
double getReferenceTime() const;
osg::Group* getLightRoot();
SceneUtil::LightManager* getLightRoot();
void setNightEyeFactor(float factor);
@ -128,7 +129,8 @@ namespace MWRender
void skySetMoonColour(bool red);
void setSunDirection(const osg::Vec3f& direction);
void setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular);
void setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular, float sunVis);
void setNight(bool isNight) { mNight = isNight; }
void configureAmbient(const ESM::Cell* cell);
void configureFog(const ESM::Cell* cell);
@ -192,6 +194,8 @@ namespace MWRender
Animation* getAnimation(const MWWorld::Ptr& ptr);
const Animation* getAnimation(const MWWorld::ConstPtr& ptr) const;
PostProcessor* getPostProcessor();
void addWaterRippleEmitter(const MWWorld::Ptr& ptr);
void removeWaterRippleEmitter(const MWWorld::Ptr& ptr);
void emitWaterRipple(const osg::Vec3f& pos);
@ -247,6 +251,8 @@ namespace MWRender
void updateProjectionMatrix();
void setScreenRes(int width, int height);
private:
void updateTextureFiltering();
void updateAmbient();
@ -266,7 +272,7 @@ namespace MWRender
osg::ref_ptr<osgViewer::Viewer> mViewer;
osg::ref_ptr<osg::Group> mRootNode;
osg::ref_ptr<osg::Group> mSceneRoot;
osg::ref_ptr<SceneUtil::LightManager> mSceneRoot;
Resource::ResourceSystem* mResourceSystem;
osg::ref_ptr<SceneUtil::WorkQueue> mWorkQueue;
@ -310,6 +316,7 @@ namespace MWRender
float mFieldOfView;
float mFirstPersonFieldOfView;
bool mUpdateProjectionMatrix = false;
bool mNight = false;
void operator = (const RenderingManager&);
RenderingManager(const RenderingManager&);

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

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

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

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

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

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

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

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

@ -307,7 +307,11 @@ namespace MWWorld
void advanceTime(double hours, bool incremental);
unsigned int getWeatherID() const;
int getWeatherID() const { return mCurrentWeather; }
int getNextWeatherID() const { return mNextWeather; }
float getTransitionFactor() const { return mTransitionFactor; }
bool useTorches(float hour) const;

@ -28,6 +28,7 @@
#include <components/resource/resourcesystem.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include <components/detournavigator/navigator.hpp>
#include <components/detournavigator/settings.hpp>
@ -55,6 +56,7 @@
#include "../mwrender/renderingmanager.hpp"
#include "../mwrender/camera.hpp"
#include "../mwrender/vismask.hpp"
#include "../mwrender/postprocessor.hpp"
#include "../mwscript/globalscripts.hpp"
@ -196,7 +198,7 @@ namespace MWWorld
}
mRendering.reset(new MWRender::RenderingManager(viewer, rootNode, resourceSystem, workQueue, resourcePath, *mNavigator, mGroundcoverStore));
mProjectileManager.reset(new ProjectileManager(mRendering->getLightRoot(), resourceSystem, mRendering.get(), mPhysics.get()));
mProjectileManager.reset(new ProjectileManager(mRendering->getLightRoot()->asGroup(), resourceSystem, mRendering.get(), mPhysics.get()));
mRendering->preloadCommonAssets();
mWeatherManager.reset(new MWWorld::WeatherManager(*mRendering, mStore));
@ -2045,6 +2047,16 @@ namespace MWWorld
return mWeatherManager->getWeatherID();
}
int World::getNextWeather() const
{
return mWeatherManager->getNextWeatherID();
}
float World::getWeatherTransition() const
{
return mWeatherManager->getTransitionFactor();
}
unsigned int World::getNightDayMode() const
{
return mWeatherManager->getNightDayMode();
@ -3986,4 +3998,8 @@ namespace MWWorld
return mPrng;
}
MWRender::PostProcessor* World::getPostProcessor()
{
return mRendering->getPostProcessor();
}
}

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

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

@ -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);
}
std::string getPath() override
{
return "TestFile";
}
private:
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(fx pass technique lexer widgets stateupdater)
add_component_dir(std140 ubo)
add_component_dir (esm3
esmreader esmwriter loadacti loadalch loadappa loadarmo loadbody loadbook loadbsgn loadcell
loadclas loadclot loadcont loadcrea loaddial loaddoor loadench loadfact loadglob loadgmst

@ -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,283 @@
#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/lightmanager.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 highp @builtinSampler omw_SamplerDepth;
uniform @builtinSampler omw_SamplerNormals;
uniform vec4 omw_PointLights[@pointLightCount];
uniform int omw_PointLightsCount;
int omw_GetPointLightCount()
{
return omw_PointLightsCount;
}
vec3 omw_GetPointLightViewPos(int index)
{
return omw_PointLights[(index * 3)].xyz;
}
vec3 omw_GetPointLightDiffuse(int index)
{
return omw_PointLights[(index * 3) + 1].xyz;
}
vec3 omw_GetPointLightAttenuation(int index)
{
return omw_PointLights[(index * 3) + 2].xyz;
}
float omw_GetPointLightRadius(int index)
{
return omw_PointLights[(index * 3) + 2].w;
}
#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 = {
{"@pointLightCount", std::to_string(SceneUtil::PPLightBuffer::sMaxPPLightsArraySize)},
{"@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,63 @@
#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());
}
if (mPointLightBuffer)
mPointLightBuffer->applyUniforms(nv->getTraversalNumber(), stateset);
}
}

@ -0,0 +1,200 @@
#ifndef OPENMW_COMPONENTS_FX_STATEUPDATER_H
#define OPENMW_COMPONENTS_FX_STATEUPDATER_H
#include <osg/BufferTemplate>
#include <components/sceneutil/lightmanager.hpp>
#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; }
void bindPointLights(std::shared_ptr<SceneUtil::PPLightBuffer> buffer)
{
mPointLightBuffer = buffer;
}
static std::string getStructDefinition()
{
static std::string definition = UniformData::getDefinition("_omw_data");
return definition;
}
void setDefaults(osg::StateSet* stateset) override;
void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override;
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
>;
UniformData mData;
bool mUseUBO;
std::shared_ptr<SceneUtil::PPLightBuffer> mPointLightBuffer;
};
}
#endif

File diff suppressed because it is too large Load Diff

@ -0,0 +1,303 @@
#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; }
bool getLights() const { return mLights; }
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;
bool mLights;
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

@ -39,6 +39,9 @@ const float TorsoHeight = 0.75f;
static constexpr float sStepSizeUp = 34.0f;
static constexpr float sMaxSlope = 46.0f;
// Identifier for main scene camera
const std::string SceneCamera = "SceneCam";
}
#endif

@ -49,6 +49,7 @@ namespace Resource
: ResourceManager(vfs)
, mWarningImage(createWarningImage())
, mOptions(new osgDB::Options("dds_flip dds_dxt1_detect_rgba ignoreTga2Fields"))
, mOptionsNoFlip(new osgDB::Options("dds_dxt1_detect_rgba ignoreTga2Fields"))
{
}
@ -82,7 +83,7 @@ namespace Resource
return true;
}
osg::ref_ptr<osg::Image> ImageManager::getImage(const std::string &filename)
osg::ref_ptr<osg::Image> ImageManager::getImage(const std::string &filename, bool disableFlip)
{
const std::string normalized = mVFS->normalizeFilename(filename);
@ -135,7 +136,7 @@ namespace Resource
stream->seekg(0);
}
osgDB::ReaderWriter::ReadResult result = reader->readImage(*stream, mOptions);
osgDB::ReaderWriter::ReadResult result = reader->readImage(*stream, disableFlip ? mOptionsNoFlip : mOptions);
if (!result.success())
{
Log(Debug::Error) << "Error loading " << filename << ": " << result.message() << " code " << result.status();

@ -28,7 +28,7 @@ namespace Resource
/// Create or retrieve an Image
/// Returns the dummy image if the given image is not found.
osg::ref_ptr<osg::Image> getImage(const std::string& filename);
osg::ref_ptr<osg::Image> getImage(const std::string& filename, bool disableFlip = false);
osg::Image* getWarningImage();
@ -37,6 +37,7 @@ namespace Resource
private:
osg::ref_ptr<osg::Image> mWarningImage;
osg::ref_ptr<osgDB::Options> mOptions;
osg::ref_ptr<osgDB::Options> mOptionsNoFlip;
ImageManager(const ImageManager&);
void operator = (const ImageManager&);

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

@ -91,9 +91,6 @@ namespace Resource
void setClampLighting(bool clamp);
bool getClampLighting() const;
void setDepthFormat(GLenum format);
GLenum getDepthFormat() const;
/// @see ShaderVisitor::setAutoUseNormalMaps
void setAutoUseNormalMaps(bool use);
@ -112,12 +109,13 @@ namespace Resource
void setSupportedLightingMethods(const SceneUtil::LightManager::SupportedMethods& supported);
bool isSupportedLightingMethod(SceneUtil::LightingMethod method) const;
void setOpaqueDepthTex(osg::ref_ptr<osg::Texture2D> texture);
void setOpaqueDepthTex(osg::ref_ptr<osg::Texture2D> texturePing, osg::ref_ptr<osg::Texture2D> texturePong);
enum class UBOBinding
{
// If we add more UBO's, we should probably assign their bindings dynamically according to the current count of UBO's in the programTemplate
LightBuffer
LightBuffer,
PostProcessor
};
void setLightingMethod(SceneUtil::LightingMethod method);
SceneUtil::LightingMethod getLightingMethod() const;
@ -195,6 +193,9 @@ namespace Resource
void reportStats(unsigned int frameNumber, osg::Stats* stats) const override;
void setSupportsNormalsRT(bool supports) { mSupportsNormalsRT = supports; }
bool getSupportsNormalsRT() const { return mSupportsNormalsRT; }
private:
Shader::ShaderVisitor* createShaderVisitor(const std::string& shaderPrefix = "objects");
@ -211,8 +212,8 @@ namespace Resource
SceneUtil::LightingMethod mLightingMethod;
SceneUtil::LightManager::SupportedMethods mSupportedLightingMethods;
bool mConvertAlphaTestToAlphaToCoverage;
GLenum mDepthFormat;
osg::ref_ptr<osg::Texture2D> mOpaqueDepthTex;
bool mSupportsNormalsRT;
std::array<osg::ref_ptr<osg::Texture2D>, 2> mOpaqueDepthTex;
osg::ref_ptr<Resource::SharedStateManager> mSharedStateManager;
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)
{
constexpr std::array<GLenum, 8> formats = {

@ -45,9 +45,6 @@ namespace SceneUtil
// Returns an orthographic projection matrix for use with a reversed z-buffer.
osg::Matrix getReversedZProjectionMatrixAsOrtho(double left, double right, double bottom, double top, double near, double far);
// Returns true if the GL format is a floating point depth format.
bool isFloatingPointDepthFormat(GLenum format);
// Returns true if the GL format is a depth format
bool isDepthFormat(GLenum format);

@ -18,6 +18,7 @@
#include <components/misc/hash.hpp>
#include <components/misc/stringops.hpp>
#include <components/misc/constants.hpp>
#include <components/debug/debuglog.hpp>
@ -839,6 +840,7 @@ namespace SceneUtil
, mPointLightFadeEnd(copy.mPointLightFadeEnd)
, mPointLightFadeStart(copy.mPointLightFadeStart)
, mMaxLights(copy.mMaxLights)
, mPPLightBuffer(copy.mPPLightBuffer)
{
}
@ -1001,6 +1003,9 @@ namespace SceneUtil
void LightManager::update(size_t frameNum)
{
if (mPPLightBuffer)
mPPLightBuffer->clear(frameNum);
getLightIndexMap(frameNum).clear();
mLights.clear();
mLightsInViewSpace.clear();
@ -1132,6 +1137,17 @@ namespace SceneUtil
l.mLightSource = transform.mLightSource;
l.mViewBound = viewBound;
it->second.push_back(l);
if (mPPLightBuffer && it->first->getName() == Constants::SceneCamera)
{
const auto* light = l.mLightSource->getLight(frameNum);
mPPLightBuffer->setLight(frameNum, light->getPosition() * (*viewMatrix),
light->getDiffuse(),
light->getConstantAttenuation(),
light->getLinearAttenuation(),
light->getQuadraticAttenuation(),
l.mLightSource->getRadius());
}
}
}
@ -1168,6 +1184,14 @@ namespace SceneUtil
return uniform;
}
void LightManager::setCollectPPLights(bool enabled)
{
if (enabled)
mPPLightBuffer = std::make_shared<PPLightBuffer>();
else
mPPLightBuffer = nullptr;
}
LightSource::LightSource()
: mRadius(0.f)
, mActorFade(1.f)

@ -26,6 +26,64 @@ namespace SceneUtil
class LightBuffer;
struct StateSetGenerator;
class PPLightBuffer
{
public:
inline static constexpr auto sMaxPPLights = 30;
inline static constexpr auto sMaxPPLightsArraySize = sMaxPPLights * 3;
PPLightBuffer()
{
for (size_t i = 0; i < 2; ++i)
{
mIndex[i] = 0;
mUniformBuffers[i] = new osg::Uniform(osg::Uniform::FLOAT_VEC4, "omw_PointLights", sMaxPPLightsArraySize);
mUniformCount[i] = new osg::Uniform("omw_PointLightsCount", static_cast<int>(0));
}
}
void applyUniforms(size_t frame, osg::StateSet* stateset)
{
size_t frameId = frame % 2;
if (!stateset->getUniform("omw_PointLights"))
stateset->addUniform(mUniformBuffers[frameId]);
if (!stateset->getUniform("omw_PointLightsCount"))
stateset->addUniform(mUniformCount[frameId]);
mUniformBuffers[frameId]->dirty();
mUniformCount[frameId]->dirty();
}
void clear(size_t frame)
{
mIndex[frame % 2] = 0;
}
void setLight(size_t frame, const osg::Vec4f& position, osg::Vec4f diffuse, float ac, float al, float aq, float radius)
{
size_t frameId = frame % 2;
size_t i = mIndex[frameId];
if (i >= (sMaxPPLights - 1))
return;
i *= 3;
mUniformBuffers[frameId]->setElement(i + 0, position);
mUniformBuffers[frameId]->setElement(i + 1, diffuse);
mUniformBuffers[frameId]->setElement(i + 2, osg::Vec4f(ac, al, aq, radius));
mIndex[frameId]++;
mUniformCount[frameId]->set(static_cast<int>(mIndex[frameId]));
}
private:
std::array<size_t, 2> mIndex;
std::array<osg::ref_ptr<osg::Uniform>, 2> mUniformBuffers;
std::array<osg::ref_ptr<osg::Uniform>, 2> mUniformCount;
};
enum class LightingMethod
{
FFP,
@ -227,6 +285,11 @@ namespace SceneUtil
osg::ref_ptr<osg::Uniform> generateLightBufferUniform(const osg::Matrixf& sun);
// Whether to collect main scene camera points lights into a buffer to be later sent to postprocessing shaders
void setCollectPPLights(bool enabled);
std::shared_ptr<PPLightBuffer> getPPLightsBuffer() { return mPPLightBuffer; }
private:
void initFFP(int targetLights);
void initPerObjectUniform(int targetLights);
@ -285,6 +348,8 @@ namespace SceneUtil
static constexpr auto mFFPMaxLights = 8;
static const std::unordered_map<std::string, LightingMethod> mLightingMethodSettingMap;
std::shared_ptr<PPLightBuffer> mPPLightBuffer;
};
/// To receive lighting, objects must be decorated by a LightListCallback. Light list callbacks must be added via

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

@ -152,42 +152,6 @@ void GlowUpdater::setDuration(float duration)
mDuration = duration;
}
// Allows camera to render to a color and floating point depth texture with a multisampled framebuffer.
class AttachMultisampledDepthColorCallback : public SceneUtil::NodeCallback<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 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/Texture>
#include <osg/ValueObject>
#include <osg/Capability>
#include <osgParticle/ParticleSystem>
@ -28,6 +29,50 @@
#include "removedalphafunc.hpp"
#include "shadermanager.hpp"
namespace
{
class OpaqueDepthAttribute : public osg::StateAttribute
{
public:
OpaqueDepthAttribute() = default;
OpaqueDepthAttribute(const OpaqueDepthAttribute& copy, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY)
: osg::StateAttribute(copy, copyop), mTextures(copy.mTextures), mUnit(copy.mUnit) {}
void setTexturesAndUnit(const std::array<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
{
/**
@ -165,6 +210,7 @@ namespace Shader
, mAutoUseSpecularMaps(false)
, mApplyLightingToEnvMaps(false)
, mConvertAlphaTestToAlphaToCoverage(false)
, mSupportsNormalsRT(false)
, mShaderManager(shaderManager)
, mImageManager(imageManager)
, mDefaultShaderPrefix(defaultShaderPrefix)
@ -611,6 +657,14 @@ namespace Shader
defineMap["endLight"] = "0";
}
if (reqs.mAlphaBlend && mSupportsNormalsRT)
{
if (reqs.mSoftParticles)
defineMap["disableNormals"] = "1";
else
writableStateSet->setAttribute(new osg::Disablei(GL_BLEND, 1));
}
if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT && !previousAddedState->hasMode(GL_ALPHA_TEST))
removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST));
// This disables the deprecated fixed-function alpha test
@ -629,7 +683,7 @@ namespace Shader
updateRemovedState(*writableUserData, removedState);
}
if (reqs.mSoftParticles)
if (reqs.mSoftParticles && mOpaqueDepthTex.front())
{
osg::ref_ptr<osg::Depth> depth = new SceneUtil::AutoDepth;
depth->setWriteMask(false);
@ -639,14 +693,18 @@ namespace Shader
writableStateSet->addUniform(new osg::Uniform("particleSize", reqs.mSoftParticleSize));
addedState->addUniform("particleSize");
writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", 2));
constexpr int unit = 2;
writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", unit));
addedState->addUniform("opaqueDepthTex");
writableStateSet->setTextureAttributeAndModes(2, mOpaqueDepthTex, osg::StateAttribute::ON);
addedState->setTextureAttributeAndModes(2, mOpaqueDepthTex);
osg::ref_ptr<OpaqueDepthAttribute> opaqueDepthAttr = new OpaqueDepthAttribute;
opaqueDepthAttr->setTexturesAndUnit(mOpaqueDepthTex, unit);
writableStateSet->setAttributeAndModes(opaqueDepthAttr, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
addedState->setAttributeAndModes(opaqueDepthAttr);
}
defineMap["softParticles"] = reqs.mSoftParticles ? "1" : "0";
defineMap["softParticles"] = reqs.mSoftParticles && mOpaqueDepthTex.front() ? "1" : "0";
Stereo::Manager::instance().shaderStereoDefines(defineMap);
@ -840,7 +898,7 @@ namespace Shader
{
pushRequirements(drawable);
if (partsys && mOpaqueDepthTex)
if (partsys)
{
mRequirements.back().mSoftParticles = true;
mRequirements.back().mSoftParticleSize = partsys->getDefaultParticleTemplate().getSizeRange().maximum;
@ -915,9 +973,9 @@ namespace Shader
mConvertAlphaTestToAlphaToCoverage = convert;
}
void ShaderVisitor::setOpaqueDepthTex(osg::ref_ptr<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)

@ -1,6 +1,8 @@
#ifndef OPENMW_COMPONENTS_SHADERVISITOR_H
#define OPENMW_COMPONENTS_SHADERVISITOR_H
#include <array>
#include <osg/NodeVisitor>
#include <osg/Program>
#include <osg/Texture2D>
@ -46,7 +48,9 @@ namespace Shader
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;
@ -73,6 +77,8 @@ namespace Shader
bool mConvertAlphaTestToAlphaToCoverage;
bool mSupportsNormalsRT;
ShaderManager& mShaderManager;
Resource::ImageManager& mImageManager;
@ -87,7 +93,7 @@ namespace Shader
bool mShaderRequired;
int mColorMode;
bool mMaterialOverridden;
bool mAlphaTestOverridden;
bool mAlphaBlendOverridden;
@ -116,7 +122,7 @@ namespace Shader
bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs);
osg::ref_ptr<const osg::Program> mProgramTemplate;
osg::ref_ptr<osg::Texture2D> mOpaqueDepthTex;
std::array<osg::ref_ptr<osg::Texture2D>, 2> mOpaqueDepthTex;
};
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/GLExtensions>
#include <osg/Texture2D>
#include <osg/Texture2DMultisample>
#include <osg/Texture2DArray>
#include <osgUtil/RenderStage>
#include <osgUtil/CullVisitor>
@ -322,10 +321,7 @@ namespace Stereo
for (unsigned i = 0; i < 2; i++)
{
if (mSamples > 1)
{
mMsaaColorTexture[i] = createTextureMsaa(sourceFormat, sourceType, internalFormat);
mLayerMsaaFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mMsaaColorTexture[i]));
}
mLayerMsaaFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(new osg::RenderBuffer(mWidth, mHeight, internalFormat, mSamples)));
mColorTexture[i] = createTexture(sourceFormat, sourceType, internalFormat);
mLayerFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mColorTexture[i]));
}
@ -351,10 +347,7 @@ namespace Stereo
for (unsigned i = 0; i < 2; i++)
{
if (mSamples > 1)
{
mMsaaDepthTexture[i] = createTextureMsaa(sourceFormat, sourceType, internalFormat);
mLayerMsaaFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mMsaaDepthTexture[i]));
}
mLayerMsaaFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(new osg::RenderBuffer(mWidth, mHeight, internalFormat, mSamples)));
mDepthTexture[i] = createTexture(sourceFormat, sourceType, internalFormat);
mLayerFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mDepthTexture[i]));
}
@ -381,6 +374,11 @@ namespace Stereo
return mMultiviewColorTexture;
}
osg::Texture2DArray* MultiviewFramebuffer::multiviewDepthBuffer()
{
return mMultiviewDepthTexture;
}
osg::Texture2D* MultiviewFramebuffer::layerColorBuffer(int i)
{
return mColorTexture[i];
@ -451,22 +449,6 @@ namespace Stereo
return texture;
}
osg::Texture2DMultisample* MultiviewFramebuffer::createTextureMsaa(GLint sourceFormat, GLint sourceType, GLint internalFormat)
{
osg::Texture2DMultisample* texture = new osg::Texture2DMultisample;
texture->setTextureSize(mWidth, mHeight);
texture->setNumSamples(mSamples);
texture->setSourceFormat(sourceFormat);
texture->setSourceType(sourceType);
texture->setInternalFormat(internalFormat);
texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
texture->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE);
return texture;
}
osg::Texture2DArray* MultiviewFramebuffer::createTextureArray(GLint sourceFormat, GLint sourceType, GLint internalFormat)
{
osg::Texture2DArray* textureArray = new osg::Texture2DArray;

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

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

@ -180,7 +180,7 @@ std::vector<osg::ref_ptr<osg::StateSet> > ChunkManager::createPasses(float chunk
float blendmapScale = mStorage->getBlendmapScale(chunkSize);
return ::Terrain::createPasses(useShaders, &mSceneManager->getShaderManager(), layers, blendmapTextures, blendmapScale, blendmapScale);
return ::Terrain::createPasses(useShaders, mSceneManager, layers, blendmapTextures, blendmapScale, blendmapScale);
}
osg::ref_ptr<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.mParallax = 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
{

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

@ -10,9 +10,9 @@ namespace osg
class Texture2D;
}
namespace Shader
namespace Resource
{
class ShaderManager;
class SceneManager;
}
namespace Terrain
@ -26,7 +26,7 @@ namespace Terrain
bool mSpecular;
};
std::vector<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<osg::ref_ptr<osg::Texture2D> >& blendmaps, int blendmapScale, float layerTileSize);

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

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

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

@ -105,6 +105,17 @@ namespace VFS
return {};
}
std::string Manager::getAbsoluteFileName(const std::string& name) const
{
std::string normalized = name;
normalize_path(normalized, mStrict);
std::map<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
{
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.
RecursiveDirectoryRange getRecursiveDirectoryIterator(const std::string& path) const;
/// Retrieve the absolute path to the file
/// @note Throws an exception if the file can not be found.
/// @note May be called from any thread once the index has been built.
std::string getAbsoluteFileName(const std::string& name) const;
private:
bool mStrict;

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

@ -20,6 +20,7 @@ Lua API reference
openmw_input
openmw_ui
openmw_camera
openmw_postprocessing
openmw_aux_calendar
openmw_aux_util
openmw_aux_time
@ -46,35 +47,37 @@ It can not be overloaded even if there is a lua file with the same name.
The list of available packages is different for global and for local scripts.
Player scripts are local scripts that are attached to a player.
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
| Package | Can be used | Description |
+=========================================================+====================+===============================================================+
|:ref:`openmw.interfaces <Script interfaces>` | everywhere | | Public interfaces of other scripts. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.util <Package openmw.util>` | everywhere | | Defines utility functions and classes like 3D vectors, |
| | | | 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 |
| | | | between game sessions. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|: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.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.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.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.camera <Package openmw.camera>` | by player scripts | | Controls camera. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
| Package | Can be used | Description |
+============================================================+====================+===============================================================+
|: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, |
| | | | 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 |
| | | | between game sessions. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|: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.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.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.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.camera <Package openmw.camera>` | by player scripts | | Controls camera. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.postprocessing <Package openmw.postprocessing>`| by player scripts | | Controls post-process shaders. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
**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.
Player scripts are local scripts that are attached to a player.
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
| Package | Can be used | Description |
+=========================================================+====================+===============================================================+
|: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, |
| | | | 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 |
| | | | between game sessions. |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|: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.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.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.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.camera <Package openmw.camera>` | by player scripts | | Controls camera |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
| Package | Can be used | Description |
+============================================================+====================+===============================================================+
|: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, |
| | | | 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 |
| | | | between game sessions. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|: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.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.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.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.camera <Package openmw.camera>` | by player scripts | | Controls camera |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.postprocessing <Package openmw.postprocessing>`| by player scripts | | Controls postprocess shaders |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
openmw_aux
----------

@ -71,3 +71,4 @@ The ranges included with each setting are the physically possible ranges, not re
navigator
physics
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.

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

Loading…
Cancel
Save