Reworked LLPostProcess and implemented FBO support (much faster if multiple post shaders are enabled, or need a lot of passes).
Tweaked LLRenderTarget to support depth textures if FBO support is lacking. Prefer LLRenderTarget::getFBO() over LLRenderTarget::sUseFBO when determining how to handle a specific LLRenderTarget object. (Decoupling to simplify logic without having to track a global)
This commit is contained in:
@@ -33,34 +33,247 @@
|
||||
#include "linden_common.h"
|
||||
|
||||
#include "llpostprocess.h"
|
||||
#include "llglslshader.h"
|
||||
#include "llsdserialize.h"
|
||||
#include "llrender.h"
|
||||
#include "llvertexbuffer.h"
|
||||
|
||||
#include "lldir.h"
|
||||
#include "llgl.h"
|
||||
#include "llglslshader.h"
|
||||
#include "llrender.h"
|
||||
#include "llsdserialize.h"
|
||||
#include "llsdutil.h"
|
||||
#include "llsdutil_math.h"
|
||||
#include "llvertexbuffer.h"
|
||||
#include "llfasttimer.h"
|
||||
|
||||
extern LLGLSLShader gPostColorFilterProgram;
|
||||
extern LLGLSLShader gPostNightVisionProgram;
|
||||
extern LLGLSLShader gPostGaussianBlurProgram;
|
||||
extern LLGLSLShader gPostPosterizeProgram;
|
||||
|
||||
static const unsigned int NOISE_SIZE = 512;
|
||||
|
||||
/// CALCULATING LUMINANCE (Using NTSC lum weights)
|
||||
/// http://en.wikipedia.org/wiki/Luma_%28video%29
|
||||
static const float LUMINANCE_R = 0.299f;
|
||||
static const float LUMINANCE_G = 0.587f;
|
||||
static const float LUMINANCE_B = 0.114f;
|
||||
|
||||
static const char * const XML_FILENAME = "postprocesseffects.xml";
|
||||
|
||||
LLPostProcess::LLPostProcess(void) :
|
||||
mVBO(NULL),
|
||||
mAllEffects(LLSD::emptyMap()),
|
||||
mScreenWidth(1), mScreenHeight(1)
|
||||
template<> LLSD LLPostProcessShader::LLShaderSetting<LLVector4>::getDefaultValue()
|
||||
{
|
||||
mSceneRenderTexture = NULL ;
|
||||
mNoiseTexture = NULL ;
|
||||
|
||||
return mDefault.getValue();
|
||||
}
|
||||
template<> void LLPostProcessShader::LLShaderSetting<LLVector4>::setValue(const LLSD& value)
|
||||
{
|
||||
mValue = ll_vector4_from_sd(value);
|
||||
}
|
||||
|
||||
LLSD LLPostProcessShader::getDefaults()
|
||||
{
|
||||
LLSD defaults;
|
||||
for(std::vector<LLShaderSettingBase*>::iterator it=mSettings.begin();it!=mSettings.end();++it)
|
||||
{
|
||||
defaults[(*it)->mSettingName]=(*it)->getDefaultValue();
|
||||
}
|
||||
return defaults;
|
||||
}
|
||||
void LLPostProcessShader::loadSettings(const LLSD& settings)
|
||||
{
|
||||
for(std::vector<LLShaderSettingBase*>::iterator it=mSettings.begin();it!=mSettings.end();++it)
|
||||
{
|
||||
LLSD value = settings[(*it)->mSettingName];
|
||||
(*it)->setValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
class LLColorFilterShader : public LLPostProcessShader
|
||||
{
|
||||
private:
|
||||
LLShaderSetting<bool> mEnabled;
|
||||
LLShaderSetting<F32> mGamma, mBrightness, mContrast, mSaturation;
|
||||
LLShaderSetting<LLVector4> mContrastBase;
|
||||
public:
|
||||
LLColorFilterShader() :
|
||||
mEnabled("enable_color_filter",false),
|
||||
mGamma("gamma",1.f),
|
||||
mBrightness("brightness",1.f),
|
||||
mContrast("contrast",1.f),
|
||||
mSaturation("saturation",1.f),
|
||||
mContrastBase("contrast_base",LLVector4(1.f,1.f,1.f,0.5f))
|
||||
{
|
||||
mSettings.push_back(&mEnabled);
|
||||
mSettings.push_back(&mGamma);
|
||||
mSettings.push_back(&mBrightness);
|
||||
mSettings.push_back(&mContrast);
|
||||
mSettings.push_back(&mSaturation);
|
||||
mSettings.push_back(&mContrastBase);
|
||||
}
|
||||
|
||||
bool isEnabled() { return mEnabled && gPostColorFilterProgram.mProgramObject; }
|
||||
S32 getColorChannel() { return 0; }
|
||||
S32 getDepthChannel() { return -1; }
|
||||
|
||||
QuadType bind()
|
||||
{
|
||||
if(!isEnabled())
|
||||
return QUAD_NONE;
|
||||
|
||||
/// CALCULATING LUMINANCE (Using NTSC lum weights)
|
||||
/// http://en.wikipedia.org/wiki/Luma_%28video%29
|
||||
static const float LUMINANCE_R = 0.299f;
|
||||
static const float LUMINANCE_G = 0.587f;
|
||||
static const float LUMINANCE_B = 0.114f;
|
||||
|
||||
gPostColorFilterProgram.bind();
|
||||
|
||||
gPostColorFilterProgram.uniform1f("gamma", mGamma);
|
||||
gPostColorFilterProgram.uniform1f("brightness", mBrightness);
|
||||
gPostColorFilterProgram.uniform1f("contrast", mContrast);
|
||||
float baseI = (mContrastBase.mValue[VX] + mContrastBase.mValue[VY] + mContrastBase.mValue[VZ]) / 3.0f;
|
||||
baseI = mContrastBase.mValue[VW] / ((baseI < 0.001f) ? 0.001f : baseI);
|
||||
float baseR = mContrastBase.mValue[VX] * baseI;
|
||||
float baseG = mContrastBase.mValue[VY] * baseI;
|
||||
float baseB = mContrastBase.mValue[VZ] * baseI;
|
||||
gPostColorFilterProgram.uniform3fv("contrastBase", 1, LLVector3(baseR, baseG, baseB).mV);
|
||||
gPostColorFilterProgram.uniform1f("saturation", mSaturation);
|
||||
gPostColorFilterProgram.uniform3fv("lumWeights", 1, LLVector3(LUMINANCE_R, LUMINANCE_G, LUMINANCE_B).mV);
|
||||
return QUAD_NORMAL;
|
||||
}
|
||||
bool draw(U32 pass) {return pass == 1;}
|
||||
void unbind()
|
||||
{
|
||||
gPostColorFilterProgram.unbind();
|
||||
}
|
||||
};
|
||||
|
||||
class LLNightVisionShader : public LLPostProcessShader
|
||||
{
|
||||
private:
|
||||
LLShaderSetting<bool> mEnabled;
|
||||
LLShaderSetting<F32> mBrightnessMult, mNoiseStrength;
|
||||
public:
|
||||
LLNightVisionShader() :
|
||||
mEnabled("enable_night_vision",false),
|
||||
mBrightnessMult("brightness_multiplier",3.f),
|
||||
mNoiseStrength("noise_strength",.4f)
|
||||
{
|
||||
mSettings.push_back(&mEnabled);
|
||||
mSettings.push_back(&mBrightnessMult);
|
||||
mSettings.push_back(&mNoiseStrength);
|
||||
}
|
||||
bool isEnabled() { return mEnabled && gPostNightVisionProgram.mProgramObject; }
|
||||
S32 getColorChannel() { return 0; }
|
||||
S32 getDepthChannel() { return -1; }
|
||||
QuadType bind()
|
||||
{
|
||||
if(!isEnabled())
|
||||
return QUAD_NONE;
|
||||
|
||||
gPostNightVisionProgram.bind();
|
||||
|
||||
LLPostProcess::getInstance()->bindNoise(1);
|
||||
|
||||
gPostNightVisionProgram.uniform1f("brightMult", mBrightnessMult);
|
||||
gPostNightVisionProgram.uniform1f("noiseStrength", mNoiseStrength);
|
||||
|
||||
return QUAD_NOISE;
|
||||
|
||||
}
|
||||
bool draw(U32 pass) {return pass == 1;}
|
||||
void unbind()
|
||||
{
|
||||
gPostNightVisionProgram.unbind();
|
||||
}
|
||||
};
|
||||
|
||||
class LLGaussBlurShader : public LLPostProcessShader
|
||||
{
|
||||
private:
|
||||
LLShaderSetting<bool> mEnabled;
|
||||
LLShaderSetting<S32> mNumPasses;
|
||||
GLint mPassLoc;
|
||||
public:
|
||||
LLGaussBlurShader() :
|
||||
mEnabled("enable_gauss_blur",false),
|
||||
mNumPasses("gauss_blur_passes",2),
|
||||
mPassLoc(0)
|
||||
{
|
||||
mSettings.push_back(&mEnabled);
|
||||
mSettings.push_back(&mNumPasses);
|
||||
}
|
||||
bool isEnabled() { return mEnabled && mNumPasses && gPostGaussianBlurProgram.mProgramObject; }
|
||||
S32 getColorChannel() { return 0; }
|
||||
S32 getDepthChannel() { return -1; }
|
||||
QuadType bind()
|
||||
{
|
||||
if(!isEnabled())
|
||||
return QUAD_NONE;
|
||||
|
||||
gPostGaussianBlurProgram.bind();
|
||||
|
||||
mPassLoc = gPostGaussianBlurProgram.getUniformLocation("horizontalPass");
|
||||
|
||||
return QUAD_NORMAL;
|
||||
}
|
||||
bool draw(U32 pass)
|
||||
{
|
||||
if((S32)pass > mNumPasses*2)
|
||||
return false;
|
||||
glUniform1iARB(mPassLoc, (pass-1) % 2);
|
||||
return true;
|
||||
}
|
||||
void unbind()
|
||||
{
|
||||
gPostGaussianBlurProgram.unbind();
|
||||
}
|
||||
};
|
||||
|
||||
class LLPosterizeShader : public LLPostProcessShader
|
||||
{
|
||||
private:
|
||||
LLShaderSetting<bool> mEnabled;
|
||||
LLShaderSetting<S32> mNumLayers;
|
||||
public:
|
||||
LLPosterizeShader() :
|
||||
mEnabled("enable_posterize",false),
|
||||
mNumLayers("posterize_layers",2)
|
||||
{
|
||||
mSettings.push_back(&mEnabled);
|
||||
mSettings.push_back(&mNumLayers);
|
||||
}
|
||||
bool isEnabled() { return mEnabled && gPostPosterizeProgram.mProgramObject; }
|
||||
S32 getColorChannel() { return 0; }
|
||||
S32 getDepthChannel() { return -1; }
|
||||
QuadType bind()
|
||||
{
|
||||
if(!isEnabled())
|
||||
return QUAD_NONE;
|
||||
|
||||
gPostPosterizeProgram.bind();
|
||||
|
||||
gPostPosterizeProgram.uniform1i("layerCount", mNumLayers);
|
||||
|
||||
return QUAD_NORMAL;
|
||||
}
|
||||
bool draw(U32 pass)
|
||||
{
|
||||
return pass == 1;
|
||||
}
|
||||
void unbind()
|
||||
{
|
||||
gPostPosterizeProgram.unbind();
|
||||
}
|
||||
};
|
||||
|
||||
LLPostProcess::LLPostProcess(void) :
|
||||
mVBO(NULL),
|
||||
mDepthTexture(0),
|
||||
mNoiseTexture(NULL),
|
||||
mScreenWidth(0),
|
||||
mScreenHeight(0),
|
||||
mNoiseTextureScale(0.f),
|
||||
mSelectedEffectInfo(LLSD::emptyMap()),
|
||||
mAllEffectInfo(LLSD::emptyMap())
|
||||
{
|
||||
mShaders.push_back(new LLColorFilterShader());
|
||||
mShaders.push_back(new LLNightVisionShader());
|
||||
mShaders.push_back(new LLGaussBlurShader());
|
||||
mShaders.push_back(new LLPosterizeShader());
|
||||
|
||||
/* Do nothing. Needs to be updated to use our current shader system, and to work with the move into llrender.*/
|
||||
std::string pathName(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "windlight", XML_FILENAME));
|
||||
LL_DEBUGS2("AppInit", "Shaders") << "Loading PostProcess Effects settings from " << pathName << LL_ENDL;
|
||||
@@ -71,99 +284,43 @@ LLPostProcess::LLPostProcess(void) :
|
||||
{
|
||||
LLPointer<LLSDParser> parser = new LLSDXMLParser();
|
||||
|
||||
parser->parse(effectsXML, mAllEffects, LLSDSerialize::SIZE_UNLIMITED);
|
||||
parser->parse(effectsXML, mAllEffectInfo, LLSDSerialize::SIZE_UNLIMITED);
|
||||
}
|
||||
|
||||
if (!mAllEffects.has("default"))
|
||||
if (!mAllEffectInfo.has("default"))
|
||||
mAllEffectInfo["default"] = LLSD::emptyMap();
|
||||
|
||||
LLSD& defaults = mAllEffectInfo["default"];
|
||||
|
||||
for(std::list<LLPointer<LLPostProcessShader> >::iterator it=mShaders.begin();it!=mShaders.end();++it)
|
||||
{
|
||||
LLSD & defaultEffect = (mAllEffects["default"] = LLSD::emptyMap());
|
||||
|
||||
/*defaultEffect["enable_night_vision"] = LLSD::Boolean(false);
|
||||
defaultEffect["enable_color_filter"] = LLSD::Boolean(false);*/
|
||||
|
||||
/// NVG Defaults
|
||||
defaultEffect["brightness_multiplier"] = 3.0;
|
||||
defaultEffect["noise_size"] = 25.0;
|
||||
defaultEffect["noise_strength"] = 0.4;
|
||||
|
||||
// TODO BTest potentially add this to tweaks?
|
||||
mNoiseTextureScale = 1.0f;
|
||||
|
||||
/// Color Filter Defaults
|
||||
defaultEffect["gamma"] = 1.0;
|
||||
defaultEffect["brightness"] = 1.0;
|
||||
defaultEffect["contrast"] = 1.0;
|
||||
defaultEffect["saturation"] = 1.0;
|
||||
|
||||
LLSD& contrastBase = (defaultEffect["contrast_base"] = LLSD::emptyArray());
|
||||
contrastBase.append(1.0);
|
||||
contrastBase.append(1.0);
|
||||
contrastBase.append(1.0);
|
||||
contrastBase.append(0.5);
|
||||
|
||||
defaultEffect["gauss_blur_passes"] = 2;
|
||||
LLSD shader_defaults = (*it)->getDefaults();
|
||||
for (LLSD::map_const_iterator it2 = defaults.beginMap();it2 != defaults.endMap();++it2)
|
||||
{
|
||||
if(!defaults.has(it2->first))
|
||||
defaults[it2->first]=it2->second;
|
||||
}
|
||||
}
|
||||
for(std::list<LLPointer<LLPostProcessShader> >::iterator it=mShaders.begin();it!=mShaders.end();++it)
|
||||
{
|
||||
(*it)->loadSettings(defaults);
|
||||
}
|
||||
|
||||
setSelectedEffect("default");
|
||||
// */
|
||||
}
|
||||
|
||||
LLPostProcess::~LLPostProcess(void)
|
||||
{
|
||||
invalidate() ;
|
||||
}
|
||||
|
||||
/*static*/void LLPostProcess::cleanupClass()
|
||||
{
|
||||
if(instanceExists())
|
||||
getInstance()->invalidate() ;
|
||||
}
|
||||
|
||||
void LLPostProcess::setSelectedEffect(std::string const & effectName)
|
||||
{
|
||||
mSelectedEffectName = effectName;
|
||||
static_cast<LLSD &>(tweaks) = mAllEffects[effectName];
|
||||
}
|
||||
|
||||
void LLPostProcess::saveEffect(std::string const & effectName)
|
||||
{
|
||||
mAllEffects[effectName] = tweaks;
|
||||
|
||||
std::string pathName(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "windlight", XML_FILENAME));
|
||||
//llinfos << "Saving PostProcess Effects settings to " << pathName << llendl;
|
||||
|
||||
llofstream effectsXML(pathName);
|
||||
|
||||
LLPointer<LLSDFormatter> formatter = new LLSDXMLFormatter();
|
||||
|
||||
formatter->format(mAllEffects, effectsXML);
|
||||
}
|
||||
void LLPostProcess::invalidate()
|
||||
{
|
||||
mSceneRenderTexture = NULL ;
|
||||
mNoiseTexture = NULL ;
|
||||
mVBO = NULL ;
|
||||
}
|
||||
|
||||
void LLPostProcess::apply(unsigned int width, unsigned int height)
|
||||
{
|
||||
if(shadersEnabled())
|
||||
{
|
||||
if (mVBO.isNull() || width != mScreenWidth || height != mScreenHeight)
|
||||
{
|
||||
initialize(width, height);
|
||||
}
|
||||
doEffects();
|
||||
}
|
||||
destroyGL() ;
|
||||
}
|
||||
|
||||
void LLPostProcess::initialize(unsigned int width, unsigned int height)
|
||||
{
|
||||
invalidate();
|
||||
destroyGL();
|
||||
mScreenWidth = width;
|
||||
mScreenHeight = height;
|
||||
|
||||
createScreenTexture();
|
||||
createScreenTextures();
|
||||
createNoiseTexture();
|
||||
|
||||
//Setup our VBO.
|
||||
{
|
||||
@@ -185,127 +342,129 @@ void LLPostProcess::initialize(unsigned int width, unsigned int height)
|
||||
|
||||
mVBO->flush();
|
||||
}
|
||||
|
||||
checkError();
|
||||
createNoiseTexture();
|
||||
checkError();
|
||||
stop_glerror();
|
||||
}
|
||||
|
||||
inline bool LLPostProcess::shadersEnabled(void)
|
||||
void LLPostProcess::createScreenTextures()
|
||||
{
|
||||
return (tweaks.useColorFilter().asBoolean() ||
|
||||
tweaks.useNightVisionShader().asBoolean() ||
|
||||
tweaks.useGaussBlurFilter().asBoolean() );
|
||||
const LLTexUnit::eTextureType type = LLTexUnit::TT_RECT_TEXTURE;
|
||||
|
||||
mRenderTarget[0].allocate(mScreenWidth,mScreenHeight,GL_RGBA,FALSE,FALSE,type,FALSE);
|
||||
if(mRenderTarget[0].getFBO())//Only need pingpong between two rendertargets if FBOs are supported.
|
||||
mRenderTarget[1].allocate(mScreenWidth,mScreenHeight,GL_RGBA,FALSE,FALSE,type,FALSE);
|
||||
stop_glerror();
|
||||
|
||||
if(mDepthTexture)
|
||||
LLImageGL::deleteTextures(type, 0, 0, 1, &mDepthTexture, true);
|
||||
|
||||
LLImageGL::generateTextures(type, GL_DEPTH_COMPONENT24, 1, &mDepthTexture);
|
||||
gGL.getTexUnit(0)->bindManual(type, mDepthTexture);
|
||||
LLImageGL::setManualImage(LLTexUnit::getInternalType(type), 0, GL_DEPTH_COMPONENT24, mScreenWidth, mScreenHeight, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL, false);
|
||||
stop_glerror();
|
||||
gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT);
|
||||
gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_CLAMP);
|
||||
stop_glerror();
|
||||
}
|
||||
|
||||
void LLPostProcess::applyShaders(void)
|
||||
{
|
||||
bool copy_buffer = false;
|
||||
if (tweaks.useColorFilter())
|
||||
{
|
||||
applyColorFilterShader();
|
||||
checkError();
|
||||
copy_buffer = true;
|
||||
}
|
||||
if (tweaks.useGaussBlurFilter())
|
||||
{
|
||||
/// If any of the above shaders have been called update the frame buffer;
|
||||
if (copy_buffer)
|
||||
copyFrameBuffer();
|
||||
applyGaussBlurShader();
|
||||
checkError();
|
||||
copy_buffer = true;
|
||||
}
|
||||
if (tweaks.useNightVisionShader())
|
||||
{
|
||||
/// If any of the above shaders have been called update the frame buffer;
|
||||
if (copy_buffer)
|
||||
copyFrameBuffer();
|
||||
applyNightVisionShader();
|
||||
checkError();
|
||||
copy_buffer = true;
|
||||
}
|
||||
}
|
||||
|
||||
void LLPostProcess::applyColorFilterShader(void)
|
||||
void LLPostProcess::createNoiseTexture()
|
||||
{
|
||||
if(gPostColorFilterProgram.mProgramObject == 0)
|
||||
return;
|
||||
|
||||
gPostColorFilterProgram.bind();
|
||||
|
||||
gGL.getTexUnit(0)->bind(mSceneRenderTexture);
|
||||
|
||||
gPostColorFilterProgram.uniform1f("gamma", tweaks.getGamma());
|
||||
gPostColorFilterProgram.uniform1f("brightness", tweaks.getBrightness());
|
||||
gPostColorFilterProgram.uniform1f("contrast", tweaks.getContrast());
|
||||
float baseI = (tweaks.getContrastBaseR() + tweaks.getContrastBaseG() + tweaks.getContrastBaseB()) / 3.0f;
|
||||
baseI = tweaks.getContrastBaseIntensity() / ((baseI < 0.001f) ? 0.001f : baseI);
|
||||
float baseR = tweaks.getContrastBaseR() * baseI;
|
||||
float baseG = tweaks.getContrastBaseG() * baseI;
|
||||
float baseB = tweaks.getContrastBaseB() * baseI;
|
||||
gPostColorFilterProgram.uniform3fv("contrastBase", 1, LLVector3(baseR, baseG, baseB).mV);
|
||||
gPostColorFilterProgram.uniform1f("saturation", tweaks.getSaturation());
|
||||
gPostColorFilterProgram.uniform3fv("lumWeights", 1, LLVector3(LUMINANCE_R, LUMINANCE_G, LUMINANCE_B).mV);
|
||||
|
||||
/// Draw a screen space quad
|
||||
drawOrthoQuad(QUAD_NORMAL);
|
||||
gPostColorFilterProgram.unbind();
|
||||
}
|
||||
|
||||
void LLPostProcess::applyNightVisionShader(void)
|
||||
{
|
||||
if(gPostNightVisionProgram.mProgramObject == 0)
|
||||
return;
|
||||
|
||||
gPostNightVisionProgram.bind();
|
||||
|
||||
gGL.getTexUnit(0)->bind(mSceneRenderTexture);
|
||||
gGL.getTexUnit(1)->bind(mNoiseTexture);
|
||||
|
||||
gPostNightVisionProgram.uniform1f("brightMult", tweaks.getBrightMult());
|
||||
gPostNightVisionProgram.uniform1f("noiseStrength", tweaks.getNoiseStrength());
|
||||
mNoiseTextureScale = 0.001f + ((100.f - tweaks.getNoiseSize()) / 100.f);
|
||||
mNoiseTextureScale *= (mScreenHeight / NOISE_SIZE);
|
||||
|
||||
/// Draw a screen space quad
|
||||
drawOrthoQuad(QUAD_NOISE);
|
||||
gPostNightVisionProgram.unbind();
|
||||
}
|
||||
|
||||
void LLPostProcess::applyGaussBlurShader(void)
|
||||
{
|
||||
int pass_count = tweaks.getGaussBlurPasses();
|
||||
if(!pass_count || gPostGaussianBlurProgram.mProgramObject == 0)
|
||||
return;
|
||||
|
||||
gPostGaussianBlurProgram.bind();
|
||||
|
||||
gGL.getTexUnit(0)->bind(mSceneRenderTexture);
|
||||
|
||||
GLint horiz_pass = gPostGaussianBlurProgram.getUniformLocation("horizontalPass");
|
||||
for(int i = 0;i<pass_count;++i)
|
||||
{
|
||||
for(int j = 0;j<2;++j)
|
||||
{
|
||||
if(i || j)
|
||||
copyFrameBuffer();
|
||||
glUniform1iARB(horiz_pass, j);
|
||||
drawOrthoQuad(QUAD_NORMAL);
|
||||
std::vector<GLubyte> buffer(NOISE_SIZE * NOISE_SIZE);
|
||||
for (unsigned int i = 0; i < NOISE_SIZE; i++){
|
||||
for (unsigned int k = 0; k < NOISE_SIZE; k++){
|
||||
buffer[(i * NOISE_SIZE) + k] = (GLubyte)((double) rand() / ((double) RAND_MAX + 1.f) * 255.f);
|
||||
}
|
||||
}
|
||||
|
||||
for(std::list<LLPointer<LLPostProcessShader> >::iterator it=mShaders.begin();it!=mShaders.end();++it)
|
||||
{
|
||||
if((*it)->getDepthChannel()>=0)
|
||||
{
|
||||
mNoiseTexture = new LLImageGL(FALSE) ;
|
||||
if(mNoiseTexture->createGLTexture())
|
||||
{
|
||||
gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, mNoiseTexture->getTexName());
|
||||
LLImageGL::setManualImage(GL_TEXTURE_2D, 0, GL_LUMINANCE, NOISE_SIZE, NOISE_SIZE, GL_LUMINANCE, GL_UNSIGNED_BYTE, &buffer[0]);
|
||||
stop_glerror();
|
||||
gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_BILINEAR);
|
||||
gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_WRAP);
|
||||
stop_glerror();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LLPostProcess::destroyGL()
|
||||
{
|
||||
mRenderTarget[0].release();
|
||||
mRenderTarget[1].release();
|
||||
if(mDepthTexture)
|
||||
LLImageGL::deleteTextures(LLTexUnit::TT_RECT_TEXTURE, 0, 0, 1, &mDepthTexture, true);
|
||||
mDepthTexture=0;
|
||||
mNoiseTexture = NULL ;
|
||||
mVBO = NULL ;
|
||||
}
|
||||
|
||||
/*static*/void LLPostProcess::cleanupClass()
|
||||
{
|
||||
if(instanceExists())
|
||||
getInstance()->destroyGL() ;
|
||||
}
|
||||
|
||||
void LLPostProcess::copyFrameBuffer()
|
||||
{
|
||||
mRenderTarget[!!mRenderTarget[0].getFBO()].bindTexture(0,0);
|
||||
glCopyTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB,0,0,0,0,0,mScreenWidth, mScreenHeight);
|
||||
|
||||
if(mDepthTexture)
|
||||
{
|
||||
for(std::list<LLPointer<LLPostProcessShader> >::iterator it=mShaders.begin();it!=mShaders.end();++it)
|
||||
{
|
||||
if((*it)->isEnabled() && (*it)->getDepthChannel()>=0)
|
||||
{
|
||||
gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_RECT_TEXTURE, mDepthTexture);
|
||||
glCopyTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB,0,0,0,0,0,mScreenWidth, mScreenHeight);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void LLPostProcess::bindNoise(U32 channel)
|
||||
{
|
||||
gGL.getTexUnit(channel)->bind(mNoiseTexture);
|
||||
}
|
||||
|
||||
void LLPostProcess::renderEffects(unsigned int width, unsigned int height)
|
||||
{
|
||||
for(std::list<LLPointer<LLPostProcessShader> >::iterator it=mShaders.begin();it!=mShaders.end();++it)
|
||||
{
|
||||
if((*it)->isEnabled())
|
||||
{
|
||||
if (mVBO.isNull() || width != mScreenWidth || height != mScreenHeight)
|
||||
{
|
||||
initialize(width, height);
|
||||
}
|
||||
doEffects();
|
||||
return;
|
||||
}
|
||||
}
|
||||
gPostGaussianBlurProgram.unbind();
|
||||
}
|
||||
|
||||
void LLPostProcess::doEffects(void)
|
||||
{
|
||||
LLVertexBuffer::unbind();
|
||||
|
||||
mNoiseTextureScale = 0.001f + ((100.f - mSelectedEffectInfo["noise_size"].asFloat()) / 100.f);
|
||||
mNoiseTextureScale *= (mScreenHeight / NOISE_SIZE);
|
||||
|
||||
/// Copy the screen buffer to the render texture
|
||||
copyFrameBuffer();
|
||||
stop_glerror();
|
||||
|
||||
//Disable depth. Set blendmode to replace.
|
||||
LLGLDepthTest depth(GL_FALSE);
|
||||
LLGLDepthTest depth(GL_FALSE,GL_FALSE);
|
||||
LLGLEnable blend(GL_BLEND);
|
||||
gGL.setSceneBlendType(LLRender::BT_REPLACE);
|
||||
|
||||
@@ -319,7 +478,6 @@ void LLPostProcess::doEffects(void)
|
||||
gGL.loadIdentity();
|
||||
|
||||
applyShaders();
|
||||
checkError();
|
||||
|
||||
LLGLSLShader::bindNoShader();
|
||||
|
||||
@@ -333,13 +491,46 @@ void LLPostProcess::doEffects(void)
|
||||
gGL.setSceneBlendType(LLRender::BT_ALPHA); //Restore blendstate. Alpha is ASSUMED for hud/ui render, etc.
|
||||
|
||||
gGL.getTexUnit(1)->disable();
|
||||
checkError();
|
||||
}
|
||||
|
||||
void LLPostProcess::copyFrameBuffer()
|
||||
void LLPostProcess::applyShaders(void)
|
||||
{
|
||||
gGL.getTexUnit(0)->bindManual(mSceneRenderTexture->getTarget(), mSceneRenderTexture->getTexName());
|
||||
glCopyTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, 0, 0, mScreenWidth, mScreenHeight, 0);
|
||||
QuadType quad;
|
||||
bool primary_rendertarget = 1;
|
||||
for(std::list<LLPointer<LLPostProcessShader> >::iterator it=mShaders.begin();it!=mShaders.end();++it)
|
||||
{
|
||||
if((quad = (*it)->bind()) != QUAD_NONE)
|
||||
{
|
||||
S32 color_channel = (*it)->getColorChannel();
|
||||
S32 depth_channel = (*it)->getDepthChannel();
|
||||
|
||||
if(depth_channel >= 0)
|
||||
gGL.getTexUnit(depth_channel)->bindManual(LLTexUnit::TT_RECT_TEXTURE, mDepthTexture);
|
||||
|
||||
U32 pass = 1;
|
||||
|
||||
while((*it)->draw(pass++))
|
||||
{
|
||||
mRenderTarget[!primary_rendertarget].bindTarget();
|
||||
|
||||
if(color_channel >= 0)
|
||||
mRenderTarget[mRenderTarget[0].getFBO() ? primary_rendertarget : !primary_rendertarget].bindTexture(0,color_channel);
|
||||
|
||||
drawOrthoQuad(quad);
|
||||
mRenderTarget[!primary_rendertarget].flush();
|
||||
if(mRenderTarget[0].getFBO())
|
||||
primary_rendertarget = !primary_rendertarget;
|
||||
}
|
||||
(*it)->unbind();
|
||||
}
|
||||
}
|
||||
//Only need to copy to framebuffer if FBOs are supported, else we've already been drawing to the framebuffer to begin with.
|
||||
if(mRenderTarget[0].getFBO())
|
||||
{
|
||||
//copyContentsToFramebuffer also binds the main framebuffer.
|
||||
LLRenderTarget::copyContentsToFramebuffer(mRenderTarget[primary_rendertarget],0,0,mScreenWidth,mScreenHeight,0,0,mScreenWidth,mScreenHeight,GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
}
|
||||
stop_glerror();
|
||||
}
|
||||
|
||||
void LLPostProcess::drawOrthoQuad(QuadType type)
|
||||
@@ -364,64 +555,59 @@ void LLPostProcess::drawOrthoQuad(QuadType type)
|
||||
mVBO->drawArrays(LLRender::TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
void LLPostProcess::createScreenTexture()
|
||||
void LLPostProcess::setSelectedEffect(std::string const & effectName)
|
||||
{
|
||||
std::vector<GLubyte> data(mScreenWidth * mScreenHeight * 3, 0) ;
|
||||
|
||||
mSceneRenderTexture = new LLImageGL(FALSE) ;
|
||||
if(mSceneRenderTexture->createGLTexture())
|
||||
mSelectedEffectName = effectName;
|
||||
mSelectedEffectInfo = mAllEffectInfo[effectName];
|
||||
for(std::list<LLPointer<LLPostProcessShader> >::iterator it=mShaders.begin();it!=mShaders.end();++it)
|
||||
{
|
||||
gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_RECT_TEXTURE, mSceneRenderTexture->getTexName());
|
||||
LLImageGL::setManualImage(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGB, mScreenWidth, mScreenHeight, GL_RGB, GL_UNSIGNED_BYTE, &data[0]);
|
||||
gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT);
|
||||
gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_CLAMP);
|
||||
(*it)->loadSettings(mSelectedEffectInfo);
|
||||
}
|
||||
}
|
||||
|
||||
void LLPostProcess::createNoiseTexture()
|
||||
{
|
||||
std::vector<GLubyte> buffer(NOISE_SIZE * NOISE_SIZE);
|
||||
for (unsigned int i = 0; i < NOISE_SIZE; i++){
|
||||
for (unsigned int k = 0; k < NOISE_SIZE; k++){
|
||||
buffer[(i * NOISE_SIZE) + k] = (GLubyte)((double) rand() / ((double) RAND_MAX + 1.f) * 255.f);
|
||||
}
|
||||
}
|
||||
|
||||
mNoiseTexture = new LLImageGL(FALSE) ;
|
||||
if(mNoiseTexture->createGLTexture())
|
||||
{
|
||||
gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, mNoiseTexture->getTexName());
|
||||
LLImageGL::setManualImage(GL_TEXTURE_2D, 0, GL_LUMINANCE, NOISE_SIZE, NOISE_SIZE, GL_LUMINANCE, GL_UNSIGNED_BYTE, &buffer[0]);
|
||||
gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_BILINEAR);
|
||||
gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_WRAP);
|
||||
}
|
||||
}
|
||||
|
||||
bool LLPostProcess::checkError(void)
|
||||
void LLPostProcess::setSelectedEffectValue(std::string const & setting, LLSD& value)
|
||||
{
|
||||
GLenum glErr;
|
||||
bool retCode = false;
|
||||
|
||||
glErr = glGetError();
|
||||
while (glErr != GL_NO_ERROR)
|
||||
{
|
||||
// shaderErrorLog << (const char *) gluErrorString(glErr) << std::endl;
|
||||
char const * err_str_raw = (const char *) gluErrorString(glErr);
|
||||
|
||||
if(err_str_raw == NULL)
|
||||
{
|
||||
std::ostringstream err_builder;
|
||||
err_builder << "unknown error number " << glErr;
|
||||
mShaderErrorString = err_builder.str();
|
||||
}
|
||||
else
|
||||
{
|
||||
mShaderErrorString = err_str_raw;
|
||||
}
|
||||
|
||||
retCode = true;
|
||||
glErr = glGetError();
|
||||
}
|
||||
return retCode;
|
||||
char buf[256];
|
||||
S32 elem=0;
|
||||
if(sscanf(setting.c_str(),"%255[^[][%d]", buf, &elem) == 2)
|
||||
{
|
||||
mSelectedEffectInfo[buf][elem] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
mSelectedEffectInfo[setting] = value;
|
||||
}
|
||||
for(std::list<LLPointer<LLPostProcessShader> >::iterator it=mShaders.begin();it!=mShaders.end();++it)
|
||||
{
|
||||
(*it)->loadSettings(mSelectedEffectInfo);
|
||||
}
|
||||
}
|
||||
|
||||
void LLPostProcess::resetSelectedEffect()
|
||||
{
|
||||
if(!llsd_equals(mAllEffectInfo[mSelectedEffectName], mSelectedEffectInfo))
|
||||
{
|
||||
mSelectedEffectInfo = mAllEffectInfo[mSelectedEffectName];
|
||||
for(std::list<LLPointer<LLPostProcessShader> >::iterator it=mShaders.begin();it!=mShaders.end();++it)
|
||||
{
|
||||
(*it)->loadSettings(mSelectedEffectInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LLPostProcess::saveEffectAs(std::string const & effectName)
|
||||
{
|
||||
mAllEffectInfo[effectName] = mSelectedEffectInfo;
|
||||
|
||||
std::string pathName(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "windlight", XML_FILENAME));
|
||||
//llinfos << "Saving PostProcess Effects settings to " << pathName << llendl;
|
||||
|
||||
llofstream effectsXML(pathName);
|
||||
|
||||
LLPointer<LLSDFormatter> formatter = new LLSDXMLFormatter();
|
||||
|
||||
formatter->format(mAllEffectInfo, effectsXML);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -34,189 +34,123 @@
|
||||
#define LL_POSTPROCESS_H
|
||||
|
||||
#include <map>
|
||||
#include <fstream>
|
||||
#include "llgl.h"
|
||||
#include "llglheaders.h"
|
||||
#include "llsd.h"
|
||||
#include "llrendertarget.h"
|
||||
|
||||
class LLSD;
|
||||
|
||||
typedef enum _QuadType {
|
||||
QUAD_NONE,
|
||||
QUAD_NORMAL,
|
||||
QUAD_NOISE
|
||||
} QuadType;
|
||||
|
||||
//LLPostProcessShader is an attempt to encapsulate the shaders a little better.
|
||||
class LLPostProcessShader : public LLRefCount //Abstract. PostProcess shaders derive off of this common base.
|
||||
{
|
||||
protected:
|
||||
//LLShaderSetting is used to associate key names to member variables to avoid LLSD lookups when drawing.
|
||||
//It also facilitates automating the assigning of defaults to, as well as parsing from, the effects LLSD list.
|
||||
//This replaces the entire old PostProcessTweaks structure. More will be done in the future to move into a more
|
||||
//xml-driven configuration.
|
||||
struct LLShaderSettingBase
|
||||
{
|
||||
LLShaderSettingBase(const char* name) : mSettingName(name) {}
|
||||
const char* mSettingName; //LLSD key names as found in postprocesseffects.xml. eg 'contrast_base'
|
||||
virtual LLSD getDefaultValue() = 0; //Converts the member variable as an LLSD. Used to set defaults absent in postprocesseffects.xml
|
||||
virtual void setValue(const LLSD& value) = 0; //Connects the LLSD element to the member variable. Used when loading effects (such as default)
|
||||
};
|
||||
template<typename T>
|
||||
struct LLShaderSetting : public LLShaderSettingBase
|
||||
{
|
||||
LLShaderSetting(const char* setting_name, T def) : LLShaderSettingBase(setting_name), mValue(def), mDefault(def) {}
|
||||
T mValue; //The member variable mentioned above.
|
||||
T mDefault; //Set via ctor. Value is inserted into the defaults LLSD list if absent from postprocesseffects.xml
|
||||
LLSD getDefaultValue() { return mDefault; } //See LLShaderSettingBase::getDefaultValue
|
||||
void setValue(const LLSD& value) { mValue = value; } //See LLShaderSettingBase::setValue
|
||||
operator T() { return mValue; } //Typecast operator overload so this object can be handled as if it was whatever T represents.
|
||||
};
|
||||
std::vector<LLShaderSettingBase*> mSettings; //Contains a list of all the 'settings' this shader uses. Manually add via push_back in ctor.
|
||||
public:
|
||||
virtual ~LLPostProcessShader() {};
|
||||
virtual bool isEnabled() = 0; //Returning false avoids bind/draw/unbind calls. If no shaders are enabled, framebuffer copying is skipped.
|
||||
virtual S32 getColorChannel() = 0; //If color buffer is used in this shader returns > -1 to cue LLPostProcess on copying it from the framebuffer.
|
||||
virtual S32 getDepthChannel() = 0; //If depth buffer is used in this shader returns > -1 to cue LLPostProcess on copying it from the framebuffer.
|
||||
virtual QuadType bind() = 0; //Bind shader and textures, set up attribs. Returns the 'type' of quad to be drawn.
|
||||
virtual bool draw(U32 pass) = 0; //returning false means finished. Used to update per-pass attributes and such. LLPostProcess will call
|
||||
//drawOrthoQuad when this returns true, increment pass, then call this again, and keep repeating this until false is returned.
|
||||
virtual void unbind() = 0; //Unbind shader and textures.
|
||||
|
||||
LLSD getDefaults(); //Returns a full LLSD kvp list filled with default values.
|
||||
void loadSettings(const LLSD& settings); //Parses the effects LLSD list and sets the member variables linked to them (via LLShaderSetting::setValue())
|
||||
};
|
||||
|
||||
//LLVector4 does not implicitly convert to and from LLSD, so template specilizations are necessary.
|
||||
template<> LLSD LLPostProcessShader::LLShaderSetting<LLVector4>::getDefaultValue();
|
||||
template<> void LLPostProcessShader::LLShaderSetting<LLVector4>::setValue(const LLSD& value);
|
||||
|
||||
class LLPostProcess : public LLSingleton<LLPostProcess>
|
||||
{
|
||||
public:
|
||||
|
||||
typedef enum _QuadType {
|
||||
QUAD_NORMAL,
|
||||
QUAD_NOISE
|
||||
} QuadType;
|
||||
|
||||
/// GLSL Shader Encapsulation Struct
|
||||
//typedef std::map<const char *, GLuint> glslUniforms;
|
||||
|
||||
struct PostProcessTweaks : public LLSD {
|
||||
inline PostProcessTweaks() : LLSD(LLSD::emptyMap())
|
||||
{
|
||||
}
|
||||
|
||||
inline LLSD & brightMult() {
|
||||
return (*this)["brightness_multiplier"];
|
||||
}
|
||||
|
||||
inline LLSD & noiseStrength() {
|
||||
return (*this)["noise_strength"];
|
||||
}
|
||||
|
||||
inline LLSD & noiseSize() {
|
||||
return (*this)["noise_size"];
|
||||
}
|
||||
|
||||
inline LLSD & brightness() {
|
||||
return (*this)["brightness"];
|
||||
}
|
||||
|
||||
inline LLSD & contrast() {
|
||||
return (*this)["contrast"];
|
||||
}
|
||||
|
||||
inline LLSD & contrastBaseR() {
|
||||
return (*this)["contrast_base"][0];
|
||||
}
|
||||
|
||||
inline LLSD & contrastBaseG() {
|
||||
return (*this)["contrast_base"][1];
|
||||
}
|
||||
|
||||
inline LLSD & contrastBaseB() {
|
||||
return (*this)["contrast_base"][2];
|
||||
}
|
||||
|
||||
inline LLSD & contrastBaseIntensity() {
|
||||
return (*this)["contrast_base"][3];
|
||||
}
|
||||
|
||||
inline LLSD & saturation() {
|
||||
return (*this)["saturation"];
|
||||
}
|
||||
|
||||
inline LLSD & useNightVisionShader() {
|
||||
return (*this)["enable_night_vision"];
|
||||
}
|
||||
|
||||
inline LLSD & useColorFilter() {
|
||||
return (*this)["enable_color_filter"];
|
||||
}
|
||||
|
||||
inline LLSD & useGaussBlurFilter() {
|
||||
return (*this)["enable_gauss_blur"];
|
||||
}
|
||||
|
||||
inline F32 getBrightMult() const {
|
||||
return F32((*this)["brightness_multiplier"].asReal());
|
||||
}
|
||||
|
||||
inline F32 getNoiseStrength() const {
|
||||
return F32((*this)["noise_strength"].asReal());
|
||||
}
|
||||
|
||||
inline F32 getNoiseSize() const {
|
||||
return F32((*this)["noise_size"].asReal());
|
||||
}
|
||||
|
||||
inline F32 getGamma() const {
|
||||
return F32((*this)["gamma"].asReal());
|
||||
}
|
||||
|
||||
inline F32 getBrightness() const {
|
||||
return F32((*this)["brightness"].asReal());
|
||||
}
|
||||
|
||||
inline F32 getContrast() const {
|
||||
return F32((*this)["contrast"].asReal());
|
||||
}
|
||||
|
||||
inline F32 getContrastBaseR() const {
|
||||
return F32((*this)["contrast_base"][0].asReal());
|
||||
}
|
||||
|
||||
inline F32 getContrastBaseG() const {
|
||||
return F32((*this)["contrast_base"][1].asReal());
|
||||
}
|
||||
|
||||
inline F32 getContrastBaseB() const {
|
||||
return F32((*this)["contrast_base"][2].asReal());
|
||||
}
|
||||
|
||||
inline F32 getContrastBaseIntensity() const {
|
||||
return F32((*this)["contrast_base"][3].asReal());
|
||||
}
|
||||
|
||||
inline F32 getSaturation() const {
|
||||
return F32((*this)["saturation"].asReal());
|
||||
}
|
||||
|
||||
inline LLSD & getGaussBlurPasses() {
|
||||
return (*this)["gauss_blur_passes"];
|
||||
}
|
||||
};
|
||||
|
||||
PostProcessTweaks tweaks;
|
||||
|
||||
// the map of all availible effects
|
||||
LLSD mAllEffects;
|
||||
|
||||
private:
|
||||
std::list<LLPointer<LLPostProcessShader> > mShaders; //List of all registered LLPostProcessShader instances.
|
||||
|
||||
LLPointer<LLVertexBuffer> mVBO;
|
||||
LLPointer<LLImageGL> mSceneRenderTexture ;
|
||||
U32 mNextDrawTarget; //Need to pingpong between two rendertargets. Cannot sample target texture of currently bound FBO.
|
||||
// However this is ONLY the case if fbos are actually supported, else swapping isn't needed.
|
||||
LLRenderTarget mRenderTarget[2];
|
||||
U32 mDepthTexture;
|
||||
LLPointer<LLImageGL> mNoiseTexture ;
|
||||
|
||||
U32 mScreenWidth;
|
||||
U32 mScreenHeight;
|
||||
F32 mNoiseTextureScale;
|
||||
|
||||
// The name of currently selected effect in mAllEffectInfo
|
||||
std::string mSelectedEffectName;
|
||||
// The map of settings for currently selected effect.
|
||||
LLSD mSelectedEffectInfo;
|
||||
// The map of all availible effects
|
||||
LLSD mAllEffectInfo;
|
||||
|
||||
public:
|
||||
LLPostProcess(void);
|
||||
|
||||
~LLPostProcess(void);
|
||||
private:
|
||||
// OpenGL initialization
|
||||
void initialize(unsigned int width, unsigned int height); //Sets mScreenWidth and mScreenHeight
|
||||
// calls createScreenTextures and createNoiseTexture
|
||||
// creates VBO
|
||||
void createScreenTextures(); //Creates color texture and depth texture(if needed).
|
||||
void createNoiseTexture(); //Creates 'random' noise texture.
|
||||
|
||||
void apply(unsigned int width, unsigned int height);
|
||||
void invalidate() ;
|
||||
|
||||
// Cleanup of global data that's only inited once per class.
|
||||
public:
|
||||
// Teardown
|
||||
// Called on destroyGL or cleanupClass. Releases VBOs, rendertargets and textures.
|
||||
void destroyGL();
|
||||
// Cleanup of global data that's only inited once per class.
|
||||
static void cleanupClass();
|
||||
|
||||
void setSelectedEffect(std::string const & effectName);
|
||||
|
||||
inline std::string const & getSelectedEffect(void) const {
|
||||
return mSelectedEffectName;
|
||||
}
|
||||
|
||||
void saveEffect(std::string const & effectName);
|
||||
|
||||
private:
|
||||
/// read in from file
|
||||
std::string mShaderErrorString;
|
||||
unsigned int mScreenWidth;
|
||||
unsigned int mScreenHeight;
|
||||
|
||||
float mNoiseTextureScale;
|
||||
|
||||
// the name of currently selected effect in mAllEffects
|
||||
// invariant: tweaks == mAllEffects[mSelectedEffectName]
|
||||
std::string mSelectedEffectName;
|
||||
|
||||
/// General functions
|
||||
void initialize(unsigned int width, unsigned int height);
|
||||
void doEffects(void);
|
||||
void applyShaders(void);
|
||||
bool shadersEnabled(void);
|
||||
|
||||
/// Night Vision Functions
|
||||
void applyNightVisionShader(void);
|
||||
|
||||
/// Color Filter Functions
|
||||
void applyColorFilterShader(void);
|
||||
|
||||
/// Gaussian blur Filter Functions
|
||||
void applyGaussBlurShader(void);
|
||||
|
||||
/// OpenGL Helper Functions
|
||||
// Setup for draw.
|
||||
void copyFrameBuffer();
|
||||
void createScreenTexture();
|
||||
void createNoiseTexture();
|
||||
bool checkError(void);
|
||||
void drawOrthoQuad(QuadType type);
|
||||
void bindNoise(U32 channel);
|
||||
|
||||
// Draw
|
||||
void renderEffects(unsigned int width, unsigned int height); //Entry point for newview.
|
||||
private:
|
||||
void doEffects(void); //Sets up viewmatrix, blits the framebuffer, then calls applyShaders.
|
||||
void applyShaders(void); //Iterates over all active post shaders, manages binding, calls drawOrthoQuad for render.
|
||||
void drawOrthoQuad(QuadType type); //Finally draws fullscreen quad with the shader currently bound.
|
||||
|
||||
public:
|
||||
// UI interaction
|
||||
// Getters
|
||||
inline LLSD const & getAllEffectInfo(void) const { return mAllEffectInfo; }
|
||||
inline std::string const & getSelectedEffectName(void) const { return mSelectedEffectName; }
|
||||
inline LLSD const & getSelectedEffectInfo(void) const { return mSelectedEffectInfo; }
|
||||
// Setters
|
||||
void setSelectedEffect(std::string const & effectName);
|
||||
void setSelectedEffectValue(std::string const & setting, LLSD& value);
|
||||
void resetSelectedEffect();
|
||||
void saveEffectAs(std::string const & effectName);
|
||||
};
|
||||
#endif // LL_POSTPROCESS_H
|
||||
|
||||
@@ -380,7 +380,7 @@ bool LLTexUnit::bind(LLRenderTarget* renderTarget, bool bindDepth)
|
||||
|
||||
if (bindDepth)
|
||||
{
|
||||
if (renderTarget->hasStencil())
|
||||
if (renderTarget->hasStencil() && renderTarget->getFBO())
|
||||
{
|
||||
llerrs << "Cannot bind a render buffer for sampling. Allocate render target without a stencil buffer if sampling of depth buffer is required." << llendl;
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ void LLRenderTarget::resize(U32 resx, U32 resy, U32 color_fmt)
|
||||
|
||||
if (mDepth)
|
||||
{ //resize depth attachment
|
||||
if (mStencil)
|
||||
if (mStencil && mFBO)
|
||||
{
|
||||
//use render buffers where stencil buffers are in play
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, mDepth);
|
||||
@@ -104,7 +104,10 @@ void LLRenderTarget::resize(U32 resx, U32 resy, U32 color_fmt)
|
||||
{
|
||||
gGL.getTexUnit(0)->bindManual(mUsage, mDepth);
|
||||
U32 internal_type = LLTexUnit::getInternalType(mUsage);
|
||||
LLImageGL::setManualImage(internal_type, 0, GL_DEPTH_COMPONENT24, mResX, mResY, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL, false);
|
||||
if(!mStencil)
|
||||
LLImageGL::setManualImage(internal_type, 0, GL_DEPTH_COMPONENT24, mResX, mResY, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL, false);
|
||||
else
|
||||
LLImageGL::setManualImage(internal_type, 0, GL_DEPTH24_STENCIL8, mResX, mResY, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL, false);
|
||||
}
|
||||
|
||||
sBytesAllocated += pix_diff*4;
|
||||
@@ -131,9 +134,10 @@ bool LLRenderTarget::allocate(U32 resx, U32 resy, U32 color_fmt, bool depth, boo
|
||||
mUsage = usage;
|
||||
mUseDepth = depth;
|
||||
|
||||
|
||||
if ((sUseFBO || use_fbo) && gGLManager.mHasFramebufferObject)
|
||||
{
|
||||
glGenFramebuffers(1, (GLuint *) &mFBO);
|
||||
|
||||
if (depth)
|
||||
{
|
||||
if (!allocateDepth())
|
||||
@@ -143,8 +147,6 @@ bool LLRenderTarget::allocate(U32 resx, U32 resy, U32 color_fmt, bool depth, boo
|
||||
}
|
||||
}
|
||||
|
||||
glGenFramebuffers(1, (GLuint *) &mFBO);
|
||||
|
||||
if (mDepth)
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, mFBO);
|
||||
@@ -162,7 +164,7 @@ bool LLRenderTarget::allocate(U32 resx, U32 resy, U32 color_fmt, bool depth, boo
|
||||
}
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
|
||||
stop_glerror();
|
||||
}
|
||||
|
||||
@@ -255,7 +257,7 @@ bool LLRenderTarget::addColorAttachment(U32 color_fmt)
|
||||
|
||||
bool LLRenderTarget::allocateDepth()
|
||||
{
|
||||
if (mStencil)
|
||||
if (mStencil && mFBO)
|
||||
{
|
||||
//use render buffers where stencil buffers are in play
|
||||
glGenRenderbuffers(1, (GLuint *) &mDepth);
|
||||
@@ -267,13 +269,19 @@ bool LLRenderTarget::allocateDepth()
|
||||
}
|
||||
else
|
||||
{
|
||||
LLImageGL::generateTextures(mUsage, GL_DEPTH_COMPONENT24, 1, &mDepth);
|
||||
if(!mStencil)
|
||||
LLImageGL::generateTextures(mUsage, GL_DEPTH_COMPONENT24, 1, &mDepth);
|
||||
else
|
||||
LLImageGL::generateTextures(mUsage, GL_DEPTH24_STENCIL8, 1, &mDepth);
|
||||
gGL.getTexUnit(0)->bindManual(mUsage, mDepth);
|
||||
|
||||
U32 internal_type = LLTexUnit::getInternalType(mUsage);
|
||||
stop_glerror();
|
||||
clear_glerror();
|
||||
LLImageGL::setManualImage(internal_type, 0, GL_DEPTH_COMPONENT24, mResX, mResY, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL, false);
|
||||
if(!mStencil)
|
||||
LLImageGL::setManualImage(internal_type, 0, GL_DEPTH_COMPONENT24, mResX, mResY, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL, false);
|
||||
else
|
||||
LLImageGL::setManualImage(internal_type, 0, GL_DEPTH24_STENCIL8, mResX, mResY, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL, false);
|
||||
gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT);
|
||||
}
|
||||
|
||||
@@ -337,7 +345,7 @@ void LLRenderTarget::release()
|
||||
{
|
||||
if (mDepth)
|
||||
{
|
||||
if (mStencil)
|
||||
if (mStencil && mFBO)
|
||||
{
|
||||
glDeleteRenderbuffers(1, (GLuint*) &mDepth);
|
||||
stop_glerror();
|
||||
@@ -490,6 +498,7 @@ void LLRenderTarget::flush(bool fetch_depth)
|
||||
{
|
||||
gGL.getTexUnit(0)->bind(this);
|
||||
glCopyTexSubImage2D(LLTexUnit::getInternalType(mUsage), 0, 0, 0, 0, 0, mResX, mResY);
|
||||
stop_glerror();
|
||||
|
||||
if (fetch_depth)
|
||||
{
|
||||
@@ -498,8 +507,10 @@ void LLRenderTarget::flush(bool fetch_depth)
|
||||
allocateDepth();
|
||||
}
|
||||
|
||||
gGL.getTexUnit(0)->bind(this);
|
||||
glCopyTexImage2D(LLTexUnit::getInternalType(mUsage), 0, GL_DEPTH24_STENCIL8, 0, 0, mResX, mResY, 0);
|
||||
gGL.getTexUnit(0)->bind(this,true);
|
||||
glCopyTexSubImage2D(LLTexUnit::getInternalType(mUsage), 0, 0, 0, 0, 0, mResX, mResY);
|
||||
stop_glerror();
|
||||
//glCopyTexImage2D(LLTexUnit::getInternalType(mUsage), 0, GL_DEPTH24_STENCIL8, 0, 0, mResX, mResY, 0);
|
||||
}
|
||||
|
||||
gGL.getTexUnit(0)->disable();
|
||||
@@ -589,7 +600,7 @@ void LLRenderTarget::copyContents(LLRenderTarget& source, S32 srcX0, S32 srcY0,
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mask == GL_DEPTH_BUFFER_BIT && source.mStencil != mStencil)
|
||||
if (mask == GL_DEPTH_BUFFER_BIT && !mStencil && source.mStencil != mStencil)
|
||||
{
|
||||
stop_glerror();
|
||||
|
||||
|
||||
@@ -151,6 +151,8 @@ public:
|
||||
//one renderable attachment (i.e. color buffer, depth buffer).
|
||||
bool isComplete() const;
|
||||
|
||||
U32 getFBO() const {return mFBO;}
|
||||
|
||||
static LLRenderTarget* getCurrentBoundTarget() { return sBoundTarget; }
|
||||
|
||||
protected:
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @file colorFilterF.glsl
|
||||
*
|
||||
* Copyright (c) 2007-$CurrentYear$, Linden Research, Inc.
|
||||
* $License$
|
||||
*/
|
||||
|
||||
#extension GL_ARB_texture_rectangle : enable
|
||||
|
||||
#ifdef DEFINE_GL_FRAGCOLOR
|
||||
out vec4 gl_FragColor;
|
||||
#endif
|
||||
|
||||
uniform sampler2DRect tex0;
|
||||
uniform int layerCount;
|
||||
|
||||
VARYING vec2 vary_texcoord0;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
vec3 color = pow(floor(pow(vec3(texture2D(tex0, vary_texcoord0.st)),vec3(.6)) * layerCount)/layerCount,vec3(1.66666));
|
||||
gl_FragColor = vec4(color, 1.0);
|
||||
}
|
||||
@@ -174,6 +174,8 @@
|
||||
<boolean>0</boolean>
|
||||
<key>enable_gauss_blur</key>
|
||||
<boolean>0</boolean>
|
||||
<key>enable_posterize</key>
|
||||
<boolean>0</boolean>
|
||||
<key>gauss_blur_passes</key>
|
||||
<integer>2</integer>
|
||||
<key>extract_high</key>
|
||||
@@ -186,6 +188,8 @@
|
||||
<real>0.40000000000000002</real>
|
||||
<key>saturation</key>
|
||||
<real>1</real>
|
||||
</map>
|
||||
<key>posterize_layers</key>
|
||||
<real>10</real>
|
||||
</map>
|
||||
</map>
|
||||
</llsd>
|
||||
@@ -57,7 +57,6 @@
|
||||
#include "lldaycyclemanager.h"
|
||||
#include "llwlparamset.h"
|
||||
#include "llwlparammanager.h"
|
||||
#include "llpostprocess.h"
|
||||
#include "llfloaterwindlight.h"
|
||||
|
||||
|
||||
|
||||
@@ -115,17 +115,7 @@ LLFloaterPostProcess* LLFloaterPostProcess::instance()
|
||||
|
||||
void LLFloaterPostProcess::onControlChanged(LLUICtrl* ctrl, void* userData)
|
||||
{
|
||||
char const *VariableName = (char const *)userData;
|
||||
char buf[256];
|
||||
S32 elem=0;
|
||||
if(sscanf(VariableName,"%255[^[][%d]", buf, &elem) == 2)
|
||||
{
|
||||
LLPostProcess::getInstance()->tweaks[(const char*)buf][elem] = ctrl->getValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
LLPostProcess::getInstance()->tweaks[VariableName] = ctrl->getValue();
|
||||
}
|
||||
LLPostProcess::getInstance()->setSelectedEffectValue((char const *)userData,ctrl->getValue());
|
||||
}
|
||||
|
||||
void LLFloaterPostProcess::onLoadEffect(void* userData)
|
||||
@@ -145,7 +135,7 @@ void LLFloaterPostProcess::onSaveEffect(void* userData)
|
||||
|
||||
std::string effectName(editBox->getValue().asString());
|
||||
|
||||
if (LLPostProcess::getInstance()->mAllEffects.has(effectName))
|
||||
if (LLPostProcess::getInstance()->getAllEffectInfo().has(effectName))
|
||||
{
|
||||
LLSD payload;
|
||||
payload["effect_name"] = effectName;
|
||||
@@ -153,7 +143,7 @@ void LLFloaterPostProcess::onSaveEffect(void* userData)
|
||||
}
|
||||
else
|
||||
{
|
||||
LLPostProcess::getInstance()->saveEffect(effectName);
|
||||
LLPostProcess::getInstance()->saveEffectAs(effectName);
|
||||
sPostProcess->syncMenu();
|
||||
}
|
||||
}
|
||||
@@ -175,7 +165,7 @@ bool LLFloaterPostProcess::saveAlertCallback(const LLSD& notification, const LLS
|
||||
// if they choose save, do it. Otherwise, don't do anything
|
||||
if (option == 0)
|
||||
{
|
||||
LLPostProcess::getInstance()->saveEffect(notification["payload"]["effect_name"].asString());
|
||||
LLPostProcess::getInstance()->saveEffectAs(notification["payload"]["effect_name"].asString());
|
||||
|
||||
sPostProcess->syncMenu();
|
||||
}
|
||||
@@ -209,17 +199,17 @@ void LLFloaterPostProcess::syncMenu()
|
||||
comboBox->removeall();
|
||||
|
||||
LLSD::map_const_iterator currEffect;
|
||||
for(currEffect = LLPostProcess::getInstance()->mAllEffects.beginMap();
|
||||
currEffect != LLPostProcess::getInstance()->mAllEffects.endMap();
|
||||
for(currEffect = LLPostProcess::getInstance()->getAllEffectInfo().beginMap();
|
||||
currEffect != LLPostProcess::getInstance()->getAllEffectInfo().endMap();
|
||||
++currEffect)
|
||||
{
|
||||
comboBox->add(currEffect->first);
|
||||
}
|
||||
|
||||
// set the current effect as selected.
|
||||
comboBox->selectByValue(LLPostProcess::getInstance()->getSelectedEffect());
|
||||
comboBox->selectByValue(LLPostProcess::getInstance()->getSelectedEffectName());
|
||||
|
||||
LLSD &tweaks = LLPostProcess::getInstance()->tweaks;
|
||||
const LLSD &tweaks = LLPostProcess::getInstance()->getSelectedEffectInfo();
|
||||
//Iterate down all uniforms handled by post-process shaders. Update any linked ui elements.
|
||||
for (LLSD::map_const_iterator it = tweaks.beginMap(); it != tweaks.endMap(); ++it)
|
||||
{
|
||||
|
||||
@@ -62,7 +62,6 @@
|
||||
|
||||
#include "llwaterparamset.h"
|
||||
#include "llwaterparammanager.h"
|
||||
#include "llpostprocess.h"
|
||||
|
||||
#undef max
|
||||
|
||||
|
||||
@@ -62,7 +62,6 @@
|
||||
|
||||
#include "llwlparamset.h"
|
||||
#include "llwlparammanager.h"
|
||||
#include "llpostprocess.h"
|
||||
|
||||
#undef max
|
||||
|
||||
|
||||
@@ -200,7 +200,6 @@
|
||||
#include "llnamelistctrl.h"
|
||||
#include "llnamebox.h"
|
||||
#include "llnameeditor.h"
|
||||
#include "llpostprocess.h"
|
||||
#include "llwlparammanager.h"
|
||||
#include "llwaterparammanager.h"
|
||||
#include "llagentlanguage.h"
|
||||
|
||||
@@ -1009,7 +1009,7 @@ void display(BOOL rebuild, F32 zoom_factor, int subfield, BOOL for_snapshot, boo
|
||||
if (LLPipeline::sRenderDeferred && !LLPipeline::sUnderWaterRender)
|
||||
{
|
||||
gPipeline.mDeferredScreen.flush();
|
||||
if(LLRenderTarget::sUseFBO)
|
||||
if(gPipeline.mDeferredScreen.getFBO())
|
||||
{
|
||||
LLRenderTarget::copyContentsToFramebuffer(gPipeline.mDeferredScreen, 0, 0, gPipeline.mDeferredScreen.getWidth(),
|
||||
gPipeline.mDeferredScreen.getHeight(), 0, 0,
|
||||
@@ -1021,7 +1021,7 @@ void display(BOOL rebuild, F32 zoom_factor, int subfield, BOOL for_snapshot, boo
|
||||
else
|
||||
{
|
||||
gPipeline.mScreen.flush();
|
||||
if(LLRenderTarget::sUseFBO)
|
||||
if(gPipeline.mScreen.getFBO())
|
||||
{
|
||||
LLRenderTarget::copyContentsToFramebuffer(gPipeline.mScreen, 0, 0, gPipeline.mScreen.getWidth(),
|
||||
gPipeline.mScreen.getHeight(), 0, 0,
|
||||
@@ -1283,14 +1283,12 @@ void render_ui(F32 zoom_factor, int subfield, bool tiling)
|
||||
if (to_texture)
|
||||
{
|
||||
gPipeline.renderBloom(gSnapshot, zoom_factor, subfield, tiling);
|
||||
gPipeline.mScreen.flush(); //blit, etc.
|
||||
}
|
||||
/// We copy the frame buffer straight into a texture here,
|
||||
/// and then display it again with compositor effects.
|
||||
/// Using render to texture would be faster/better, but I don't have a
|
||||
/// grasp of their full display stack just yet.
|
||||
|
||||
if(gPipeline.canUseVertexShaders())
|
||||
LLPostProcess::getInstance()->apply(gViewerWindow->getWindowDisplayWidth(), gViewerWindow->getWindowDisplayHeight());
|
||||
{
|
||||
LLPostProcess::getInstance()->renderEffects(gViewerWindow->getWindowDisplayWidth(), gViewerWindow->getWindowDisplayHeight());
|
||||
}
|
||||
|
||||
render_hud_elements();
|
||||
render_hud_attachments();
|
||||
|
||||
@@ -170,6 +170,7 @@ LLGLSLShader gGlowExtractProgram(LLViewerShaderMgr::SHADER_EFFECT); //Not in
|
||||
LLGLSLShader gPostColorFilterProgram(LLViewerShaderMgr::SHADER_EFFECT); //Not in mShaderList
|
||||
LLGLSLShader gPostNightVisionProgram(LLViewerShaderMgr::SHADER_EFFECT); //Not in mShaderList
|
||||
LLGLSLShader gPostGaussianBlurProgram(LLViewerShaderMgr::SHADER_EFFECT); //Not in mShaderList
|
||||
LLGLSLShader gPostPosterizeProgram(LLViewerShaderMgr::SHADER_EFFECT); //Not in mShaderList
|
||||
|
||||
// Deferred rendering shaders
|
||||
LLGLSLShader gDeferredImpostorProgram(LLViewerShaderMgr::SHADER_DEFERRED);
|
||||
@@ -958,6 +959,23 @@ BOOL LLViewerShaderMgr::loadShadersEffects()
|
||||
gPostGaussianBlurProgram.uniform1i("tex0", 0);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
vector<string> shaderUniforms;
|
||||
shaderUniforms.reserve(1);
|
||||
shaderUniforms.push_back("layerCount");
|
||||
|
||||
gPostPosterizeProgram.mName = "Posterize Shader (Post)";
|
||||
gPostPosterizeProgram.mShaderFiles.clear();
|
||||
gPostPosterizeProgram.mShaderFiles.push_back(make_pair("effects/PosterizeF.glsl", GL_FRAGMENT_SHADER_ARB));
|
||||
gPostPosterizeProgram.mShaderFiles.push_back(make_pair("interface/onetexturenocolorV.glsl", GL_VERTEX_SHADER_ARB));
|
||||
gPostPosterizeProgram.mShaderLevel = mVertexShaderLevel[SHADER_EFFECT];
|
||||
if(gPostPosterizeProgram.createShader(NULL, &shaderUniforms))
|
||||
{
|
||||
gPostPosterizeProgram.bind();
|
||||
gPostPosterizeProgram.uniform1i("tex0", 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return success;
|
||||
|
||||
@@ -4839,7 +4839,7 @@ void LLViewerWindow::stopGL(BOOL save_state)
|
||||
|
||||
if(LLPostProcess::instanceExists())
|
||||
{
|
||||
LLPostProcess::getInstance()->invalidate();
|
||||
LLPostProcess::getInstance()->destroyGL();
|
||||
}
|
||||
|
||||
gTextureList.destroyGL(save_state);
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
#include "llglheaders.h"
|
||||
#include "llrender.h"
|
||||
#include "llwindow.h"
|
||||
#include "llpostprocess.h"
|
||||
|
||||
// newview includes
|
||||
#include "llagent.h"
|
||||
@@ -863,6 +864,9 @@ void LLPipeline::releaseGLBuffers()
|
||||
|
||||
gBumpImageList.destroyGL();
|
||||
LLVOAvatar::resetImpostors();
|
||||
|
||||
if(LLPostProcess::instanceExists())
|
||||
LLPostProcess::getInstance()->destroyGL();
|
||||
}
|
||||
|
||||
void LLPipeline::releaseLUTBuffers()
|
||||
@@ -6111,12 +6115,14 @@ void LLPipeline::doResetVertexBuffers()
|
||||
|
||||
LLVOPartGroup::destroyGL();
|
||||
|
||||
if(LLPostProcess::instanceExists())
|
||||
LLPostProcess::getInstance()->destroyGL();
|
||||
|
||||
LLVertexBuffer::cleanupClass();
|
||||
|
||||
//delete all name pool caches
|
||||
LLGLNamePool::cleanupPools();
|
||||
|
||||
|
||||
if (LLVertexBuffer::sGLCount > 0)
|
||||
{
|
||||
llwarns << "VBO wipe failed -- " << LLVertexBuffer::sGLCount << " buffers remaining." << llendl;
|
||||
@@ -6923,7 +6929,7 @@ void LLPipeline::renderBloom(BOOL for_snapshot, F32 zoom_factor, int subfield, b
|
||||
}
|
||||
|
||||
|
||||
if (LLRenderTarget::sUseFBO)
|
||||
if (mScreen.getFBO())
|
||||
{ //copy depth buffer from mScreen to framebuffer
|
||||
LLRenderTarget::copyContentsToFramebuffer(mScreen, 0, 0, mScreen.getWidth(), mScreen.getHeight(),
|
||||
0, 0, mScreen.getWidth(), mScreen.getHeight(), GL_DEPTH_BUFFER_BIT, GL_NEAREST);
|
||||
|
||||
@@ -146,6 +146,25 @@
|
||||
left="14" max_val="1" min_val="0" mouse_opaque="true"
|
||||
name="noise_strength" show_text="true" value="1.0" width="200" />
|
||||
</panel>
|
||||
<panel border="true" bottom="-180" follows="left|top|right|bottom" height="400"
|
||||
label="Posterize" left="1" mouse_opaque="false"
|
||||
name="PosterizePanel" width="398">
|
||||
<check_box bottom="-20" follows="left|top"
|
||||
font="SansSerifSmall" height="16" initial_value="false" label="Enable"
|
||||
left="14" mouse_opaque="true" name="enable_posterize" width="200" />
|
||||
<text type="string" length="1" bg_visible="false" border_drop_shadow_visible="false" border_visible="false"
|
||||
bottom_delta="-21" drop_shadow_visible="true" follows="left|top|right"
|
||||
font="SansSerif" h_pad="0" halign="left" height="16"
|
||||
left="10" mouse_opaque="true" name="PosterLayersText" v_pad="0"
|
||||
width="355">
|
||||
Layer Count
|
||||
</text>
|
||||
<slider bottom_delta="-30" can_edit_text="true"
|
||||
decimal_digits="3" follows="left"
|
||||
height="18" increment="1" initial_val="10" label="" left="14"
|
||||
max_val="20" min_val="1" mouse_opaque="true"
|
||||
name="posterize_layers" show_text="true" value="10" width="200" />
|
||||
</panel>
|
||||
<!--<panel border="true" bottom="-180" follows="left|top|right|bottom" height="400"
|
||||
label="Bloom" left="1" mouse_opaque="true"
|
||||
name="BloomPanel" width="398">
|
||||
|
||||
Reference in New Issue
Block a user