mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-16 16:29:55 +00:00
fix persistent buffers and glsl_version
This commit is contained in:
parent
e67135a517
commit
9a5fa9b8d6
9 changed files with 172 additions and 58 deletions
|
@ -238,10 +238,35 @@ namespace MWRender
|
||||||
|
|
||||||
if (pass.mRenderTarget)
|
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);
|
pass.mRenderTarget->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER);
|
||||||
|
|
||||||
if (pass.mRenderTexture->getNumMipmapLevels() > 0)
|
if (pass.mRenderTexture->getNumMipmapLevels() > 0)
|
||||||
{
|
{
|
||||||
|
|
||||||
state.setActiveTextureUnit(0);
|
state.setActiveTextureUnit(0);
|
||||||
state.applyTextureAttribute(0,
|
state.applyTextureAttribute(0,
|
||||||
pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0)
|
pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0)
|
||||||
|
|
|
@ -30,6 +30,8 @@ namespace MWRender
|
||||||
|
|
||||||
void dirty() { mDirty = true; }
|
void dirty() { mDirty = true; }
|
||||||
|
|
||||||
|
void resizeRenderTargets() { mDirtyAttachments = true; }
|
||||||
|
|
||||||
const fx::DispatchArray& getPasses() { return mPasses; }
|
const fx::DispatchArray& getPasses() { return mPasses; }
|
||||||
|
|
||||||
void setPasses(fx::DispatchArray&& passes);
|
void setPasses(fx::DispatchArray&& passes);
|
||||||
|
@ -65,6 +67,7 @@ namespace MWRender
|
||||||
osg::ref_ptr<osg::Texture> mTextureNormals;
|
osg::ref_ptr<osg::Texture> mTextureNormals;
|
||||||
|
|
||||||
mutable bool mDirty = false;
|
mutable bool mDirty = false;
|
||||||
|
mutable bool mDirtyAttachments = false;
|
||||||
mutable osg::ref_ptr<osg::Viewport> mRenderViewport;
|
mutable osg::ref_ptr<osg::Viewport> mRenderViewport;
|
||||||
mutable osg::ref_ptr<osg::FrameBufferObject> mMultiviewResolveFramebuffer;
|
mutable osg::ref_ptr<osg::FrameBufferObject> mMultiviewResolveFramebuffer;
|
||||||
mutable osg::ref_ptr<osg::FrameBufferObject> mDestinationFBO;
|
mutable osg::ref_ptr<osg::FrameBufferObject> mDestinationFBO;
|
||||||
|
|
|
@ -211,25 +211,14 @@ namespace MWRender
|
||||||
if (Stereo::getStereo())
|
if (Stereo::getStereo())
|
||||||
Stereo::Manager::instance().screenResolutionChanged();
|
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;
|
size_t frameId = frame() % 2;
|
||||||
|
|
||||||
createObjectsForFrame(frameId);
|
createObjectsForFrame(frameId);
|
||||||
|
|
||||||
mRendering.updateProjectionMatrix();
|
mRendering.updateProjectionMatrix();
|
||||||
mRendering.setScreenRes(width, height);
|
mRendering.setScreenRes(renderWidth(), renderHeight());
|
||||||
|
|
||||||
dirtyTechniques();
|
dirtyTechniques(true);
|
||||||
|
|
||||||
mDirty = true;
|
mDirty = true;
|
||||||
mDirtyFrameId = !frameId;
|
mDirtyFrameId = !frameId;
|
||||||
|
@ -534,7 +523,7 @@ namespace MWRender
|
||||||
mCanvases[frameId]->dirty();
|
mCanvases[frameId]->dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PostProcessor::dirtyTechniques()
|
void PostProcessor::dirtyTechniques(bool dirtyAttachments)
|
||||||
{
|
{
|
||||||
size_t frameId = frame() % 2;
|
size_t frameId = frame() % 2;
|
||||||
|
|
||||||
|
@ -613,8 +602,6 @@ namespace MWRender
|
||||||
uniform->mName.c_str(), *type, uniform->getNumElements()));
|
uniform->mName.c_str(), *type, uniform->getNumElements()));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unordered_map<osg::Texture2D*, osg::Texture2D*> renderTargetCache;
|
|
||||||
|
|
||||||
for (const auto& pass : technique->getPasses())
|
for (const auto& pass : technique->getPasses())
|
||||||
{
|
{
|
||||||
int subTexUnit = texUnit;
|
int subTexUnit = texUnit;
|
||||||
|
@ -626,32 +613,27 @@ namespace MWRender
|
||||||
|
|
||||||
if (!pass->getTarget().empty())
|
if (!pass->getTarget().empty())
|
||||||
{
|
{
|
||||||
const auto& rt = technique->getRenderTargetsMap()[pass->getTarget()];
|
const auto& renderTarget = technique->getRenderTargetsMap()[pass->getTarget()];
|
||||||
|
subPass.mSize = renderTarget.mSize;
|
||||||
const auto [w, h] = rt.mSize.get(renderWidth(), renderHeight());
|
subPass.mRenderTexture = renderTarget.mTarget;
|
||||||
|
subPass.mMipMap = renderTarget.mMipMap;
|
||||||
subPass.mRenderTexture = new osg::Texture2D(*rt.mTarget);
|
subPass.mStateSet->setAttributeAndModes(new osg::Viewport(
|
||||||
renderTargetCache[rt.mTarget] = subPass.mRenderTexture;
|
0, 0, subPass.mRenderTexture->getTextureWidth(), subPass.mRenderTexture->getTextureHeight()));
|
||||||
subPass.mRenderTexture->setTextureSize(w, h);
|
|
||||||
subPass.mRenderTexture->setName(std::string(pass->getTarget()));
|
|
||||||
|
|
||||||
if (rt.mMipMap)
|
|
||||||
subPass.mRenderTexture->setNumMipmapLevels(osg::Image::computeNumberOfMipmapLevels(w, h));
|
|
||||||
|
|
||||||
subPass.mRenderTarget = new osg::FrameBufferObject;
|
subPass.mRenderTarget = new osg::FrameBufferObject;
|
||||||
subPass.mRenderTarget->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0,
|
subPass.mRenderTarget->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0,
|
||||||
osg::FrameBufferAttachment(subPass.mRenderTexture));
|
osg::FrameBufferAttachment(subPass.mRenderTexture));
|
||||||
|
|
||||||
|
const auto [w, h] = renderTarget.mSize.get(renderWidth(), renderHeight());
|
||||||
subPass.mStateSet->setAttributeAndModes(new osg::Viewport(0, 0, w, h));
|
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);
|
subPass.mStateSet->setTextureAttribute(subTexUnit, technique->getRenderTargetsMap()[name].mTarget);
|
||||||
if (it != technique->getRenderTargetsMap().end() && renderTargetCache[it->second.mTarget])
|
subPass.mStateSet->addUniform(new osg::Uniform(name.c_str(), subTexUnit));
|
||||||
{
|
|
||||||
subPass.mStateSet->setTextureAttribute(subTexUnit, renderTargetCache[it->second.mTarget]);
|
subTexUnit++;
|
||||||
subPass.mStateSet->addUniform(new osg::Uniform(std::string(it->first).c_str(), subTexUnit++));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
node.mPasses.emplace_back(std::move(subPass));
|
node.mPasses.emplace_back(std::move(subPass));
|
||||||
|
@ -668,6 +650,9 @@ namespace MWRender
|
||||||
hud->updateTechniques();
|
hud->updateTechniques();
|
||||||
|
|
||||||
mRendering.getSkyManager()->setSunglare(sunglare);
|
mRendering.getSkyManager()->setSunglare(sunglare);
|
||||||
|
|
||||||
|
if (dirtyAttachments)
|
||||||
|
mCanvases[frameId]->resizeRenderTargets();
|
||||||
}
|
}
|
||||||
|
|
||||||
PostProcessor::Status PostProcessor::enableTechnique(
|
PostProcessor::Status PostProcessor::enableTechnique(
|
||||||
|
@ -774,7 +759,7 @@ namespace MWRender
|
||||||
for (auto& technique : mTemplates)
|
for (auto& technique : mTemplates)
|
||||||
technique->compile();
|
technique->compile();
|
||||||
|
|
||||||
dirtyTechniques();
|
dirtyTechniques(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PostProcessor::disableDynamicShaders()
|
void PostProcessor::disableDynamicShaders()
|
||||||
|
|
|
@ -204,7 +204,7 @@ namespace MWRender
|
||||||
|
|
||||||
void createObjectsForFrame(size_t frameId);
|
void createObjectsForFrame(size_t frameId);
|
||||||
|
|
||||||
void dirtyTechniques();
|
void dirtyTechniques(bool dirtyAttachments = false);
|
||||||
|
|
||||||
void update(size_t frameId);
|
void update(size_t frameId);
|
||||||
|
|
||||||
|
|
|
@ -339,7 +339,7 @@ float omw_EstimateFogCoverageFromUV(vec2 uv)
|
||||||
if (mCompiled)
|
if (mCompiled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
mLegacyGLSL = technique.getGLSLVersion() != 330;
|
mLegacyGLSL = technique.getGLSLVersion() < 330;
|
||||||
|
|
||||||
if (mType == Type::Pixel)
|
if (mType == Type::Pixel)
|
||||||
{
|
{
|
||||||
|
|
|
@ -279,6 +279,7 @@ namespace fx
|
||||||
rt.mTarget->setSourceType(GL_UNSIGNED_BYTE);
|
rt.mTarget->setSourceType(GL_UNSIGNED_BYTE);
|
||||||
rt.mTarget->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
|
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->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
|
||||||
|
rt.mTarget->setName(std::string(mBlockName));
|
||||||
|
|
||||||
while (!isNext<Lexer::Close_bracket>() && !isNext<Lexer::Eof>())
|
while (!isNext<Lexer::Close_bracket>() && !isNext<Lexer::Eof>())
|
||||||
{
|
{
|
||||||
|
|
|
@ -54,10 +54,14 @@ namespace fx
|
||||||
osg::ref_ptr<osg::FrameBufferObject> mRenderTarget;
|
osg::ref_ptr<osg::FrameBufferObject> mRenderTarget;
|
||||||
osg::ref_ptr<osg::Texture2D> mRenderTexture;
|
osg::ref_ptr<osg::Texture2D> mRenderTexture;
|
||||||
bool mResolve = false;
|
bool mResolve = false;
|
||||||
|
Types::SizeProxy mSize;
|
||||||
|
bool mMipMap;
|
||||||
|
|
||||||
SubPass(const SubPass& other, const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY)
|
SubPass(const SubPass& other, const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY)
|
||||||
: mStateSet(new osg::StateSet(*other.mStateSet, copyOp))
|
: mStateSet(new osg::StateSet(*other.mStateSet, copyOp))
|
||||||
, mResolve(other.mResolve)
|
, mResolve(other.mResolve)
|
||||||
|
, mSize(other.mSize)
|
||||||
|
, mMipMap(other.mMipMap)
|
||||||
{
|
{
|
||||||
if (other.mRenderTarget)
|
if (other.mRenderTarget)
|
||||||
mRenderTarget = new osg::FrameBufferObject(*other.mRenderTarget, copyOp);
|
mRenderTarget = new osg::FrameBufferObject(*other.mRenderTarget, copyOp);
|
||||||
|
|
|
@ -29,6 +29,16 @@ namespace fx
|
||||||
std::optional<int> mWidth;
|
std::optional<int> mWidth;
|
||||||
std::optional<int> mHeight;
|
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
|
std::tuple<int, int> get(int width, int height) const
|
||||||
{
|
{
|
||||||
int scaledWidth = width;
|
int scaledWidth = width;
|
||||||
|
|
|
@ -529,48 +529,134 @@ is not wanted and you want a custom render target.
|
||||||
| mipmaps | boolean | Whether mipmaps should be generated every frame |
|
| 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,
|
Blending modes can be useful at times. Below is a simple shader which, when activated, will slowly turn the screen pure red.
|
||||||
only three render targets can be bound per pass with ``rt1``, ``rt2``, ``rt3``, respectively.
|
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
|
.. code-block:: none
|
||||||
|
|
||||||
render_target RT_Downsample {
|
render_target RT_Red {
|
||||||
width_ratio = 0.5;
|
width = 4;
|
||||||
height_ratio = 0.5;
|
height = 4;
|
||||||
internal_format = r16f;
|
source_format = rgb;
|
||||||
|
internal_format = rgb16f;
|
||||||
source_type = float;
|
source_type = float;
|
||||||
source_format = red;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render_target RT_Downsample4 {
|
fragment red(target=RT_Red,blend=(add, src_color, one), rt1=RT_Red) {
|
||||||
width_ratio = 0.25;
|
|
||||||
height_ratio = 0.25;
|
|
||||||
}
|
|
||||||
|
|
||||||
fragment downsample2x(target=RT_Downsample) {
|
|
||||||
|
|
||||||
omw_In vec2 omw_TexCoord;
|
omw_In vec2 omw_TexCoord;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
omw_FragColor.r = omw_GetLastShader(omw_TexCoord).r;
|
omw_FragColor.rgb = vec3(0.01,0,0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment downsample4x(target=RT_Downsample4, rt1=RT_Downsample) {
|
fragment view(rt1=RT_Red) {
|
||||||
|
|
||||||
omw_In vec2 omw_TexCoord;
|
omw_In vec2 omw_TexCoord;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
omw_FragColor = omw_Texture2D(RT_Downsample, omw_TexCoord);
|
omw_FragColor = omw_Texture2D(RT_Red, omw_TexCoord);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Now, when the `downsample2x` pass runs it will write to the target buffer instead of the default
|
technique {
|
||||||
one assigned by the engine.
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
vec3 orgi = pow(omw_GetLastShader(omw_TexCoord), vec4(2.2)).rgb;
|
||||||
|
omw_FragColor.rgb = orgi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
technique {
|
||||||
|
author = "OpenMW";
|
||||||
|
passes = calculateLum, fetchLumAvg, store, adaptation;
|
||||||
|
glsl_version = 330;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Simple Example
|
Simple Example
|
||||||
##############
|
##############
|
||||||
|
|
Loading…
Reference in a new issue