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