mirror of https://github.com/OpenMW/openmw.git
moddable post-processing pipeline
parent
e7fb8b6fd8
commit
04843fed6d
@ -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
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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
|
@ -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
@ -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
|
@ -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"));
|
||||
}
|
||||
}
|
@ -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());
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,301 @@
|
||||
#include "lexer.hpp"
|
||||
|
||||
#include <string_view>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <optional>
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <exception>
|
||||
|
||||
#include <components/misc/stringops.hpp>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include "types.hpp"
|
||||
|
||||
namespace fx
|
||||
{
|
||||
namespace Lexer
|
||||
{
|
||||
Lexer::Lexer(std::string_view buffer)
|
||||
: mHead(buffer.data())
|
||||
, mTail(mHead + buffer.length())
|
||||
, mAbsolutePos(0)
|
||||
, mColumn(0)
|
||||
, mLine(0)
|
||||
, mBuffer(buffer)
|
||||
, mLastToken(Eof{})
|
||||
{ }
|
||||
|
||||
Token Lexer::next()
|
||||
{
|
||||
if (mLookahead)
|
||||
{
|
||||
auto token = *mLookahead;
|
||||
drop();
|
||||
return token;
|
||||
}
|
||||
|
||||
mLastToken = scanToken();
|
||||
|
||||
return mLastToken;
|
||||
}
|
||||
|
||||
Token Lexer::peek()
|
||||
{
|
||||
if (!mLookahead)
|
||||
mLookahead = scanToken();
|
||||
|
||||
return *mLookahead;
|
||||
}
|
||||
|
||||
void Lexer::drop()
|
||||
{
|
||||
mLookahead = std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string_view> Lexer::jump()
|
||||
{
|
||||
bool multi = false;
|
||||
bool single = false;
|
||||
auto start = mHead;
|
||||
std::size_t level = 1;
|
||||
|
||||
mLastJumpBlock.line = mLine;
|
||||
|
||||
if (head() == '}')
|
||||
{
|
||||
mLastJumpBlock.content = {};
|
||||
return mLastJumpBlock.content;
|
||||
}
|
||||
|
||||
for (; mHead != mTail; advance())
|
||||
{
|
||||
if (head() == '\n')
|
||||
{
|
||||
mLine++;
|
||||
mColumn = 0;
|
||||
if (single)
|
||||
{
|
||||
single = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (multi && head() == '*' && peekChar('/'))
|
||||
{
|
||||
multi = false;
|
||||
advance();
|
||||
continue;
|
||||
}
|
||||
else if (multi || single)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (head() == '/' && peekChar('/'))
|
||||
{
|
||||
single = true;
|
||||
advance();
|
||||
continue;
|
||||
}
|
||||
else if (head() == '/' && peekChar('*'))
|
||||
{
|
||||
multi = true;
|
||||
advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (head() == '{')
|
||||
level++;
|
||||
else if (head() == '}')
|
||||
level--;
|
||||
|
||||
if (level == 0)
|
||||
{
|
||||
mHead--;
|
||||
auto sv = std::string_view{start, static_cast<std::string_view::size_type>(mHead + 1 - start)};
|
||||
mLastJumpBlock.content = sv;
|
||||
return sv;
|
||||
}
|
||||
}
|
||||
|
||||
mLastJumpBlock = {};
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Lexer::Block Lexer::getLastJumpBlock() const
|
||||
{
|
||||
return mLastJumpBlock;
|
||||
}
|
||||
|
||||
[[noreturn]] void Lexer::error(const std::string& msg)
|
||||
{
|
||||
throw LexerException(Misc::StringUtils::format("Line %zu Col %zu. %s", mLine + 1, mColumn, msg));
|
||||
}
|
||||
|
||||
void Lexer::advance()
|
||||
{
|
||||
mAbsolutePos++;
|
||||
mHead++;
|
||||
mColumn++;
|
||||
}
|
||||
|
||||
char Lexer::head()
|
||||
{
|
||||
return *mHead;
|
||||
}
|
||||
|
||||
bool Lexer::peekChar(char c)
|
||||
{
|
||||
if (mHead == mTail)
|
||||
return false;
|
||||
return *(mHead + 1) == c;
|
||||
}
|
||||
|
||||
Token Lexer::scanToken()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (mHead == mTail)
|
||||
return {Eof{}};
|
||||
|
||||
if (head() == '\n')
|
||||
{
|
||||
mLine++;
|
||||
mColumn = 0;
|
||||
}
|
||||
|
||||
if (!std::isspace(head()))
|
||||
break;
|
||||
|
||||
advance();
|
||||
}
|
||||
|
||||
if (head() == '\"')
|
||||
return scanStringLiteral();
|
||||
|
||||
if (std::isalpha(head()))
|
||||
return scanLiteral();
|
||||
|
||||
if (std::isdigit(head()) || head() == '.' || head() == '-')
|
||||
return scanNumber();
|
||||
|
||||
switch(head())
|
||||
{
|
||||
case '=':
|
||||
advance();
|
||||
return {Equal{}};
|
||||
case '{':
|
||||
advance();
|
||||
return {Open_bracket{}};
|
||||
case '}':
|
||||
advance();
|
||||
return {Close_bracket{}};
|
||||
case '(':
|
||||
advance();
|
||||
return {Open_Parenthesis{}};
|
||||
case ')':
|
||||
advance();
|
||||
return {Close_Parenthesis{}};
|
||||
case '\"':
|
||||
advance();
|
||||
return {Quote{}};
|
||||
case ':':
|
||||
advance();
|
||||
return {Colon{}};
|
||||
case ';':
|
||||
advance();
|
||||
return {SemiColon{}};
|
||||
case '|':
|
||||
advance();
|
||||
return {VBar{}};
|
||||
case ',':
|
||||
advance();
|
||||
return {Comma{}};
|
||||
default:
|
||||
error(Misc::StringUtils::format("unexpected token <%c>", head()));
|
||||
}
|
||||
}
|
||||
|
||||
Token Lexer::scanLiteral()
|
||||
{
|
||||
auto start = mHead;
|
||||
advance();
|
||||
|
||||
while (mHead != mTail && (std::isalnum(head()) || head() == '_'))
|
||||
advance();
|
||||
|
||||
std::string_view value{start, static_cast<std::string_view::size_type>(mHead - start)};
|
||||
|
||||
if (value == "shared") return Shared{};
|
||||
if (value == "technique") return Technique{};
|
||||
if (value == "main_pass") return Main_Pass{};
|
||||
if (value == "render_target") return Render_Target{};
|
||||
if (value == "vertex") return Vertex{};
|
||||
if (value == "fragment") return Fragment{};
|
||||
if (value == "compute") return Compute{};
|
||||
if (value == "sampler_1d") return Sampler_1D{};
|
||||
if (value == "sampler_2d") return Sampler_2D{};
|
||||
if (value == "sampler_3d") return Sampler_3D{};
|
||||
if (value == "uniform_bool") return Uniform_Bool{};
|
||||
if (value == "uniform_float") return Uniform_Float{};
|
||||
if (value == "uniform_int") return Uniform_Int{};
|
||||
if (value == "uniform_vec2") return Uniform_Vec2{};
|
||||
if (value == "uniform_vec3") return Uniform_Vec3{};
|
||||
if (value == "uniform_vec4") return Uniform_Vec4{};
|
||||
if (value == "true") return True{};
|
||||
if (value == "false") return False{};
|
||||
if (value == "vec2") return Vec2{};
|
||||
if (value == "vec3") return Vec3{};
|
||||
if (value == "vec4") return Vec4{};
|
||||
|
||||
return Literal{value};
|
||||
}
|
||||
|
||||
Token Lexer::scanStringLiteral()
|
||||
{
|
||||
advance(); // consume quote
|
||||
auto start = mHead;
|
||||
|
||||
bool terminated = false;
|
||||
|
||||
for (; mHead != mTail; advance())
|
||||
{
|
||||
if (head() == '\"')
|
||||
{
|
||||
terminated = true;
|
||||
advance();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!terminated)
|
||||
error("unterminated string");
|
||||
|
||||
return String{{start, static_cast<std::string_view::size_type>(mHead - start - 1)}};
|
||||
}
|
||||
|
||||
Token Lexer::scanNumber()
|
||||
{
|
||||
double buffer;
|
||||
|
||||
char* endPtr;
|
||||
buffer = std::strtod(mHead, &endPtr);
|
||||
|
||||
if (endPtr == nullptr)
|
||||
error("critical error while parsing number");
|
||||
|
||||
const char* tmp = mHead;
|
||||
mHead = endPtr;
|
||||
|
||||
for (; tmp != endPtr; ++tmp)
|
||||
{
|
||||
if ((*tmp == '.'))
|
||||
return Float{static_cast<float>(buffer)};
|
||||
}
|
||||
|
||||
return Integer{static_cast<int>(buffer)};
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
#ifndef OPENMW_COMPONENTS_FX_LEXER_H
|
||||
#define OPENMW_COMPONENTS_FX_LEXER_H
|
||||
|
||||
#include <string_view>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <optional>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <osg/Vec2f>
|
||||
#include <osg/Vec3f>
|
||||
#include <osg/Vec4f>
|
||||
|
||||
#include "lexer_types.hpp"
|
||||
|
||||
namespace fx
|
||||
{
|
||||
namespace Lexer
|
||||
{
|
||||
struct LexerException : std::runtime_error
|
||||
{
|
||||
LexerException(const std::string& message) : std::runtime_error(message) {}
|
||||
LexerException(const char* message) : std::runtime_error(message) {}
|
||||
};
|
||||
|
||||
class Lexer
|
||||
{
|
||||
public:
|
||||
struct Block
|
||||
{
|
||||
int line;
|
||||
std::string_view content;
|
||||
};
|
||||
|
||||
Lexer(std::string_view buffer);
|
||||
Lexer() = delete;
|
||||
|
||||
Token next();
|
||||
Token peek();
|
||||
|
||||
// Jump ahead to next uncommented closing bracket at level zero. Assumes the head is at an opening bracket.
|
||||
// Returns the contents of the block excluding the brackets and places cursor at closing bracket.
|
||||
std::optional<std::string_view> jump();
|
||||
|
||||
Block getLastJumpBlock() const;
|
||||
|
||||
[[noreturn]] void error(const std::string& msg);
|
||||
|
||||
private:
|
||||
void drop();
|
||||
void advance();
|
||||
char head();
|
||||
bool peekChar(char c);
|
||||
|
||||
Token scanToken();
|
||||
Token scanLiteral();
|
||||
Token scanStringLiteral();
|
||||
Token scanNumber();
|
||||
|
||||
const char* mHead;
|
||||
const char* mTail;
|
||||
std::size_t mAbsolutePos;
|
||||
std::size_t mColumn;
|
||||
std::size_t mLine;
|
||||
std::string_view mBuffer;
|
||||
Token mLastToken;
|
||||
std::optional<Token> mLookahead;
|
||||
|
||||
Block mLastJumpBlock;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,56 @@
|
||||
#ifndef OPENMW_COMPONENTS_FX_LEXER_TYPES_H
|
||||
#define OPENMW_COMPONENTS_FX_LEXER_TYPES_H
|
||||
|
||||
#include <variant>
|
||||
#include <string_view>
|
||||
|
||||
namespace fx
|
||||
{
|
||||
namespace Lexer
|
||||
{
|
||||
struct Float { inline static constexpr std::string_view repr = "float"; float value = 0.0;};
|
||||
struct Integer { inline static constexpr std::string_view repr = "integer"; int value = 0;};
|
||||
struct Boolean { inline static constexpr std::string_view repr = "boolean"; bool value = false;};
|
||||
struct Literal { inline static constexpr std::string_view repr = "literal"; std::string_view value;};
|
||||
struct String { inline static constexpr std::string_view repr = "string"; std::string_view value;};
|
||||
struct Shared { inline static constexpr std::string_view repr = "shared"; };
|
||||
struct Vertex { inline static constexpr std::string_view repr = "vertex"; };
|
||||
struct Fragment { inline static constexpr std::string_view repr = "fragment"; };
|
||||
struct Compute { inline static constexpr std::string_view repr = "compute"; };
|
||||
struct Technique { inline static constexpr std::string_view repr = "technique"; };
|
||||
struct Main_Pass { inline static constexpr std::string_view repr = "main_pass"; };
|
||||
struct Render_Target { inline static constexpr std::string_view repr = "render_target"; };
|
||||
struct Sampler_1D { inline static constexpr std::string_view repr = "sampler_1d"; };
|
||||
struct Sampler_2D { inline static constexpr std::string_view repr = "sampler_2d"; };
|
||||
struct Sampler_3D { inline static constexpr std::string_view repr = "sampler_3d"; };
|
||||
struct Uniform_Bool { inline static constexpr std::string_view repr = "uniform_bool"; };
|
||||
struct Uniform_Float { inline static constexpr std::string_view repr = "uniform_float"; };
|
||||
struct Uniform_Int { inline static constexpr std::string_view repr = "uniform_int"; };
|
||||
struct Uniform_Vec2 { inline static constexpr std::string_view repr = "uniform_vec2"; };
|
||||
struct Uniform_Vec3 { inline static constexpr std::string_view repr = "uniform_vec3"; };
|
||||
struct Uniform_Vec4 { inline static constexpr std::string_view repr = "uniform_vec4"; };
|
||||
struct Eof { inline static constexpr std::string_view repr = "eof"; };
|
||||
struct Equal { inline static constexpr std::string_view repr = "equal"; };
|
||||
struct Open_bracket { inline static constexpr std::string_view repr = "open_bracket"; };
|
||||
struct Close_bracket { inline static constexpr std::string_view repr = "close_bracket"; };
|
||||
struct Open_Parenthesis { inline static constexpr std::string_view repr = "open_parenthesis"; };
|
||||
struct Close_Parenthesis{ inline static constexpr std::string_view repr = "close_parenthesis"; };
|
||||
struct Quote { inline static constexpr std::string_view repr = "quote"; };
|
||||
struct SemiColon { inline static constexpr std::string_view repr = "semicolon"; };
|
||||
struct Comma { inline static constexpr std::string_view repr = "comma"; };
|
||||
struct VBar { inline static constexpr std::string_view repr = "vbar"; };
|
||||
struct Colon { inline static constexpr std::string_view repr = "colon"; };
|
||||
struct True { inline static constexpr std::string_view repr = "true"; };
|
||||
struct False { inline static constexpr std::string_view repr = "false"; };
|
||||
struct Vec2 { inline static constexpr std::string_view repr = "vec2"; };
|
||||
struct Vec3 { inline static constexpr std::string_view repr = "vec3"; };
|
||||
struct Vec4 { inline static constexpr std::string_view repr = "vec4"; };
|
||||
|
||||
using Token = std::variant<Float, Integer, Boolean, String, Literal, Equal, Open_bracket, Close_bracket, Open_Parenthesis,
|
||||
Close_Parenthesis, Quote, SemiColon, Comma, VBar, Colon, Shared, Technique, Render_Target, Vertex, Fragment,
|
||||
Compute, Sampler_1D, Sampler_2D, Sampler_3D, Uniform_Bool, Uniform_Float, Uniform_Int, Uniform_Vec2, Uniform_Vec3, Uniform_Vec4,
|
||||
True, False, Vec2, Vec3, Vec4, Main_Pass, Eof>;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,133 @@
|
||||
#ifndef OPENMW_COMPONENTS_FX_PARSE_CONSTANTS_H
|
||||
#define OPENMW_COMPONENTS_FX_PARSE_CONSTANTS_H
|
||||
|
||||
#include <array>
|
||||
#include <string_view>
|
||||
|
||||
#include <osg/Texture>
|
||||
#include <osg/Image>
|
||||
#include <osg/BlendFunc>
|
||||
#include <osg/BlendEquation>
|
||||
|
||||
#include <components/sceneutil/color.hpp>
|
||||
|
||||
#include "technique.hpp"
|
||||
|
||||
namespace fx
|
||||
{
|
||||
namespace constants
|
||||
{
|
||||
constexpr std::array<std::pair<std::string_view, fx::FlagsType>, 6> TechniqueFlag = {{
|
||||
{"disable_interiors" , Technique::Flag_Disable_Interiors},
|
||||
{"disable_exteriors" , Technique::Flag_Disable_Exteriors},
|
||||
{"disable_underwater" , Technique::Flag_Disable_Underwater},
|
||||
{"disable_abovewater" , Technique::Flag_Disable_Abovewater},
|
||||
{"disable_sunglare" , Technique::Flag_Disable_SunGlare},
|
||||
{"hidden" , Technique::Flag_Hidden}
|
||||
}};
|
||||
|
||||
constexpr std::array<std::pair<std::string_view, int>, 6> SourceFormat = {{
|
||||
{"red" , GL_RED},
|
||||
{"rg" , GL_RG},
|
||||
{"rgb" , GL_RGB},
|
||||
{"bgr" , GL_BGR},
|
||||
{"rgba", GL_RGBA},
|
||||
{"bgra", GL_BGRA},
|
||||
}};
|
||||
|
||||
constexpr std::array<std::pair<std::string_view, int>, 9> SourceType = {{
|
||||
{"byte" , GL_BYTE},
|
||||
{"unsigned_byte" , GL_UNSIGNED_BYTE},
|
||||
{"short" , GL_SHORT},
|
||||
{"unsigned_short" , GL_UNSIGNED_SHORT},
|
||||
{"int" , GL_INT},
|
||||
{"unsigned_int" , GL_UNSIGNED_INT},
|
||||
{"unsigned_int_24_8", GL_UNSIGNED_INT_24_8},
|
||||
{"float" , GL_FLOAT},
|
||||
{"double" , GL_DOUBLE},
|
||||
}};
|
||||
|
||||
constexpr std::array<std::pair<std::string_view, int>, 16> InternalFormat = {{
|
||||
{"red" , GL_RED},
|
||||
{"r16f" , GL_R16F},
|
||||
{"r32f" , GL_R32F},
|
||||
{"rg" , GL_RG},
|
||||
{"rg16f" , GL_RG16F},
|
||||
{"rg32f" , GL_RG32F},
|
||||
{"rgb" , GL_RGB},
|
||||
{"rgb16f" , GL_RGB16F},
|
||||
{"rgb32f" , GL_RGB32F},
|
||||
{"rgba" , GL_RGBA},
|
||||
{"rgba16f" , GL_RGBA16F},
|
||||
{"rgba32f" , GL_RGBA32F},
|
||||
{"depth_component16" , GL_DEPTH_COMPONENT16},
|
||||
{"depth_component24" , GL_DEPTH_COMPONENT24},
|
||||
{"depth_component32" , GL_DEPTH_COMPONENT32},
|
||||
{"depth_component32f", GL_DEPTH_COMPONENT32F}
|
||||
}};
|
||||
|
||||
constexpr std::array<std::pair<std::string_view, osg::Texture::InternalFormatMode>, 13> Compression = {{
|
||||
{"auto" , osg::Texture::USE_USER_DEFINED_FORMAT},
|
||||
{"arb" , osg::Texture::USE_ARB_COMPRESSION},
|
||||
{"s3tc_dxt1" , osg::Texture::USE_S3TC_DXT1_COMPRESSION},
|
||||
{"s3tc_dxt3" , osg::Texture::USE_S3TC_DXT3_COMPRESSION},
|
||||
{"s3tc_dxt5" , osg::Texture::USE_S3TC_DXT5_COMPRESSION},
|
||||
{"pvrtc_2bpp" , osg::Texture::USE_PVRTC_2BPP_COMPRESSION},
|
||||
{"pvrtc_4bpp" , osg::Texture::USE_PVRTC_4BPP_COMPRESSION},
|
||||
{"etc" , osg::Texture::USE_ETC_COMPRESSION},
|
||||
{"etc2" , osg::Texture::USE_ETC2_COMPRESSION},
|
||||
{"rgtc1" , osg::Texture::USE_RGTC1_COMPRESSION},
|
||||
{"rgtc2" , osg::Texture::USE_RGTC2_COMPRESSION},
|
||||
{"s3tc_dxt1c" , osg::Texture::USE_S3TC_DXT1c_COMPRESSION},
|
||||
{"s3tc_dxt1a" , osg::Texture::USE_S3TC_DXT1a_COMPRESSION}
|
||||
}};
|
||||
|
||||
constexpr std::array<std::pair<std::string_view, osg::Texture::WrapMode>, 6> WrapMode = {{
|
||||
{"clamp" , osg::Texture::CLAMP},
|
||||
{"clamp_to_edge" , osg::Texture::CLAMP_TO_EDGE},
|
||||
{"clamp_to_border", osg::Texture::CLAMP_TO_BORDER},
|
||||
{"repeat" , osg::Texture::REPEAT},
|
||||
{"mirror" , osg::Texture::MIRROR}
|
||||
}};
|
||||
|
||||
constexpr std::array<std::pair<std::string_view, osg::Texture::FilterMode>, 6> FilterMode = {{
|
||||
{"linear" , osg::Texture::LINEAR},
|
||||
{"linear_mipmap_linear" , osg::Texture::LINEAR_MIPMAP_LINEAR},
|
||||
{"linear_mipmap_nearest" , osg::Texture::LINEAR_MIPMAP_NEAREST},
|
||||
{"nearest" , osg::Texture::NEAREST},
|
||||
{"nearest_mipmap_linear" , osg::Texture::NEAREST_MIPMAP_LINEAR},
|
||||
{"nearest_mipmap_nearest", osg::Texture::NEAREST_MIPMAP_NEAREST}
|
||||
}};
|
||||
|
||||
constexpr std::array<std::pair<std::string_view, osg::BlendFunc::BlendFuncMode>, 15> BlendFunc = {{
|
||||
{"dst_alpha" , osg::BlendFunc::DST_ALPHA},
|
||||
{"dst_color" , osg::BlendFunc::DST_COLOR},
|
||||
{"one" , osg::BlendFunc::ONE},
|
||||
{"one_minus_dst_alpha" , osg::BlendFunc::ONE_MINUS_DST_ALPHA},
|
||||
{"one_minus_dst_color" , osg::BlendFunc::ONE_MINUS_DST_COLOR},
|
||||
{"one_minus_src_alpha" , osg::BlendFunc::ONE_MINUS_SRC_ALPHA},
|
||||
{"one_minus_src_color" , osg::BlendFunc::ONE_MINUS_SRC_COLOR},
|
||||
{"src_alpha" , osg::BlendFunc::SRC_ALPHA},
|
||||
{"src_alpha_saturate" , osg::BlendFunc::SRC_ALPHA_SATURATE},
|
||||
{"src_color" , osg::BlendFunc::SRC_COLOR},
|
||||
{"constant_color" , osg::BlendFunc::CONSTANT_COLOR},
|
||||
{"one_minus_constant_color" , osg::BlendFunc::ONE_MINUS_CONSTANT_COLOR},
|
||||
{"constant_alpha" , osg::BlendFunc::CONSTANT_ALPHA},
|
||||
{"one_minus_constant_alpha" , osg::BlendFunc::ONE_MINUS_CONSTANT_ALPHA},
|
||||
{"zero" , osg::BlendFunc::ZERO}
|
||||
}};
|
||||
|
||||
constexpr std::array<std::pair<std::string_view, osg::BlendEquation::Equation>, 8> BlendEquation = {{
|
||||
{"rgba_min" , osg::BlendEquation::RGBA_MIN},
|
||||
{"rgba_max" , osg::BlendEquation::RGBA_MAX},
|
||||
{"alpha_min" , osg::BlendEquation::ALPHA_MIN},
|
||||
{"alpha_max" , osg::BlendEquation::ALPHA_MAX},
|
||||
{"logic_op" , osg::BlendEquation::LOGIC_OP},
|
||||
{"add" , osg::BlendEquation::FUNC_ADD},
|
||||
{"subtract" , osg::BlendEquation::FUNC_SUBTRACT},
|
||||
{"reverse_subtract" , osg::BlendEquation::FUNC_REVERSE_SUBTRACT}
|
||||
}};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,253 @@
|
||||
#include "pass.hpp"
|
||||
|
||||
#include <unordered_set>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
#include <osg/Program>
|
||||
#include <osg/Shader>
|
||||
#include <osg/State>
|
||||
#include <osg/StateSet>
|
||||
#include <osg/BindImageTexture>
|
||||
#include <osg/FrameBufferObject>
|
||||
|
||||
#include <components/misc/stringops.hpp>
|
||||
#include <components/sceneutil/util.hpp>
|
||||
#include <components/sceneutil/clearcolor.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
#include <components/stereo/multiview.hpp>
|
||||
|
||||
#include "technique.hpp"
|
||||
#include "stateupdater.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr char s_DefaultVertex[] = R"GLSL(
|
||||
#if OMW_USE_BINDINGS
|
||||
omw_In vec2 omw_Vertex;
|
||||
#endif
|
||||
omw_Out vec2 omw_TexCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
omw_Position = vec4(omw_Vertex.xy, 0.0, 1.0);
|
||||
omw_TexCoord = omw_Position.xy * 0.5 + 0.5;
|
||||
})GLSL";
|
||||
|
||||
}
|
||||
|
||||
namespace fx
|
||||
{
|
||||
Pass::Pass(Pass::Type type, Pass::Order order, bool ubo)
|
||||
: mCompiled(false)
|
||||
, mType(type)
|
||||
, mOrder(order)
|
||||
, mLegacyGLSL(true)
|
||||
, mUBO(ubo)
|
||||
{
|
||||
}
|
||||
|
||||
std::string Pass::getPassHeader(Technique& technique, std::string_view preamble, bool fragOut)
|
||||
{
|
||||
std::string header = R"GLSL(
|
||||
#version @version @profile
|
||||
@extensions
|
||||
|
||||
@uboStruct
|
||||
|
||||
#define OMW_REVERSE_Z @reverseZ
|
||||
#define OMW_RADIAL_FOG @radialFog
|
||||
#define OMW_HDR @hdr
|
||||
#define OMW_NORMALS @normals
|
||||
#define OMW_USE_BINDINGS @useBindings
|
||||
#define OMW_MULTIVIEW @multiview
|
||||
#define omw_In @in
|
||||
#define omw_Out @out
|
||||
#define omw_Position @position
|
||||
#define omw_Texture1D @texture1D
|
||||
#define omw_Texture2D @texture2D
|
||||
#define omw_Texture3D @texture3D
|
||||
#define omw_Vertex @vertex
|
||||
#define omw_FragColor @fragColor
|
||||
|
||||
@fragBinding
|
||||
|
||||
uniform @builtinSampler omw_SamplerLastShader;
|
||||
uniform @builtinSampler omw_SamplerLastPass;
|
||||
uniform @builtinSampler omw_SamplerDepth;
|
||||
uniform @builtinSampler omw_SamplerNormals;
|
||||
|
||||
#if @ubo
|
||||
layout(std140) uniform _data { _omw_data omw; };
|
||||
#else
|
||||
uniform _omw_data omw;
|
||||
#endif
|
||||
|
||||
float omw_GetDepth(vec2 uv)
|
||||
{
|
||||
#if OMW_MULTIVIEW
|
||||
float depth = omw_Texture2D(omw_SamplerDepth, vec3(uv, gl_ViewID_OVR)).r;
|
||||
#else
|
||||
float depth = omw_Texture2D(omw_SamplerDepth, uv).r;
|
||||
#endif
|
||||
#if OMW_REVERSE_Z
|
||||
return 1.0 - depth;
|
||||
#else
|
||||
return depth;
|
||||
#endif
|
||||
}
|
||||
|
||||
vec4 omw_GetLastShader(vec2 uv)
|
||||
{
|
||||
#if OMW_MULTIVIEW
|
||||
return omw_Texture2D(omw_SamplerLastShader, vec3(uv, gl_ViewID_OVR));
|
||||
#else
|
||||
return omw_Texture2D(omw_SamplerLastShader, uv);
|
||||
#endif
|
||||
}
|
||||
|
||||
vec4 omw_GetLastPass(vec2 uv)
|
||||
{
|
||||
#if OMW_MULTIVIEW
|
||||
return omw_Texture2D(omw_SamplerLastPass, vec3(uv, gl_ViewID_OVR));
|
||||
#else
|
||||
return omw_Texture2D(omw_SamplerLastPass, uv);
|
||||
#endif
|
||||
}
|
||||
|
||||
vec3 omw_GetNormals(vec2 uv)
|
||||
{
|
||||
#if OMW_MULTIVIEW
|
||||
return omw_Texture2D(omw_SamplerNormals, vec3(uv, gl_ViewID_OVR)).rgb * 2.0 - 1.0;
|
||||
#else
|
||||
return omw_Texture2D(omw_SamplerNormals, uv).rgb * 2.0 - 1.0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if OMW_HDR
|
||||
uniform sampler2D omw_EyeAdaptation;
|
||||
#endif
|
||||
|
||||
float omw_GetEyeAdaptation()
|
||||
{
|
||||
#if OMW_HDR
|
||||
return omw_Texture2D(omw_EyeAdaptation, vec2(0.5, 0.5)).r;
|
||||
#else
|
||||
return 1.0;
|
||||
#endif
|
||||
}
|
||||
)GLSL";
|
||||
|
||||
std::stringstream extBlock;
|
||||
for (const auto& extension : technique.getGLSLExtensions())
|
||||
extBlock << "#ifdef " << extension << '\n' << "\t#extension " << extension << ": enable" << '\n' << "#endif" << '\n';
|
||||
|
||||
const std::vector<std::pair<std::string,std::string>> defines = {
|
||||
{"@version", std::to_string(technique.getGLSLVersion())},
|
||||
{"@multiview", Stereo::getMultiview() ? "1" : "0"},
|
||||
{"@builtinSampler", Stereo::getMultiview() ? "sampler2DArray" : "sampler2D"},
|
||||
{"@profile", technique.getGLSLProfile()},
|
||||
{"@extensions", extBlock.str()},
|
||||
{"@uboStruct", StateUpdater::getStructDefinition()},
|
||||
{"@ubo", mUBO ? "1" : "0"},
|
||||
{"@normals", technique.getNormals() ? "1" : "0"},
|
||||
{"@reverseZ", SceneUtil::AutoDepth::isReversed() ? "1" : "0"},
|
||||
{"@radialFog", Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"},
|
||||
{"@hdr", technique.getHDR() ? "1" : "0"},
|
||||
{"@in", mLegacyGLSL ? "varying" : "in"},
|
||||
{"@out", mLegacyGLSL ? "varying" : "out"},
|
||||
{"@position", "gl_Position"},
|
||||
{"@texture1D", mLegacyGLSL ? "texture1D" : "texture"},
|
||||
{"@texture2D", mLegacyGLSL ? "texture2D" : "texture"},
|
||||
{"@texture3D", mLegacyGLSL ? "texture3D" : "texture"},
|
||||
{"@vertex", mLegacyGLSL ? "gl_Vertex" : "_omw_Vertex"},
|
||||
{"@fragColor", mLegacyGLSL ? "gl_FragColor" : "_omw_FragColor"},
|
||||
{"@useBindings", mLegacyGLSL ? "0" : "1"},
|
||||
{"@fragBinding", mLegacyGLSL ? "" : "out vec4 omw_FragColor;"}
|
||||
};
|
||||
|
||||
for (const auto& [define, value]: defines)
|
||||
for (size_t pos = header.find(define); pos != std::string::npos; pos = header.find(define))
|
||||
header.replace(pos, define.size(), value);
|
||||
|
||||
for (auto& uniform : technique.getUniformMap())
|
||||
if (auto glsl = uniform->getGLSL())
|
||||
header.append(glsl.value());
|
||||
|
||||
header.append(preamble);
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
void Pass::prepareStateSet(osg::StateSet* stateSet, const std::string& name) const
|
||||
{
|
||||
osg::ref_ptr<osg::Program> program = new osg::Program;
|
||||
if (mType == Type::Pixel)
|
||||
{
|
||||
program->addShader(new osg::Shader(*mVertex));
|
||||
program->addShader(new osg::Shader(*mFragment));
|
||||
}
|
||||
else if (mType == Type::Compute)
|
||||
{
|
||||
program->addShader(new osg::Shader(*mCompute));
|
||||
}
|
||||
|
||||
if (mUBO)
|
||||
program->addBindUniformBlock("_data", static_cast<int>(Resource::SceneManager::UBOBinding::PostProcessor));
|
||||
|
||||
program->setName(name);
|
||||
|
||||
if (!mLegacyGLSL)
|
||||
{
|
||||
program->addBindFragDataLocation("_omw_FragColor", 0);
|
||||
program->addBindAttribLocation("_omw_Vertex", 0);
|
||||
}
|
||||
|
||||
stateSet->setAttribute(program);
|
||||
|
||||
if (mBlendSource && mBlendDest)
|
||||
stateSet->setAttribute(new osg::BlendFunc(mBlendSource.value(), mBlendDest.value()));
|
||||
|
||||
if (mBlendEq)
|
||||
stateSet->setAttribute(new osg::BlendEquation(mBlendEq.value()));
|
||||
|
||||
if (mClearColor)
|
||||
stateSet->setAttribute(new SceneUtil::ClearColor(mClearColor.value(), GL_COLOR_BUFFER_BIT));
|
||||
}
|
||||
|
||||
void Pass::dirty()
|
||||
{
|
||||
mVertex = nullptr;
|
||||
mFragment = nullptr;
|
||||
mCompute = nullptr;
|
||||
mCompiled = false;
|
||||
}
|
||||
|
||||
void Pass::compile(Technique& technique, std::string_view preamble)
|
||||
{
|
||||
if (mCompiled)
|
||||
return;
|
||||
|
||||
mLegacyGLSL = technique.getGLSLVersion() != 330;
|
||||
|
||||
if (mType == Type::Pixel)
|
||||
{
|
||||
if (!mVertex)
|
||||
mVertex = new osg::Shader(osg::Shader::VERTEX, s_DefaultVertex);
|
||||
|
||||
mVertex->setShaderSource(getPassHeader(technique, preamble).append(mVertex->getShaderSource()));
|
||||
mFragment->setShaderSource(getPassHeader(technique, preamble, true).append(mFragment->getShaderSource()));
|
||||
|
||||
mVertex->setName(mName);
|
||||
mFragment->setName(mName);
|
||||
}
|
||||
else if (mType == Type::Compute)
|
||||
{
|
||||
mCompute->setShaderSource(getPassHeader(technique, preamble).append(mCompute->getShaderSource()));
|
||||
mCompute->setName(mName);
|
||||
}
|
||||
|
||||
mCompiled = true;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
#ifndef OPENMW_COMPONENTS_FX_PASS_H
|
||||
#define OPENMW_COMPONENTS_FX_PASS_H
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <cstdint>
|
||||
#include <unordered_set>
|
||||
#include <optional>
|
||||
|
||||
#include <osg/Timer>
|
||||
#include <osg/Program>
|
||||
#include <osg/Shader>
|
||||
#include <osg/State>
|
||||
#include <osg/Texture2D>
|
||||
#include <osg/BlendEquation>
|
||||
#include <osg/BlendFunc>
|
||||
|
||||
namespace fx
|
||||
{
|
||||
class Technique;
|
||||
|
||||
class Pass
|
||||
{
|
||||
public:
|
||||
|
||||
enum class Order
|
||||
{
|
||||
Forward,
|
||||
Post
|
||||
};
|
||||
|
||||
enum class Type
|
||||
{
|
||||
None,
|
||||
Pixel,
|
||||
Compute
|
||||
};
|
||||
|
||||
friend class Technique;
|
||||
|
||||
Pass(Type type=Type::Pixel, Order order=Order::Post, bool ubo = false);
|
||||
|
||||
void compile(Technique& technique, std::string_view preamble);
|
||||
|
||||
std::string_view getTarget() const { return mTarget; }
|
||||
|
||||
void prepareStateSet(osg::StateSet* stateSet, const std::string& name) const;
|
||||
|
||||
std::string getName() const { return mName; }
|
||||
|
||||
void dirty();
|
||||
|
||||
private:
|
||||
std::string getPassHeader(Technique& technique, std::string_view preamble, bool fragOut = false);
|
||||
|
||||
bool mCompiled;
|
||||
|
||||
osg::ref_ptr<osg::Shader> mVertex;
|
||||
osg::ref_ptr<osg::Shader> mFragment;
|
||||
osg::ref_ptr<osg::Shader> mCompute;
|
||||
|
||||
Type mType;
|
||||
Order mOrder;
|
||||
std::string mName;
|
||||
bool mLegacyGLSL;
|
||||
bool mUBO;
|
||||
bool mSupportsNormals;
|
||||
|
||||
std::string_view mTarget;
|
||||
std::optional<osg::Vec4f> mClearColor;
|
||||
|
||||
std::optional<osg::BlendFunc::BlendFuncMode> mBlendSource;
|
||||
std::optional<osg::BlendFunc::BlendFuncMode> mBlendDest;
|
||||
std::optional<osg::BlendEquation::Equation> mBlendEq;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,60 @@
|
||||
#include "stateupdater.hpp"
|
||||
|
||||
#include <osg/BufferObject>
|
||||
#include <osg/BufferIndexBinding>
|
||||
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
namespace fx
|
||||
{
|
||||
StateUpdater::StateUpdater(bool useUBO) : mUseUBO(useUBO) {}
|
||||
|
||||
void StateUpdater::setDefaults(osg::StateSet* stateset)
|
||||
{
|
||||
if (mUseUBO)
|
||||
{
|
||||
osg::ref_ptr<osg::UniformBufferObject> ubo = new osg::UniformBufferObject;
|
||||
|
||||
osg::ref_ptr<osg::BufferTemplate<UniformData::BufferType>> data = new osg::BufferTemplate<UniformData::BufferType>();
|
||||
data->setBufferObject(ubo);
|
||||
|
||||
osg::ref_ptr<osg::UniformBufferBinding> ubb = new osg::UniformBufferBinding(static_cast<int>(Resource::SceneManager::UBOBinding::PostProcessor), data, 0, mData.getGPUSize());
|
||||
|
||||
stateset->setAttributeAndModes(ubb, osg::StateAttribute::ON);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto createUniform = [&] (const auto& v) {
|
||||
using T = std::decay_t<decltype(v)>;
|
||||
std::string name = "omw." + std::string(T::sName);
|
||||
stateset->addUniform(new osg::Uniform(name.c_str(), mData.get<T>()));
|
||||
};
|
||||
|
||||
std::apply([&] (const auto& ... v) { (createUniform(v) , ...); }, mData.getData());
|
||||
}
|
||||
}
|
||||
|
||||
void StateUpdater::apply(osg::StateSet* stateset, osg::NodeVisitor* nv)
|
||||
{
|
||||
if (mUseUBO)
|
||||
{
|
||||
osg::UniformBufferBinding* ubb = dynamic_cast<osg::UniformBufferBinding*>(stateset->getAttribute(osg::StateAttribute::UNIFORMBUFFERBINDING, static_cast<int>(Resource::SceneManager::UBOBinding::PostProcessor)));
|
||||
|
||||
auto& dest = static_cast<osg::BufferTemplate<UniformData::BufferType>*>(ubb->getBufferData())->getData();
|
||||
mData.copyTo(dest);
|
||||
|
||||
ubb->getBufferData()->dirty();
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto setUniform = [&] (const auto& v) {
|
||||
using T = std::decay_t<decltype(v)>;
|
||||
std::string name = "omw." + std::string(T::sName);
|
||||
stateset->getUniform(name)->set(mData.get<T>());
|
||||
};
|
||||
|
||||
std::apply([&] (const auto& ... v) { (setUniform(v) , ...); }, mData.getData());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,192 @@
|
||||
#ifndef OPENMW_COMPONENTS_FX_STATEUPDATER_H
|
||||
#define OPENMW_COMPONENTS_FX_STATEUPDATER_H
|
||||
|
||||
#include <osg/BufferTemplate>
|
||||
|
||||
#include <components/sceneutil/statesetupdater.hpp>
|
||||
#include <components/std140/ubo.hpp>
|
||||
|
||||
namespace fx
|
||||
{
|
||||
class StateUpdater : public SceneUtil::StateSetUpdater
|
||||
{
|
||||
public:
|
||||
StateUpdater(bool useUBO);
|
||||
|
||||
void setProjectionMatrix(const osg::Matrixf& matrix)
|
||||
{
|
||||
mData.get<ProjectionMatrix>() = matrix;
|
||||
mData.get<InvProjectionMatrix>() = osg::Matrixf::inverse(matrix);
|
||||
}
|
||||
|
||||
void setViewMatrix(const osg::Matrixf& matrix) { mData.get<ViewMatrix>() = matrix; }
|
||||
|
||||
void setInvViewMatrix(const osg::Matrixf& matrix) { mData.get<InvViewMatrix>() = matrix; }
|
||||
|
||||
void setPrevViewMatrix(const osg::Matrixf& matrix) { mData.get<PrevViewMatrix>() = matrix;}
|
||||
|
||||
void setEyePos(const osg::Vec3f& pos) { mData.get<EyePos>() = osg::Vec4f(pos, 0.f); }
|
||||
|
||||
void setEyeVec(const osg::Vec3f& vec) { mData.get<EyeVec>() = osg::Vec4f(vec, 0.f); }
|
||||
|
||||
void setFogColor(const osg::Vec4f& color) { mData.get<FogColor>() = color; }
|
||||
|
||||
void setSunColor(const osg::Vec4f& color) { mData.get<SunColor>() = color; }
|
||||
|
||||
void setSunPos(const osg::Vec4f& pos, bool night)
|
||||
{
|
||||
mData.get<SunPos>() = pos;
|
||||
|
||||
if (night)
|
||||
mData.get<SunPos>().z() *= -1.f;
|
||||
}
|
||||
|
||||
void setResolution(const osg::Vec2f& size)
|
||||
{
|
||||
mData.get<Resolution>() = size;
|
||||
mData.get<RcpResolution>() = {1.f / size.x(), 1.f / size.y()};
|
||||
}
|
||||
|
||||
void setSunVis(float vis)
|
||||
{
|
||||
mData.get<SunVis>() = vis;
|
||||
}
|
||||
|
||||
void setFogRange(float near, float far)
|
||||
{
|
||||
mData.get<FogNear>() = near;
|
||||
mData.get<FogFar>() = far;
|
||||
}
|
||||
|
||||
void setNearFar(float near, float far)
|
||||
{
|
||||
mData.get<Near>() = near;
|
||||
mData.get<Far>() = far;
|
||||
}
|
||||
|
||||
void setIsUnderwater(bool underwater) { mData.get<IsUnderwater>() = underwater; }
|
||||
|
||||
void setIsInterior(bool interior) { mData.get<IsInterior>() = interior; }
|
||||
|
||||
void setFov(float fov) { mData.get<Fov>() = fov; }
|
||||
|
||||
void setGameHour(float hour) { mData.get<GameHour>() = hour; }
|
||||
|
||||
void setWeatherId(int id) { mData.get<WeatherID>() = id; }
|
||||
|
||||
void setNextWeatherId(int id) { mData.get<NextWeatherID>() = id; }
|
||||
|
||||
void setWaterHeight(float height) { mData.get<WaterHeight>() = height; }
|
||||
|
||||
void setSimulationTime(float time) { mData.get<SimulationTime>() = time; }
|
||||
|
||||
void setDeltaSimulationTime(float time) { mData.get<DeltaSimulationTime>() = time; }
|
||||
|
||||
void setWindSpeed(float speed) { mData.get<WindSpeed>() = speed; }
|
||||
|
||||
void setWeatherTransition(float transition) { mData.get<WeatherTransition>() = transition; }
|
||||
|
||||
static std::string getStructDefinition()
|
||||
{
|
||||
static std::string definition = UniformData::getDefinition("_omw_data");
|
||||
return definition;
|
||||
}
|
||||
|
||||
private:
|
||||
struct ProjectionMatrix : std140::Mat4 { static constexpr std::string_view sName = "projectionMatrix"; };
|
||||
|
||||
struct InvProjectionMatrix : std140::Mat4 { static constexpr std::string_view sName = "invProjectionMatrix"; };
|
||||
|
||||
struct ViewMatrix : std140::Mat4 { static constexpr std::string_view sName = "viewMatrix"; };
|
||||
|
||||
struct PrevViewMatrix : std140::Mat4 { static constexpr std::string_view sName = "prevViewMatrix"; };
|
||||
|
||||
struct InvViewMatrix : std140::Mat4 { static constexpr std::string_view sName = "invViewMatrix"; };
|
||||
|
||||
struct EyePos : std140::Vec4 { static constexpr std::string_view sName = "eyePos"; };
|
||||
|
||||
struct EyeVec : std140::Vec4 { static constexpr std::string_view sName = "eyeVec"; };
|
||||
|
||||
struct FogColor : std140::Vec4 { static constexpr std::string_view sName = "fogColor"; };
|
||||
|
||||
struct SunColor : std140::Vec4 { static constexpr std::string_view sName = "sunColor"; };
|
||||
|
||||
struct SunPos : std140::Vec4 { static constexpr std::string_view sName = "sunPos"; };
|
||||
|
||||
struct Resolution : std140::Vec2 { static constexpr std::string_view sName = "resolution"; };
|
||||
|
||||
struct RcpResolution : std140::Vec2 { static constexpr std::string_view sName = "rcpResolution"; };
|
||||
|
||||
struct FogNear : std140::Float { static constexpr std::string_view sName = "fogNear"; };
|
||||
|
||||
struct FogFar : std140::Float { static constexpr std::string_view sName = "fogFar"; };
|
||||
|
||||
struct Near : std140::Float { static constexpr std::string_view sName = "near"; };
|
||||
|
||||
struct Far : std140::Float { static constexpr std::string_view sName = "far"; };
|
||||
|
||||
struct Fov : std140::Float { static constexpr std::string_view sName = "fov"; };
|
||||
|
||||
struct GameHour : std140::Float { static constexpr std::string_view sName = "gameHour"; };
|
||||
|
||||
struct SunVis : std140::Float { static constexpr std::string_view sName = "sunVis"; };
|
||||
|
||||
struct WaterHeight : std140::Float { static constexpr std::string_view sName = "waterHeight"; };
|
||||
|
||||
struct SimulationTime : std140::Float { static constexpr std::string_view sName = "simulationTime"; };
|
||||
|
||||
struct DeltaSimulationTime : std140::Float { static constexpr std::string_view sName = "deltaSimulationTime"; };
|
||||
|
||||
struct WindSpeed : std140::Float { static constexpr std::string_view sName = "windSpeed"; };
|
||||
|
||||
struct WeatherTransition : std140::Float { static constexpr std::string_view sName = "weatherTransition"; };
|
||||
|
||||
struct WeatherID : std140::Int { static constexpr std::string_view sName = "weatherID"; };
|
||||
|
||||
struct NextWeatherID : std140::Int { static constexpr std::string_view sName = "nextWeatherID"; };
|
||||
|
||||
struct IsUnderwater : std140::Bool { static constexpr std::string_view sName = "isUnderwater"; };
|
||||
|
||||
struct IsInterior : std140::Bool { static constexpr std::string_view sName = "isInterior"; };
|
||||
|
||||
using UniformData = std140::UBO<
|
||||
ProjectionMatrix,
|
||||
InvProjectionMatrix,
|
||||
ViewMatrix,
|
||||
PrevViewMatrix,
|
||||
InvViewMatrix,
|
||||
EyePos,
|
||||
EyeVec,
|
||||
FogColor,
|
||||
SunColor,
|
||||
SunPos,
|
||||
Resolution,
|
||||
RcpResolution,
|
||||
FogNear,
|
||||
FogFar,
|
||||
Near,
|
||||
Far,
|
||||
Fov,
|
||||
GameHour,
|
||||
SunVis,
|
||||
WaterHeight,
|
||||
SimulationTime,
|
||||
DeltaSimulationTime,
|
||||
WindSpeed,
|
||||
WeatherTransition,
|
||||
WeatherID,
|
||||
NextWeatherID,
|
||||
IsUnderwater,
|
||||
IsInterior
|
||||
>;
|
||||
|
||||
private:
|
||||
void setDefaults(osg::StateSet* stateset) override;
|
||||
void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override;
|
||||
|
||||
UniformData mData;
|
||||
bool mUseUBO;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,300 @@
|
||||
#ifndef OPENMW_COMPONENTS_FX_TECHNIQUE_H
|
||||
#define OPENMW_COMPONENTS_FX_TECHNIQUE_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <filesystem>
|
||||
|
||||
#include <osg/Node>
|
||||
#include <osg/Program>
|
||||
#include <osg/Shader>
|
||||
#include <osg/Texture2D>
|
||||
#include <osg/StateSet>
|
||||
#include <osg/FrameBufferObject>
|
||||
#include <osg/Vec2f>
|
||||
#include <osg/Vec3f>
|
||||
#include <osg/Vec4f>
|
||||
#include <osg/BlendFunc>
|
||||
#include <osg/BlendEquation>
|
||||
|
||||
#include "pass.hpp"
|
||||
#include "lexer.hpp"
|
||||
#include "types.hpp"
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
class ImageManager;
|
||||
}
|
||||
|
||||
namespace VFS
|
||||
{
|
||||
class Manager;
|
||||
}
|
||||
|
||||
namespace fx
|
||||
{
|
||||
using FlagsType = size_t;
|
||||
|
||||
struct DispatchNode
|
||||
{
|
||||
DispatchNode() = default;
|
||||
|
||||
DispatchNode(const DispatchNode& other, const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY)
|
||||
: mHandle(other.mHandle)
|
||||
, mFlags(other.mFlags)
|
||||
, mRootStateSet(other.mRootStateSet)
|
||||
{
|
||||
mPasses.reserve(other.mPasses.size());
|
||||
|
||||
for (const auto& subpass : other.mPasses)
|
||||
mPasses.emplace_back(subpass, copyOp);
|
||||
}
|
||||
|
||||
struct SubPass {
|
||||
SubPass() = default;
|
||||
|
||||
osg::ref_ptr<osg::StateSet> mStateSet = new osg::StateSet;
|
||||
osg::ref_ptr<osg::FrameBufferObject> mRenderTarget;
|
||||
osg::ref_ptr<osg::Texture2D> mRenderTexture;
|
||||
|
||||
SubPass(const SubPass& other, const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY)
|
||||
: mStateSet(new osg::StateSet(*other.mStateSet, copyOp))
|
||||
{
|
||||
if (other.mRenderTarget)
|
||||
mRenderTarget = new osg::FrameBufferObject(*other.mRenderTarget, copyOp);
|
||||
if (other.mRenderTexture)
|
||||
mRenderTexture = new osg::Texture2D(*other.mRenderTexture, copyOp);
|
||||
}
|
||||
};
|
||||
|
||||
// not safe to read/write in draw thread
|
||||
std::shared_ptr<fx::Technique> mHandle = nullptr;
|
||||
|
||||
FlagsType mFlags = 0;
|
||||
|
||||
std::vector<SubPass> mPasses;
|
||||
|
||||
osg::ref_ptr<osg::StateSet> mRootStateSet = new osg::StateSet;
|
||||
};
|
||||
|
||||
using DispatchArray = std::vector<DispatchNode>;
|
||||
|
||||
class Technique
|
||||
{
|
||||
public:
|
||||
using PassList = std::vector<std::shared_ptr<Pass>>;
|
||||
using TexList = std::vector<osg::ref_ptr<osg::Texture>>;
|
||||
|
||||
using UniformMap = std::vector<std::shared_ptr<Types::UniformBase>>;
|
||||
using RenderTargetMap = std::unordered_map<std::string_view, Types::RenderTarget>;
|
||||
|
||||
inline static std::string sExt = ".omwfx";
|
||||
inline static std::string sSubdir = "shaders";
|
||||
|
||||
enum class Status
|
||||
{
|
||||
Success,
|
||||
Uncompiled,
|
||||
File_Not_exists,
|
||||
Parse_Error
|
||||
};
|
||||
|
||||
static constexpr FlagsType Flag_Disable_Interiors = (1 << 0);
|
||||
static constexpr FlagsType Flag_Disable_Exteriors = (1 << 1);
|
||||
static constexpr FlagsType Flag_Disable_Underwater = (1 << 2);
|
||||
static constexpr FlagsType Flag_Disable_Abovewater = (1 << 3);
|
||||
static constexpr FlagsType Flag_Disable_SunGlare = (1 << 4);
|
||||
static constexpr FlagsType Flag_Hidden = (1 << 5);
|
||||
|
||||
Technique(const VFS::Manager& vfs, Resource::ImageManager& imageManager, const std::string& name, int width, int height, bool ubo, bool supportsNormals);
|
||||
|
||||
bool compile();
|
||||
|
||||
std::string getName() const;
|
||||
|
||||
std::string getFileName() const;
|
||||
|
||||
void setLastModificationTime(std::filesystem::file_time_type timeStamp, bool dirty = true);
|
||||
|
||||
bool isDirty() const { return mDirty; }
|
||||
|
||||
void setDirty(bool dirty) { mDirty = dirty; }
|
||||
|
||||
bool isValid() const { return mValid; }
|
||||
|
||||
bool getHDR() const { return mHDR; }
|
||||
|
||||
bool getNormals() const { return mNormals && mSupportsNormals; }
|
||||
|
||||
const PassList& getPasses() { return mPasses; }
|
||||
|
||||
const TexList& getTextures() const { return mTextures; }
|
||||
|
||||
Status getStatus() const { return mStatus; }
|
||||
|
||||
std::string_view getAuthor() const { return mAuthor; }
|
||||
|
||||
std::string_view getDescription() const { return mDescription; }
|
||||
|
||||
std::string_view getVersion() const { return mVersion; }
|
||||
|
||||
int getGLSLVersion() const { return mGLSLVersion; }
|
||||
|
||||
std::string getGLSLProfile() const { return mGLSLProfile; }
|
||||
|
||||
const std::unordered_set<std::string>& getGLSLExtensions() const { return mGLSLExtensions; }
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> getMainTemplate() const { return mMainTemplate; }
|
||||
|
||||
FlagsType getFlags() const { return mFlags; }
|
||||
|
||||
bool getHidden() const { return mFlags & Flag_Hidden; }
|
||||
|
||||
UniformMap& getUniformMap() { return mDefinedUniforms; }
|
||||
|
||||
RenderTargetMap& getRenderTargetsMap() { return mRenderTargets; }
|
||||
|
||||
std::string getLastError() const { return mLastError; }
|
||||
|
||||
UniformMap::iterator findUniform(const std::string& name);
|
||||
|
||||
private:
|
||||
[[noreturn]] void error(const std::string& msg);
|
||||
|
||||
void clear();
|
||||
|
||||
std::string_view asLiteral() const;
|
||||
|
||||
template<class T>
|
||||
void expect(const std::string& err="");
|
||||
|
||||
template<class T, class T2>
|
||||
void expect(const std::string& err="");
|
||||
|
||||
template <class T>
|
||||
bool isNext();
|
||||
|
||||
void parse(std::string&& buffer);
|
||||
|
||||
template <class SrcT, class T>
|
||||
void parseUniform();
|
||||
|
||||
template <class T>
|
||||
void parseSampler();
|
||||
|
||||
template <class T>
|
||||
void parseBlock(bool named=true);
|
||||
|
||||
template <class T>
|
||||
void parseBlockImp() {}
|
||||
|
||||
void parseBlockHeader();
|
||||
|
||||
bool parseBool();
|
||||
|
||||
std::string_view parseString();
|
||||
|
||||
float parseFloat();
|
||||
|
||||
int parseInteger();
|
||||
|
||||
int parseInternalFormat();
|
||||
|
||||
int parseSourceType();
|
||||
|
||||
int parseSourceFormat();
|
||||
|
||||
osg::BlendEquation::Equation parseBlendEquation();
|
||||
|
||||
osg::BlendFunc::BlendFuncMode parseBlendFuncMode();
|
||||
|
||||
osg::Texture::WrapMode parseWrapMode();
|
||||
|
||||
osg::Texture::InternalFormatMode parseCompression();
|
||||
|
||||
FlagsType parseFlags();
|
||||
|
||||
osg::Texture::FilterMode parseFilterMode();
|
||||
|
||||
template <class TDelimeter>
|
||||
std::vector<std::string_view> parseLiteralList();
|
||||
|
||||
template <class OSGVec, class T>
|
||||
OSGVec parseVec();
|
||||
|
||||
std::string getBlockWithLineDirective();
|
||||
|
||||
std::unique_ptr<Lexer::Lexer> mLexer;
|
||||
Lexer::Token mToken;
|
||||
|
||||
std::string mShared;
|
||||
std::string mName;
|
||||
std::string mFileName;
|
||||
std::string_view mBlockName;
|
||||
std::string_view mAuthor;
|
||||
std::string_view mDescription;
|
||||
std::string_view mVersion;
|
||||
|
||||
std::unordered_set<std::string> mGLSLExtensions;
|
||||
int mGLSLVersion;
|
||||
std::string mGLSLProfile;
|
||||
|
||||
FlagsType mFlags;
|
||||
|
||||
Status mStatus;
|
||||
|
||||
bool mEnabled;
|
||||
|
||||
std::filesystem::file_time_type mLastModificationTime;
|
||||
bool mDirty;
|
||||
bool mValid;
|
||||
bool mHDR;
|
||||
bool mNormals;
|
||||
int mWidth;
|
||||
int mHeight;
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> mMainTemplate;
|
||||
RenderTargetMap mRenderTargets;
|
||||
|
||||
TexList mTextures;
|
||||
PassList mPasses;
|
||||
|
||||
std::unordered_map<std::string_view, std::shared_ptr<Pass>> mPassMap;
|
||||
std::vector<std::string_view> mPassKeys;
|
||||
|
||||
Pass::Type mLastAppliedType;
|
||||
|
||||
UniformMap mDefinedUniforms;
|
||||
|
||||
const VFS::Manager& mVFS;
|
||||
Resource::ImageManager& mImageManager;
|
||||
bool mUBO;
|
||||
bool mSupportsNormals;
|
||||
|
||||
std::string mBuffer;
|
||||
|
||||
std::string mLastError;
|
||||
};
|
||||
|
||||
template<> void Technique::parseBlockImp<Lexer::Shared>();
|
||||
template<> void Technique::parseBlockImp<Lexer::Technique>();
|
||||
template<> void Technique::parseBlockImp<Lexer::Main_Pass>();
|
||||
template<> void Technique::parseBlockImp<Lexer::Render_Target>();
|
||||
template<> void Technique::parseBlockImp<Lexer::Vertex>();
|
||||
template<> void Technique::parseBlockImp<Lexer::Fragment>();
|
||||
template<> void Technique::parseBlockImp<Lexer::Compute>();
|
||||
template<> void Technique::parseBlockImp<Lexer::Sampler_1D>();
|
||||
template<> void Technique::parseBlockImp<Lexer::Sampler_2D>();
|
||||
template<> void Technique::parseBlockImp<Lexer::Sampler_3D>();
|
||||
template<> void Technique::parseBlockImp<Lexer::Uniform_Bool>();
|
||||
template<> void Technique::parseBlockImp<Lexer::Uniform_Float>();
|
||||
template<> void Technique::parseBlockImp<Lexer::Uniform_Int>();
|
||||
template<> void Technique::parseBlockImp<Lexer::Uniform_Vec2>();
|
||||
template<> void Technique::parseBlockImp<Lexer::Uniform_Vec3>();
|
||||
template<> void Technique::parseBlockImp<Lexer::Uniform_Vec4>();
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,259 @@
|
||||
#ifndef OPENMW_COMPONENTS_FX_TYPES_H
|
||||
#define OPENMW_COMPONENTS_FX_TYPES_H
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <osg/Camera>
|
||||
#include <osg/Uniform>
|
||||
#include <osg/Texture2D>
|
||||
#include <osg/FrameBufferObject>
|
||||
#include <osg/BlendFunc>
|
||||
#include <osg/BlendEquation>
|
||||
|
||||
#include <MyGUI_Widget.h>
|
||||
|
||||
#include <components/sceneutil/depth.hpp>
|
||||
#include <components/settings/shadermanager.hpp>
|
||||
#include <components/misc/stringops.hpp>
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include "pass.hpp"
|
||||
|
||||
namespace fx
|
||||
{
|
||||
namespace Types
|
||||
{
|
||||
struct SizeProxy
|
||||
{
|
||||
std::optional<float> mWidthRatio;
|
||||
std::optional<float> mHeightRatio;
|
||||
std::optional<int> mWidth;
|
||||
std::optional<int> mHeight;
|
||||
|
||||
std::tuple<int,int> get(int width, int height) const
|
||||
{
|
||||
int scaledWidth = width;
|
||||
int scaledHeight = height;
|
||||
|
||||
if (mWidthRatio)
|
||||
scaledWidth = width * mWidthRatio.value();
|
||||
else if (mWidth)
|
||||
scaledWidth = mWidth.value();
|
||||
|
||||
if (mHeightRatio > 0.f)
|
||||
scaledHeight = height * mHeightRatio.value();
|
||||
else if (mHeight)
|
||||
scaledHeight = mHeight.value();
|
||||
|
||||
return std::make_tuple(scaledWidth, scaledHeight);
|
||||
}
|
||||
};
|
||||
|
||||
struct RenderTarget
|
||||
{
|
||||
osg::ref_ptr<osg::Texture2D> mTarget = new osg::Texture2D;
|
||||
SizeProxy mSize;
|
||||
bool mMipMap = false;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct Uniform
|
||||
{
|
||||
std::optional<T> mValue;
|
||||
T mDefault;
|
||||
T mMin = std::numeric_limits<T>::lowest();
|
||||
T mMax = std::numeric_limits<T>::max();
|
||||
|
||||
using value_type = T;
|
||||
|
||||
T getValue() const
|
||||
{
|
||||
return mValue.value_or(mDefault);
|
||||
}
|
||||
};
|
||||
|
||||
using Uniform_t = std::variant<
|
||||
Uniform<osg::Vec2f>,
|
||||
Uniform<osg::Vec3f>,
|
||||
Uniform<osg::Vec4f>,
|
||||
Uniform<bool>,
|
||||
Uniform<float>,
|
||||
Uniform<int>
|
||||
>;
|
||||
|
||||
enum SamplerType
|
||||
{
|
||||
Texture_1D,
|
||||
Texture_2D,
|
||||
Texture_3D
|
||||
};
|
||||
|
||||
struct UniformBase
|
||||
{
|
||||
std::string mName;
|
||||
std::string mHeader;
|
||||
std::string mTechniqueName;
|
||||
std::string mDescription;
|
||||
|
||||
bool mStatic = true;
|
||||
std::optional<SamplerType> mSamplerType = std::nullopt;
|
||||
double mStep;
|
||||
|
||||
Uniform_t mData;
|
||||
|
||||
template <class T>
|
||||
T getValue() const
|
||||
{
|
||||
auto value = Settings::ShaderManager::get().getValue<T>(mTechniqueName, mName);
|
||||
|
||||
return value.value_or(std::get<Uniform<T>>(mData).getValue());
|
||||
}
|
||||
|
||||
template <class T>
|
||||
T getMin() const
|
||||
{
|
||||
return std::get<Uniform<T>>(mData).mMin;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
T getMax() const
|
||||
{
|
||||
return std::get<Uniform<T>>(mData).mMax;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
T getDefault() const
|
||||
{
|
||||
return std::get<Uniform<T>>(mData).mDefault;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void setValue(const T& value)
|
||||
{
|
||||
std::visit([&, value](auto&& arg){
|
||||
using U = typename std::decay_t<decltype(arg)>::value_type;
|
||||
|
||||
if constexpr (std::is_same_v<T, U>)
|
||||
{
|
||||
arg.mValue = value;
|
||||
|
||||
if (mStatic)
|
||||
Settings::ShaderManager::get().setValue<T>(mTechniqueName, mName, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(Debug::Warning) << "Attempting to set uniform '" << mName << "' with wrong type";
|
||||
}
|
||||
}, mData);
|
||||
}
|
||||
|
||||
void setUniform(osg::Uniform* uniform)
|
||||
{
|
||||
auto type = getType();
|
||||
if (!type || type.value() != uniform->getType())
|
||||
return;
|
||||
|
||||
std::visit([&](auto&& arg)
|
||||
{
|
||||
const auto value = arg.getValue();
|
||||
uniform->set(value);
|
||||
}, mData);
|
||||
}
|
||||
|
||||
std::optional<osg::Uniform::Type> getType() const
|
||||
{
|
||||
return std::visit([](auto&& arg) -> std::optional<osg::Uniform::Type> {
|
||||
using T = typename std::decay_t<decltype(arg)>::value_type;
|
||||
|
||||
if constexpr (std::is_same_v<T, osg::Vec2f>)
|
||||
return osg::Uniform::FLOAT_VEC2;
|
||||
else if constexpr (std::is_same_v<T, osg::Vec3f>)
|
||||
return osg::Uniform::FLOAT_VEC3;
|
||||
else if constexpr (std::is_same_v<T, osg::Vec4f>)
|
||||
return osg::Uniform::FLOAT_VEC4;
|
||||
else if constexpr (std::is_same_v<T, float>)
|
||||
return osg::Uniform::FLOAT;
|
||||
else if constexpr (std::is_same_v<T, int>)
|
||||
return osg::Uniform::INT;
|
||||
else if constexpr (std::is_same_v<T, bool>)
|
||||
return osg::Uniform::BOOL;
|
||||
|
||||
return std::nullopt;
|
||||
}, mData);
|
||||
}
|
||||
|
||||
std::optional<std::string> getGLSL()
|
||||
{
|
||||
if (mSamplerType)
|
||||
{
|
||||
switch (mSamplerType.value())
|
||||
{
|
||||
case Texture_1D:
|
||||
return Misc::StringUtils::format("uniform sampler1D %s;", mName);
|
||||
case Texture_2D:
|
||||
return Misc::StringUtils::format("uniform sampler2D %s;", mName);
|
||||
case Texture_3D:
|
||||
return Misc::StringUtils::format("uniform sampler3D %s;", mName);
|
||||
}
|
||||
}
|
||||
|
||||
bool useUniform = (Settings::ShaderManager::get().getMode() == Settings::ShaderManager::Mode::Debug || mStatic == false);
|
||||
|
||||
return std::visit([&](auto&& arg) -> std::optional<std::string> {
|
||||
using T = typename std::decay_t<decltype(arg)>::value_type;
|
||||
|
||||
auto value = arg.getValue();
|
||||
|
||||
if constexpr (std::is_same_v<T, osg::Vec2f>)
|
||||
{
|
||||
if (useUniform)
|
||||
return Misc::StringUtils::format("uniform vec2 %s;", mName);
|
||||
|
||||
return Misc::StringUtils::format("const vec2 %s=vec2(%f,%f);", mName, value[0], value[1]);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, osg::Vec3f>)
|
||||
{
|
||||
if (useUniform)
|
||||
return Misc::StringUtils::format("uniform vec3 %s;", mName);
|
||||
|
||||
return Misc::StringUtils::format("const vec3 %s=vec3(%f,%f,%f);", mName, value[0], value[1], value[2]);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, osg::Vec4f>)
|
||||
{
|
||||
if (useUniform)
|
||||
return Misc::StringUtils::format("uniform vec4 %s;", mName);
|
||||
|
||||
return Misc::StringUtils::format("const vec4 %s=vec4(%f,%f,%f,%f);", mName, value[0], value[1], value[2], value[3]);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, float>)
|
||||
{
|
||||
if (useUniform)
|
||||
return Misc::StringUtils::format("uniform float %s;", mName);
|
||||
|
||||
return Misc::StringUtils::format("const float %s=%f;", mName, value);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, int>)
|
||||
{
|
||||
if (useUniform)
|
||||
return Misc::StringUtils::format("uniform int %s;", mName);
|
||||
|
||||
return Misc::StringUtils::format("const int %s=%i;", mName, value);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, bool>)
|
||||
{
|
||||
if (useUniform)
|
||||
return Misc::StringUtils::format("uniform bool %s;", mName);
|
||||
|
||||
return Misc::StringUtils::format("const bool %s=%s;", mName, value ? "true" : "false");
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
|
||||
}, mData);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,164 @@
|
||||
#include "widgets.hpp"
|
||||
|
||||
#include <components/widgets/box.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
template <class T, class WidgetT>
|
||||
void createVectorWidget(const std::shared_ptr<fx::Types::UniformBase>& uniform, MyGUI::Widget* client, fx::Widgets::UniformBase* base)
|
||||
{
|
||||
int height = client->getHeight();
|
||||
base->setSize(base->getSize().width, (base->getSize().height - height) + (height * T::num_components));
|
||||
client->setSize(client->getSize().width, height * T::num_components);
|
||||
|
||||
for (int i = 0; i < T::num_components; ++i)
|
||||
{
|
||||
auto* widget = client->createWidget<WidgetT>("MW_ValueEditNumber", {0, height * i, client->getWidth(), height}, MyGUI::Align::Default);
|
||||
widget->setData(uniform, static_cast<fx::Widgets::Index>(i));
|
||||
base->addItem(widget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace fx
|
||||
{
|
||||
namespace Widgets
|
||||
{
|
||||
void EditBool::setValue(bool value)
|
||||
{
|
||||
auto uniform = mUniform.lock();
|
||||
|
||||
if (!uniform)
|
||||
return;
|
||||
|
||||
mCheckbutton->setCaptionWithReplacing(value ? "#{sOn}" : "#{sOff}");
|
||||
|
||||
uniform->setValue<bool>(value);
|
||||
}
|
||||
|
||||
void EditBool::setValueFromUniform()
|
||||
{
|
||||
auto uniform = mUniform.lock();
|
||||
|
||||
if (!uniform)
|
||||
return;
|
||||
|
||||
setValue(uniform->template getValue<bool>());
|
||||
}
|
||||
|
||||
void EditBool::toDefault()
|
||||
{
|
||||
auto uniform = mUniform.lock();
|
||||
|
||||
if (!uniform)
|
||||
return;
|
||||
|
||||
setValue(uniform->getDefault<bool>());
|
||||
}
|
||||
|
||||
void EditBool::initialiseOverride()
|
||||
{
|
||||
Base::initialiseOverride();
|
||||
|
||||
assignWidget(mCheckbutton, "Checkbutton");
|
||||
|
||||
mCheckbutton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditBool::notifyMouseButtonClick);
|
||||
}
|
||||
|
||||
|
||||
void EditBool::notifyMouseButtonClick(MyGUI::Widget* sender)
|
||||
{
|
||||
auto uniform = mUniform.lock();
|
||||
|
||||
if (!uniform)
|
||||
return;
|
||||
|
||||
setValue(!uniform->getValue<bool>());
|
||||
}
|
||||
|
||||
void UniformBase::init(const std::shared_ptr<fx::Types::UniformBase>& uniform)
|
||||
{
|
||||
mLabel->setCaption(uniform->mName);
|
||||
|
||||
if (uniform->mDescription.empty())
|
||||
{
|
||||
mLabel->setUserString("ToolTipType", "");
|
||||
}
|
||||
else
|
||||
{
|
||||
mLabel->setUserString("ToolTipType", "Layout");
|
||||
mLabel->setUserString("ToolTipLayout", "TextToolTip");
|
||||
mLabel->setUserString("Caption_Text", uniform->mDescription);
|
||||
}
|
||||
|
||||
std::visit([this, &uniform](auto&& arg) {
|
||||
using T = typename std::decay_t<decltype(arg)>::value_type;
|
||||
|
||||
if constexpr (std::is_same_v<osg::Vec4f, T>)
|
||||
{
|
||||
createVectorWidget<T, EditNumberFloat4>(uniform, mClient, this);
|
||||
}
|
||||
else if constexpr (std::is_same_v<osg::Vec3f, T>)
|
||||
{
|
||||
createVectorWidget<T, EditNumberFloat3>(uniform, mClient, this);
|
||||
}
|
||||
else if constexpr (std::is_same_v<osg::Vec2f, T>)
|
||||
{
|
||||
createVectorWidget<T, EditNumberFloat2>(uniform, mClient, this);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, float>)
|
||||
{
|
||||
auto* widget = mClient->createWidget<EditNumberFloat>("MW_ValueEditNumber", {0, 0, mClient->getWidth(), mClient->getHeight()}, MyGUI::Align::Stretch);
|
||||
widget->setData(uniform);
|
||||
mBases.emplace_back(widget);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, int>)
|
||||
{
|
||||
auto* widget = mClient->createWidget<EditNumberInt>("MW_ValueEditNumber", {0, 0, mClient->getWidth(), mClient->getHeight()}, MyGUI::Align::Stretch);
|
||||
widget->setData(uniform);
|
||||
mBases.emplace_back(widget);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, bool>)
|
||||
{
|
||||
auto* widget = mClient->createWidget<EditBool>("MW_ValueEditBool", {0, 0, mClient->getWidth(), mClient->getHeight()}, MyGUI::Align::Stretch);
|
||||
widget->setData(uniform);
|
||||
mBases.emplace_back(widget);
|
||||
}
|
||||
|
||||
mReset->eventMouseButtonClick += MyGUI::newDelegate(this, &UniformBase::notifyResetClicked);
|
||||
|
||||
for (EditBase* base : mBases)
|
||||
base->setValueFromUniform();
|
||||
|
||||
}, uniform->mData);
|
||||
}
|
||||
|
||||
void UniformBase::addItem(EditBase* item)
|
||||
{
|
||||
mBases.emplace_back(item);
|
||||
}
|
||||
|
||||
void UniformBase::toDefault()
|
||||
{
|
||||
for (EditBase* base : mBases)
|
||||
{
|
||||
if (base)
|
||||
base->toDefault();
|
||||
}
|
||||
}
|
||||
|
||||
void UniformBase::notifyResetClicked(MyGUI::Widget* sender)
|
||||
{
|
||||
toDefault();
|
||||
}
|
||||
|
||||
void UniformBase::initialiseOverride()
|
||||
{
|
||||
Base::initialiseOverride();
|
||||
|
||||
assignWidget(mReset, "Reset");
|
||||
assignWidget(mLabel, "Label");
|
||||
assignWidget(mClient, "Client");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,266 @@
|
||||
#ifndef OPENMW_COMPONENTS_FX_WIDGETS_H
|
||||
#define OPENMW_COMPONENTS_FX_WIDGETS_H
|
||||
|
||||
#include <MyGUI_Gui.h>
|
||||
#include <MyGUI_Button.h>
|
||||
#include <MyGUI_ScrollView.h>
|
||||
#include <MyGUI_InputManager.h>
|
||||
|
||||
#include <osg/Vec2f>
|
||||
#include <osg/Vec3f>
|
||||
#include <osg/Vec4f>
|
||||
|
||||
#include <components/misc/stringops.hpp>
|
||||
|
||||
#include "technique.hpp"
|
||||
#include "types.hpp"
|
||||
|
||||
namespace Gui
|
||||
{
|
||||
class AutoSizedTextBox;
|
||||
class AutoSizedButton;
|
||||
}
|
||||
|
||||
namespace fx
|
||||
{
|
||||
namespace Widgets
|
||||
{
|
||||
enum Index
|
||||
{
|
||||
None = -1,
|
||||
Zero = 0,
|
||||
One = 1,
|
||||
Two = 2,
|
||||
Three = 3
|
||||
};
|
||||
|
||||
class EditBase
|
||||
{
|
||||
public:
|
||||
virtual ~EditBase() = default;
|
||||
|
||||
void setData(const std::shared_ptr<fx::Types::UniformBase>& uniform, Index index = None)
|
||||
{
|
||||
mUniform = uniform;
|
||||
mIndex = index;
|
||||
}
|
||||
|
||||
virtual void setValueFromUniform() = 0;
|
||||
|
||||
virtual void toDefault() = 0;
|
||||
|
||||
protected:
|
||||
std::weak_ptr<fx::Types::UniformBase> mUniform;
|
||||
Index mIndex;
|
||||
};
|
||||
|
||||
class EditBool : public EditBase, public MyGUI::Widget
|
||||
{
|
||||
MYGUI_RTTI_DERIVED(EditBool)
|
||||
|
||||
public:
|
||||
void setValue(bool value);
|
||||
void setValueFromUniform() override;
|
||||
void toDefault() override;
|
||||
|
||||
private:
|
||||
void initialiseOverride() override;
|
||||
void notifyMouseButtonClick(MyGUI::Widget* sender);
|
||||
|
||||
MyGUI::Button* mCheckbutton;
|
||||
};
|
||||
|
||||
template <class T, class UType>
|
||||
class EditNumber : public EditBase, public MyGUI::Widget
|
||||
{
|
||||
MYGUI_RTTI_DERIVED(EditNumber)
|
||||
|
||||
public:
|
||||
EditNumber() : mLastPointerX(0) {}
|
||||
|
||||
void setValue(T value)
|
||||
{
|
||||
mValue = value;
|
||||
if constexpr (std::is_floating_point_v<T>)
|
||||
mValueLabel->setCaption(Misc::StringUtils::format("%.3f", mValue));
|
||||
else
|
||||
mValueLabel->setCaption(std::to_string(mValue));
|
||||
|
||||
if (auto uniform = mUniform.lock())
|
||||
{
|
||||
if constexpr (std::is_fundamental_v<UType>)
|
||||
uniform->template setValue<UType>(mValue);
|
||||
else
|
||||
{
|
||||
UType uvalue = uniform->template getValue<UType>();
|
||||
uvalue[mIndex] = mValue;
|
||||
uniform->template setValue<UType>(uvalue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setValueFromUniform() override
|
||||
{
|
||||
if (auto uniform = mUniform.lock())
|
||||
{
|
||||
T value;
|
||||
|
||||
if constexpr (std::is_fundamental_v<UType>)
|
||||
value = uniform->template getValue<UType>();
|
||||
else
|
||||
value = uniform->template getValue<UType>()[mIndex];
|
||||
|
||||
setValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
void toDefault() override
|
||||
{
|
||||
if (auto uniform = mUniform.lock())
|
||||
{
|
||||
if constexpr (std::is_fundamental_v<UType>)
|
||||
setValue(uniform->template getDefault<UType>());
|
||||
else
|
||||
setValue(uniform->template getDefault<UType>()[mIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void initialiseOverride() override
|
||||
{
|
||||
Base::initialiseOverride();
|
||||
|
||||
assignWidget(mDragger, "Dragger");
|
||||
assignWidget(mValueLabel, "Value");
|
||||
assignWidget(mButtonIncrease, "ButtonIncrease");
|
||||
assignWidget(mButtonDecrease, "ButtonDecrease");
|
||||
|
||||
mButtonIncrease->eventMouseButtonClick += MyGUI::newDelegate(this, &EditNumber::notifyButtonClicked);
|
||||
mButtonDecrease->eventMouseButtonClick += MyGUI::newDelegate(this, &EditNumber::notifyButtonClicked);
|
||||
|
||||
mDragger->eventMouseButtonPressed += MyGUI::newDelegate(this, &EditNumber::notifyMouseButtonPressed);
|
||||
mDragger->eventMouseDrag += MyGUI::newDelegate(this, &EditNumber::notifyMouseButtonDragged);
|
||||
mDragger->eventMouseWheel += MyGUI::newDelegate(this, &EditNumber::notifyMouseWheel);
|
||||
}
|
||||
|
||||
void notifyMouseWheel(MyGUI::Widget* sender, int rel)
|
||||
{
|
||||
auto uniform = mUniform.lock();
|
||||
|
||||
if (!uniform)
|
||||
return;
|
||||
|
||||
if (rel > 0)
|
||||
increment(uniform->mStep);
|
||||
else
|
||||
increment(-uniform->mStep);
|
||||
}
|
||||
|
||||
void notifyMouseButtonDragged(MyGUI::Widget* sender, int left, int top, MyGUI::MouseButton id)
|
||||
{
|
||||
if (id != MyGUI::MouseButton::Left)
|
||||
return;
|
||||
|
||||
auto uniform = mUniform.lock();
|
||||
|
||||
if (!uniform)
|
||||
return;
|
||||
|
||||
int delta = left - mLastPointerX;
|
||||
|
||||
// allow finer tuning when shift is pressed
|
||||
constexpr double scaling = 20.0;
|
||||
T step = MyGUI::InputManager::getInstance().isShiftPressed() ? uniform->mStep / scaling : uniform->mStep;
|
||||
|
||||
if (step == 0)
|
||||
{
|
||||
if constexpr (std::is_integral_v<T>)
|
||||
step = 1;
|
||||
else
|
||||
step = uniform->mStep;
|
||||
}
|
||||
|
||||
if (delta > 0)
|
||||
increment(step);
|
||||
else if (delta < 0)
|
||||
increment(-step);
|
||||
|
||||
mLastPointerX = left;
|
||||
}
|
||||
|
||||
void notifyMouseButtonPressed(MyGUI::Widget* sender, int left, int top, MyGUI::MouseButton id)
|
||||
{
|
||||
if (id != MyGUI::MouseButton::Left)
|
||||
return;
|
||||
|
||||
mLastPointerX = left;
|
||||
}
|
||||
|
||||
void increment(T step)
|
||||
{
|
||||
auto uniform = mUniform.lock();
|
||||
|
||||
if (!uniform)
|
||||
return;
|
||||
|
||||
if constexpr (std::is_fundamental_v<UType>)
|
||||
setValue(std::clamp<T>(uniform->template getValue<UType>() + step, uniform->template getMin<UType>(), uniform->template getMax<T>()));
|
||||
else
|
||||
setValue(std::clamp<T>(uniform->template getValue<UType>()[mIndex] + step, uniform->template getMin<UType>()[mIndex], uniform->template getMax<UType>()[mIndex]));
|
||||
}
|
||||
|
||||
void notifyButtonClicked(MyGUI::Widget* sender)
|
||||
{
|
||||
auto uniform = mUniform.lock();
|
||||
|
||||
if (!uniform)
|
||||
return;
|
||||
|
||||
if (sender == mButtonDecrease)
|
||||
increment(-uniform->mStep);
|
||||
else if (sender == mButtonIncrease)
|
||||
increment(uniform->mStep);
|
||||
}
|
||||
|
||||
MyGUI::Button* mButtonDecrease;
|
||||
MyGUI::Button* mButtonIncrease;
|
||||
MyGUI::Widget* mDragger;
|
||||
MyGUI::TextBox* mValueLabel;
|
||||
T mValue;
|
||||
|
||||
int mLastPointerX;
|
||||
};
|
||||
|
||||
class EditNumberFloat4 : public EditNumber<float, osg::Vec4f> { MYGUI_RTTI_DERIVED(EditNumberFloat4) };
|
||||
class EditNumberFloat3 : public EditNumber<float, osg::Vec3f> { MYGUI_RTTI_DERIVED(EditNumberFloat3) };
|
||||
class EditNumberFloat2 : public EditNumber<float, osg::Vec2f> { MYGUI_RTTI_DERIVED(EditNumberFloat2) };
|
||||
class EditNumberFloat : public EditNumber<float, float> { MYGUI_RTTI_DERIVED(EditNumberFloat) };
|
||||
class EditNumberInt : public EditNumber<int, int> { MYGUI_RTTI_DERIVED(EditNumberInt) };
|
||||
|
||||
class UniformBase final : public MyGUI::Widget
|
||||
{
|
||||
MYGUI_RTTI_DERIVED(UniformBase)
|
||||
|
||||
public:
|
||||
void init(const std::shared_ptr<fx::Types::UniformBase>& uniform);
|
||||
|
||||
void toDefault();
|
||||
|
||||
void addItem(EditBase* item);
|
||||
|
||||
private:
|
||||
|
||||
void notifyResetClicked(MyGUI::Widget* sender);
|
||||
|
||||
void initialiseOverride() override;
|
||||
|
||||
Gui::AutoSizedButton* mReset;
|
||||
Gui::AutoSizedTextBox* mLabel;
|
||||
MyGUI::Widget* mClient;
|
||||
std::vector<EditBase*> mBases;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -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
|
@ -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
|
@ -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
|
@ -0,0 +1,5 @@
|
||||
Package openmw.postprocessing
|
||||
=============================
|
||||
|
||||
.. raw:: html
|
||||
:file: generated_html/openmw_postprocessing.html
|
@ -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.
|
@ -0,0 +1,11 @@
|
||||
######################
|
||||
OpenMW Post Processing
|
||||
######################
|
||||
|
||||
.. toctree::
|
||||
:caption: Table of Contents
|
||||
:includehidden:
|
||||
:maxdepth: 2
|
||||
|
||||
overview
|
||||
omwfx
|
@ -0,0 +1,862 @@
|
||||
#########################
|
||||
OMWFX Language Reference
|
||||
#########################
|
||||
|
||||
Overview
|
||||
########
|
||||
|
||||
Shaders are written in a OpenMW specific ``*.omwfx`` format. This is a light
|
||||
wrapper around GLSL, so a basic understanding of GLSL should be acquired before
|
||||
attempting to write any shaders. Every shader must be contained within a single
|
||||
``*.omwfx`` file, ``#include`` directives are currently unsupported.
|
||||
|
||||
By default, all shaders only guarantee support of GLSL 120 features. To target a
|
||||
newer GLSL version, you must specify it in the `technique`_ block properties. If
|
||||
the specified version is not supported on the target machine, the shader will
|
||||
not load.
|
||||
|
||||
Reserved Keywords
|
||||
#################
|
||||
|
||||
GLSL doesn't support namespaces, instead reserved prefixes are used. Do not
|
||||
attempt to name anything starting with ``_`` or ``omw``, this will cause
|
||||
name clashes.
|
||||
|
||||
|
||||
Builtin Samplers
|
||||
################
|
||||
|
||||
+------------------+---------------------------+---------------------------------------------+
|
||||
| GLSL Type | Name | Description |
|
||||
+==================+===========================+=============================================+
|
||||
| sampler2D[Array] |``omw_SamplerLastShader`` | Color output of the last shader |
|
||||
+------------------+---------------------------+---------------------------------------------+
|
||||
| sampler2D[Array] |``omw_SamplerLastPass`` | Color output of the last pass |
|
||||
+------------------+---------------------------+---------------------------------------------+
|
||||
| sampler2D[Array] |``omw_SamplerDepth`` | Non-linear normalized depth |
|
||||
+------------------+---------------------------+---------------------------------------------+
|
||||
| sampler2D[Array] |``omw_SamplerNormals`` | Normalized world-space normals [0, 1] |
|
||||
+------------------+---------------------------+---------------------------------------------+
|
||||
|
||||
These are included in a common header in every pass, they do not need to be re-defined.
|
||||
It is recommended to use the accessor functions to retrieve the sampler value.
|
||||
OpenMW supports multiview rendering, so these samplers will either be a
|
||||
``sampler2D`` or ``sampler2DArray``. If you want more control over how you
|
||||
sample textures, use the ``OMW_MULTIVIEW`` macro to determine the appropriate functions to use.
|
||||
|
||||
|
||||
Builtin Uniforms
|
||||
################
|
||||
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| GLSL Type | Name | Description |
|
||||
+=============+==============================+==================================================+
|
||||
| mat4 | ``omw.projectionMatrix`` | The camera's projection matrix |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| mat4 | ``omw.invProjectionMatrix`` | The inverse of the camera's projection matrix |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| mat4 | ``omw.viewMatrix`` | The camera's view matrix |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| mat4 | ``omw.prevViewMatrix`` | The camera's previous frame view matrix |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| mat4 | ``omw.invViewMatrix`` | The inverse of the camera's view matrix |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| vec4 | ``omw.eyePos`` | The camera's eye position |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| vec4 | ``omw.eyeVec`` | The normalized camera's eye vector |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| vec4 | ``omw.fogColor`` | The RGBA color of fog |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| vec4 | ``omw.sunColor`` | The RGBA color of sun |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| vec4 | ``omw.sunPos`` | The normalized sun direction |
|
||||
| | | |
|
||||
| | | When the sun is set `omw.sunpos.z` is negated |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| vec2 | ``omw.resolution`` | The render target's resolution |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| vec2 | ``omw.rcpResolution`` | Reciprocal of the render target resolution |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| vec2 | ``omw.fogNear`` | The units at which the fog begins to render |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| float | ``omw.fogFar`` | The units at which the fog ends |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| float | ``omw.near`` | The camera's near clip |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| float | ``omw.far`` | The camera's far clip |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| float | ``omw.gameHour`` | The game hour in range [0,24) |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| float | ``omw.sunVis`` | The sun's visibility between [0, 1] |
|
||||
| | | |
|
||||
| | | Influenced by types of weather |
|
||||
| | | |
|
||||
| | | Closer to zero during overcast weathers |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| float | ``omw.waterHeight`` | The water height of current cell |
|
||||
| | | |
|
||||
| | | Exterior water level is always zero |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| float | ``omw.simulationTime`` | The time in milliseconds since simulation began |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| float | ``omw.deltaSimulationTime`` | The change in `omw.simulationTime` |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| float | ``omw.windSpeed`` | The current wind speed |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| float | ``omw.weatherTransition`` | The transition factor between weathers [0, 1] |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| int | ``omw.weatherID`` | The current weather ID |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| int | ``omw.nextWeatherID`` | The next weather ID |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| bool | ``omw.isUnderwater`` | True if player is submerged underwater |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
| bool | ``omw.isInterior`` | True if player is in an interior |
|
||||
| | | |
|
||||
| | | False for interiors that behave like exteriors |
|
||||
+-------------+------------------------------+--------------------------------------------------+
|
||||
|
||||
|
||||
Builtin Macros
|
||||
##############
|
||||
|
||||
+------------------+----------------+---------------------------------------------------------------------------+
|
||||
| Macro | Definition | Description |
|
||||
+==================+================+===========================================================================+
|
||||
|``OMW_REVERSE_Z`` | ``0`` or ``1`` | Whether a reversed depth buffer is in use. |
|
||||
| | | |
|
||||
| | | ``0`` Depth sampler will be in range [1, 0] |
|
||||
| | | |
|
||||
| | | ``1`` Depth sampler will be in range [0, 1] |
|
||||
+------------------+----------------+---------------------------------------------------------------------------+
|
||||
|``OMW_RADIAL_FOG``| ``0`` or ``1`` | Whether radial fog is in use. |
|
||||
| | | |
|
||||
| | | ``0`` Fog is linear |
|
||||
| | | |
|
||||
| | | ``1`` Fog is radial |
|
||||
+------------------+----------------+---------------------------------------------------------------------------+
|
||||
| ``OMW_HDR`` | ``0`` or ``1`` | Whether average scene luminance is computed every frame. |
|
||||
| | | |
|
||||
| | | ``0`` Average scene luminance is not computed |
|
||||
| | | |
|
||||
| | | ``1`` Average scene luminance is computed |
|
||||
+------------------+----------------+---------------------------------------------------------------------------+
|
||||
| ``OMW_NORMALS`` | ``0`` or ``1`` | Whether normals are available as a sampler in the technique. |
|
||||
| | | |
|
||||
| | | ``0`` Normals are not available |
|
||||
| | | |
|
||||
| | | ``1`` Normals are available. |
|
||||
+------------------+----------------+---------------------------------------------------------------------------+
|
||||
| ``OMW_MULTIVIEW``| ``0`` or ``1`` | Whether multiview rendering is in use. |
|
||||
| | | |
|
||||
| | | ``0`` Multiview not in use |
|
||||
| | | |
|
||||
| | | ``1`` Multiview in use. |
|
||||
+------------------+----------------+---------------------------------------------------------------------------+
|
||||
|
||||
|
||||
Builtin Functions
|
||||
#################
|
||||
|
||||
The following functions can be accessed in any fragment or vertex shader.
|
||||
|
||||
+----------------------------------------+-------------------------------------------------------------------------------+
|
||||
| Function | Description |
|
||||
+========================================+===============================================================================+
|
||||
| ``float omw_GetDepth(vec2)`` | Returns the depth value from a sampler given a uv coordinate. |
|
||||
| | |
|
||||
| | Reverses sampled value when ``OMW_REVERSE_Z`` is set. |
|
||||
+----------------------------------------+-------------------------------------------------------------------------------+
|
||||
| ``float omw_GetEyeAdaptation()`` | Returns the average scene luminance in range [0, 1]. |
|
||||
| | |
|
||||
| | If HDR is not in use, this returns `1.0` |
|
||||
| | |
|
||||
| | Scene luminance is always calculated on original scene texture. |
|
||||
+----------------------------------------+-------------------------------------------------------------------------------+
|
||||
| ``vec4 omw_GetDepth(vec2 uv)`` | Returns non-linear normalized depth |
|
||||
+----------------------------------------+-------------------------------------------------------------------------------+
|
||||
| ``vec4 omw_GetLastShader(vec2 uv)`` | Returns RGBA color output of the last shader |
|
||||
+----------------------------------------+-------------------------------------------------------------------------------+
|
||||
| ``vec4 omw_GetLastPass(vec2 uv)`` | Returns RGBA color output of the last pass |
|
||||
+----------------------------------------+-------------------------------------------------------------------------------+
|
||||
| ``vec3 omw_GetNormals(vec2 uv)`` | Returns normalized worldspace normals [-1, 1] |
|
||||
| | |
|
||||
| | The values in sampler are in [0, 1] but are transformed to [-1, 1] |
|
||||
+----------------------------------------+-----------------------+-------------------------------------------------------+
|
||||
|
||||
|
||||
Special Attributes
|
||||
##################
|
||||
|
||||
To maintain maximum compatability with future releases, OpenMW defines specific keywords, attributes, and functions for you to use. These should be used instead of their
|
||||
GLSL equivalent. Refer to the table below to view these mappings.
|
||||
|
||||
+-------------------+---------------------------------------------------------+
|
||||
| .omwfx | Description |
|
||||
+===================+=========================================================+
|
||||
| omw_In | use in place of ``in`` and ``varying`` |
|
||||
+-------------------+---------------------------------------------------------+
|
||||
| omw_Out | use in place of ``out`` and ```varying`` |
|
||||
+-------------------+---------------------------------------------------------+
|
||||
| omw_Position | use in place of ``gl_Position`` |
|
||||
+-------------------+---------------------------------------------------------+
|
||||
| omw_Vertex | use in place of ``gl_Vertex`` |
|
||||
+-------------------+---------------------------------------------------------+
|
||||
| omw_Fragment | use in place of ``gl_FragData[*]`` and ``gl_FragColor``|
|
||||
+-------------------+---------------------------------------------------------+
|
||||
| omw_Texture1D() | use in place of ``texture1D()`` or ``texture()`` |
|
||||
+-------------------+---------------------------------------------------------+
|
||||
| omw_Texture2D() | use in place of ``texture2D()`` or ``texture()`` |
|
||||
+-------------------+---------------------------------------------------------+
|
||||
| omw_Texture3D() | use in place of ``texture3D()`` or ``texture()`` |
|
||||
+-------------------+---------------------------------------------------------+
|
||||
|
||||
Blocks
|
||||
######
|
||||
|
||||
``fragment``
|
||||
*************
|
||||
|
||||
Declare your passes with ``fragment`` followed by a unique name. We will define the order of these passes later on.
|
||||
Each ``fragment`` block must contain valid GLSL. Below is a simple example of defining two passes.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
fragment pass {
|
||||
void main()
|
||||
{
|
||||
omw_FragColor = vec4(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
fragment otherPass {
|
||||
|
||||
omw_In vec2 omw_TexCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
omw_FragColor = omw_GetLastPass(omw_TexCoord);
|
||||
}
|
||||
}
|
||||
|
||||
``vertex``
|
||||
***********
|
||||
|
||||
For every ``fragment`` block you declare, OpenMW generates a default vertex shader if you do not define one. This is used to draw the fullscreen triangle used in postprocessing.
|
||||
This means you rarely need to use a custom vertex shader. Using a vertex shader can sometimes be useful when you need to do lots of complicated calculations that don't rely on pixel location.
|
||||
The vertex shader only invocates on the `3` vertices of the fullscreen triangle.
|
||||
Below is an example of passing a value through a custom vertex shader to the fragment shader.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
vertex pass {
|
||||
#if OMW_USE_BINDINGS
|
||||
omw_In vec2 omw_Vertex;
|
||||
#endif
|
||||
|
||||
uniform sampler2D noiseSampler;
|
||||
|
||||
omw_Out vec2 omw_TexCoord;
|
||||
|
||||
// custom output from vertex shader
|
||||
omw_Out float noise;
|
||||
|
||||
void main()
|
||||
{
|
||||
omw_Position = vec4(omw_Vertex.xy, 0.0, 1.0);
|
||||
omw_TexCoord = omw_Position.xy * 0.5 + 0.5;
|
||||
|
||||
noise = sqrt(omw_Texture2D(noiseSampler, vec2(0.5, 0.5)).r);
|
||||
}
|
||||
}
|
||||
|
||||
fragment pass {
|
||||
omw_Out vec2 omw_TexCoord;
|
||||
|
||||
// our custom output from the vertex shader is available
|
||||
omw_Out float noise;
|
||||
|
||||
void main()
|
||||
{
|
||||
omw_FragColor = vec4(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
``technique``
|
||||
*************
|
||||
|
||||
Exactly one ``technique`` block is required for every shader file. In this we define important traits like author, description, requirements, and flags.
|
||||
|
||||
|
||||
+------------------+--------------------+---------------------------------------------------+
|
||||
| Property | Type | Description |
|
||||
+==================+====================+===================================================+
|
||||
| passes | literal list | ``,`` separated list of pass names |
|
||||
+------------------+--------------------+---------------------------------------------------+
|
||||
| version | string | Shader version that shows in HUD |
|
||||
+------------------+--------------------+---------------------------------------------------+
|
||||
| description | string | Shader description that shows in HUD |
|
||||
+------------------+--------------------+---------------------------------------------------+
|
||||
| author | string | Shader authors that shows in HUD |
|
||||
+------------------+--------------------+---------------------------------------------------+
|
||||
| glsl_Version | integer | GLSL version |
|
||||
+------------------+--------------------+---------------------------------------------------+
|
||||
| glsl_profile | string | GLSL profile, like ``compatability`` |
|
||||
+------------------+--------------------+---------------------------------------------------+
|
||||
| glsl_extensions | literal list | ``,`` separated list of required GLSL extensions |
|
||||
+------------------+--------------------+---------------------------------------------------+
|
||||
| hdr | boolean | Whether HDR eye adaptation is required. |
|
||||
+------------------+--------------------+---------------------------------------------------+
|
||||
| pass_normals | boolean | Pass normals from the forward passes. |
|
||||
| | | |
|
||||
| | | If unsupported, `OMW_NORMALS` will be set to `0` |
|
||||
+------------------+--------------------+---------------------------------------------------+
|
||||
| flags | `SHADER_FLAG`_ | ``,`` separated list of shader flags |
|
||||
+------------------+--------------------+---------------------------------------------------+
|
||||
|
||||
In the code snippet below, a shader is defined that requires GLSL `330`, HDR capatiblities, and is only enabled underwater in exteriors.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
fragment dummy {
|
||||
void main()
|
||||
{
|
||||
omw_FragColor = vec4(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
technique {
|
||||
passes = dummy;
|
||||
glsl_version = 330;
|
||||
hdr = true;
|
||||
flags = disable_interiors | disable_abovewater;
|
||||
}
|
||||
|
||||
|
||||
``sampler_*``
|
||||
*************
|
||||
|
||||
Any texture in the VFS can be loaded by a shader. All passes within the technique will have access to this texture as a sampler.
|
||||
OpenMW currently supports ``1D``, ``2D``, and ``3D`` texture samplers, cubemaps can not yet be loaded.
|
||||
|
||||
+-------------+
|
||||
| Block |
|
||||
+=============+
|
||||
| sampler_1d |
|
||||
+-------------+
|
||||
| sampler_2d |
|
||||
+-------------+
|
||||
| sampler_3d |
|
||||
+-------------+
|
||||
|
||||
The properites for a ``sampler_*`` block are as following.
|
||||
The only required property for a texture is its ``source``.
|
||||
|
||||
+-----------------------+-----------------------+
|
||||
| Property | Type |
|
||||
+=======================+=======================+
|
||||
|``source`` | string |
|
||||
+-----------------------+-----------------------+
|
||||
|``min_filter`` | `FILTER_MODE`_ |
|
||||
+-----------------------+-----------------------+
|
||||
|``mag_filter`` | `FILTER_MODE`_ |
|
||||
+-----------------------+-----------------------+
|
||||
|``wrap_s`` | `WRAP_MODE`_ |
|
||||
+-----------------------+-----------------------+
|
||||
|``wrap_t`` | `WRAP_MODE`_ |
|
||||
+-----------------------+-----------------------+
|
||||
|``wrap_r`` | `WRAP_MODE`_ |
|
||||
+-----------------------+-----------------------+
|
||||
|``compression`` | `COMPRESSION_MODE`_ |
|
||||
+-----------------------+-----------------------+
|
||||
|``source_format`` | `SOURCE_FORMAT`_ |
|
||||
+-----------------------+-----------------------+
|
||||
|``source_type`` | `SOURCE_TYPE`_ |
|
||||
+-----------------------+-----------------------+
|
||||
|``internal_format`` | `INTERNAL_FORMAT`_ |
|
||||
+-----------------------+-----------------------+
|
||||
|
||||
In the code snippet below, a simple noise texture is loaded with nearest filtering.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
sampler_2d noise {
|
||||
source = "textures/noise.png";
|
||||
mag_filter = nearest;
|
||||
min_filter = nearest;
|
||||
}
|
||||
|
||||
To use the sampler, define the appropriately named `sampler2D` in any of your passes.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
fragment pass {
|
||||
omw_In vec2 omw_TexCoord;
|
||||
|
||||
uniform sampler2D noise;
|
||||
|
||||
void main()
|
||||
{
|
||||
// ...
|
||||
vec4 noise = omw_Texture2D(noise, omw_TexCoord);
|
||||
}
|
||||
}
|
||||
|
||||
``uniform_*``
|
||||
**************
|
||||
|
||||
It is possible to define settings for your shaders that can be adjusted by either users or a Lua script.
|
||||
|
||||
|
||||
+-----------------+----------+----------+----------+---------+----------+--------------+---------+
|
||||
| Block | default | min | max | static | step | description | header |
|
||||
+=================+==========+==========+==========+=========+==========+==============+=========+
|
||||
|``uniform_bool`` | boolean | x | x | boolean | x | string | string |
|
||||
+-----------------+----------+----------+----------+---------+----------+--------------+---------+
|
||||
|``uniform_float``| float | float | float | boolean | float | string | string |
|
||||
+-----------------+----------+----------+----------+---------+----------+--------------+---------+
|
||||
|``uniform_int`` | integer | integer | integer | boolean | integer | string | string |
|
||||
+-----------------+----------+----------+----------+---------+----------+--------------+---------+
|
||||
|``uniform_vec2`` | vec2 | vec2 | vec2 | boolean | vec2 | string | string |
|
||||
+-----------------+----------+----------+----------+---------+----------+--------------+---------+
|
||||
|``uniform_vec3`` | vec3 | vec3 | vec3 | boolean | vec3 | string | string |
|
||||
+-----------------+----------+----------+----------+---------+----------+--------------+---------+
|
||||
|``uniform_vec4`` | vec4 | vec4 | vec4 | boolean | vec4 | string | string |
|
||||
+-----------------+----------+----------+----------+---------+----------+--------------+---------+
|
||||
|
||||
The ``description`` field is used to display a toolip when viewed in the in-game HUD. The ``header`` field
|
||||
field can be used to organize uniforms into groups in the HUD.
|
||||
|
||||
If you would like a uniform to be adjustable with Lua API you `must` set ``static = false;``. Doing this
|
||||
will also remove the uniform from the players HUD.
|
||||
|
||||
Below is an example of declaring a ``vec3`` uniform.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
uniform_vec3 uColor {
|
||||
default = vec3(0,1,1);
|
||||
min = vec3(0,0,0);
|
||||
max = vec3(1,1,1);
|
||||
step = vec3(0.1, 0.1, 0.1);
|
||||
description = "Color uniform";
|
||||
static = true;
|
||||
header = "Colors";
|
||||
}
|
||||
|
||||
To use the uniform you can reference it in any pass, it should **not** be declared with the ``uniform`` keyword.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
fragment pass {
|
||||
void main()
|
||||
{
|
||||
// ...
|
||||
vec3 color = uColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
``render_target``
|
||||
*****************
|
||||
|
||||
Normally when defining passes, OpenMW will take care of setting up all of the render targets. Sometimes, this behavior
|
||||
is not wanted and you want a custom render target.
|
||||
|
||||
|
||||
+------------------+---------------------+-----------------------------------------------------------------------------+
|
||||
| Property | Type | Description |
|
||||
+==================+=====================+=============================================================================+
|
||||
| min_filter | `FILTER_MODE`_ | x |
|
||||
+------------------+---------------------+-----------------------------------------------------------------------------+
|
||||
| mag_filter | `FILTER_MODE`_ | x |
|
||||
+------------------+---------------------+-----------------------------------------------------------------------------+
|
||||
| wrap_s | `WRAP_MODE`_ | x |
|
||||
+------------------+---------------------+-----------------------------------------------------------------------------+
|
||||
| wrap_t | `WRAP_MODE`_ | x |
|
||||
+------------------+---------------------+-----------------------------------------------------------------------------+
|
||||
| internal_format | `INTERNAL_FORMAT`_ | x |
|
||||
+------------------+---------------------+-----------------------------------------------------------------------------+
|
||||
| source_type | `SOURCE_TYPE`_ | x |
|
||||
+------------------+---------------------+-----------------------------------------------------------------------------+
|
||||
| source_format | `SOURCE_FORMAT`_ | x |
|
||||
+------------------+---------------------+-----------------------------------------------------------------------------+
|
||||
| width_ratio | float | Automatic width as a percentage of screen width |
|
||||
+------------------+---------------------+-----------------------------------------------------------------------------+
|
||||
| height_ratio | float | Automatic width as a percentage of screen height |
|
||||
+------------------+---------------------+-----------------------------------------------------------------------------+
|
||||
| width | float | Width in pixels |
|
||||
+------------------+---------------------+-----------------------------------------------------------------------------+
|
||||
| height | float | Height in pixels |
|
||||
+------------------+---------------------+-----------------------------------------------------------------------------+
|
||||
| mipmaps | boolean | Whether mipmaps should be generated every frame |
|
||||
+------------------+---------------------+-----------------------------------------------------------------------------+
|
||||
|
||||
|
||||
To use the render target you must assign passes to it, along with any optional clear modes or custom blend modes.
|
||||
|
||||
In the code snippet below a rendertarget is used to draw the red cannel of a scene at half resolution.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
render_target RT_Downsample {
|
||||
width_ratio = 0.5;
|
||||
height_ratio = 0.5;
|
||||
internal_format = r16f;
|
||||
source_type = float;
|
||||
source_format = red;
|
||||
}
|
||||
|
||||
fragment downsample2x(target=RT_Downsample) {
|
||||
|
||||
omw_In vec2 omw_TexCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
omw_FragColor.r = omw_GetLastShader(omw_TexCoord).r;
|
||||
}
|
||||
}
|
||||
|
||||
Now, if we ever run the `downsample2x` pass it will write to the target buffer instead of the default
|
||||
one assigned by the engine.
|
||||
|
||||
To use the uniform you can reference it in any pass, it should **not** be declared with the ``uniform`` keyword.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
fragment pass {
|
||||
void main()
|
||||
{
|
||||
// ...
|
||||
vec3 color = uColor;
|
||||
}
|
||||
}
|
||||
|
||||
Simple Example
|
||||
##############
|
||||
|
||||
Let us go through a simple example in which we apply a simple desaturation
|
||||
filter with a user-configurable factor.
|
||||
|
||||
Our first step is defining our user-configurable variable. In this case all we
|
||||
want is a normalized value between 0 and 1 to influence the amount of
|
||||
desaturation to apply to the scene. Here we setup a new variable of type
|
||||
``float``, define a few basic properties, and give it a tooltip description.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
uniform_float uDesaturationFactor {
|
||||
default = 0.5;
|
||||
min = 0.0;
|
||||
max = 1.0;
|
||||
step = 0.05;
|
||||
static = true;
|
||||
description = "Desaturation factor. A value of 1.0 is full grayscale.";
|
||||
}
|
||||
|
||||
Now, we can setup our first pass. Remember a pass is just a pixel shader invocation.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
fragment desaturate {
|
||||
omw_In vec2 omw_TexCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
// fetch scene texture from last shader
|
||||
vec4 scene = omw_GetLastShader(omw_TexCoord);
|
||||
|
||||
// desaturate RGB component
|
||||
const vec3 luminance = vec3(0.299, 0.587, 0.144);
|
||||
float gray = dot(luminance, scene.rgb);
|
||||
|
||||
omw_FragColor = vec4(mix(scene.rgb, vec3(gray), uDesaturationFactor), scene.a);
|
||||
}
|
||||
}
|
||||
|
||||
Next we can define our ``technique`` block, which is in charge of glueing
|
||||
together passes, setting up metadata, and setting up various flags.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
technique {
|
||||
description = "Desaturates scene";
|
||||
passes = desaturate;
|
||||
version = "1.0";
|
||||
author = "Fargoth";
|
||||
passes = desaturate;
|
||||
}
|
||||
|
||||
|
||||
Putting it all together we have this simple shader.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
uniform_float uDesaturationFactor {
|
||||
default = 0.5;
|
||||
min = 0.0;
|
||||
max = 1.0;
|
||||
step = 0.05;
|
||||
description = "Desaturation factor. A value of 1.0 is full grayscale.";
|
||||
}
|
||||
|
||||
fragment desaturate {
|
||||
omw_In vec2 omw_TexCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
// fetch scene texture from last shader
|
||||
vec4 scene = omw_GetLastShader(omw_TexCoord);
|
||||
|
||||
// desaturate RGB component
|
||||
const vec3 luminance = vec3(0.299, 0.587, 0.144);
|
||||
float gray = dot(luminance, scene.rgb);
|
||||
|
||||
omw_FragColor = vec4(mix(scene.rgb, vec3(gray), uDesaturationFactor), scene.a);
|
||||
}
|
||||
}
|
||||
|
||||
technique {
|
||||
description = "Desaturates scene";
|
||||
passes = desaturate;
|
||||
version = "1.0";
|
||||
author = "Fargoth";
|
||||
passes = desaturate;
|
||||
}
|
||||
|
||||
|
||||
Types
|
||||
#####
|
||||
|
||||
`SHADER_FLAG`
|
||||
*************
|
||||
|
||||
+--------------------+--------------------------------------------------------------------------+
|
||||
| Flag | Description |
|
||||
+====================+==========================================================================+
|
||||
| disable_interiors | Disable in interiors. |
|
||||
+--------------------+--------------------------------------------------------------------------+
|
||||
| disable_exteriors | Disable when in exteriors or interiors which behave like exteriors. |
|
||||
+--------------------+--------------------------------------------------------------------------+
|
||||
| disable_underwater | Disable when underwater. |
|
||||
+--------------------+--------------------------------------------------------------------------+
|
||||
| disable_abovewater | Disable when above water. |
|
||||
+--------------------+--------------------------------------------------------------------------+
|
||||
| disable_sunglare | Disables builtin sunglare. |
|
||||
+--------------------+--------------------------------------------------------------------------+
|
||||
| hidden | Shader does not show in the HUD. Useful for shaders driven by Lua API. |
|
||||
+--------------------+--------------------------------------------------------------------------+
|
||||
|
||||
`BLEND_EQ`
|
||||
**********
|
||||
|
||||
+-------------------+---------------------------+
|
||||
| .omwfx | OpenGL |
|
||||
+===================+===========================+
|
||||
| rgba_min | GL_MIN |
|
||||
+-------------------+---------------------------+
|
||||
| rgba_max | GL_MAX |
|
||||
+-------------------+---------------------------+
|
||||
| alpha_min | GL_ALPHA_MIN_SGIX |
|
||||
+-------------------+---------------------------+
|
||||
| alpha_max | GL_ALPHA_MAX_SGIX |
|
||||
+-------------------+---------------------------+
|
||||
| logic_op | GL_LOGIC_OP |
|
||||
+-------------------+---------------------------+
|
||||
| add | GL_FUNC_ADD |
|
||||
+-------------------+---------------------------+
|
||||
| subtract | GL_FUNC_SUBTRACT |
|
||||
+-------------------+---------------------------+
|
||||
| reverse_subtract | GL_FUNC_REVERSE_SUBTRACT |
|
||||
+-------------------+---------------------------+
|
||||
|
||||
`BLEND_FUNC`
|
||||
************
|
||||
|
||||
+---------------------------+------------------------------+
|
||||
| .omwfx | OpenGL |
|
||||
+===========================+==============================+
|
||||
| dst_alpha | GL_DST_ALPHA |
|
||||
+---------------------------+------------------------------+
|
||||
| dst_color | GL_DST_COLOR |
|
||||
+---------------------------+------------------------------+
|
||||
| one | GL_ONE |
|
||||
+---------------------------+------------------------------+
|
||||
| one_minus_dst_alpha | GL_ONE_MINUS_DST_ALPHA |
|
||||
+---------------------------+------------------------------+
|
||||
| one_minus_dst_color | GL_ONE_MINUS_DST_COLOR |
|
||||
+---------------------------+------------------------------+
|
||||
| one_minus_src_alpha | GL_ONE_MINUS_SRC_ALPHA |
|
||||
+---------------------------+------------------------------+
|
||||
| one_minus_src_color | GL_ONE_MINUS_SRC_COLOR |
|
||||
+---------------------------+------------------------------+
|
||||
| src_alpha | GL_SRC_ALPHA |
|
||||
+---------------------------+------------------------------+
|
||||
| src_alpha_saturate | GL_SRC_ALPHA_SATURATE |
|
||||
+---------------------------+------------------------------+
|
||||
| src_color | GL_SRC_COLOR |
|
||||
+---------------------------+------------------------------+
|
||||
| constant_color | GL_CONSTANT_COLOR |
|
||||
+---------------------------+------------------------------+
|
||||
| one_minus_constant_color | GL_ONE_MINUS_CONSTANT_COLOR |
|
||||
+---------------------------+------------------------------+
|
||||
| constant_alpha | GL_CONSTANT_ALPHA |
|
||||
+---------------------------+------------------------------+
|
||||
| one_minus_constant_alpha | GL_ONE_MINUS_CONSTANT_ALPHA |
|
||||
+---------------------------+------------------------------+
|
||||
| zero | GL_ZERO |
|
||||
+---------------------------+------------------------------+
|
||||
|
||||
`INTERNAL_FORMAT`
|
||||
*****************
|
||||
|
||||
+--------------------+-----------------------+
|
||||
| .omwfx | OpenGL |
|
||||
+====================+=======================+
|
||||
| red | GL_RED |
|
||||
+--------------------+-----------------------+
|
||||
| r16f | GL_R16F |
|
||||
+--------------------+-----------------------+
|
||||
| r32f | GL_R32F |
|
||||
+--------------------+-----------------------+
|
||||
| rg | GL_RG |
|
||||
+--------------------+-----------------------+
|
||||
| rg16f | GL_RG16F |
|
||||
+--------------------+-----------------------+
|
||||
| rg32f | GL_RG32F |
|
||||
+--------------------+-----------------------+
|
||||
| rgb | GL_RGB |
|
||||
+--------------------+-----------------------+
|
||||
| rgb16f | GL_RGB16F |
|
||||
+--------------------+-----------------------+
|
||||
| rgb32f | GL_RGB32F |
|
||||
+--------------------+-----------------------+
|
||||
| rgba | GL_RGBA |
|
||||
+--------------------+-----------------------+
|
||||
| rgba16f | GL_RGBA16F |
|
||||
+--------------------+-----------------------+
|
||||
| rgba32f | GL_RGBA32F |
|
||||
+--------------------+-----------------------+
|
||||
| depth_component16 | GL_DEPTH_COMPONENT16 |
|
||||
+--------------------+-----------------------+
|
||||
| depth_component24 | GL_DEPTH_COMPONENT24 |
|
||||
+--------------------+-----------------------+
|
||||
| depth_component32 | GL_DEPTH_COMPONENT32 |
|
||||
+--------------------+-----------------------+
|
||||
| depth_component32f | GL_DEPTH_COMPONENT32F |
|
||||
+--------------------+-----------------------+
|
||||
|
||||
`SOURCE_TYPE`
|
||||
*************
|
||||
|
||||
+--------------------+-----------------------+
|
||||
| .omwfx | OpenGL |
|
||||
+====================+=======================+
|
||||
| byte | GL_BYTE |
|
||||
+--------------------+-----------------------+
|
||||
| unsigned_byte | GL_UNSIGNED_BYTE |
|
||||
+--------------------+-----------------------+
|
||||
| short | GL_SHORT |
|
||||
+--------------------+-----------------------+
|
||||
| unsigned_short | GL_UNSIGNED_SHORT |
|
||||
+--------------------+-----------------------+
|
||||
| int | GL_INT |
|
||||
+--------------------+-----------------------+
|
||||
| unsigned_int | GL_UNSIGNED_INT |
|
||||
+--------------------+-----------------------+
|
||||
| unsigned_int_24_8 | GL_UNSIGNED_INT_24_8 |
|
||||
+--------------------+-----------------------+
|
||||
| float | GL_FLOAT |
|
||||
+--------------------+-----------------------+
|
||||
| double | GL_DOUBLE |
|
||||
+--------------------+-----------------------+
|
||||
|
||||
|
||||
`SOURCE_FORMAT`
|
||||
***************
|
||||
|
||||
+---------+---------+
|
||||
| .omwfx | OpenGL |
|
||||
+=========+=========+
|
||||
| red | GL_RED |
|
||||
+---------+---------+
|
||||
| rg | GL_RG |
|
||||
+---------+---------+
|
||||
| rgb | GL_RGB |
|
||||
+---------+---------+
|
||||
| bgr | GL_BGR |
|
||||
+---------+---------+
|
||||
| rgba | GL_RGBA |
|
||||
+---------+---------+
|
||||
| bgra | GL_BGRA |
|
||||
+---------+---------+
|
||||
|
||||
`FILTER_MODE`
|
||||
*************
|
||||
|
||||
+-------------------------+----------------------------+
|
||||
| .omwfx | OpenGL |
|
||||
+=========================+============================+
|
||||
| linear | GL_LINEAR |
|
||||
+-------------------------+----------------------------+
|
||||
| linear_mipmap_linear | GL_LINEAR_MIPMAP_LINEAR |
|
||||
+-------------------------+----------------------------+
|
||||
| linear_mipmap_nearest | GL_LINEAR_MIPMAP_NEAREST |
|
||||
+-------------------------+----------------------------+
|
||||
| nearest | GL_NEAREST |
|
||||
+-------------------------+----------------------------+
|
||||
| nearest_mipmap_linear | GL_NEAREST_MIPMAP_LINEAR |
|
||||
+-------------------------+----------------------------+
|
||||
| nearest_mipmap_nearest | GL_NEAREST_MIPMAP_NEAREST |
|
||||
+-------------------------+----------------------------+
|
||||
|
||||
`WRAP_MODE`
|
||||
***********
|
||||
|
||||
+------------------+---------------------+
|
||||
| .omwfx | OpenGL |
|
||||
+==================+=====================+
|
||||
| clamp | GL_CLAMP |
|
||||
+------------------+---------------------+
|
||||
| clamp_to_edge | GL_CLAMP_TO_EDGE |
|
||||
+------------------+---------------------+
|
||||
| clamp_to_border | GL_CLAMP_TO_BORDER |
|
||||
+------------------+---------------------+
|
||||
| repeat | GL_REPEAT |
|
||||
+------------------+---------------------+
|
||||
| mirror | GL_MIRRORED_REPEAT |
|
||||
+------------------+---------------------+
|
||||
|
||||
`COMPRESSION_MODE`
|
||||
******************
|
||||
|
||||
+-------------+
|
||||
| .omwfx |
|
||||
+=============+
|
||||
| auto |
|
||||
+-------------+
|
||||
| arb |
|
||||
+-------------+
|
||||
| s3tc_dxt1 |
|
||||
+-------------+
|
||||
| s3tc_dxt3 |
|
||||
+-------------+
|
||||
| s3tc_dxt5 |
|
||||
+-------------+
|
||||
| pvrtc_2bpp |
|
||||
+-------------+
|
||||
| pvrtc_4bpp |
|
||||
+-------------+
|
||||
| etc |
|
||||
+-------------+
|
||||
| etc2 |
|
||||
+-------------+
|
||||
| rgtc1 |
|
||||
+-------------+
|
||||
| rgtc2 |
|
||||
+-------------+
|
||||
| s3tc_dxt1c |
|
||||
+-------------+
|
||||
| s3tc_dxt1a |
|
||||
+-------------+
|
@ -0,0 +1,45 @@
|
||||
#####################################
|
||||
Overview of Post Processing Framework
|
||||
#####################################
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
OpenMW supports a moddable post process framework for creating and
|
||||
controlling screenspace effects. This is integrated into OpenMW's Lua API, see
|
||||
`reference <../lua-scripting/openmw_shader.html>`_ for details.
|
||||
|
||||
Basic concepts
|
||||
==============
|
||||
|
||||
Pass
|
||||
Describes a single shader invocation pass. Currently only pixel (also known
|
||||
as fragment) shaders are supported.
|
||||
|
||||
Technique/Shader
|
||||
An ordered list of passes, techniques will encompass a single effect like
|
||||
bloom or SSAO. Technique is interchangable with shader.
|
||||
|
||||
Installing and Activating
|
||||
=========================
|
||||
|
||||
Shaders are managed through the virtual file system, simply install the associated
|
||||
archive or folder as described in :ref:`mod-install<install>`. Shaders must be
|
||||
in the `Shaders` directory to be discoverable. A shader can be activated in one
|
||||
of two ways:
|
||||
|
||||
1. Adding the shaders filename (without its extension) to the end of the
|
||||
:ref:`chain` list in ``settings.cfg``.
|
||||
2. Using the in game post processor HUD, which can be activated with the ``F2``
|
||||
key by default. This is the recommended method as manual editing can be error
|
||||
prone.
|
||||
|
||||
Hot Reloading
|
||||
==============
|
||||
|
||||
It is possible to modify a shader without restarting OpenMW, :ref:`live reload`
|
||||
must be enabled in ``settings.cfg``. Whenever a file is modified and saved, the
|
||||
shader will automatically reload in game. This allows shaders to be written in a
|
||||
text editor you are comfortable with. The only restriction is that new shaders
|
||||
cannot be added, as the VFS will not be rebuilt and OpenMW will not be aware of
|
||||
the new file.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue