Skip to content

Commit

Permalink
Merge branch 'dont_reset_luminance' into 'master'
Browse files Browse the repository at this point in the history
consistent average scene luminance (#7679)

See merge request OpenMW/openmw!3579
  • Loading branch information
jvoisin committed Nov 14, 2023
2 parents fe02978 + 85fcfba commit 710b560
Show file tree
Hide file tree
Showing 11 changed files with 100 additions and 45 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
Bug #7654: Tooltips for enchantments with invalid effects cause crashes
Bug #7660: Some inconsistencies regarding Invisibility breaking
Bug #7675: Successful lock spell doesn't produce a sound
Bug #7679: Scene luminance value flashes when toggling shaders
Feature #3537: Shader-based water ripples
Feature #5492: Let rain and snow collide with statics
Feature #6149: Dehardcode Lua API_REVISION
Expand Down
37 changes: 22 additions & 15 deletions apps/openmw/mwrender/luminancecalculator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,6 @@ namespace MWRender

mResolveProgram = shaderManager.getProgram(vertex, std::move(resolveFragment));
mLuminanceProgram = shaderManager.getProgram(vertex, std::move(luminanceFragment));
}

void LuminanceCalculator::compile()
{
int mipmapLevels = osg::Image::computeNumberOfMipmapLevels(mWidth, mHeight);

for (auto& buffer : mBuffers)
{
Expand All @@ -38,7 +33,6 @@ namespace MWRender
osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR_MIPMAP_NEAREST);
buffer.mipmappedSceneLuminanceTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::LINEAR);
buffer.mipmappedSceneLuminanceTex->setTextureSize(mWidth, mHeight);
buffer.mipmappedSceneLuminanceTex->setNumMipmapLevels(mipmapLevels);

buffer.luminanceTex = new osg::Texture2D;
buffer.luminanceTex->setInternalFormat(GL_R16F);
Expand All @@ -62,14 +56,6 @@ namespace MWRender
buffer.luminanceProxyFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0,
osg::FrameBufferAttachment(buffer.luminanceProxyTex));

buffer.resolveSceneLumFbo = new osg::FrameBufferObject;
buffer.resolveSceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0,
osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex, mipmapLevels - 1));

buffer.sceneLumFbo = new osg::FrameBufferObject;
buffer.sceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0,
osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex));

buffer.sceneLumSS = new osg::StateSet;
buffer.sceneLumSS->setAttributeAndModes(mLuminanceProgram);
buffer.sceneLumSS->addUniform(new osg::Uniform("sceneTex", 0));
Expand All @@ -84,6 +70,26 @@ namespace MWRender

mBuffers[0].resolveSS->setTextureAttributeAndModes(1, mBuffers[1].luminanceTex);
mBuffers[1].resolveSS->setTextureAttributeAndModes(1, mBuffers[0].luminanceTex);
}

void LuminanceCalculator::compile()
{
int mipmapLevels = osg::Image::computeNumberOfMipmapLevels(mWidth, mHeight);

for (auto& buffer : mBuffers)
{
buffer.mipmappedSceneLuminanceTex->setTextureSize(mWidth, mHeight);
buffer.mipmappedSceneLuminanceTex->setNumMipmapLevels(mipmapLevels);
buffer.mipmappedSceneLuminanceTex->dirtyTextureObject();

buffer.resolveSceneLumFbo = new osg::FrameBufferObject;
buffer.resolveSceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0,
osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex, mipmapLevels - 1));

buffer.sceneLumFbo = new osg::FrameBufferObject;
buffer.sceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0,
osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex));
}

mCompiled = true;
}
Expand Down Expand Up @@ -114,13 +120,14 @@ namespace MWRender
buffer.luminanceProxyFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER);
ext->glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST);

if (dirty)
if (mIsBlank)
{
// Use current frame data for previous frame to warm up calculations and prevent popin
mBuffers[(frameId + 1) % 2].resolveFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER);
ext->glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST);

buffer.luminanceProxyFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER);
mIsBlank = false;
}

buffer.resolveFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER);
Expand Down
1 change: 1 addition & 0 deletions apps/openmw/mwrender/luminancecalculator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ namespace MWRender

bool mCompiled = false;
bool mEnabled = false;
bool mIsBlank = true;

int mWidth = 1;
int mHeight = 1;
Expand Down
44 changes: 35 additions & 9 deletions apps/openmw/mwrender/pingpongcanvas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,39 @@ namespace MWRender
}
};

// When textures are created (or resized) we need to either dirty them and/or clear them.
// Otherwise, there will be undefined behavior when reading from a texture that has yet to be written to in a
// later pass.
for (const auto& attachment : mDirtyAttachments)
{
const auto [w, h]
= attachment.mSize.get(mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight());

attachment.mTarget->setTextureSize(w, h);
if (attachment.mMipMap)
attachment.mTarget->setNumMipmapLevels(osg::Image::computeNumberOfMipmapLevels(w, h));
attachment.mTarget->dirtyTextureObject();

osg::ref_ptr<osg::FrameBufferObject> fbo = new osg::FrameBufferObject;

fbo->setAttachment(
osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(attachment.mTarget));
fbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER);

glViewport(0, 0, attachment.mTarget->getTextureWidth(), attachment.mTarget->getTextureHeight());
state.haveAppliedAttribute(osg::StateAttribute::VIEWPORT);
glClearColor(attachment.mClearColor.r(), attachment.mClearColor.g(), attachment.mClearColor.b(),
attachment.mClearColor.a());
glClear(GL_COLOR_BUFFER_BIT);

if (attachment.mTarget->getNumMipmapLevels() > 0)
{
state.setActiveTextureUnit(0);
state.applyTextureAttribute(0, attachment.mTarget);
ext->glGenerateMipmap(GL_TEXTURE_2D);
}
}

for (const size_t& index : filtered)
{
const auto& node = mPasses[index];
Expand Down Expand Up @@ -239,16 +272,11 @@ namespace MWRender

if (pass.mRenderTarget)
{
if (mDirtyAttachments)
if (mDirtyAttachments.size() > 0)
{
const auto [w, h]
= pass.mSize.get(mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight());

pass.mRenderTexture->setTextureSize(w, h);
if (pass.mMipMap)
pass.mRenderTexture->setNumMipmapLevels(osg::Image::computeNumberOfMipmapLevels(w, h));
pass.mRenderTexture->dirtyTextureObject();

// Custom render targets must be shared between frame ids, so it's impossible to double buffer
// without expensive copies. That means the only thread-safe place to resize is in the draw
// thread.
Expand All @@ -265,7 +293,6 @@ namespace MWRender

if (pass.mRenderTexture->getNumMipmapLevels() > 0)
{

state.setActiveTextureUnit(0);
state.applyTextureAttribute(0,
pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0)
Expand Down Expand Up @@ -336,7 +363,6 @@ namespace MWRender
bindDestinationFbo();
}

if (mDirtyAttachments)
mDirtyAttachments = false;
mDirtyAttachments.clear();
}
}
7 changes: 5 additions & 2 deletions apps/openmw/mwrender/pingpongcanvas.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ namespace MWRender

void dirty() { mDirty = true; }

void resizeRenderTargets() { mDirtyAttachments = true; }
void setDirtyAttachments(const std::vector<fx::Types::RenderTarget>& attachments)
{
mDirtyAttachments = attachments;
}

const fx::DispatchArray& getPasses() { return mPasses; }

Expand Down Expand Up @@ -68,7 +71,7 @@ namespace MWRender
osg::ref_ptr<osg::Texture> mTextureNormals;

mutable bool mDirty = false;
mutable bool mDirtyAttachments = false;
mutable std::vector<fx::Types::RenderTarget> mDirtyAttachments;
mutable osg::ref_ptr<osg::Viewport> mRenderViewport;
mutable osg::ref_ptr<osg::FrameBufferObject> mMultiviewResolveFramebuffer;
mutable osg::ref_ptr<osg::FrameBufferObject> mDestinationFBO;
Expand Down
24 changes: 20 additions & 4 deletions apps/openmw/mwrender/postprocessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,8 @@ namespace MWRender
mNormals = false;
mPassLights = false;

std::vector<fx::Types::RenderTarget> attachmentsToDirty;

for (const auto& technique : mTechniques)
{
if (!technique->isValid())
Expand Down Expand Up @@ -617,7 +619,7 @@ namespace MWRender

if (!pass->getTarget().empty())
{
const auto& renderTarget = technique->getRenderTargetsMap()[pass->getTarget()];
auto& renderTarget = technique->getRenderTargetsMap()[pass->getTarget()];
subPass.mSize = renderTarget.mSize;
subPass.mRenderTexture = renderTarget.mTarget;
subPass.mMipMap = renderTarget.mMipMap;
Expand All @@ -628,13 +630,27 @@ namespace MWRender

const auto [w, h] = renderTarget.mSize.get(renderWidth(), renderHeight());
subPass.mStateSet->setAttributeAndModes(new osg::Viewport(0, 0, w, h));

if (std::find_if(attachmentsToDirty.cbegin(), attachmentsToDirty.cend(),
[renderTarget](const auto& rt) { return renderTarget.mTarget == rt.mTarget; })
== attachmentsToDirty.cend())
{
attachmentsToDirty.push_back(fx::Types::RenderTarget(renderTarget));
}
}

for (const auto& name : pass->getRenderTargets())
{
subPass.mStateSet->setTextureAttribute(subTexUnit, technique->getRenderTargetsMap()[name].mTarget);
auto& renderTarget = technique->getRenderTargetsMap()[name];
subPass.mStateSet->setTextureAttribute(subTexUnit, renderTarget.mTarget);
subPass.mStateSet->addUniform(new osg::Uniform(name.c_str(), subTexUnit));

if (std::find_if(attachmentsToDirty.cbegin(), attachmentsToDirty.cend(),
[renderTarget](const auto& rt) { return renderTarget.mTarget == rt.mTarget; })
== attachmentsToDirty.cend())
{
attachmentsToDirty.push_back(fx::Types::RenderTarget(renderTarget));
}
subTexUnit++;
}

Expand All @@ -654,7 +670,7 @@ namespace MWRender
mRendering.getSkyManager()->setSunglare(sunglare);

if (dirtyAttachments)
mCanvases[frameId]->resizeRenderTargets();
mCanvases[frameId]->setDirtyAttachments(attachmentsToDirty);
}

PostProcessor::Status PostProcessor::enableTechnique(
Expand All @@ -668,7 +684,7 @@ namespace MWRender
int pos = std::min<int>(location.value_or(mTechniques.size()), mTechniques.size());

mTechniques.insert(mTechniques.begin() + pos, technique);
dirtyTechniques();
dirtyTechniques(Settings::ShaderManager::get().getMode() == Settings::ShaderManager::Mode::Debug);

return Status_Toggled;
}
Expand Down
4 changes: 0 additions & 4 deletions components/fx/pass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
#include <osg/StateSet>

#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/clearcolor.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include <components/settings/values.hpp>
#include <components/stereo/multiview.hpp>
Expand Down Expand Up @@ -326,9 +325,6 @@ float omw_EstimateFogCoverageFromUV(vec2 uv)

if (mBlendEq)
stateSet->setAttributeAndModes(new osg::BlendEquation(mBlendEq.value()));

if (mClearColor)
stateSet->setAttributeAndModes(new SceneUtil::ClearColor(mClearColor.value(), GL_COLOR_BUFFER_BIT));
}

