Skip to content

Commit

Permalink
Merge branch 'fix_persistent_buffers' into 'master'
Browse files Browse the repository at this point in the history
Fix persistent buffers and issue with glsl_version

See merge request OpenMW/openmw!3553
  • Loading branch information
Capostrophic committed Nov 3, 2023
2 parents f992d0d + 9a5fa9b commit c1f7a9c
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 54 deletions.
25 changes: 25 additions & 0 deletions apps/openmw/mwrender/pingpongcanvas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,35 @@ namespace MWRender

if (pass.mRenderTarget)
{
if (mDirtyAttachments)
{
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.
osg::Texture2D* texture = const_cast<osg::Texture2D*>(dynamic_cast<const osg::Texture2D*>(
pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0)
.getTexture()));

texture->setTextureSize(w, h);
texture->setNumMipmapLevels(pass.mRenderTexture->getNumMipmapLevels());
texture->dirtyTextureObject();

mDirtyAttachments = false;
}

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)
Expand Down
3 changes: 3 additions & 0 deletions apps/openmw/mwrender/pingpongcanvas.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ namespace MWRender

void dirty() { mDirty = true; }

void resizeRenderTargets() { mDirtyAttachments = true; }

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

void setPasses(fx::DispatchArray&& passes);
Expand Down Expand Up @@ -65,6 +67,7 @@ namespace MWRender
osg::ref_ptr<osg::Texture> mTextureNormals;

mutable bool mDirty = false;
mutable bool mDirtyAttachments = false;
mutable osg::ref_ptr<osg::Viewport> mRenderViewport;
mutable osg::ref_ptr<osg::FrameBufferObject> mMultiviewResolveFramebuffer;
mutable osg::ref_ptr<osg::FrameBufferObject> mDestinationFBO;
Expand Down
55 changes: 20 additions & 35 deletions apps/openmw/mwrender/postprocessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,25 +211,14 @@ namespace MWRender
if (Stereo::getStereo())
Stereo::Manager::instance().screenResolutionChanged();

auto width = renderWidth();
auto height = renderHeight();
for (auto& technique : mTechniques)
{
for (auto& [name, rt] : technique->getRenderTargetsMap())
{
const auto [w, h] = rt.mSize.get(width, height);
rt.mTarget->setTextureSize(w, h);
}
}

size_t frameId = frame() % 2;

createObjectsForFrame(frameId);

mRendering.updateProjectionMatrix();
mRendering.setScreenRes(width, height);
mRendering.setScreenRes(renderWidth(), renderHeight());

dirtyTechniques();
dirtyTechniques(true);

mDirty = true;
mDirtyFrameId = !frameId;
Expand Down Expand Up @@ -534,7 +523,7 @@ namespace MWRender
mCanvases[frameId]->dirty();
}

void PostProcessor::dirtyTechniques()
void PostProcessor::dirtyTechniques(bool dirtyAttachments)
{
size_t frameId = frame() % 2;

Expand Down Expand Up @@ -613,8 +602,6 @@ namespace MWRender
uniform->mName.c_str(), *type, uniform->getNumElements()));
}

std::unordered_map<osg::Texture2D*, osg::Texture2D*> renderTargetCache;