void Pass::dirty()
Expand Down
1 change: 0 additions & 1 deletion components/fx/pass.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ namespace fx
std::array<std::string, 3> mRenderTargets;

std::string mTarget;
std::optional<osg::Vec4f> mClearColor;

std::optional<osg::BlendFunc::BlendFuncMode> mBlendSource;
std::optional<osg::BlendFunc::BlendFuncMode> mBlendDest;
Expand Down
12 changes: 2 additions & 10 deletions components/fx/technique.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,8 @@ namespace fx
rt.mTarget->setSourceFormat(parseSourceFormat());
else if (key == "mipmaps")
rt.mMipMap = parseBool();
else if (key == "clear_color")
rt.mClearColor = parseVec<osg::Vec4f, Lexer::Vec4>();
else
error(Misc::StringUtils::format("unexpected key '%s'", std::string(key)));

Expand Down Expand Up @@ -798,9 +800,6 @@ namespace fx
if (!pass)
pass = std::make_shared<fx::Pass>();

bool clear = true;
osg::Vec4f clearColor = { 1, 1, 1, 1 };

while (!isNext<Lexer::Eof>())
{
expect<Lexer::Literal>("invalid key in block header");
Expand Down Expand Up @@ -844,10 +843,6 @@ namespace fx
if (blendEq != osg::BlendEquation::FUNC_ADD)
pass->mBlendEq = blendEq;
}
else if (key == "clear")
clear = parseBool();
else if (key == "clear_color")
clearColor = parseVec<osg::Vec4f, Lexer::Vec4>();
else
error(Misc::StringUtils::format("unrecognized key '%s' in block header", std::string(key)));

Expand All @@ -865,9 +860,6 @@ namespace fx
return;
}

if (clear)
pass->mClearColor = clearColor;

error("malformed block header");
}

Expand Down
11 changes: 11 additions & 0 deletions components/fx/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@ namespace fx
osg::ref_ptr<osg::Texture2D> mTarget = new osg::Texture2D;
SizeProxy mSize;
bool mMipMap = false;
osg::Vec4f mClearColor = osg::Vec4f(0.0, 0.0, 0.0, 1.0);

RenderTarget() = default;

RenderTarget(const RenderTarget& other)
: mTarget(other.mTarget)
, mSize(other.mSize)
, mMipMap(other.mMipMap)
, mClearColor(other.mClearColor)
{
}
};

template <class T>
Expand Down
3 changes: 3 additions & 0 deletions docs/source/reference/postprocessing/omwfx.rst
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,8 @@ is not wanted and you want a custom render target.
+------------------+---------------------+-----------------------------------------------------------------------------+
| mipmaps | boolean | Whether mipmaps should be generated every frame |
+------------------+---------------------+-----------------------------------------------------------------------------+
| clear_color | vec4 | The color the texture will be cleared to when it's first created |
+------------------+---------------------+-----------------------------------------------------------------------------+

To use the render target a pass must be assigned to it, along with any optional blend modes.
As a restriction, only three render targets can be bound per pass with ``rt1``, ``rt2``, ``rt3``, respectively.
Expand All @@ -555,6 +557,7 @@ color buffer will accumulate.
source_format = rgb;
internal_format = rgb16f;
source_type = float;
clear_color = vec4(1,0,0,1);
}
fragment red(target=RT_Red,blend=(add, src_color, one), rt1=RT_Red) {
Expand Down

0 comments on commit 710b560

Please sign in to comment.