for (const auto& pass : technique->getPasses())
{
int subTexUnit = texUnit;
Expand All @@ -626,32 +613,27 @@ namespace MWRender

if (!pass->getTarget().empty())
{
const auto& rt = technique->getRenderTargetsMap()[pass->getTarget()];

const auto [w, h] = rt.mSize.get(renderWidth(), renderHeight());

subPass.mRenderTexture = new osg::Texture2D(*rt.mTarget);
renderTargetCache[rt.mTarget] = subPass.mRenderTexture;
subPass.mRenderTexture->setTextureSize(w, h);
subPass.mRenderTexture->setName(std::string(pass->getTarget()));

if (rt.mMipMap)
subPass.mRenderTexture->setNumMipmapLevels(osg::Image::computeNumberOfMipmapLevels(w, h));
const auto& renderTarget = technique->getRenderTargetsMap()[pass->getTarget()];
subPass.mSize = renderTarget.mSize;
subPass.mRenderTexture = renderTarget.mTarget;
subPass.mMipMap = renderTarget.mMipMap;
subPass.mStateSet->setAttributeAndModes(new osg::Viewport(
0, 0, subPass.mRenderTexture->getTextureWidth(), subPass.mRenderTexture->getTextureHeight()));

subPass.mRenderTarget = new osg::FrameBufferObject;
subPass.mRenderTarget->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0,
osg::FrameBufferAttachment(subPass.mRenderTexture));

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

for (const auto& whitelist : pass->getRenderTargets())
for (const auto& name : pass->getRenderTargets())
{
auto it = technique->getRenderTargetsMap().find(whitelist);
if (it != technique->getRenderTargetsMap().end() && renderTargetCache[it->second.mTarget])
{
subPass.mStateSet->setTextureAttribute(subTexUnit, renderTargetCache[it->second.mTarget]);
subPass.mStateSet->addUniform(new osg::Uniform(std::string(it->first).c_str(), subTexUnit++));
}
subPass.mStateSet->setTextureAttribute(subTexUnit, technique->getRenderTargetsMap()[name].mTarget);
subPass.mStateSet->addUniform(new osg::Uniform(name.c_str(), subTexUnit));

subTexUnit++;
}

node.mPasses.emplace_back(std::move(subPass));
Expand All @@ -668,6 +650,9 @@ namespace MWRender
hud->updateTechniques();

mRendering.getSkyManager()->setSunglare(sunglare);

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

PostProcessor::Status PostProcessor::enableTechnique(
Expand Down Expand Up @@ -774,7 +759,7 @@ namespace MWRender
for (auto& technique : mTemplates)
technique->compile();

dirtyTechniques();
dirtyTechniques(true);
}

void PostProcessor::disableDynamicShaders()
Expand Down
2 changes: 1 addition & 1 deletion apps/openmw/mwrender/postprocessor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ namespace MWRender

void createObjectsForFrame(size_t frameId);

void dirtyTechniques();
void dirtyTechniques(bool dirtyAttachments = false);

void update(size_t frameId);

Expand Down
2 changes: 1 addition & 1 deletion components/fx/pass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ float omw_EstimateFogCoverageFromUV(vec2 uv)
if (mCompiled)
return;

mLegacyGLSL = technique.getGLSLVersion() != 330;
mLegacyGLSL = technique.getGLSLVersion() < 330;

if (mType == Type::Pixel)
{
Expand Down
1 change: 1 addition & 0 deletions components/fx/technique.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ namespace fx
rt.mTarget->setSourceType(GL_UNSIGNED_BYTE);
rt.mTarget->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
rt.mTarget->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
rt.mTarget->setName(std::string(mBlockName));

while (!isNext<Lexer::Close_bracket>() && !isNext<Lexer::Eof>())
{
Expand Down
4 changes: 4 additions & 0 deletions components/fx/technique.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,14 @@ namespace fx
osg::ref_ptr<osg::FrameBufferObject> mRenderTarget;
osg::ref_ptr<osg::Texture2D> mRenderTexture;
bool mResolve = false;
Types::SizeProxy mSize;
bool mMipMap;

SubPass(const SubPass& other, const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY)
: mStateSet(new osg::StateSet(*other.mStateSet, copyOp))
, mResolve(other.mResolve)
, mSize(other.mSize)
, mMipMap(other.mMipMap)
{
if (other.mRenderTarget)
mRenderTarget = new osg::FrameBufferObject(*other.mRenderTarget, copyOp);
Expand Down
10 changes: 10 additions & 0 deletions components/fx/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ namespace fx
std::optional<int> mWidth;
std::optional<int> mHeight;

SizeProxy() = default;

SizeProxy(const SizeProxy& other)
: mWidthRatio(other.mWidthRatio)
, mHeightRatio(other.mHeightRatio)
, mWidth(other.mWidth)
, mHeight(other.mHeight)
{
}

std::tuple<int, int> get(int width, int height) const
{
int scaledWidth = width;
Expand Down
120 changes: 103 additions & 17 deletions docs/source/reference/postprocessing/omwfx.rst
Original file line number Diff line number Diff line change
Expand Up @@ -529,48 +529,134 @@ is not wanted and you want a custom render target.
| mipmaps | boolean | Whether mipmaps should be generated every frame |
+------------------+---------------------+-----------------------------------------------------------------------------+

To use the render target a pass must be assigned to it, along with any optional clear or blend modes.
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.

In the code snippet below a rendertarget is used to draw the red channel of a scene at half resolution, then a quarter. As a restriction,
only three render targets can be bound per pass with ``rt1``, ``rt2``, ``rt3``, respectively.
Blending modes can be useful at times. Below is a simple shader which, when activated, will slowly turn the screen pure red.
Notice how we only ever write the value `.01` to the `RT_Red` buffer. Since we're using appropriate blending modes the
color buffer will accumulate.

.. code-block:: none
render_target RT_Downsample {
width_ratio = 0.5;
height_ratio = 0.5;
internal_format = r16f;
render_target RT_Red {
width = 4;
height = 4;
source_format = rgb;
internal_format = rgb16f;
source_type = float;
source_format = red;
}
render_target RT_Downsample4 {
width_ratio = 0.25;
height_ratio = 0.25;
fragment red(target=RT_Red,blend=(add, src_color, one), rt1=RT_Red) {
omw_In vec2 omw_TexCoord;
void main()
{
omw_FragColor.rgb = vec3(0.01,0,0);
}
}
fragment view(rt1=RT_Red) {
omw_In vec2 omw_TexCoord;
void main()
{
omw_FragColor = omw_Texture2D(RT_Red, omw_TexCoord);
}
}
technique {
author = "OpenMW";
passes = red, view;
}
These custom render targets are persistent and ownership is given to the shader which defines them.
This gives potential to implement temporal effects by storing previous frame data in these buffers.
Below is an example which calculates a naive average scene luminance and transitions between values smoothly.

.. code-block:: none
render_target RT_Lum {
width = 256;
height = 256;
mipmaps = true;
source_format = rgb;
internal_format = rgb16f;
source_type = float;
min_filter = linear_mipmap_linear;
mag_filter = linear;
}
render_target RT_LumAvg {
source_type = float;
source_format = rgb;
internal_format = rgb16f;
min_filter = nearest;
mag_filter = nearest;
}
fragment downsample2x(target=RT_Downsample) {
render_target RT_LumAvgLastFrame {
source_type = float;
source_format = rgb;
internal_format = rgb16f;
min_filter = nearest;
mag_filter = nearest;
}
fragment calculateLum(target=RT_Lum) {
omw_In vec2 omw_TexCoord;
void main()
{
omw_FragColor.r = omw_GetLastShader(omw_TexCoord).r;
vec3 orgi = pow(omw_GetLastShader(omw_TexCoord), vec4(2.2)).rgb;
omw_FragColor.rgb = orgi;
}
}
fragment downsample4x(target=RT_Downsample4, rt1=RT_Downsample) {
fragment fetchLumAvg(target=RT_LumAvg, rt1=RT_Lum, rt2=RT_LumAvgLastFrame) {
omw_In vec2 omw_TexCoord;
void main()
{
vec3 avgLumaCurrFrame = textureLod(RT_Lum, vec2(0.5, 0.5), 6).rgb;
vec3 avgLumaLastFrame = omw_Texture2D(RT_LumAvgLastFrame, vec2(0.5, 0.5)).rgb;
const float speed = 0.9;
vec3 avgLuma = avgLumaLastFrame + (avgLumaCurrFrame - avgLumaLastFrame) * (1.0 - exp(-omw.deltaSimulationTime * speed));
omw_FragColor.rgb = avgLuma;
}
}
fragment adaptation(rt1=RT_LumAvg) {
omw_In vec2 omw_TexCoord;
void main()
{
omw_FragColor = omw_Texture2D(RT_Downsample, omw_TexCoord);
vec3 avgLuma = omw_Texture2D(RT_LumAvg, vec2(0.5, 0.5)).rgb;
if (omw_TexCoord.y < 0.2)
omw_FragColor = vec4(avgLuma, 1.0);
else
omw_FragColor = omw_GetLastShader(omw_TexCoord);
}
}
fragment store(target=RT_LumAvgLastFrame, rt1=RT_LumAvg) {
void main()
{
vec3 avgLuma = omw_Texture2D(RT_LumAvg, vec2(0.5, 0.5)).rgb;
omw_FragColor.rgb = avgLuma;
}
}
Now, when the `downsample2x` pass runs it will write to the target buffer instead of the default
one assigned by the engine.
technique {
author = "OpenMW";
passes = calculateLum, fetchLumAvg, store, adaptation;
glsl_version = 330;
}
Simple Example
##############
Expand Down

0 comments on commit c1f7a9c

Please sign in to comment.